fix: defer the open crossfade so main no longer fades into a gap
Opening the preview faded the main track out at focus-acquire time, but the first preview clip only becomes audible after resolve/load/decode — so on the way IN the 340 ms main fade completed into silence before the preview arrived (a gap the user heard; the exit was already fine). The main fade is now deferred: focusBridge.requestSilence only arms the commitment and grants; coordinator.beginMainSilence performs the actual fade-pause, and the service fires it from afterChange on the opening session's first audible clip (or ITEM_FAILED). Main-cos-out then overlaps preview-sin-in — a true cross. resumeMain skips the fade-resume when the fade never fired (close before audible), and beginMainSilence re-checks the main is sounding before fading. The focus coordinator's acquisition logic and tests are untouched. Verified live: preview audible at 275 ms, main played until then, faded ~343 ms, paused at 618 ms and held frozen; close resumed from the exact position in 15 ms.
This commit is contained in:
1 parent
691fdcdc9a
commit
abc959ca2f
3 files changed
+88
-19
No files matched your search
@@ -99,7 +99,7 @@ function makeTransport() {
|
||||
}
|
||||
|
||||
function makeFocus({ grant = true, pausedByPreview = true } = {}) {
|
||||
const calls = { acquire: 0, release: 0, handoff: 0 };
|
||||
const calls = { acquire: 0, release: 0, handoff: 0, beginMainSilence: 0 };
|
||||
let live = null;
|
||||
return {
|
||||
calls,
|
||||
@@ -111,6 +111,9 @@ function makeFocus({ grant = true, pausedByPreview = true } = {}) {
|
||||
live = { id: `t${calls.acquire}`, pausedByPreview };
|
||||
return { granted: true, token: live };
|
||||
},
|
||||
beginMainSilence() {
|
||||
calls.beginMainSilence += 1;
|
||||
},
|
||||
release(token) {
|
||||
calls.release += 1;
|
||||
const ok = token === live;
|
||||
@@ -161,6 +164,24 @@ test("open: focus, settle, resolve, load audible, play", async () => {
|
||||
assert.ok(transport.log.some((e) => e[0] === "play"));
|
||||
});
|
||||
|
||||
test("open crossfade: main is NOT silenced until the first preview clip is audible", async () => {
|
||||
const { service, scheduler, focus, manifest } = makeService();
|
||||
await service.open({ sessionId: SID, manifest });
|
||||
// Focus is granted, but the clip hasn't resolved/played — the main track
|
||||
// must still be sounding (the whole point of the deferred fade).
|
||||
assert.equal(focus.calls.beginMainSilence, 0, "no main fade through resolve/load");
|
||||
await scheduler.flush(); // resolve → load → play → MEDIA_PLAYING
|
||||
assert.equal(service.getState().phase, PHASE.PLAYING);
|
||||
assert.equal(focus.calls.beginMainSilence, 1, "main fades exactly when the preview is audible");
|
||||
});
|
||||
|
||||
test("open crossfade: a parked (denied) open never fades the main track", async () => {
|
||||
const { service, scheduler, focus, manifest } = makeService({ grant: false });
|
||||
await service.open({ sessionId: SID, manifest });
|
||||
await scheduler.flush();
|
||||
assert.equal(focus.calls.beginMainSilence, 0, "parked preview leaves the main playing");
|
||||
});
|
||||
|
||||
test("open with focus denied: session opens PARKED, wait state surfaced", async () => {
|
||||
const { service, scheduler, transport, manifest } = makeService({ grant: false });
|
||||
await service.open({ sessionId: SID, manifest });
|
||||
|
||||
Reference in new issue
Block a user