mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-11 06:30:03 +00:00
fix(core,producer): stamp render ids on empty-src media and pair the snapshot by them (#3513)
Residual of #3340: runtime-assigned src is skipped by the static parse, so the browser snapshot was still keying clips by author id. Colliding scenes collapsed onto one window.
This commit is contained in:
@@ -107,6 +107,16 @@ describe("discoverMediaFromBrowser", () => {
|
||||
);
|
||||
expect(media[0]).toMatchObject({ start: 0, end: 2, duration: 2, mediaStart: 1 });
|
||||
});
|
||||
|
||||
it("reports colliding empty-src videos by render id, not author id", async () => {
|
||||
const media = await discover(
|
||||
`<video id="clip" data-hf-render-id="clip" src="" data-start="0" data-end="4" data-media-start="10"></video>` +
|
||||
`<video id="clip" data-hf-render-id="clip__hf2" src="" data-start="4" data-end="8" data-media-start="40"></video>`,
|
||||
{},
|
||||
);
|
||||
expect(media.map((entry) => entry.id)).toEqual(["clip", "clip__hf2"]);
|
||||
expect(media.map((entry) => entry.mediaStart)).toEqual([10, 40]);
|
||||
});
|
||||
});
|
||||
|
||||
function validTestMediaResponse(): Response {
|
||||
@@ -2811,6 +2821,31 @@ describe("duplicate media ids across nested compositions", () => {
|
||||
expect(compiled.audios[0]).toMatchObject({ start: 0, end: 3, mediaStart: 5 });
|
||||
expect(compiled.audios[1]).toMatchObject({ start: 3, end: 6, mediaStart: 50 });
|
||||
});
|
||||
|
||||
it("stamps unique render ids on empty-src videos across two scenes", async () => {
|
||||
const { projectDir, indexPath } = writeTwoSceneProject(
|
||||
"scene-a.html",
|
||||
"scene-b.html",
|
||||
(label, mediaStart) =>
|
||||
`<div data-composition-id="${label}" data-start="0" data-duration="3"
|
||||
data-width="640" data-height="360">
|
||||
<video id="clip" src="" data-start="0" data-duration="3"
|
||||
data-media-start="${mediaStart}" data-track-index="0"></video>
|
||||
</div>`,
|
||||
);
|
||||
|
||||
const compiled = await compileForRender(projectDir, indexPath, projectDir);
|
||||
|
||||
// Static parse still omits empty src from the media list; the stamp is
|
||||
// what the snapshot uses to keep the two clips distinct.
|
||||
expect(compiled.videos).toHaveLength(0);
|
||||
const { document } = parseHTML(compiled.html);
|
||||
expect(
|
||||
Array.from(document.querySelectorAll("video")).map((el) =>
|
||||
el.getAttribute("data-hf-render-id"),
|
||||
),
|
||||
).toEqual(["clip", "clip__hf2"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("STUDIO-5433 — ffprobe failure includes src URL for attribution", () => {
|
||||
|
||||
@@ -2090,7 +2090,9 @@ export async function compileForRender(
|
||||
* Discover media elements from the browser DOM after JavaScript has run.
|
||||
* This catches videos/audios whose `src` is set dynamically via JS
|
||||
* (e.g. `document.getElementById("pip-video").src = URL`), which the
|
||||
* static regex parsers miss because the HTML has `src=""`.
|
||||
* static regex parsers miss because the HTML has `src=""`. Clips are keyed
|
||||
* by `data-hf-render-id` when present — author ids collide across inlined
|
||||
* scenes, and this snapshot is the only identity those empty-src elements get.
|
||||
*/
|
||||
export interface BrowserMediaElement {
|
||||
id: string;
|
||||
@@ -2152,7 +2154,14 @@ export async function discoverMediaFromBrowser(page: Page): Promise<BrowserMedia
|
||||
: htmlEl.tagName.toLowerCase() === "video"
|
||||
? "video"
|
||||
: "audio";
|
||||
const id = htmlEl.id || (isImage ? autoImageIds.get(htmlEl) : undefined);
|
||||
// Render id is document-unique after inlining; author id is only unique
|
||||
// per composition file. Empty-src media is skipped by the static parse
|
||||
// and lives or dies on this snapshot — keying by author id collapses
|
||||
// colliding scenes onto one clip (residual of #3340).
|
||||
const id =
|
||||
htmlEl.getAttribute("data-hf-render-id") ||
|
||||
htmlEl.id ||
|
||||
(isImage ? autoImageIds.get(htmlEl) : undefined);
|
||||
if (!id) return;
|
||||
|
||||
// currentSrc is authoritative for <video>/<audio><source> and responsive images.
|
||||
@@ -2213,7 +2222,10 @@ export async function discoverAudioVolumeAutomationFromTimeline(
|
||||
const sampleStep = 1 / Math.min(60, Math.max(1, sampleFps));
|
||||
const rawWindows = await page.evaluate((ids: string[]) => {
|
||||
return ids.flatMap((id) => {
|
||||
const el = document.getElementById(id) ?? document.getElementById(id.replace(/-audio$/, ""));
|
||||
const el =
|
||||
window.__hfMediaEl?.(id) ??
|
||||
document.getElementById(id) ??
|
||||
document.getElementById(id.replace(/-audio$/, ""));
|
||||
if (!(el instanceof HTMLAudioElement) && !(el instanceof HTMLVideoElement)) return [];
|
||||
return [
|
||||
{
|
||||
@@ -2302,7 +2314,9 @@ export async function discoverAudioVolumeAutomationFromTimeline(
|
||||
|
||||
for (const { id, start, end } of clips) {
|
||||
const el =
|
||||
document.getElementById(id) ?? document.getElementById(id.replace(/-audio$/, ""));
|
||||
window.__hfMediaEl?.(id) ??
|
||||
document.getElementById(id) ??
|
||||
document.getElementById(id.replace(/-audio$/, ""));
|
||||
if (!(el instanceof HTMLAudioElement) && !(el instanceof HTMLVideoElement)) continue;
|
||||
|
||||
const sampleStart = Math.max(0, start);
|
||||
@@ -2419,7 +2433,7 @@ export async function discoverVideoVisibilityFromTimeline(
|
||||
lastVisible: number | null;
|
||||
}[] = [];
|
||||
for (const videoEl of videos) {
|
||||
const id = videoEl.id;
|
||||
const id = videoEl.getAttribute?.("data-hf-render-id") || videoEl.id;
|
||||
if (!id) continue;
|
||||
entries.push({
|
||||
id,
|
||||
|
||||
Reference in New Issue
Block a user