impl async storage for extra queue data

finally fixes shuffle (ugh)
This commit is contained in:
austinried 2021-07-22 11:23:18 +09:00
parent f859be51a5
commit 4f69b36c7b
4 changed files with 148 additions and 84 deletions

View File

@ -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 <></>

View File

@ -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 = () => {
</View>
<View style={controlsStyles.center}>
<PressableOpacity onPress={undefined} disabled={disabled}>
<Icon name="shuffle" size={26} color="white" />
<PressableOpacity onPress={() => toggleShuffle()} disabled={disabled}>
<Icon name="shuffle" size={26} color={shuffle ? colors.accent : 'white'} />
</PressableOpacity>
</View>
</View>

View File

@ -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<QueueExt>('@queue', {})
export const queueNameAtom = atom<string | undefined>(get => get(queueExtAtom).name)
export const queueShuffledAtom = atom<boolean>(get => get(queueExtAtom).shuffleOrder !== undefined)
const queueShuffleOrderAtom = atom<number[] | undefined, number[] | undefined>(
get => get(queueExtAtom).shuffleOrder,
(get, set, update) => {
const queueExt = get(queueExtAtom)
if (!equal(queueExt.shuffleOrder, update)) {
set(queueExtAtom, {
...queueExt,
shuffleOrder: update,
})
}
},
)
const queueNameWriteAtom = atom<string | undefined, string | undefined>(
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>(State.None)
export const playerStateAtom = atom<State, State>(
get => get(playerState),
@ -51,26 +86,6 @@ export const queueAtom = atom<TrackExt[], TrackExt[]>(
},
)
export const queueNameAtom = atom<string | undefined>(get => {
const queue = get(_queue)
return queue.length > 0 ? queue[0].queueName : undefined
})
export const queueShuffledAtom = atom<boolean>(get => {
const queue = get(_queue)
return queue.length > 0 ? queue[0].queueIndex !== undefined : false
})
export const orderedQueueAtom = atom<TrackExt[]>(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<Progress>({ position: 0, duration: 0, buffered: 0 })
export const progressAtom = atom<Progress, Progress>(
get => get(_progress),
@ -100,6 +115,11 @@ const getTrack = async (index: number): Promise<TrackExt> => {
return ((await TrackPlayer.getTrack(index)) as TrackExt) || undefined
}
const getCurrentTrack = async (): Promise<number | undefined> => {
const current = await TrackPlayer.getCurrentTrack()
return typeof current === 'number' ? current : undefined
}
const getPlayerState = async (): Promise<State> => {
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',

View File

@ -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"