mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 12:54:29 +00:00
* fix(producer): give inlined media a document-unique render id
Element ids are unique per composition file, but the render document is
the inlined union of every file. The producer merged the per-file media
lists and deduplicated by id, so clips that shared an id collapsed into a
single entry, and every id-keyed stage (extract, inject, visibility,
bounds) resolved to whichever element came first in the document. The
surviving clip's frames landed on the wrong element and the visible scene
rendered without footage.
Two shapes hit this, and neither is author error:
- Two scenes that each declare `<video id="clip">`. Legal per file, and
unavoidable when a scene is duplicated into a copy with inner ids
kept, or when one file is mounted twice.
- Two scenes that each declare a bare `<video>`. The timing compiler
numbers auto-ids per file, so both arrive as `hf-video-0` with no
authored id involved at all.
Stamp a document-unique `data-hf-render-id` while inlining, and read the
media list off the inlined document instead of merging per-file lists.
The render id equals the element id whenever that id is already unique,
so documents without a collision keep identical pipeline keys.
Author `id` attributes are left alone: 158 of the 161 registry blocks
reference their own ids from `#id` CSS or getElementById, so renaming
would trade broken footage for broken styling. The engine resolves media
elements through the render id instead, falling back to getElementById
for documents the producer never compiled.
Collecting from the inlined document also retires the per-file media
extraction in parseSubCompositions along with its offset bookkeeping;
host offsets are recovered from the composition hosts the clip sits in.
* fix(core): resolve render-frame siblings by render id in the runtime
The injector creates each `__render_frame_<id>__` sibling from the media
element's render id, but four runtime readers still built that id from the
plain `el.id`. On a document where two compositions share a media id, all
of them resolved the first collider's frame.
colorGrading is the one that changes pixels: findRenderFrameImage returns
the image the grading pass samples, with no class check to catch the
mismatch, so the second video was graded from the first one's frame.
media, mediaProxy and video-texture-compat use it as a render-mode or
substitute-source signal, where both colliders happen to agree during
render, but none of them should rest on that.
Add renderFrameSibling as the single owner of "which frame belongs to
this element" and route all four through it. It reads the stamped render
id and falls back to the author id, so a collision-free document resolves
exactly as before and an uncompiled one (preview, snapshot, check) is
unchanged.
The engine's in-page bridge keeps its own copy of the rule because code
serialized into page.evaluate cannot import; it now names core as the
definition, and a test pins the sibling-id format both sides build so
they cannot drift apart silently.
* refactor(engine): build render-frame sibling ids from core's definition
The drift guard named both sides but pinned one. renderFrameSibling.test
asserts core's format, while the engine rebuilt the same id from a literal
template at six independent sites. Changing the format on either side left
the test green and every runtime reader silently unable to find its frame —
this PR's own failure mode, one level up.
Export the affixes and renderFrameIdForRenderId from core, and take the id
from there at all six. Four sites resolve it on the Node side, where the
engine can import; the two that iterate the DOM in-page receive the affixes
as evaluate arguments, which avoids depending on bridge install order.
Also switch two `__hfMediaId?.(el) ?? el.id` reads to `||`. The bridge
returns "" for an element with neither id, so `??` kept the empty string
and built `__render_frame___`, which no reader looks for. Inert today
because the compiler assigns positional ids to id-less timed media, but it
made the two sides disagree in the one case they could.
60 lines
2.5 KiB
TypeScript
60 lines
2.5 KiB
TypeScript
/**
|
|
* Resolve the injected render-frame `<img>` that stands in for a `<video>`
|
|
* during render.
|
|
*
|
|
* The producer's frame-injection pipeline hides each `<video>` and paints from
|
|
* a sibling `<img id="__render_frame_<renderId>__">`. That id is derived from
|
|
* the element's *render* id, not its author id: author ids are only unique
|
|
* within one composition file, and the render document is the inlined union of
|
|
* many, so two scenes can carry the same `<video id="clip">`. Deriving from
|
|
* `el.id` there resolves every collider to the first one's frame — the reader
|
|
* silently reads another clip's pixels.
|
|
*
|
|
* This module owns that derivation for the runtime. The engine mirrors the same
|
|
* rule in-page (`mediaRenderIdBridge`), because code shipped into
|
|
* `page.evaluate` cannot import; `renderFrameElementId` is the definition both
|
|
* sides follow, and its format is pinned by test.
|
|
*/
|
|
|
|
import { MEDIA_RENDER_ID_ATTR } from "../compiler/mediaRenderIds.js";
|
|
|
|
/**
|
|
* The render id an element is addressed by: its stamped, document-unique id
|
|
* when the producer compiled this document, else its author id. The two are
|
|
* the same string whenever no collision forced a suffix.
|
|
*/
|
|
export function readMediaRenderId(media: Element): string | null {
|
|
return media.getAttribute(MEDIA_RENDER_ID_ATTR) || media.id || null;
|
|
}
|
|
|
|
/**
|
|
* Affixes of the sibling id. Exported so the engine can build the same id
|
|
* inside `page.evaluate`, where it cannot import: it passes these through as
|
|
* evaluate arguments rather than repeating the literals. Keeping them here is
|
|
* what makes this module the one definition of the format.
|
|
*/
|
|
export const RENDER_FRAME_ID_PREFIX = "__render_frame_";
|
|
export const RENDER_FRAME_ID_SUFFIX = "__";
|
|
|
|
/** The id of the render-frame `<img>` paired with a given render id. */
|
|
export function renderFrameIdForRenderId(renderId: string): string {
|
|
return `${RENDER_FRAME_ID_PREFIX}${renderId}${RENDER_FRAME_ID_SUFFIX}`;
|
|
}
|
|
|
|
/** The id of the render-frame `<img>` paired with a media element. */
|
|
export function renderFrameElementId(media: Element): string | null {
|
|
const renderId = readMediaRenderId(media);
|
|
return renderId ? renderFrameIdForRenderId(renderId) : null;
|
|
}
|
|
|
|
/**
|
|
* The injected render-frame `<img>` for a media element, or null in preview
|
|
* (where the producer never creates one).
|
|
*/
|
|
export function findInjectedRenderFrame(media: Element): HTMLImageElement | null {
|
|
const frameId = renderFrameElementId(media);
|
|
if (!frameId) return null;
|
|
const frame = document.getElementById(frameId);
|
|
return frame instanceof HTMLImageElement ? frame : null;
|
|
}
|