mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-22 14:35:41 +00:00
fix(orbit): make initial-sync seek visually stick on join (#528)
* fix(orbit): make initial-sync seek visually stick on join Reported: "I join the room, the waveform shows the host's live position for a second or two, then snaps back to 0:00 and audio is still playing from the start. Only after that snap can I press Catch Up." Two compounding causes in `useOrbitGuest`: **1. Poll fires `applyMirror` before the engine is genuinely playing.** `playTrack` flips `isPlaying` to `true` *synchronously* in its optimistic store write, so the post-`playTrack` poll satisfied its "engine ready" check before the Tauri `audio_play` had even started producing sample. The `seek` inside `applyMirror` updates the store position immediately (waveform jumps to host's live position), but `audio_seek` is debounced and lands on a not-ready engine — it silently no-ops. The engine then starts playing from 0, and its first progress events overwrite the optimistic seek position, snapping the waveform back. Add `currentTime > 0.1` to the poll's "engine genuinely playing" condition: once audio has flowed past the cold-start barrier, the seek commits and the seek-target guard correctly filters subsequent progress events. **2. `applyMirror`'s play-state mirror raced its seek**, same shape as the `onCatchUp` race fixed in #527. `player.seek` debounces `audio_seek` via setTimeout(0) while `pause`/`resume` invoke synchronously — pause arriving first leaves the engine paused at the old position. Defer the play-state mirror by 200 ms so the seek lands first. * docs(changelog): add initial-sync seek-stickiness bullet
This commit is contained in:
committed by
GitHub
parent
6c5465e9c7
commit
eae649bcad
@@ -91,8 +91,20 @@ export function useOrbitGuest(): void {
|
||||
const p = usePlayerStore.getState();
|
||||
if (cancelled || p.currentTrack?.id !== trackId) return false;
|
||||
p.seek(calcFraction());
|
||||
if (hostState.isPlaying && !p.isPlaying) p.resume();
|
||||
else if (!hostState.isPlaying && p.isPlaying) p.pause();
|
||||
// Defer the play-state mirror so the seek's `audio_seek` invoke
|
||||
// arrives at the engine before pause/resume. `player.seek` is
|
||||
// debounced via setTimeout(0); `pause`/`resume` fire their
|
||||
// invokes synchronously — without the delay the play-state
|
||||
// change can race ahead of the seek and leave the engine in
|
||||
// the wrong position.
|
||||
if (hostState.isPlaying !== p.isPlaying) {
|
||||
window.setTimeout(() => {
|
||||
const fresh = usePlayerStore.getState();
|
||||
if (cancelled || fresh.currentTrack?.id !== trackId) return;
|
||||
if (hostState.isPlaying && !fresh.isPlaying) fresh.resume();
|
||||
else if (!hostState.isPlaying && fresh.isPlaying) fresh.pause();
|
||||
}, 200);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -132,7 +144,21 @@ export function useOrbitGuest(): void {
|
||||
if (cancelled) { resolve(false); return; }
|
||||
const p = usePlayerStore.getState();
|
||||
const trackReady = p.currentTrack?.id === trackId;
|
||||
if (trackReady && p.isPlaying) { resolve(applyMirror()); return; }
|
||||
// Wait for the engine to *actually* be playing, not just for
|
||||
// `isPlaying = true` (which `playTrack` flips synchronously
|
||||
// before the audio engine has produced a single sample).
|
||||
// Also require `currentTime > 0.1` — once audio has flowed
|
||||
// past the cold-start barrier, the engine is genuinely
|
||||
// playing and a `seek` will commit. Without this check the
|
||||
// seek inside `applyMirror` lands on a not-yet-ready engine,
|
||||
// silently no-ops, and the engine's first progress events
|
||||
// overwrite the optimistic store position — the visible
|
||||
// symptom on join is "the waveform shows the host's live
|
||||
// position for a second, then snaps back to 0:00".
|
||||
const enginePlaying = trackReady
|
||||
&& p.isPlaying
|
||||
&& (p.currentTime ?? 0) > 0.1;
|
||||
if (enginePlaying) { resolve(applyMirror()); return; }
|
||||
if (Date.now() >= deadline) { resolve(false); return; }
|
||||
window.setTimeout(poll, 100);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user