fix: gapless player wiped the queue on natural track end (stale closure)
The engine is created once (mount effect), so its onIndex/onEndOfQueue callbacks
captured the first-render syncIndex/setPlaying. useGlobal's setData reads the
`data` from the render that created the setter when given a functional updater —
that's the INITIAL empty state. So when a track ended and the engine called
syncIndex(i) → setState(s => ({...s, index:i})), `s` was {queue:[], index:0}:
the queue got wiped, `current` went undefined, playback stopped and the dock
vanished. Manual track selection was unaffected because playQueue passes a plain
object (no stale `data` read) — which is why it seemed to work.
Route the two engine-invoked setters through refs kept current each render, so a
natural advance mirrors into the live state instead of the stale initial one.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -60,6 +60,13 @@ export const MusicPlayerHost = () => {
|
|||||||
const isRestoringRef = useRef(false);
|
const isRestoringRef = useRef(false);
|
||||||
const seekToRef = useRef<number | null>(null);
|
const seekToRef = useRef<number | null>(null);
|
||||||
const engineIndexRef = useRef(0);
|
const engineIndexRef = useRef(0);
|
||||||
|
// The engine is created once, so its callbacks would capture first-render closures. useMusicPlayer's
|
||||||
|
// functional setters (syncIndex/setPlaying) read the state captured at THAT render (the initial EMPTY
|
||||||
|
// queue) — calling them from a stale closure wipes the queue. Route them through refs kept current.
|
||||||
|
const syncIndexRef = useRef(syncIndex);
|
||||||
|
syncIndexRef.current = syncIndex;
|
||||||
|
const setPlayingRef = useRef(setPlaying);
|
||||||
|
setPlayingRef.current = setPlaying;
|
||||||
|
|
||||||
const withToken = (u: string) => (token ? `${u}${u.includes('?') ? '&' : '?'}token=${encodeURIComponent(token)}` : u);
|
const withToken = (u: string) => (token ? `${u}${u.includes('?') ? '&' : '?'}token=${encodeURIComponent(token)}` : u);
|
||||||
const streamUrl = (t: PlayerTrack) =>
|
const streamUrl = (t: PlayerTrack) =>
|
||||||
@@ -92,9 +99,9 @@ export const MusicPlayerHost = () => {
|
|||||||
},
|
},
|
||||||
onIndex: (i) => {
|
onIndex: (i) => {
|
||||||
engineIndexRef.current = i; // engine advanced on its own → mirror to UI without restarting
|
engineIndexRef.current = i; // engine advanced on its own → mirror to UI without restarting
|
||||||
syncIndex(i);
|
syncIndexRef.current(i);
|
||||||
},
|
},
|
||||||
onEndOfQueue: () => setPlaying(false),
|
onEndOfQueue: () => setPlayingRef.current(false),
|
||||||
onLoadingChange: setLoading,
|
onLoadingChange: setLoading,
|
||||||
});
|
});
|
||||||
engineRef.current = engine;
|
engineRef.current = engine;
|
||||||
|
|||||||
Reference in New Issue
Block a user