From 4f69b36c7bdc3bd0c9328f66e759d4405f23511a Mon Sep 17 00:00:00 2001 From: austinried <4966622+austinried@users.noreply.github.com> Date: Thu, 22 Jul 2021 11:23:18 +0900 Subject: [PATCH] impl async storage for extra queue data finally fixes shuffle (ugh) --- app/components/TrackPlayerState.tsx | 4 +- app/screens/NowPlayingLayout.tsx | 8 +- app/state/trackplayer.ts | 214 ++++++++++++++++++---------- yarn.lock | 6 +- 4 files changed, 148 insertions(+), 84 deletions(-) diff --git a/app/components/TrackPlayerState.tsx b/app/components/TrackPlayerState.tsx index d488b21..aed4356 100644 --- a/app/components/TrackPlayerState.tsx +++ b/app/components/TrackPlayerState.tsx @@ -135,10 +135,10 @@ const ProgressState = () => { } const Debug = () => { - const value = useAtomValue(currentTrackAtom) + const value = useAtomValue(queueAtom) useEffect(() => { - // ToastAndroid.show(value?.title || 'undefined', 1) + console.log(value.map(t => t.title)) }, [value]) return <> diff --git a/app/screens/NowPlayingLayout.tsx b/app/screens/NowPlayingLayout.tsx index d261eb3..420599b 100644 --- a/app/screens/NowPlayingLayout.tsx +++ b/app/screens/NowPlayingLayout.tsx @@ -12,11 +12,13 @@ import { currentTrackAtom, playerStateAtom, queueNameAtom, + queueShuffledAtom, useNext, usePause, usePlay, usePrevious, useProgress, + useToggleShuffle, } from '@app/state/trackplayer' import colors from '@app/styles/colors' import font from '@app/styles/font' @@ -206,6 +208,8 @@ const PlayerControls = () => { const pause = usePause() const next = useNext() const previous = usePrevious() + const shuffle = useAtomValue(queueShuffledAtom) + const toggleShuffle = useToggleShuffle() let playPauseIcon: string let playPauseAction: undefined | (() => void) @@ -253,8 +257,8 @@ const PlayerControls = () => { - - + toggleShuffle()} disabled={disabled}> + diff --git a/app/state/trackplayer.ts b/app/state/trackplayer.ts index 5404081..67e157b 100644 --- a/app/state/trackplayer.ts +++ b/app/state/trackplayer.ts @@ -1,15 +1,14 @@ +import { Song } from '@app/models/music' +import atomWithAsyncStorage from '@app/storage/atomWithAsyncStorage' +import PromiseQueue from '@app/util/PromiseQueue' import equal from 'fast-deep-equal' import { atom } from 'jotai' import { useAtomCallback, useAtomValue, useUpdateAtom } from 'jotai/utils' -import { useEffect } from 'react' +import { useCallback, useEffect } from 'react' import TrackPlayer, { State, Track } from 'react-native-track-player' -import { Song } from '@app/models/music' -import PromiseQueue from '@app/util/PromiseQueue' type TrackExt = Track & { id: string - queueName: string - queueIndex?: number artworkThumb?: string } @@ -21,6 +20,42 @@ type Progress = { buffered: number } +type QueueExt = { + name?: string + shuffleOrder?: number[] +} + +const queueExtAtom = atomWithAsyncStorage('@queue', {}) + +export const queueNameAtom = atom(get => get(queueExtAtom).name) +export const queueShuffledAtom = atom(get => get(queueExtAtom).shuffleOrder !== undefined) + +const queueShuffleOrderAtom = atom( + get => get(queueExtAtom).shuffleOrder, + (get, set, update) => { + const queueExt = get(queueExtAtom) + if (!equal(queueExt.shuffleOrder, update)) { + set(queueExtAtom, { + ...queueExt, + shuffleOrder: update, + }) + } + }, +) + +const queueNameWriteAtom = atom( + get => get(queueExtAtom).name, + (get, set, update) => { + const queueExt = get(queueExtAtom) + if (!equal(queueExt.name, update)) { + set(queueExtAtom, { + ...queueExt, + name: update, + }) + } + }, +) + const playerState = atom(State.None) export const playerStateAtom = atom( get => get(playerState), @@ -51,26 +86,6 @@ export const queueAtom = atom( }, ) -export const queueNameAtom = atom(get => { - const queue = get(_queue) - return queue.length > 0 ? queue[0].queueName : undefined -}) - -export const queueShuffledAtom = atom(get => { - const queue = get(_queue) - return queue.length > 0 ? queue[0].queueIndex !== undefined : false -}) - -export const orderedQueueAtom = atom(get => { - const queue = get(_queue) - - if (queue.length === 0 || queue[0].queueIndex === undefined) { - return queue - } - - return queue.map(t => t.queueIndex as number).map(i => queue[i]) -}) - const _progress = atom({ position: 0, duration: 0, buffered: 0 }) export const progressAtom = atom( get => get(_progress), @@ -100,6 +115,11 @@ const getTrack = async (index: number): Promise => { return ((await TrackPlayer.getTrack(index)) as TrackExt) || undefined } +const getCurrentTrack = async (): Promise => { + const current = await TrackPlayer.getCurrentTrack() + return typeof current === 'number' ? current : undefined +} + const getPlayerState = async (): Promise => { return (await TrackPlayer.getState()) || State.None } @@ -200,92 +220,132 @@ export const useNext = () => { }) } -// export const useAdd = () => { -// const setQueue = useUpdateAtom(queueAtom) -// const setCurrentTrack = useUpdateAtom(currentTrackAtom) - -// return (tracks: TrackExt | TrackExt[], insertBeforeindex?: number) => -// trackPlayerCommands.enqueue(async () => { -// await TrackPlayer.add(tracks, insertBeforeindex) - -// const queue = await getQueue() -// setQueue(queue) -// setCurrentTrack(queue.length > 0 ? queue[await TrackPlayer.getCurrentTrack()] : undefined) -// }) -// } - export const useReset = (enqueue = true) => { const setQueue = useUpdateAtom(queueAtom) + const setQueueExt = useUpdateAtom(queueExtAtom) const setCurrentTrack = useUpdateAtom(currentTrackAtom) const reset = async () => { await TrackPlayer.reset() setQueue([]) + setQueueExt({}) setCurrentTrack(undefined) } 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 = useUpdateAtom(queueAtom) + const setQueueShuffleOrder = useUpdateAtom(queueShuffleOrderAtom) + const getQueueShuffleOrder = useAtomCallback(useCallback(get => get(queueShuffleOrderAtom), [])) + + return async () => { + return trackPlayerCommands.enqueue(async () => { + const queue = await getQueue() + const current = await getCurrentTrack() + const queueShuffleOrder = await getQueueShuffleOrder() + + 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) + setQueueShuffleOrder(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) + } + + setQueueShuffleOrder(undefined) + } + + setQueue(await getQueue()) + }) + } +} + export const useSetQueue = () => { const setCurrentTrack = useUpdateAtom(currentTrackAtom) const setQueue = useUpdateAtom(queueAtom) + const setQueueShuffleOrder = useUpdateAtom(queueShuffleOrderAtom) + const setQueueName = useUpdateAtom(queueNameWriteAtom) const reset = useReset(false) - const getShuffled = useAtomCallback(get => get(queueShuffledAtom)) + const getQueueShuffled = useAtomCallback(useCallback(get => get(queueShuffledAtom), [])) return async (songs: Song[], name: string, playTrack?: number, shuffle?: boolean) => trackPlayerCommands.enqueue(async () => { - const shuffleTracks = shuffle !== undefined ? shuffle : await getShuffled() + const shuffled = shuffle !== undefined ? shuffle : await getQueueShuffled() await TrackPlayer.setupPlayer() await reset() if (songs.length === 0) { - setCurrentTrack(undefined) - setQueue([]) return } - let tracks = songs.map(s => mapSongToTrack(s, name)) - console.log(tracks.map(t => t.title)) + let queue = songs.map(mapSongToTrack) - if (shuffleTracks) { - let trackIndexes = tracks.map((_t, i) => i) - const shuffleOrder: number[] = [] - - for (let i = trackIndexes.length; i--; i > 0) { - tracks[i].queueIndex = i - - const randi = Math.floor(Math.random() * (i + 1)) - shuffleOrder.push(trackIndexes[randi]) - trackIndexes.splice(randi, 1) - } - - tracks = shuffleOrder.map(i => tracks[i]) - - if (playTrack !== undefined) { - tracks = [ - tracks.splice( - tracks.findIndex(t => t.queueIndex === playTrack), - 1, - )[0], - ...tracks, - ] - playTrack = 0 - } + if (shuffled) { + const { tracks, shuffleOrder } = shuffleTracks(queue, playTrack) + setQueueShuffleOrder(shuffleOrder) + queue = tracks + playTrack = 0 } - console.log(tracks.map(t => t.title)) - playTrack = playTrack || 0 - setCurrentTrack(tracks[playTrack]) + setCurrentTrack(queue[playTrack]) if (playTrack === 0) { - await TrackPlayer.add(tracks) + await TrackPlayer.add(queue) await TrackPlayer.play() } else { - const tracks1 = tracks.slice(0, playTrack) - const tracks2 = tracks.slice(playTrack) + const tracks1 = queue.slice(0, playTrack) + const tracks2 = queue.slice(playTrack) await TrackPlayer.add(tracks2) await TrackPlayer.play() @@ -294,6 +354,7 @@ export const useSetQueue = () => { } setQueue(await getQueue()) + setQueueName(name) }) } @@ -310,10 +371,9 @@ export const useProgress = () => { return progress } -function mapSongToTrack(song: Song, queueName: string): TrackExt { +function mapSongToTrack(song: Song): TrackExt { return { id: song.id, - queueName, title: song.title, artist: song.artist || 'Unknown Artist', album: song.album || 'Unknown Album', diff --git a/yarn.lock b/yarn.lock index 541e606..0cf684c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4145,9 +4145,9 @@ joi@^17.2.1: "@sideway/pinpoint" "^2.0.0" jotai@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/jotai/-/jotai-1.1.2.tgz#3f211e0c03c74e95ea6fd7a69c1d2b65731009bf" - integrity sha512-dni4wtgYGG+s9YbOJN7lcfrrhxiD6bH1SN00Pnl0F2htgOXmjxqkGlFzw02OK0Rw35wGNzBfDTJVtbGD9wHOhg== + version "1.2.2" + resolved "https://registry.yarnpkg.com/jotai/-/jotai-1.2.2.tgz#631fd7ad44e9ac26cdf9874d52282c1cfe032807" + integrity sha512-iqkkUdWsH2Mk4HY1biba/8kA77+8liVBy8E0d8Nce29qow4h9mzdDhpTasAruuFYPycw6JvfZgL5RB0JJuIZjw== "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0"