Skip to content

fix(core): preserve playhead during volume probing#2143

Merged
miguel-heygen merged 1 commit into
mainfrom
fix/issue-2141-volume-variable-admission
Jul 10, 2026
Merged

fix(core): preserve playhead during volume probing#2143
miguel-heygen merged 1 commit into
mainfrom
fix/issue-2141-volume-variable-admission

Conversation

@miguel-heygen

@miguel-heygen miguel-heygen commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

Fixes #2141

Summary

  • preserve the active GSAP timeline playhead while probing media volume automation
  • add a regression test covering restoration after sampling

Reproduction (HyperFrames#2141)

Using hyperframes@0.7.48, a composition with data-var-text="title", a GSAP opacity tween, and a later audio volume tween renders the default title when invoked with --variables '{"title":"OVERRIDE"}'. Removing only the volume tween renders OVERRIDE. The volume probe seeks the live timeline through the full duration and leaves it at the final sample, perturbing runtime initialization before capture.

Root cause

probeAndCacheElementVolume sampled the live timeline but did not restore its existing playhead. The probe therefore changed the timeline state as a side effect of metadata binding. This was our runtime behavior, not an invalid caller input.

Fix / verification

The probe now reads and restores the playhead after sampling. Added a jsdom regression test asserting the timeline position and media volume are restored while automation is still cached. Verified with bun run test -- src/runtime/mediaVolumeEnvelope.test.ts and bun run build in packages/core.

@james-russo-rames-d-jusso james-russo-rames-d-jusso left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — clean surgical fix. Leaving as a comment; stamp is James's call.

What I checked at 4c79bf2f:

  • Root cause matches the fix. probeAndCacheElementVolume runs during runtime init (called from init.ts:1641 via probeAndCacheVolumeKeyframes, which fires from bindMediaMetadataListeners). Its inner loop in probeElementVolumeKeyframes seeks the timeline from sampleStart to sampleEnd (bounded by compositionDuration) — for a 3-second composition that's ~180 sequential seeks ending at the composition tail. Read-then-restore around the probe is the right shape.
  • Signature change is correct. Making totalTime?/seek?'s t optional ((t?: number, ...)) mirrors GSAP's dual getter/setter API — .totalTime() returns the current playhead, .totalTime(x, true) seeks. Number(...) + Number.isFinite guard protects against non-numeric mock returns.
  • No parallel bug on the render side. probeElementVolumeKeyframes has only the one caller (the wrapper), and the engine's audioVolumeEnvelope.ts (offline PCM baking) receives pre-collected keyframes rather than seeking a live timeline, so it doesn't need the same fix.
  • Test asserts the right thing. Initial playhead 0.75, composition duration 1, mocked totalTime dual mode — after the probe, the test verifies playhead is back to 0.75 and audio.volume is restored, plus that a volume: 0 keyframe was captured (probe ran, restoration didn't obliterate the sampling). Solid unit-level coverage for the fix.
  • CI: initial run all SUCCESS across the CI/Windows/Player-perf/Preview/CodeQL/SDK/CLI/regression matrix; a newer run is churning on the same SHA (functionally clean).

Nits (non-blocking):

  • The Number(timeline.totalTime()) getter path returns unknown under the RuntimeTimelineRef type. The Number.isFinite(originalTime) gate correctly prevents a bogus restore, but if a caller ever passes a timeline whose getter yields a non-number (chained ref, etc.), the probe still perturbs the playhead and the restore silently no-ops. Not a real concern for GSAP, just something worth knowing.
  • The regression test covers the totalTime getter/setter path but not the seek-fallback branch. A one-line second it() with seek(next?) would round out coverage for the else if branch on lines 151–152 / 164–165. Optional.

Review by Rames D Jusso

@miguel-heygen miguel-heygen merged commit 16ab8b2 into main Jul 10, 2026
69 checks passed
@miguel-heygen miguel-heygen deleted the fix/issue-2141-volume-variable-admission branch July 10, 2026 21:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Timeline volume tween causes --variables to be silently ignored at render

2 participants