mirror of
https://github.com/navidrome/navidrome.git
synced 2026-05-03 06:51:16 +00:00
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:
parent
bdea9ed6a1
commit
2307a64da7
@ -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,
|
||||
|
||||
@ -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 = {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user