Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fix-mobile-next-track.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@audius/mobile": patch
---

Fix next/previous track buttons in the now playing drawer not playing the new track on mobile
71 changes: 59 additions & 12 deletions packages/mobile/src/components/audio/AudioPlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,23 @@ export const AudioPlayer = () => {
}
}
}
} else if (playerIndex === queueIndex) {
// Manual skip (next/previous button): queue index was already updated
// by the reducer, so playerIndex === queueIndex. Update player info
// directly to avoid relying on the saga chain (which uses a web audio
// shim that is a no-op on mobile).
const { track, playerBehavior } = queueTracks[playerIndex] ?? {}
if (track && queueTrackUids[playerIndex] !== uid) {
const { shouldPreview } = calculatePlayerBehavior(
track,
playerBehavior
)
updatePlayerInfo({
previewing: shouldPreview,
trackId: track.track_id,
uid: queueTrackUids[playerIndex]
})
}
}

const isLongFormContent =
Expand Down Expand Up @@ -760,17 +777,37 @@ export const AudioPlayer = () => {
await enqueueTracksJobRef.current
enqueueTracksJobRef.current = undefined
} else {
await TrackPlayer.reset()

await TrackPlayer.play()

const firstTrack = newQueueTracks[queueIndex]
if (!firstTrack) return

await TrackPlayer.add(await makeTrackData(firstTrack))

enqueueTracksJobRef.current = enqueueTracks(newQueueTracks, queueIndex)
await enqueueTracksJobRef.current
// Wrap the full queue setup (reset, play, first track load, middle-out
// enqueue) in a single promise and assign it to enqueueTracksJobRef
// BEFORE any awaits. This ensures that if the user presses next/previous
// before the queue is done building, handleQueueIdxChange awaits this
// promise and defers the skip until the RNTP queue is ready. Without
// this, there was a window where enqueueTracksJobRef was undefined,
// handleQueueIdxChange's await resolved immediately, and the skip was
// silently dropped because queueIndex was beyond the (still empty)
// RNTP queue length.
//
// The body is wrapped in try/catch so that a failing RNTP call doesn't
// leave enqueueTracksJobRef pointing at a rejected promise, which would
// make subsequent awaits in handleQueueIdxChange throw silently.
const setupPromise = (async () => {
try {
await TrackPlayer.reset()

await TrackPlayer.play()

const firstTrack = newQueueTracks[queueIndex]
if (!firstTrack) return

await TrackPlayer.add(await makeTrackData(firstTrack))

await enqueueTracks(newQueueTracks, queueIndex)
} catch (e) {
console.warn('handleQueueChange setup error:', e)
}
})()
enqueueTracksJobRef.current = setupPromise
await setupPromise
enqueueTracksJobRef.current = undefined
}
}, [
Expand All @@ -793,7 +830,17 @@ export const AudioPlayer = () => {
queueIndex !== playerIdx &&
queueIndex < queue.length
) {
await TrackPlayer.skip(queueIndex)
try {
await TrackPlayer.skip(queueIndex)
// RNTP v4's skip() does not reliably continue playback when called
// shortly after queue setup; it can leave the player in a Ready
// state instead of Playing. Explicitly call play() to ensure the
// new track actually plays. This is the audio-switch that the
// saga chain cannot do on mobile (audioPlayer is a no-op shim).
await TrackPlayer.play()
} catch (e) {
console.warn('TrackPlayer.skip failed:', e)
}
}
}, [queueIndex])

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,9 +243,8 @@ export const NowPlayingDrawer = memo(function NowPlayingDrawer(
dispatch(seek({ seconds: Math.min(track.duration, newPosition) }))
} else {
dispatch(next({ skip: true }))
setMediaKey((mediaKey) => mediaKey + 1)
}
}, [dispatch, setMediaKey, track])
}, [dispatch, track])

const onPrevious = useCallback(async () => {
const { position: currentPosition } = await TrackPlayer.getProgress()
Expand All @@ -258,12 +257,11 @@ export const NowPlayingDrawer = memo(function NowPlayingDrawer(
const shouldGoToPrevious = currentPosition < RESTART_THRESHOLD_SEC
if (shouldGoToPrevious) {
dispatch(previous())
setMediaKey((mediaKey) => mediaKey + 1)
} else {
dispatch(reset({ shouldAutoplay: true }))
}
}
}, [dispatch, setMediaKey, track])
}, [dispatch, track])

const onPressScrubberIn = useCallback(() => {
setIsGestureEnabled(false)
Expand Down