import { useCoverArtUri } from '@app/hooks/music' import { Song } from '@app/models/music' import { useStore } from '@app/state/store' import { getCurrentTrack, getQueue, getRepeatMode, QueueContextType, selectTrackPlayer, TrackExt, trackPlayerCommands, } from '@app/state/trackplayer' import { useCallback } from 'react' import TrackPlayer, { RepeatMode } from 'react-native-track-player' export const usePlay = () => { return () => trackPlayerCommands.enqueue(() => TrackPlayer.play()) } export const usePause = () => { return () => trackPlayerCommands.enqueue(() => TrackPlayer.pause()) } export const usePrevious = () => { return () => trackPlayerCommands.enqueue(async () => { const [current] = await Promise.all([await TrackPlayer.getCurrentTrack(), await getQueue()]) if (current > 0) { await TrackPlayer.skipToPrevious() } else { await TrackPlayer.seekTo(0) } await TrackPlayer.play() }) } export const useNext = () => { return () => trackPlayerCommands.enqueue(async () => { const [current, queue] = await Promise.all([await TrackPlayer.getCurrentTrack(), await getQueue()]) if (current >= queue.length - 1) { await TrackPlayer.skip(0) await TrackPlayer.pause() } else { await TrackPlayer.skipToNext() await TrackPlayer.play() } }) } export const useSkipTo = () => { return (track: number) => trackPlayerCommands.enqueue(async () => { const queue = await getQueue() if (track < 0 || track >= queue.length) { return } await TrackPlayer.skip(track) await TrackPlayer.play() }) } export const useSeekTo = () => { return (position: number) => trackPlayerCommands.enqueue(async () => { await TrackPlayer.seekTo(position) }) } export const useToggleRepeat = () => { const setRepeatMode = useStore(selectTrackPlayer.setRepeatMode) return () => trackPlayerCommands.enqueue(async () => { const repeatMode = await getRepeatMode() let nextMode = RepeatMode.Off switch (repeatMode) { case RepeatMode.Off: nextMode = RepeatMode.Queue break case RepeatMode.Queue: nextMode = RepeatMode.Track break default: nextMode = RepeatMode.Off break } await TrackPlayer.setRepeatMode(nextMode) setRepeatMode(nextMode) }) } export const useReset = (enqueue = true) => { const resetStore = useStore(selectTrackPlayer.reset) const reset = async () => { await TrackPlayer.reset() resetStore() } return enqueue ? () => trackPlayerCommands.enqueue(reset) : reset } function shuffleTracks(tracks: TrackExt[], firstTrack?: number) { if (tracks.length === 0) { return { tracks, shuffleOrder: [] } } const trackIndexes = tracks.map((_t, i) => i) let shuffleOrder: number[] = [] for (let i = trackIndexes.length; i--; i > 0) { const randi = Math.floor(Math.random() * (i + 1)) shuffleOrder.push(trackIndexes.splice(randi, 1)[0]) } if (firstTrack !== undefined) { shuffleOrder.splice(shuffleOrder.indexOf(firstTrack), 1) shuffleOrder = [firstTrack, ...shuffleOrder] } tracks = shuffleOrder.map(i => tracks[i]) return { tracks, shuffleOrder } } function unshuffleTracks(tracks: TrackExt[], shuffleOrder: number[]): TrackExt[] { if (tracks.length === 0 || shuffleOrder.length === 0) { return tracks } return shuffleOrder.map((_v, i) => tracks[shuffleOrder.indexOf(i)]) } export const useToggleShuffle = () => { const setQueue = useStore(selectTrackPlayer.setQueue) const setShuffleOrder = useStore(selectTrackPlayer.setShuffleOrder) const getShuffleOrder = useCallback(() => useStore.getState().shuffleOrder, []) const setProgress = useStore(selectTrackPlayer.setProgress) const getProgress = useCallback(() => useStore.getState().progress, []) return async () => { return trackPlayerCommands.enqueue(async () => { const queue = await getQueue() const current = await getCurrentTrack() const queueShuffleOrder = getShuffleOrder() const progress = getProgress() await TrackPlayer.remove(queue.map((_t, i) => i).filter(i => i !== current)) if (queueShuffleOrder === undefined) { let { tracks, shuffleOrder } = shuffleTracks(queue, current) if (tracks.length > 0) { tracks = tracks.slice(1) } await TrackPlayer.add(tracks) setShuffleOrder(shuffleOrder) } else { const tracks = unshuffleTracks(queue, queueShuffleOrder) if (current !== undefined) { const shuffledCurrent = queueShuffleOrder[current] const tracks1 = tracks.slice(0, shuffledCurrent) const tracks2 = tracks.slice(shuffledCurrent + 1) await TrackPlayer.add(tracks2) await TrackPlayer.add(tracks1, 0) } else { await TrackPlayer.add(tracks) } setShuffleOrder(undefined) } setQueue(await getQueue()) setProgress(progress) }) } } export const useSetQueue = () => { const setCurrentTrackIdx = useStore(selectTrackPlayer.setCurrentTrackIdx) const setQueue = useStore(selectTrackPlayer.setQueue) const setShuffleOrder = useStore(selectTrackPlayer.setShuffleOrder) const setQueueName = useStore(selectTrackPlayer.setName) const getQueueShuffled = useCallback(() => !!useStore.getState().shuffleOrder, []) const setQueueContextType = useStore(selectTrackPlayer.setQueueContextType) const setQueueContextId = useStore(selectTrackPlayer.setQueueContextId) const coverArtUri = useCoverArtUri() return async ( songs: Song[], name: string, contextType: QueueContextType, contextId: string, playTrack?: number, shuffle?: boolean, ) => trackPlayerCommands.enqueue(async () => { const shuffled = shuffle !== undefined ? shuffle : getQueueShuffled() await TrackPlayer.setupPlayer() await TrackPlayer.reset() if (songs.length === 0) { return } let queue = songs.map(s => mapSongToTrack(s, coverArtUri)) if (shuffled) { const { tracks, shuffleOrder } = shuffleTracks(queue, playTrack) setShuffleOrder(shuffleOrder) queue = tracks playTrack = 0 } else { setShuffleOrder(undefined) } playTrack = playTrack || 0 setQueue(queue) setCurrentTrackIdx(playTrack) setQueueName(name) setQueueContextType(contextType) setQueueContextId(contextId) if (playTrack === 0) { await TrackPlayer.add(queue) await TrackPlayer.play() } else { const tracks1 = queue.slice(0, playTrack) const tracks2 = queue.slice(playTrack) await TrackPlayer.add(tracks2) await TrackPlayer.play() await TrackPlayer.add(tracks1, 0) } }) } function mapSongToTrack(song: Song, coverArtUri: (coverArt?: string) => string | undefined): TrackExt { return { id: song.id, title: song.title, artist: song.artist || 'Unknown Artist', album: song.album || 'Unknown Album', url: song.streamUri, artwork: coverArtUri(song.coverArt), coverArt: song.coverArt, duration: song.duration, } } export function mapTrackExtToSong(track: TrackExt): Song { return { itemType: 'song', id: track.id, title: track.title as string, artist: track.artist, album: track.album, streamUri: track.url as string, coverArt: track.coverArt, duration: track.duration, } }