diff --git a/src/components/TrackPlayerState.tsx b/src/components/TrackPlayerState.tsx index 91b6f14..e20f175 100644 --- a/src/components/TrackPlayerState.tsx +++ b/src/components/TrackPlayerState.tsx @@ -2,8 +2,14 @@ import { useAppState } from '@react-native-community/hooks' import { useAtomValue, useUpdateAtom } from 'jotai/utils' import React, { useEffect } from 'react' import { View } from 'react-native' -import TrackPlayer, { Event, useTrackPlayerEvents } from 'react-native-track-player' -import { currentTrackAtom, getQueue, getTrack, playerStateAtom, queueWriteAtom } from '../state/trackplayer' +import TrackPlayer, { Event, State, useTrackPlayerEvents } from 'react-native-track-player' +import { + currentTrackAtom, + playerStateAtom, + queueWriteAtom, + useRefreshCurrentTrack, + useRefreshQueue, +} from '../state/trackplayer' const AppActiveResponder: React.FC<{ update: () => void @@ -32,23 +38,16 @@ const TrackPlayerEventResponder: React.FC<{ const CurrentTrackState = () => { const setCurrentTrack = useUpdateAtom(currentTrackAtom) + const refreshCurrentTrack = useRefreshCurrentTrack() const update = async (payload?: Payload) => { - if (payload?.type === Event.PlaybackQueueEnded && 'track' in payload) { + const queueEnded = payload?.type === Event.PlaybackQueueEnded && 'track' in payload + const remoteStop = payload?.type === Event.RemoteStop + if (queueEnded || remoteStop) { setCurrentTrack(undefined) return } - - const index = await TrackPlayer.getCurrentTrack() - if (index !== null && index >= 0) { - const track = await getTrack(index) - if (track !== null) { - setCurrentTrack(track) - return - } - } - - setCurrentTrack(undefined) + await refreshCurrentTrack() } return ( @@ -70,31 +69,36 @@ const PlayerState = () => { const setPlayerState = useUpdateAtom(playerStateAtom) const update = async (payload?: Payload) => { + if (payload?.type === Event.RemoteStop) { + setPlayerState(State.None) + return + } setPlayerState(payload?.state || (await TrackPlayer.getState())) } - return + return } const QueueState = () => { const setQueue = useUpdateAtom(queueWriteAtom) + const refreshQueue = useRefreshQueue() const update = async (payload?: Payload) => { if (payload) { setQueue([]) return } - setQueue(await getQueue()) + await refreshQueue() } return } const Debug = () => { - const value = useAtomValue(queueWriteAtom) + const value = useAtomValue(currentTrackAtom) useEffect(() => { - console.log(value.map(t => t.title)) + // ToastAndroid.show(value?.title || 'undefined', 1) }, [value]) return <> diff --git a/src/state/trackplayer.ts b/src/state/trackplayer.ts index 3711cd3..ee31564 100644 --- a/src/state/trackplayer.ts +++ b/src/state/trackplayer.ts @@ -3,6 +3,7 @@ import TrackPlayer, { State, Track } from 'react-native-track-player' import equal from 'fast-deep-equal' import { useUpdateAtom } from 'jotai/utils' import { Song } from '../models/music' +import { PromiseQueue } from '../util' type TrackExt = Track & { id: string @@ -36,7 +37,7 @@ export const queueAtom = atom(get => get(_queue)) export const queueWriteAtom = atom( get => get(_queue), (get, set, update) => { - if (get(_queue) !== update) { + if (!equal(get(_queue), update)) { set(_queue, update) } }, @@ -50,19 +51,44 @@ export const queueNameAtom = atom(get => { return undefined }) -export const getQueue = async (): Promise => { +const trackPlayerCommands = new PromiseQueue(1) + +const getQueue = async (): Promise => { return ((await TrackPlayer.getQueue()) as TrackExt[]) || [] } -export const getTrack = async (index: number): Promise => { +const getTrack = async (index: number): Promise => { return ((await TrackPlayer.getTrack(index)) as TrackExt) || undefined } +export const useRefreshQueue = () => { + const setQueue = useUpdateAtom(queueWriteAtom) + + return () => + trackPlayerCommands.enqueue(async () => { + setQueue(await getQueue()) + }) +} + +export const useRefreshCurrentTrack = () => { + const setCurrentTrack = useUpdateAtom(currentTrackAtom) + + return () => + trackPlayerCommands.enqueue(async () => { + const index = await TrackPlayer.getCurrentTrack() + if (typeof index === 'number' && index >= 0) { + setCurrentTrack(await getTrack(index)) + } else { + setCurrentTrack(undefined) + } + }) +} + export const usePrevious = () => { const setCurrentTrack = useUpdateAtom(currentTrackAtom) - return async () => { - try { + return () => + trackPlayerCommands.enqueue(async () => { const [current, queue] = await Promise.all([await TrackPlayer.getCurrentTrack(), await getQueue()]) if (current > 0) { await TrackPlayer.skipToPrevious() @@ -71,15 +97,14 @@ export const usePrevious = () => { await TrackPlayer.seekTo(0) } await TrackPlayer.play() - } catch {} - } + }) } export const useNext = () => { const setCurrentTrack = useUpdateAtom(currentTrackAtom) - return async () => { - try { + return () => + trackPlayerCommands.enqueue(async () => { const [current, queue] = await Promise.all([await TrackPlayer.getCurrentTrack(), await getQueue()]) if (current >= queue.length - 1) { await TrackPlayer.skip(0) @@ -90,64 +115,68 @@ export const useNext = () => { setCurrentTrack(queue[current + 1]) await TrackPlayer.play() } - } catch {} - } + }) } export const useAdd = () => { const setQueue = useUpdateAtom(queueWriteAtom) const setCurrentTrack = useUpdateAtom(currentTrackAtom) - return async (tracks: TrackExt | TrackExt[], insertBeforeindex?: number) => { - await TrackPlayer.add(tracks, insertBeforeindex) + 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) - } + const queue = await getQueue() + setQueue(queue) + setCurrentTrack(queue.length > 0 ? queue[await TrackPlayer.getCurrentTrack()] : undefined) + }) } -export const useReset = () => { +export const useReset = (enqueue = true) => { const setQueue = useUpdateAtom(queueWriteAtom) const setCurrentTrack = useUpdateAtom(currentTrackAtom) - return async () => { + const reset = async () => { await TrackPlayer.reset() setQueue([]) setCurrentTrack(undefined) } + + return enqueue ? () => trackPlayerCommands.enqueue(reset) : reset } export const useSetQueue = () => { const setCurrentTrack = useUpdateAtom(currentTrackAtom) const setQueue = useUpdateAtom(queueWriteAtom) + const reset = useReset(false) - return async (songs: Song[], name: string, playId?: string) => { - await TrackPlayer.reset() - const tracks = songs.map(s => mapSongToTrack(s, name)) + return async (songs: Song[], name: string, playId?: string) => + trackPlayerCommands.enqueue(async () => { + await reset() + const tracks = songs.map(s => mapSongToTrack(s, name)) - if (playId) { - setCurrentTrack(tracks.find(t => t.id === playId)) - } + if (playId) { + setCurrentTrack(tracks.find(t => t.id === playId)) + } - if (!playId) { - await TrackPlayer.add(tracks) - } else if (playId === tracks[0].id) { - await TrackPlayer.add(tracks) - await TrackPlayer.play() - } else { - const playIndex = tracks.findIndex(t => t.id === playId) - const tracks1 = tracks.slice(0, playIndex) - const tracks2 = tracks.slice(playIndex) + if (!playId) { + await TrackPlayer.add(tracks) + } else if (playId === tracks[0].id) { + await TrackPlayer.add(tracks) + await TrackPlayer.play() + } else { + const playIndex = tracks.findIndex(t => t.id === playId) + const tracks1 = tracks.slice(0, playIndex) + const tracks2 = tracks.slice(playIndex) - await TrackPlayer.add(tracks2) - await TrackPlayer.play() + await TrackPlayer.add(tracks2) + await TrackPlayer.play() - await TrackPlayer.add(tracks1, 0) - } + await TrackPlayer.add(tracks1, 0) + } - setQueue(await getQueue()) - } + setQueue(await getQueue()) + }) } function mapSongToTrack(song: Song, queueName: string): TrackExt { diff --git a/src/subsonic/api.ts b/src/subsonic/api.ts index bb209ec..5837058 100644 --- a/src/subsonic/api.ts +++ b/src/subsonic/api.ts @@ -26,6 +26,7 @@ import { } from './responses' import { Server } from '../models/settings' import paths from '../paths' +import { PromiseQueue } from '../util' export class SubsonicApiError extends Error { method: string @@ -42,37 +43,7 @@ export class SubsonicApiError extends Error { } } -type QueuePromise = () => Promise - -class Queue { - maxSimultaneously: number - - private active = 0 - private queue: QueuePromise[] = [] - - constructor(maxSimultaneously = 1) { - this.maxSimultaneously = maxSimultaneously - } - - async enqueue(func: QueuePromise) { - if (++this.active > this.maxSimultaneously) { - await new Promise(resolve => this.queue.push(resolve as QueuePromise)) - } - - try { - return await func() - } catch (err) { - throw err - } finally { - this.active-- - if (this.queue.length) { - this.queue.shift()?.() - } - } - } -} - -const downloadQueue = new Queue(1) +const downloadQueue = new PromiseQueue(1) export class SubsonicApiClient { address: string diff --git a/src/util.ts b/src/util.ts index fd73faa..4ed7106 100644 --- a/src/util.ts +++ b/src/util.ts @@ -9,3 +9,33 @@ export function formatDuration(seconds: number): string { } return time } + +type QueuedPromise = () => Promise + +export class PromiseQueue { + maxSimultaneously: number + + private active = 0 + private queue: QueuedPromise[] = [] + + constructor(maxSimultaneously = 1) { + this.maxSimultaneously = maxSimultaneously + } + + async enqueue(func: () => Promise) { + if (++this.active > this.maxSimultaneously) { + await new Promise(resolve => this.queue.push(resolve as QueuedPromise)) + } + + try { + return await func() + } catch (err) { + throw err + } finally { + this.active-- + if (this.queue.length) { + this.queue.shift()?.() + } + } + } +}