import { useTypedSelector } from 'store'; import { useCallback } from 'react'; import TrackPlayer, { Track } from 'react-native-track-player'; import { generateTrack } from './JellyfinApi'; import { EntityId } from '@reduxjs/toolkit'; import { shuffle as shuffleArray } from 'lodash'; interface PlayOptions { play: boolean; shuffle: boolean; method: 'add-to-end' | 'add-after-currently-playing' | 'replace'; } const defaults: PlayOptions = { play: true, shuffle: false, method: 'replace', }; /** * Generate a callback function that starts playing a full album given its * supplied id. */ export default function usePlayTracks() { const credentials = useTypedSelector(state => state.settings.jellyfin); const tracks = useTypedSelector(state => state.music.tracks.entities); const downloads = useTypedSelector(state => state.downloads.entities); return useCallback(async function playTracks( trackIds: EntityId[] | undefined, options: Partial<PlayOptions> = {}, ): Promise<Track[] | undefined> { if (!trackIds) { return; } // Retrieve options and queue const { play, shuffle, method, } = Object.assign({}, defaults, options); const queue = await TrackPlayer.getQueue(); // Convert all trackIds to the relevant format for react-native-track-player const generatedTracks = trackIds.map((trackId) => { const track = tracks[trackId]; // GUARD: Check that the track actually exists in Redux if (!trackId || !track) { return; } // Retrieve the generated track from Jellyfin const generatedTrack = generateTrack(track, credentials); // Check if a downloaded version exists, and if so rewrite the URL const download = downloads[trackId]; if (download?.location) { generatedTrack.url = 'file://' + download.location; } return generatedTrack; }).filter((t): t is Track => typeof t !== 'undefined'); // Potentially shuffle all tracks const newTracks = shuffle ? shuffleArray(generatedTracks) : generatedTracks; // Then, we'll need to check where to add the track switch(method) { case 'add-to-end': { await TrackPlayer.add(newTracks); // Then we'll skip to it and play it if (play) { await TrackPlayer.skip((await TrackPlayer.getQueue()).length - newTracks.length); await TrackPlayer.play(); } break; } case 'add-after-currently-playing': { // Try and locate the current track const currentTrackIndex = await TrackPlayer.getCurrentTrack(); // Since the argument is the id to insert the track BEFORE, we need // to get the current track + 1 const targetTrack = currentTrackIndex >= 0 && queue.length > 1 ? queue[currentTrackIndex + 1].id : undefined; // Depending on whether this track exists, we either add it there, // or at the end of the queue. await TrackPlayer.add(newTracks, targetTrack); if (play) { await TrackPlayer.skip(currentTrackIndex + 1); await TrackPlayer.play(); } break; } case 'replace': { await TrackPlayer.reset(); await TrackPlayer.add(newTracks); if (play) { await TrackPlayer.play(); } break; } } }, [credentials, downloads, tracks]); }