mirror of
https://github.com/austinried/subtracks.git
synced 2025-12-27 09:09:29 +01:00
impl async storage for extra queue data
finally fixes shuffle (ugh)
This commit is contained in:
parent
f859be51a5
commit
4f69b36c7b
@ -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 <></>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user