Skip to content

fix(core): an element its script hides stays hidden while studio preview plays - #5458

Merged
miguel-heygen merged 3 commits into
mainfrom
fix/preview-top-video-black-while-playing
Oct 11, 2026
Merged

miguel-heygen merged 3 commits into
mainfrom
fix/preview-top-video-black-while-playing

Conversation

@miguel-heygen

@miguel-heygen miguel-heygen commented Oct 11, 2026 •

Copy link
Copy Markdown
Collaborator

What changes for a user

In Studio preview, a video no longer goes black while playing when the composition's own script hides a layer above it. Before, a full-frame backdrop that a script kept hidden was forced visible on every playing tick, so a talking-head under it showed only when paused (captions above the backdrop kept playing). Rendered video was never affected.

Before

Before, playing: the backdrop covers the video, only the caption shows

Before, paused: the video shows

After

After, playing: the video shows

After, paused: the video shows

Why

In Studio preview only, the runtime gives every untimed element with an id under the root a full-length timing, marked data-hf-autostamped, so the timeline can list it (init.ts, the stamping pass in bindCapturedTimeline). applyTimedElementVisibility then wrote style.visibility on every timed element on every tick, including those stamped ones, so it overwrote an author's visibility: hidden. While playing, that pass runs after the composition's own per-frame update, so "visible" won; on pause the composition's update ran last, so the layer hid again.

Stamped elements have no authored timing of their own, so the pass now leaves their visibility to the author. Hide-layer (data-hidden) handling and authored timed clips are unchanged, and an in-flow stamped element still collapses with display:none outside its window, as before. Clip layout already skips stamped elements for the same reason.

The stamp only counts in Studio preview: a stamp that ends up in a project file (copying a live element) no longer changes a clip's visibility in a render, and Studio's copy now strips it. In a render such a stamp still keeps the element in page flow, as before this change. Once a person moves, trims or re-lanes a stamped element in Studio's timeline, its timing is authored, so it must hide outside its new window like any clip. The mark's name and a helper that writes authored timing and drops the mark now live in core (studioPreviewMark.ts: RUNTIME_STAMPED_ATTR, applyAuthoredTiming), and Studio's one timing write onto the preview (patchIframeDomTiming) goes through it. The preview's hidden-image skip also leaves stamped elements alone, so images inside a container its script hides are still fetched ahead and a paused seek does not wait on them.

Renders never stamp (they run as the top-level page), so export output is unchanged.

Checked

  • init.test.ts: a stamped element re-timed through the same helper and rebind Studio uses hides outside its new window (hidden / visible / hidden at 1 s, 5 s, 8 s; fails without the mark removal); a paused seek does not wait on images in a stamped container its script hides (fails without the selector change). timelineEditingHelpers.test.ts: editing a stamped element's timing makes it authored; a lane-only move makes it authored too. init.test.ts: a render times a clip by its window even when its source carries the stamp. authoredSource.test.ts: copied live markup drops the stamp.
  • Earlier init.test.ts cases: a GSAP per-frame script that hides an untimed backdrop over a video stays hidden across 120 playing ticks (fails without the change, passes three runs in a row); a stamped element its author never hides still shows; an authored clip inside a hidden authored clip under a stamped wrapper stays hidden.
  • Core: runtime suite 2011 tests and full suite 4577 tests pass; core and Studio typecheck clean. Studio src/player and src/hooks: 3859 tests pass. oxlint, oxfmt, comment ratchet clean. Generated runtime bundles are unchanged by the build.
  • In headless Chrome on Linux with a 9:16 generated fixture (a moving test video under an untimed backdrop its script hides outside one window): before, the area reads as the backdrop while playing; after, it shows the video while playing and paused, and the backdrop still appears in its own window.

Not covered here

  • Re-timing an untimed top-level element in the timeline now takes it out of the page's normal layout flow straight away (as the render places it), not only after a reload.

  • A paused seek no longer waits for images inside a stamped container its script hides, so one frame can paint before they load.

  • Not run on macOS or in other browsers.

  • Other Studio suites and the CLI/engine suites were not run.

@github-actions

github-actions Bot commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Edit accuracy: accurate 2061 (base branch 2061), smooth 1607 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)

…ts new window

Studio timing edits drop the runtime-stamped mark, and a script-hidden stamped
container no longer holds back its images.
…es the render

Only the Studio preview honours the stamp, copied live markup drops it, and a
lane-only move makes a stamped element authored like the save does.

@jrusso1020 jrusso1020 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed at c7fa1fec. Approving, no blockers.

The fix is at the right layer. Stamped elements get a full-length window only so the timeline can list them, so the per-tick visibility pass has no timing of its own to enforce on them and now leaves visibility to the author. The stamp name moves into studioPreviewMark.ts as one constant, and there's one helper (applyAuthoredTiming) for "this element's timing is now authored". That reuses the existing preview-mark module instead of adding a new one.

Checked against the PR body

  • "Studio's copy now strips it": the strip comes from adding the stamp to PREVIEW_ONLY_ATTRS. That covers liveMarkupWithoutPreviewMarks (authoredSource.ts:102) and the layer-copy deny list (domEditingLayers.ts:59), so no change to authoredSource.ts was needed.
  • "Studio's one timing write onto the preview": every live timing write goes through patchIframeDomTiming. That's useTimelineClipTimingEditing.ts:134, 255 and useTimelineGroupEditing.ts:250, 387, so moves, trims, lane changes and media-offset edits all drop the stamp.
  • Renders are unchanged: the skip is gated on stampsForStudio (window.parent !== window), the same condition the stamping pass already used.
  • One side effect, and it's correct: init.ts:297 builds RUNTIME_MEDIA_ATTRS from PREVIEW_ONLY_ATTRS, so a stamp on a <video>/<audio> is now ignored when comparing a media element's written shape. It's a runtime write, so it belongs in that set.

Run at this head

  • core init.test.ts: 233 pass. studio timelineEditingHelpers and authoredSource: 27 pass.
  • Mutants:
    • Forcing isRuntimeStamped = false (reverting the fix) fails a test.
    • Dropping the stampsForStudio guard fails the render test.

— Rames

@miguel-heygen
miguel-heygen added this pull request to the merge queue Oct 11, 2026
Merged via the queue into main with commit 2fbaef6 Oct 11, 2026
187 of 200 checks passed
@miguel-heygen
miguel-heygen deleted the fix/preview-top-video-black-while-playing branch October 11, 2026 17:01
lovicho pushed a commit to lovicho/hyperframes that referenced this pull request Oct 11, 2026
…hile the preview plays (heygen-com#5475)

A 9:16 fixture with a looping A-roll video, an untimed full-frame backdrop above
it that the film's own GSAP script shows only from 6 s to 8 s, captions on top
and two sub-compositions. The check plays it in Studio's preview and reads the
preview's pixels and the element on top at the frame's centre: the video must
show at rest and while playing outside the backdrop's window, and the backdrop
must show inside it. It fails with heygen-com#5458's visibility skip reverted.
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.

2 participants