115 lines
3.8 KiB
TypeScript
115 lines
3.8 KiB
TypeScript
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 = 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]);
|
|
} |