fix(core,engine): guard volume probe cache and restore PCM cursor (#1119)

Two perf fixes caught in #1118 review:

1. Cache guard: probeAndCacheVolumeKeyframes now short-circuits when
   the element is already in volumeKeyframeCache. Without the guard
   every bindMediaMetadataListeners call (every 30 RAF ticks) re-probed
   all bound elements — N elements × full-composition timeline seeks at
   60 Hz regardless of whether keyframes were already known.
   bindRootTimelineIfAvailable still clears the cache on a new timeline
   capture so keyframes stay fresh when the composition is rebound.

2. PCM cursor: audioVolumeEnvelope.ts had the incremental segment
   cursor (O(N+M) overall) before #1118 extracted the interpolation into
   interpolateVolumeGain. The shared function restarts from segment=0 on
   each call — fine for the preview path (one call per RAF tick) but
   O(N×M) for the PCM path (one call per sample: 48 kHz × duration).
   Napkin math: a 10-min render went from ~30M to ~460M ops. Restored
   the inline incremental scan in the engine bake loop; engine now only
   imports normaliseEnvelope from core.
This commit is contained in:
Miguel Ángel
2026-05-29 10:33:10 -04:00
committed by GitHub
parent d3c333b383
commit 0f938841cd
2 changed files with 17 additions and 4 deletions
+4 -2
View File
@@ -967,9 +967,10 @@ export function initSandboxRuntimeModular(): void {
mediaDurationFloorSeconds: resolution.mediaDurationFloorSeconds ?? null,
},
});
// (Re-)probe all already-bound media elements now that a timeline is available.
// Elements bound before this point couldn't be probed in bindMediaMetadataListeners.
// (Re-)probe all already-bound media elements against the new timeline.
// Clear the cache first so elements probed against a prior timeline get fresh keyframes.
for (const el of metadataBoundMedia) {
volumeKeyframeCache.delete(el);
probeAndCacheVolumeKeyframes(el);
}
return true;
@@ -1283,6 +1284,7 @@ export function initSandboxRuntimeModular(): void {
};
const probeAndCacheVolumeKeyframes = (mediaEl: HTMLMediaElement) => {
if (volumeKeyframeCache.has(mediaEl)) return;
probeAndCacheElementVolume(
mediaEl,
state.capturedTimeline,