fix(rtmg/web): reconnect keeps playing the stale audio-source track#274
Closed
qianghan wants to merge 1 commit into
Closed
fix(rtmg/web): reconnect keeps playing the stale audio-source track#274qianghan wants to merge 1 commit into
qianghan wants to merge 1 commit into
Conversation
…ing an outage doesn't keep playing stale When the WS drops and the user switches to a different audio source before the auto-reconnect completes, the recovered session kept playing the previously-loaded track even though the UI showed the new selection. Root cause: the reconnect path (useStartSession) rebinds the fixture snapshotted at session start. The mid-outage track change is dropped because useFixtureSwap.run() bails while status !== "ready", and nothing re-applies it once the session recovers — so the recovered backend + AudioPlayer stay bound to the stale track while the perf store's fixture shows the new pick. Fix: track the live session's bound fixture in useSessionStore (boundFixture), set on initial connect and reconnect. useFixtureSwap now reconciles on the "reconnecting" -> "ready" edge: if the selected fixture diverged from the bound one, it re-runs the swap exactly once. No effect on a fresh Play or a clean reconnect where the selection never changed. Adds needsFixtureReconcile() + a focused unit test pinning the decision matrix, and asserts boundFixture clears on session reset. Co-authored-by: Cursor <cursoragent@cursor.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bug
After an abnormal WS close, if the user switches to a different audio source during the auto-reconnect window, the recovered session keeps playing the previously-loaded track even though the UI now shows the new selection.
Root cause analysis
The reconnect path snapshots the fixture at session start and never reconciles it against the user's current selection on recovery:
Snapshot at connect.
useStartSessionresolves the active fixture once and freezes it intosessionFixture(useStartSession.ts,resolveFixtureForConnect+ theconst sessionFixturesnapshot). The reconnect factory (buildAndConnect) rebuilds every backoff attempt from that snapshot — by design, to avoid re-decoding multi-MB uploads on every retry. Its comment even asserts "the user can't have changed fixtures while a 'Reconnecting…' placard is up", which is exactly the wrong assumption here.Mid-outage track change is dropped. When the user picks a new track,
usePerformanceStore.fixtureupdates anduseFixtureSwap's subscription callsrun(name). Butrun()early-returns whensession.status !== "ready"(useFixtureSwap.ts): during"reconnecting"the swap is silently dropped andlastSwappedTois left untouched. Nothing re-applies it once the socket recovers.Recovery rebinds the stale track. On success the reconnect factory connects with the snapshot config and calls
player.swap(remote.initialBuffer)— i.e. the backend re-encodes and the player swaps to the old track's clean source. The UI (driven byusePerformanceStore.fixture) shows the new track; audio + server session are pinned to the stale one.Symptom vs. cause: the symptom is stale playback after reconnect; the true cause is that the session's bound fixture (snapshot) and the UI's selected fixture can diverge during an outage, and the reconnect completion never reconciles them.
Note: stem source-mode (full/vocals/instruments) changes during an outage were already safe, because the reconnect's
buildConfigre-readsresolveSourceMode()live — only the fixture name (and its decoded PCM) is snapshotted, which matches the report ("the track that was loaded").Fix plan
boundFixturetouseSessionStore— the single source of truth for the track the live session is actually bound to, set on initial connect and on every reconnect, cleared onreset().useFixtureSwap, reconcile on the"reconnecting"→"ready"edge: if the selected fixture diverged fromboundFixture, re-run the swap exactly once (the existing, contract-respecting swap path).boundFixturein sync after a successful swap.needsFixtureReconcile():selected === bound→ no swap.idle/connecting→ready): never the reconnecting→ready edge → no swap;useStartSessionalready binds the current selection.gen_wire_types.pyregen needed (client-only state).Code change summary
store/useSessionStore.ts: newboundFixturefield +setBoundFixture, cleared onreset().hooks/useStartSession.ts: recordboundFixtureon initial connect and (before status flips to ready) on reconnect.hooks/useFixtureSwap.ts: export pureneedsFixtureReconcile(); subscribe to the session store and reconcile on reconnect→ready; updateboundFixtureafter a swap.tests/unit/fixtureReconnectReconcile.test.ts(decision matrix);tests/unit/sessionStore.test.tsassertsboundFixtureclears on reset.Verification
npm run typecheck→ clean.npm run build→✓ Compiled successfully,Finished TypeScript, static pages generated.npx vitest run tests/unit/fixtureReconnectReconcile.test.ts tests/unit/sessionStore.test.ts→ 13 passed.Fails-before evidence: with the source changes reverted but the new test present, all 5
needsFixtureReconcilecases fail (helper absent); they pass with the fix applied.The only failing tests in the suite (
sliceEpoch,float16,replay"refs cache") are a pre-existingMath.f16round is not a functionenvironment gap in the local Node build — unrelated to this change and failing identically on the base commit.Made with Cursor