From e456ec7bead81cc19f8ff9ade810f0c737a0e7c7 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 20 May 2026 13:22:31 +0000 Subject: [PATCH] Fix mobile queue reorder playback restart Co-authored-by: Ray Jacobson --- .../src/components/audio/AudioPlayer.tsx | 73 ++++++++++++++++++- 1 file changed, 71 insertions(+), 2 deletions(-) diff --git a/packages/mobile/src/components/audio/AudioPlayer.tsx b/packages/mobile/src/components/audio/AudioPlayer.tsx index 83f4bbf644b..9a2a2cf6808 100644 --- a/packages/mobile/src/components/audio/AudioPlayer.tsx +++ b/packages/mobile/src/components/audio/AudioPlayer.tsx @@ -133,6 +133,45 @@ const unlistedTrackFallbackTrackData = { duration: 0 } +const haveSameTrackIds = (currentIds: ID[], nextIds: ID[]) => { + if (currentIds.length !== nextIds.length) return false + + const counts = new Map() + currentIds.forEach((id) => counts.set(id, (counts.get(id) ?? 0) + 1)) + + for (const id of nextIds) { + const count = counts.get(id) + if (!count) return false + if (count === 1) { + counts.delete(id) + } else { + counts.set(id, count - 1) + } + } + + return counts.size === 0 +} + +const getQueueReorderMoves = (currentIds: ID[], nextIds: ID[]) => { + if (!haveSameTrackIds(currentIds, nextIds)) return null + + const working = [...currentIds] + const moves: { from: number; to: number }[] = [] + + for (let to = 0; to < nextIds.length; to++) { + if (working[to] === nextIds[to]) continue + + const from = working.findIndex((id, i) => i > to && id === nextIds[to]) + if (from === -1) return null + + const [moved] = working.splice(from, 1) + working.splice(to, 0, moved) + moves.push({ from, to }) + } + + return moves +} + type QueueableTrack = { track: Nullable } & Pick @@ -368,6 +407,21 @@ const useQueueSync = (isAudioSetup: boolean) => { [makeTrackData] ) + // --- reorderQueue: move existing RNTP items without restarting playback --- + const reorderQueue = useCallback( + async (currentTrackIds: ID[], nextTrackIds: ID[]) => { + const moves = getQueueReorderMoves(currentTrackIds, nextTrackIds) + if (!moves) return false + + for (const { from, to } of moves) { + await TrackPlayer.move(from, to) + } + + return true + }, + [] + ) + // --- handleQueueChange: decides reset vs append --- const handleQueueChange = useCallback(async () => { const refTrackIds = queueListRef.current @@ -390,17 +444,31 @@ const useQueueSync = (isAudioSetup: boolean) => { return } - queueListRef.current = queueTrackIds - const isQueueAppend = refTrackIds.length > 0 && isEqual(queueTrackIds.slice(0, refTrackIds.length), refTrackIds) && !didPlayerBehaviorChange + const isQueueReorder = + refTrackIds.length > 0 && + haveSameTrackIds(refTrackIds, queueTrackIds) && + !didOfflineToggleChange && + !didPlayerBehaviorChange + if (isQueueAppend) { await appendToQueue(queueTracks.slice(refTrackIds.length)) + queueListRef.current = queueTrackIds + } else if (isQueueReorder) { + const didReorder = await reorderQueue(refTrackIds, queueTrackIds) + if (didReorder) { + queueListRef.current = queueTrackIds + } else { + await resetQueue(queueTracks, queueIndex) + queueListRef.current = queueTrackIds + } } else { await resetQueue(queueTracks, queueIndex) + queueListRef.current = queueTrackIds } }, [ queueTracks, @@ -410,6 +478,7 @@ const useQueueSync = (isAudioSetup: boolean) => { didPlayerBehaviorChange, queueTrackOwnersMap, appendToQueue, + reorderQueue, resetQueue ])