Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions packages/runtime/src/media/audio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,17 +228,26 @@ export class AudioDecoder {
}

public reset() {
this.release();

for (const node of this.audioNodes) {
node.stop();
}
this.audioNodes.clear();
}

/**
* Drops the decoding state but lets buffers that are already scheduled play out.
* `renderData` truncates every scheduled buffer at the clip's `trimEnd`, so the
* nodes stop on their own at the clip's out point and `onended` removes them.
*/
public release() {
if (!this.iterator) return;

this.iterator?.return();
this.iterator = null;
this.firstBuffer = null;
this.lastBuffer = null;

for (const node of this.audioNodes) {
node.stop();
}
this.audioNodes.clear();
}
}

Expand Down
7 changes: 7 additions & 0 deletions packages/runtime/src/systems/playback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,13 @@ function forwardAudioDecoder(world: World, scene: Entity, entity: Entity, audioS
relativeDelay: (origin / fps) + audioDelay,
});
framePromises(world)?.push(playPromise);
} else if (world.get(Mode)?.value !== 'realtime') {
// Offline rendering: the OfflineAudioContext runs behind the frame loop (the
// encoder lets it lag by up to a second of samples). Stopping the nodes here would
// cut the part of the clip the audio thread has not rendered yet, so every clip
// that ends before the scene does would lose its tail in the export. The scheduled
// buffers already end at the clip's out point, so let them finish.
decoder.release();
} else {
decoder.reset();
}
Expand Down