mirror of
https://github.com/austinried/subtracks.git
synced 2025-12-27 09:09:29 +01:00
queue actions for player so they don't overlap
This commit is contained in:
parent
eb4199de37
commit
b2d6840901
@ -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 <TrackPlayerEventResponder events={[Event.PlaybackState]} update={update} />
|
||||
return <TrackPlayerEventResponder events={[Event.PlaybackState, Event.RemoteStop]} update={update} />
|
||||
}
|
||||
|
||||
const QueueState = () => {
|
||||
const setQueue = useUpdateAtom(queueWriteAtom)
|
||||
const refreshQueue = useRefreshQueue()
|
||||
|
||||
const update = async (payload?: Payload) => {
|
||||
if (payload) {
|
||||
setQueue([])
|
||||
return
|
||||
}
|
||||
setQueue(await getQueue())
|
||||
await refreshQueue()
|
||||
}
|
||||
|
||||
return <TrackPlayerEventResponder events={[Event.RemoteStop]} update={update} />
|
||||
}
|
||||
|
||||
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 <></>
|
||||
|
||||
@ -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<TrackExt[]>(get => get(_queue))
|
||||
export const queueWriteAtom = atom<TrackExt[], TrackExt[]>(
|
||||
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<string | undefined>(get => {
|
||||
return undefined
|
||||
})
|
||||
|
||||
export const getQueue = async (): Promise<TrackExt[]> => {
|
||||
const trackPlayerCommands = new PromiseQueue(1)
|
||||
|
||||
const getQueue = async (): Promise<TrackExt[]> => {
|
||||
return ((await TrackPlayer.getQueue()) as TrackExt[]) || []
|
||||
}
|
||||
|
||||
export const getTrack = async (index: number): Promise<TrackExt> => {
|
||||
const getTrack = async (index: number): Promise<TrackExt> => {
|
||||
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 {
|
||||
|
||||
@ -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<any>
|
||||
|
||||
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
|
||||
|
||||
30
src/util.ts
30
src/util.ts
@ -9,3 +9,33 @@ export function formatDuration(seconds: number): string {
|
||||
}
|
||||
return time
|
||||
}
|
||||
|
||||
type QueuedPromise = () => Promise<any>
|
||||
|
||||
export class PromiseQueue {
|
||||
maxSimultaneously: number
|
||||
|
||||
private active = 0
|
||||
private queue: QueuedPromise[] = []
|
||||
|
||||
constructor(maxSimultaneously = 1) {
|
||||
this.maxSimultaneously = maxSimultaneously
|
||||
}
|
||||
|
||||
async enqueue<T>(func: () => Promise<T>) {
|
||||
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()?.()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user