fix: make soundtrack loading terminal

This commit is contained in:
2026-09-04 16:25:29 +03:00
parent 5172b5bc4b
commit 1431e65768
2 changed files with 28 additions and 10 deletions

View File

@@ -387,7 +387,9 @@ export function createSoundtrackEngine(
mainSource = main;
mainSchedule = schedule;
attachMainEnded(main);
for (const source of introSourceNodes) {
const committedIntroSources = introSourceNodes;
introSourceNodes = [];
for (const source of committedIntroSources) {
source.stopAt(transitionTime + EXIT_CROSSFADE_SECONDS);
}
loadedIntroBuffer = null;
@@ -456,22 +458,24 @@ export function createSoundtrackEngine(
void mainPromise.catch(() => undefined);
if (activatedFromStart) {
loadedIntroBuffer = await dependencies.loadBuffer(context, sources.intro, signal);
const [introBuffer, mainBuffer] = await Promise.all([
dependencies.loadBuffer(context, sources.intro, signal),
mainPromise,
]);
if (closed || signal.aborted) throw abortError();
loadedIntroBuffer = introBuffer;
if (latestChapter.atStart) {
transportStarted = true;
startIntro(loadedIntroBuffer, context.currentTime + SOURCE_LEAD_SECONDS);
return;
}
transportStarted = true;
startDirectMain(mainBuffer);
return;
}
const mainBuffer = await mainPromise;
if (closed || signal.aborted) throw abortError();
if (activatedFromStart && latestChapter.atStart && loadedIntroBuffer !== null) {
transportStarted = true;
startIntro(loadedIntroBuffer, context.currentTime + SOURCE_LEAD_SECONDS);
return;
}
transportStarted = true;
startDirectMain(mainBuffer);
};
@@ -514,6 +518,16 @@ export function createSoundtrackEngine(
mixer.dispose();
for (const source of [...ownedSources]) source.cancel();
ownedSources.clear();
pendingIntroExit = null;
introSourceNodes = [];
introProgramGain = null;
mainSource = null;
driveLiftSource = null;
afterglowSource = null;
mainSchedule = null;
mainBufferPromise = null;
loadPromise = null;
exitLoadRequest = null;
context.removeEventListener('statechange', onStateChange);
stateListeners.clear();
outputGain.disconnect();

View File

@@ -306,14 +306,18 @@ describe('soundtrack engine', () => {
expect(test.mixer.setDriveEnergy).toHaveBeenLastCalledWith(sample);
});
it('keeps dominant intro and direct-main load failures terminal', async () => {
it('keeps dominant intro and main load failures terminal on every activation path', async () => {
const intro = createEngineHarness(initialState, { rejectIntro: true });
await expect(intro.graph.resume()).rejects.toThrow('/intro.opus unavailable');
const main = createEngineHarness({
const startMain = createEngineHarness(initialState, { rejectMain: true });
await expect(startMain.graph.resume()).rejects.toThrow('/main.opus unavailable');
expect(startMain.sourcesFor('/intro.opus')).toHaveLength(0);
const directMain = createEngineHarness({
chapter: resolveChapterIntent('profile'),
drive: { energy: 0, source: 'idle' },
}, { rejectMain: true });
await expect(main.graph.resume()).rejects.toThrow('/main.opus unavailable');
await expect(directMain.graph.resume()).rejects.toThrow('/main.opus unavailable');
});
it('stops and disconnects each owned source exactly once when closed twice', async () => {