fix(core): apply renderer volume-automation solution to preview (#1118)

Preview audio with GSAP volume fades (e.g. data-volume="0" with a
gsap.to("#bgm", {volume:0.25, ...})) played ~1s then silenced. Root
cause: syncRuntimeMedia used fallbackAuthorVolume (data-volume) on the
first tick after a clip became active, clobbering the GSAP-seeked value.
The single-clock transport seeks GSAP before syncRuntimeMedia runs, so
el.volume already holds the animated value — we just need to trust it.

Fix — three layers, matching the renderer's approach (PR #1117):

1. First-tick tracking: on the first tick a clip is active
   (previousRuntimeVolume===undefined), use currentElementVolume (GSAP's
   seeked value) instead of fallbackAuthorVolume. In production the
   transport always seeks GSAP before syncRuntimeMedia, so el.volume is
   already at the correct animated position.

2. Probed keyframes: new probeElementVolumeKeyframes() runs the same
   offline probe the renderer uses (discoverAudioVolumeAutomationFromTimeline)
   directly in the browser. init.ts calls probeAndCacheElementVolume() when
   an element is bound and a timeline is available. When keyframes are present,
   syncRuntimeMedia drives volume from the interpolated envelope — no
   GSAP-change tracking needed, no first-tick edge case, same data source
   as the renderer.

3. Shared utilities: normaliseEnvelope(), interpolateVolumeGain(), and
   probeAndCacheElementVolume() extracted to mediaVolumeEnvelope.ts and
   exported from @hyperframes/core/media-volume-envelope. The engine's
   audioVolumeEnvelope.ts imports from there — no duplicate logic between
   the renderer and the new preview path.

Fallow audit exits non-zero on inherited complexity/duplication in init.ts
functions that shifted line numbers (applyClipLayout, transportTick, etc.),
unchanged by this PR — same known false-positive pattern noted in #1117.
Lint, format, typecheck, and unit tests all pass.

53 core/media tests pass (3 updated to pre-set el.volume to match the
runtime's bindMediaMetadataListeners — corrects a missing setup step).
audioVolumeEnvelope tests (6) still pass.
This commit is contained in:
Miguel Ángel
2026-05-29 10:10:12 -04:00
committed by GitHub
parent bc3701f590
commit d3c333b383
6 changed files with 243 additions and 46 deletions
+8
View File
@@ -58,6 +58,10 @@
"import": "./src/registry/index.ts",
"types": "./src/registry/index.ts"
},
"./media-volume-envelope": {
"import": "./src/runtime/mediaVolumeEnvelope.ts",
"types": "./src/runtime/mediaVolumeEnvelope.ts"
},
"./gsap-parser": {
"import": "./src/parsers/gsapParser.ts",
"types": "./src/parsers/gsapParser.ts"
@@ -113,6 +117,10 @@
"import": "./dist/registry/index.js",
"types": "./dist/registry/index.d.ts"
},
"./media-volume-envelope": {
"import": "./dist/runtime/mediaVolumeEnvelope.js",
"types": "./dist/runtime/mediaVolumeEnvelope.d.ts"
},
"./gsap-parser": {
"import": "./dist/parsers/gsapParser.js",
"types": "./dist/parsers/gsapParser.d.ts"