Skip to content

fix(engine): a render no longer decodes the videos it replaces with extracted frames - #6142

Merged
miguel-heygen merged 4 commits into
mainfrom
fix/render-skips-preload-of-extracted-video
Oct 23, 2026
Merged

miguel-heygen merged 4 commits into
mainfrom
fix/render-skips-preload-of-extracted-video

Conversation

@miguel-heygen

Copy link
Copy Markdown
Collaborator

What

A render no longer starts a video decoder for each video it replaces with extracted frames. A film cut into 163 pieces from one 1080p source renders in 1.4 GB of Chrome memory instead of 6.9 GB, with identical output.

Why

For a video the renderer replaces with frames extracted by ffmpeg, the page's own decoder never supplies a pixel; the renderer already skips waiting for these videos (skipReadinessVideoIds). But the page still decoded every one: Chrome starts a parsed <video> at its default preload, and the runtime then forced preload="auto" and load() on each (preloadMedia, and the per-tick promotion in syncRuntimeMedia). Chrome keeps one decoder per element, so memory grows with the number of clips, not the number of distinct sources.

Measured on the devbox, one worker, 1280x720 output from one 1080p source, whole process tree sampled every 0.5 s (peak RSS):

Clips in the film main this PR
1 1.4 GB
20 2.1 GB
80 4.1 GB 1.4 GB (with preload="none" authored; default attributes behave as the 163 case)
163 6.9 GB (Chrome 6.6 GB) 1.4 GB (Chrome 1.1 GB)

About 34 MB per extra clip on main, none with the fix. The 163-clip output is the same 490 frames with the same per-frame hash before and after.

Related work

Preview already loads only media near the playhead (#4682, #5716); this is the render-side equivalent. Preview is untouched: the new behaviour needs __HF_RENDER_FRAME_VIDEO_IDS, which only a render sets.

How

  • engine (renderFrameVideoHold.ts, frameCapture.ts): before any page script, the render hands the page the ids of the videos it replaces and a MutationObserver holds each at preload="none" the moment it is parsed (also for sub-compositions that add them later). It has to be at parse time: Chrome ignores a none set once the fetch has started.
  • core runtime (preloadMedia.ts, media.ts): preloadMedia and the per-tick preload promotion leave those videos alone. One predicate, isRenderFrameVideo, owns the rule.

Videos the render does not replace (extraction failed, or not extracted) are not in the list and load as before, so readiness waits and native decoding are unchanged for them.

Test plan

  • Failing first: preloadMedia.test.ts (a render-frame video is neither preloaded nor loaded; other videos and audio with the same id still are), media.test.ts (the per-tick sync keeps it at none), and the real-Chrome renderFrameVideoHold.browser.test.ts (listed ids parse at none at the top level, nested, and when added later; the unlisted one stays metadata). All fail against a no-op/old version and pass; core runtime 2342/2342 and the browser file 2/2, three runs in a row.
  • Engine frameCapture suites 300/300; tsc --noEmit clean in core and engine; oxfmt and oxlint clean.
  • Render and capture readiness: the 163-clip render reaches ready in about 1 s (unchanged), 490/490 frames identical.
  • Seek and playback in preview are untouched (the id list is never set outside a render).
  • Documentation updated (n/a)
  • Comments follow CONTRIBUTING.md "Comments"

@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Edit accuracy: accurate 271 (base branch 271), smooth 128 of those

The gate passes.
Smoothness is reported in the artifact, not gated. A case fails only if it fails 2 of 3 runs.

Quarantined, measured but not gated (0)

Unstable (2)

  • seqplainkeys-none-pct-r0-root-z100: tracking 0.06, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo false, teleport true / tracking 0.06, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo true, teleport true / tracking 0.06, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo true, teleport true
  • seqplainkeys-none-pct-r0-nested-z100: tracking 0.05, pressJump 0, drop 0, reload 0, render 0.02, renderKey -, undo false, teleport true / tracking 0.05, pressJump 0, drop 0, reload 0, render 0.02, renderKey -, undo true, teleport true / tracking 0.05, pressJump 0, drop 0, reload 0, render 0.02, renderKey -, undo true, teleport true

@miguel-heygen
miguel-heygen force-pushed the fix/render-skips-preload-of-extracted-video branch from 0e11989 to c2d1e76 Compare October 23, 2026 15:43

@heygengenesis heygengenesis Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Verdict: REQUEST CHANGES · 1 blocker · 0 important · 0 nits · head f387a354

The hold and isRenderFrameVideo look a video up by el.id. skipReadinessVideoIds holds render ids (data-hf-render-id, see core/src/compiler/mediaRenderIds.ts). When an id appears in two scenes, the second element is clip__hf2, but both elements still have id="clip". So an element that isn't replaced can be held at preload=none and render blank. ↓ inline

Scope

Read all 7 files. Traced where the skip list comes from (collectVideoReadinessSkipIds, renderChunk.ts:684) and how ids are bridged (mediaRenderIdBridge.ts). The existing pollVideosReady filter also keys on v.id. That's pre-existing, and its only effect there was not waiting on a video. CI was still running at review time.

@heygengenesis heygengenesis Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Verdict: APPROVE — 1 resolved, 0 open, 0 new · f387a35 → a78d3f7

@miguel-heygen
miguel-heygen force-pushed the fix/render-skips-preload-of-extracted-video branch from a78d3f7 to 6f52a79 Compare October 23, 2026 19:37
@miguel-heygen
miguel-heygen force-pushed the fix/render-skips-preload-of-extracted-video branch from 6f52a79 to b4823b8 Compare October 23, 2026 20:19

@heygengenesis heygengenesis Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Verdict: APPROVE — 0 resolved, 0 open, 0 new · a78d3f7 → b4823b8 (rebase; PR patch identical apart from the #6146 import context line)

@miguel-heygen
miguel-heygen added this pull request to the merge queue Oct 23, 2026
Merged via the queue into main with commit 01a523b Oct 23, 2026
80 checks passed
@miguel-heygen
miguel-heygen deleted the fix/render-skips-preload-of-extracted-video branch October 23, 2026 21:10
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.

1 participant