fix(ui): start new album from track 1 after closing player (#5441)

When a user played an album, advanced a few tracks, closed the player,
then played a different album, playback started mid-album at the
previous track index instead of track 1.

The root cause was in reduceSyncQueue: the hasPendingSwitch check
compared playIndex against savedPlayIndex, but after clearQueue both
reset to 0, making the check falsely conclude no switch was pending.
This caused PLAYER_SYNC_QUEUE to prematurely clear the playIndex and
clear flags before the music player library could act on them.

Fix: also treat clear=true as a signal that a track switch is pending,
since it means a new queue was just loaded.
This commit is contained in:
Deluan Quintão 2026-04-29 15:36:39 -04:00 committed by GitHub
parent bdea9ed6a1
commit 2307a64da7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 90 additions and 6 deletions

View File

@ -164,13 +164,15 @@ const reduceSetVolume = (state, { data: { volume } }) => {
}
const reduceSyncQueue = (state, { data: { audioInfo, audioLists } }) => {
// Only keep clear and playIndex alive when there is an actual pending
// track switch (playIndex differs from savedPlayIndex). This lets
// PLAYER_PLAY_TRACKS selections survive the sync, while allowing
// PLAYER_PLAY_NEXT (which sets playIndex to the current track) to
// reset immediately and avoid restarting playback.
// Keep clear and playIndex alive when there is a pending track switch.
// A switch is pending when playIndex is set AND either:
// - playIndex differs from savedPlayIndex, OR
// - clear is true (a new queue was loaded, e.g. after clearQueue + playTracks)
// The clear check handles the edge case where both playIndex and
// savedPlayIndex are 0 (close player then play a new album from track 1).
const hasPendingSwitch =
state.playIndex != null && state.playIndex !== state.savedPlayIndex
state.playIndex != null &&
(state.clear || state.playIndex !== state.savedPlayIndex)
return {
...state,
queue: audioLists,

View File

@ -96,6 +96,88 @@ describe('playerReducer', () => {
})
})
describe('play new album after closing player (issue #5440)', () => {
it('SYNC_QUEUE preserves pending playIndex=0 after clearQueue', () => {
// Scenario: user plays album A, advances to track 3, closes player,
// then plays album B. After clearQueue, savedPlayIndex=0.
// PLAYER_PLAY_TRACKS sets playIndex=0. SYNC_QUEUE must NOT clear it.
const stateAfterClearThenPlay = {
queue: [
{ trackId: 'b1', uuid: 'u1', name: 'B Song 1' },
{ trackId: 'b2', uuid: 'u2', name: 'B Song 2' },
{ trackId: 'b3', uuid: 'u3', name: 'B Song 3' },
],
current: {},
playIndex: 0,
savedPlayIndex: 0, // reset by clearQueue
clear: true,
volume: 1,
}
const action = {
type: PLAYER_SYNC_QUEUE,
data: {
audioInfo: {},
audioLists: stateAfterClearThenPlay.queue,
},
}
const result = playerReducer(stateAfterClearThenPlay, action)
expect(result.playIndex).toBe(0)
expect(result.clear).toBe(true)
})
it('CURRENT for wrong track preserves pending playIndex=0 after clearQueue', () => {
// The music player fires onAudioPlay for the old track (at index 3)
// before switching to the new track at index 0.
const stateAfterClearThenPlay = {
queue: [
{ trackId: 'b1', uuid: 'u1', name: 'B Song 1' },
{ trackId: 'b2', uuid: 'u2', name: 'B Song 2' },
{ trackId: 'b3', uuid: 'u3', name: 'B Song 3' },
{ trackId: 'b4', uuid: 'u4', name: 'B Song 4' },
],
current: {},
playIndex: 0,
savedPlayIndex: 0,
clear: true,
volume: 1,
}
// Player reports track at index 3 as current (stale callback)
const action = {
type: PLAYER_CURRENT,
data: { uuid: 'u4', name: 'B Song 4', volume: 1 },
}
const result = playerReducer(stateAfterClearThenPlay, action)
expect(result.playIndex).toBe(0)
expect(result.clear).toBe(true)
})
it('CURRENT for correct track consumes pending playIndex=0', () => {
const stateAfterClearThenPlay = {
queue: [
{ trackId: 'b1', uuid: 'u1', name: 'B Song 1' },
{ trackId: 'b2', uuid: 'u2', name: 'B Song 2' },
],
current: {},
playIndex: 0,
savedPlayIndex: 0,
clear: true,
volume: 1,
}
// Player confirms it switched to track at index 0
const action = {
type: PLAYER_CURRENT,
data: { uuid: 'u1', name: 'B Song 1', volume: 1 },
}
const result = playerReducer(stateAfterClearThenPlay, action)
expect(result.playIndex).toBeUndefined()
expect(result.clear).toBe(false)
expect(result.savedPlayIndex).toBe(0)
})
})
describe('PLAYER_REFRESH_QUEUE', () => {
it('clamps negative savedPlayIndex to 0', () => {
const state = {