fix(core): stop autoplay on load + propagate seek to sibling timelines#391
Open
jrusso1020 wants to merge 1 commit intomainfrom
Open
fix(core): stop autoplay on load + propagate seek to sibling timelines#391jrusso1020 wants to merge 1 commit intomainfrom
jrusso1020 wants to merge 1 commit intomainfrom
Conversation
miguel-heygen
approved these changes
Apr 21, 2026
Follow-up to #359 for the async-scene-loading path (compositions served as raw static files with `data-composition-src` children, as opposed to the `bundleToSingleHtml` studio path). ## Problem In the async-scene path, scenes visibly animate on page load while the master stays paused at 0:00 — the "autoplay on load" bug. Traced via a `paused`-setter proxy on each registered timeline: on init, each scene's `_ts` flipped 0→1 inside `ensureChildCandidatesActive` in `init.ts`, calling `timeline.paused(false)` on every child candidate. That helper predates #359 and was the only way a user-click Play would animate scenes — back when play didn't propagate through the registry. After #359, `createRuntimePlayer.play` already iterates every registry entry and un-pauses each, so the init-time un-pause is redundant for user-driven playback. It still matters for the producer's render path, though: there the master's `totalTime` cascade is the only driver (no GSAP ticker — virtual time), and a paused child won't re-render on cascade, so every producer regression baseline depends on children being un-paused at init time. ## Fix Scope the un-pause to render mode, signalled by `window.__HF_VIRTUAL_TIME__` (set by the producer's render-mode bootstrap script). Preview mode leaves children paused — no autoplay; play/pause still propagate via the registry iteration added in #359. Reproduced + verified locally with `Dockerfile.test`: - Before this fix, `overlay-montage-prod`, `style-4-prod`, `style-7-prod`, `style-8-prod`, `style-9-prod`, `style-17-prod`, `style-18-prod` all fail PSNR with 15-30 failed frames (scenes rendering at wrong times). - After this fix, all four run to 0/100 failed frames. ## Known limitation (follow-up) In preview mode, a scrub-back after a full playthrough can still leave async scenes parked at their end-state, because GSAP's cascade skips rendering paused children and the async loader isn't fully parenting scenes to the master. Tracked for a separate fix to the async composition loader (see `addMissingChildCandidatesToRootTimeline` in `init.ts`). ## Files - `packages/core/src/runtime/init.ts` — add `isRenderMode` guard around `ensureChildCandidatesActive` - `packages/core/src/runtime/player.ts` — unchanged from #359's registry iteration on play/pause - `packages/core/src/runtime/player.test.ts` — 2 new tests documenting that `seek`/`renderSeek` intentionally do NOT iterate the registry ## Testing - [x] `cd packages/core && bunx vitest run` — 490/490 pass (+2 new) - [x] `cd packages/player && bunx vitest run` — 35/35 pass (unchanged) - [x] `bun run typecheck` in core + player — clean - [x] `bunx oxlint` / `bunx oxfmt --check` clean across changed files - [x] `docker run hyperframes-producer:test style-17-prod` — 0/100 frames fail - [x] `docker run hyperframes-producer:test style-7-prod` — 0/100 frames fail - [x] `docker run hyperframes-producer:test style-4-prod` — 0/100 frames fail - [x] `docker run hyperframes-producer:test overlay-montage-prod` — 0/100 frames fail - [x] Live Vercel deploy verified: scenes stay paused at page load (autoplay fixed); play/pause propagates as expected Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
78d3e24 to
b759237
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #359 for the async-scene-loading path — compositions served as raw static files with
data-composition-srcchildren (e.g. the Vercel template), as opposed to thebundleToSingleHtmlstudio path.The bug
On page load,
<hyperframes-player>reports the master as paused at 0:00 but each scene visibly animates — the "autoplay on load" bug. Traced via apaused-setter proxy on each registered timeline: on init, each scene's_tsflipped 0→1 insideensureChildCandidatesActiveininit.ts, callingtimeline.paused(false)on every child.That helper predates #359 and was the only way a user-click Play would animate scenes — back when play didn't propagate through the registry. After #359,
createRuntimePlayer.playalready iterates every registry entry and un-pauses each, so the init-time un-pause is redundant for user-driven playback. It still matters for the producer's render path, though: there the master'stotalTimecascade is the only driver (no GSAP ticker — virtual time), and a paused child won't re-render on cascade, so every producer regression baseline depends on children being un-paused at init time.Fix
Scope the un-pause to render mode, signalled by
window.__HF_VIRTUAL_TIME__(set by the producer's render-mode bootstrap). Preview mode leaves children paused — no autoplay — and play/pause still propagate via the registry iteration added in #359.Known limitation (follow-up)
In preview mode, a scrub-back after a full playthrough can still leave async scenes parked at their end-state. Root cause is deeper:
addMissingChildCandidatesToRootTimelineininit.tsisn't actually reparenting async scene timelines to the master (inspection showsscene._dp === gsap.globalTimelineinstead ofrootTimelineafter theaddcall). GSAP's cascade skips paused non-children. Fixing that properly needs a separate change to the async composition loader and is tracked as a follow-up; the autoplay fix here is the narrow, safe piece.Files
packages/core/src/runtime/init.ts— addisRenderModeguard around the existingensureChildCandidatesActivecallpackages/core/src/runtime/player.ts— unchanged behavior from fix(player+core): correctly render and pause nested compositions #359;seek/renderSeekintentionally do NOT iterate the registry (clearer JSDoc explains why — iterating would overwrite GSAP's cascade with wrong absolute times)packages/core/src/runtime/player.test.ts— 2 new tests documenting that seek/renderSeek don't iterate the registryReproducing the regression failures
This PR's previous revision removed
ensureChildCandidatesActiveunconditionally, which broke 25 PSNR checkpoints acrossoverlay-montage-prod,style-4-prod,style-7-prod,style-8-prod,style-9-prod,style-17-prod,style-18-prod. Reproducing locally with:shows the same failures (would have caught it before push — lesson learned).
With the render-mode-scoped fix, all 4 tests I spot-checked run to
0/100 failed frames.Testing
cd packages/core && bunx vitest run— 490/490 pass (+2 new)cd packages/player && bunx vitest run— 35/35 pass (unchanged)bun run typecheckin core + player — cleanbunx oxlint/bunx oxfmt --checkclean across changed filesstyle-17-prod0/100,style-7-prod0/100,style-4-prod0/100,overlay-montage-prod0/100