mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-01 19:42:03 +00:00
fix(core,producer): render SDK position edits in producer pipeline
This commit is contained in:
+3
-1
@@ -72,7 +72,9 @@ tmp/
|
||||
.tmp/
|
||||
|
||||
# Generated files
|
||||
packages/core/src/generated/
|
||||
packages/core/src/generated/*
|
||||
!packages/core/src/generated/position-edits-render-inline.ts
|
||||
!packages/core/src/generated/position-edits-render-inline.test.ts
|
||||
packages/producer/src/services/fontData.generated.ts
|
||||
|
||||
# Plan documents (working artifacts, not committed)
|
||||
|
||||
@@ -119,6 +119,12 @@
|
||||
"import": "./src/runtime/positionEdits.ts",
|
||||
"types": "./src/runtime/positionEdits.ts"
|
||||
},
|
||||
"./runtime/position-edits-render": {
|
||||
"bun": "./src/generated/position-edits-render-inline.ts",
|
||||
"node": "./dist/generated/position-edits-render-inline.js",
|
||||
"import": "./src/generated/position-edits-render-inline.ts",
|
||||
"types": "./src/generated/position-edits-render-inline.ts"
|
||||
},
|
||||
"./runtime/lottie-readiness": {
|
||||
"bun": "./src/lottieReadiness.ts",
|
||||
"node": "./dist/lottieReadiness.js",
|
||||
@@ -309,6 +315,10 @@
|
||||
"import": "./dist/runtime/positionEdits.js",
|
||||
"types": "./dist/runtime/positionEdits.d.ts"
|
||||
},
|
||||
"./runtime/position-edits-render": {
|
||||
"import": "./dist/generated/position-edits-render-inline.js",
|
||||
"types": "./dist/generated/position-edits-render-inline.d.ts"
|
||||
},
|
||||
"./runtime/lottie-readiness": {
|
||||
"import": "./dist/lottieReadiness.js",
|
||||
"types": "./dist/lottieReadiness.d.ts"
|
||||
@@ -388,13 +398,14 @@
|
||||
"types": "./dist/index.d.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "bun run build:hyperframes-runtime && tsc && tsx scripts/rewrite-esm-extensions.ts",
|
||||
"build": "bun run build:hyperframes-runtime && bun run build:position-edits-render && tsc && tsx scripts/rewrite-esm-extensions.ts",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"test:coverage": "vitest run --coverage",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"lint:runtime-preview-guards": "tsx scripts/lint-runtime-preview-guards.ts",
|
||||
"build:hyperframes-runtime": "tsx scripts/build-hyperframes-runtime-artifact.ts",
|
||||
"build:position-edits-render": "tsx scripts/build-position-edits-render.ts",
|
||||
"build:hyperframes-runtime:modular": "SANDBOX_RUNTIME_VARIANT=modular tsx scripts/build-hyperframes-runtime-artifact.ts",
|
||||
"build:hyperframe-runtime": "tsx scripts/build-hyperframes-runtime-artifact.ts",
|
||||
"test:hyperframe-runtime-contract": "tsx scripts/test-hyperframe-runtime-contract.ts",
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/** Build the injectable position-edits render artifact from the canonical runtime. */
|
||||
|
||||
import { mkdirSync, writeFileSync } from "node:fs";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { buildSync } from "esbuild";
|
||||
import { execSync } from "node:child_process";
|
||||
|
||||
const thisDir = dirname(fileURLToPath(import.meta.url));
|
||||
const repoRoot = resolve(thisDir, "..");
|
||||
const entry = resolve(repoRoot, "stubs/position-edits-render-entry.ts");
|
||||
const generatedDir = resolve(repoRoot, "src/generated");
|
||||
const outPath = resolve(generatedDir, "position-edits-render-inline.ts");
|
||||
|
||||
const result = buildSync({
|
||||
entryPoints: [entry],
|
||||
bundle: true,
|
||||
write: false,
|
||||
platform: "browser",
|
||||
format: "iife",
|
||||
target: ["es2020"],
|
||||
minify: true,
|
||||
legalComments: "none",
|
||||
});
|
||||
const iife = result.outputFiles[0]?.text ?? "";
|
||||
if (!iife) throw new Error("esbuild produced no output for position-edits-render-entry.ts");
|
||||
|
||||
mkdirSync(generatedDir, { recursive: true });
|
||||
writeFileSync(
|
||||
outPath,
|
||||
[
|
||||
"// AUTO-GENERATED by scripts/build-position-edits-render.ts - do not edit",
|
||||
`const POSITION_EDITS_RENDER_IIFE: string = ${JSON.stringify(iife)};`,
|
||||
"",
|
||||
"/** Returns the pre-built position-edits render IIFE as a string constant. */",
|
||||
"export function getPositionEditsRenderScript(): string {",
|
||||
" return POSITION_EDITS_RENDER_IIFE;",
|
||||
"}",
|
||||
"",
|
||||
].join("\n"),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
try {
|
||||
execSync(`bunx oxfmt ${outPath}`, { stdio: "ignore" });
|
||||
} catch {
|
||||
// Formatting is best effort when the generator runs in a minimal environment.
|
||||
}
|
||||
|
||||
console.log(
|
||||
JSON.stringify({ event: "position_edits_render_generated", outPath, bytes: iife.length }),
|
||||
);
|
||||
@@ -0,0 +1,23 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { getPositionEditsRenderScript } from "./position-edits-render-inline";
|
||||
|
||||
describe("getPositionEditsRenderScript", () => {
|
||||
it("returns a non-empty IIFE string built from the real algorithm", () => {
|
||||
const script = getPositionEditsRenderScript();
|
||||
expect(script.length).toBeGreaterThan(0);
|
||||
expect(script).toContain("data-hf-edit-base-x");
|
||||
});
|
||||
|
||||
it("is a safe no-op without position-edit markers", () => {
|
||||
document.body.innerHTML = "<h1>no edits</h1>";
|
||||
expect(() => new Function(getPositionEditsRenderScript())()).not.toThrow();
|
||||
expect(document.querySelector("h1")?.style.getPropertyValue("translate")).toBe("");
|
||||
});
|
||||
|
||||
it("applies the translate delta when markers are present", () => {
|
||||
document.body.innerHTML =
|
||||
'<h1 data-x="10" data-y="0" data-hf-edit-base-x="0" data-hf-edit-base-y="0">hi</h1>';
|
||||
new Function(getPositionEditsRenderScript())();
|
||||
expect(document.querySelector("h1")?.style.getPropertyValue("translate")).toBe("10px 0px");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
// AUTO-GENERATED by scripts/build-position-edits-render.ts - do not edit
|
||||
const POSITION_EDITS_RENDER_IIFE: string =
|
||||
'"use strict";(()=>{function g(){return globalThis}function w(t,e){if(typeof window>"u")return;let n=g(),o=n.__hf?.onSwallowed;if(o)try{o({label:t,error:e})}catch(r){}(n.__hfDebug||n.__HYPERFRAMES_DEBUG)&&console.debug(`[hyperframes] ${t} swallowed:`,e)}var E=null;function h(t,e){if(E)try{E({source:"hf-preview",type:"analytics",event:t,properties:e??{}})}catch(n){w("runtime.analytics.site1",n)}}var l="data-hf-edit-base-x",p="data-hf-edit-base-y",f="data-hf-edit-original-translate",u=t=>{let e=parseFloat(t??"");return Number.isFinite(e)?e:0},$=t=>{let e=[],n=0,o="";for(let r of t.trim())r==="("&&(n+=1),r===")"&&(n=Math.max(0,n-1)),/\\s/.test(r)&&n===0?(o&&e.push(o),o=""):o+=r;return o&&e.push(o),e},k=/^-?(?:\\d+(?:\\.\\d+)?|\\.\\d+)px$/,_=(t,e)=>k.test(t)&&k.test(e)?`${parseFloat(t)+parseFloat(e)}px`:`calc(${t} + ${e})`,D=(t,e,n)=>{if(!t||t==="none")return`${e} ${n}`;let[o,r,i]=$(t);if(o===void 0)return`${e} ${n}`;if(r===void 0)return`${_(o,e)} ${n}`;let s=i===void 0?"":` ${i}`;return`${_(o,e)} ${_(r,n)}${s}`},L=t=>{try{t.ownerDocument.defaultView?.gsap?.getProperty?.(t,"x")}catch{}},M=t=>{let e=t.style.getPropertyValue("translate").trim();if(e)return e==="none"?"":e;try{let n=t.ownerDocument.defaultView,o=n?n.getComputedStyle(t).getPropertyValue("translate").trim():"";return o==="none"?"":o}catch{return""}},A=new WeakMap;function H(t,e){let n=A.get(t);if(!e?.force&&n!==void 0&&t.style.getPropertyValue("translate")!==n){h("position_edit_fold_skipped",{hfId:t.getAttribute("data-hf-id")});return}let o=u(t.getAttribute("data-x"))-u(t.getAttribute(l)),r=u(t.getAttribute("data-y"))-u(t.getAttribute(p));t.hasAttribute(f)||t.setAttribute(f,M(t)),n===void 0&&L(t);let i=t.getAttribute(f)??"",s=D(i,`${o}px`,`${r}px`);t.style.setProperty("translate",s),A.set(t,t.style.getPropertyValue("translate"))}function y(t,e){let n=t.querySelectorAll(`[${l}], [${p}]`),o=t.defaultView?.HTMLElement,r=0;for(let i=0;i<n.length;i++){let s=n[i];(o?s instanceof o:typeof s.style?.setProperty=="function")&&(H(s,e),r+=1)}return r}var T="__hfPositionEditsSeekReapplyWrapped";function v(t){let e=t,n=()=>{try{y(e.document,{force:!0})}catch{}},o=a=>typeof a=="function"&&!!a[T],r=a=>{try{Object.defineProperty(a,T,{value:!0})}catch{}},i=(a,P)=>{let d=a();if(typeof d!="function"||o(d))return;let m=function(...R){let x=d.apply(this,R);return n(),x};r(m),P(m),n()},s=()=>{i(()=>e.__hf?.seek,a=>{e.__hf&&(e.__hf.seek=a)}),i(()=>e.__player?.renderSeek,a=>{e.__player&&(e.__player.renderSeek=a)})};s();let c=120,S=e.setInterval(()=>{s(),c-=1,c<=0&&e.clearInterval(S)},50)}function b(){document.querySelector(`[${l}], [${p}]`)&&(y(document),v(window))}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",b,{once:!0}):b();})();\n';
|
||||
|
||||
/** Returns the pre-built position-edits render IIFE as a string constant. */
|
||||
export function getPositionEditsRenderScript(): string {
|
||||
return POSITION_EDITS_RENDER_IIFE;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { installPositionEditsSeekReapply } from "./positionEdits";
|
||||
|
||||
describe("init.ts per-seek position-edit parity", () => {
|
||||
it("reapplies the position edit after renderSeek", () => {
|
||||
document.body.innerHTML =
|
||||
'<h1 data-x="20" data-y="0" data-hf-edit-base-x="0" data-hf-edit-base-y="0">hi</h1>';
|
||||
const h1 = document.querySelector("h1");
|
||||
if (!(h1 instanceof HTMLElement)) throw new Error("test element missing");
|
||||
|
||||
// @ts-expect-error test global
|
||||
window.__player = {
|
||||
renderSeek: () => h1.style.setProperty("translate", "none"),
|
||||
};
|
||||
installPositionEditsSeekReapply(window as Window & typeof globalThis);
|
||||
// @ts-expect-error test global
|
||||
window.__player.renderSeek(1);
|
||||
|
||||
expect(h1.style.getPropertyValue("translate")).toBe("20px 0px");
|
||||
// @ts-expect-error test global
|
||||
delete window.__player;
|
||||
h1.remove();
|
||||
});
|
||||
});
|
||||
@@ -29,7 +29,7 @@ import { createRuntimeStartTimeResolver } from "./startResolver";
|
||||
import { createClipTree } from "./clipTree";
|
||||
import { loadExternalCompositions, loadInlineTemplateCompositions } from "./compositionLoader";
|
||||
import { applyCaptionOverrides } from "./captionOverrides";
|
||||
import { applyPositionEdits } from "./positionEdits";
|
||||
import { applyPositionEdits, installPositionEditsSeekReapply } from "./positionEdits";
|
||||
import { applyVariableBindings } from "./applyVariableBindings";
|
||||
import { createColorGradingRuntime, type RuntimeColorGradingApi } from "./colorGrading";
|
||||
import { TransportClock } from "./clock";
|
||||
@@ -2987,6 +2987,8 @@ export function initSandboxRuntimeModular(): void {
|
||||
}
|
||||
}
|
||||
|
||||
installPositionEditsSeekReapply(window as Window & typeof globalThis);
|
||||
|
||||
// Start the rAF tick loop
|
||||
state.transportRafId = window.requestAnimationFrame(transportTick);
|
||||
postTimeline();
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
applyPositionEditToElement,
|
||||
applyPositionEdits,
|
||||
composeTranslate,
|
||||
installPositionEditsSeekReapply,
|
||||
} from "./positionEdits";
|
||||
|
||||
function makeElement(attrs: Record<string, string>, style = ""): HTMLElement {
|
||||
@@ -174,3 +175,45 @@ describe("applyPositionEdits", () => {
|
||||
el.remove();
|
||||
});
|
||||
});
|
||||
|
||||
describe("installPositionEditsSeekReapply", () => {
|
||||
it("wraps __player.renderSeek so each call reapplies position edits", () => {
|
||||
const el = makeElement({ "data-x": "10", "data-y": "0", "data-hf-edit-base-x": "0" });
|
||||
const calls: number[] = [];
|
||||
// @ts-expect-error test global
|
||||
window.__player = { renderSeek: (time: number) => calls.push(time) };
|
||||
|
||||
installPositionEditsSeekReapply(window as Window & typeof globalThis);
|
||||
// @ts-expect-error test global
|
||||
window.__player.renderSeek(1.5);
|
||||
|
||||
expect(calls).toEqual([1.5]);
|
||||
expect(el.style.getPropertyValue("translate")).toBe("10px 0px");
|
||||
// @ts-expect-error test global
|
||||
delete window.__player;
|
||||
el.remove();
|
||||
});
|
||||
|
||||
it("is idempotent when installed twice", () => {
|
||||
const el = makeElement({ "data-x": "5", "data-y": "0", "data-hf-edit-base-x": "0" });
|
||||
const calls: number[] = [];
|
||||
// @ts-expect-error test global
|
||||
window.__player = { renderSeek: (time: number) => calls.push(time) };
|
||||
|
||||
installPositionEditsSeekReapply(window as Window & typeof globalThis);
|
||||
installPositionEditsSeekReapply(window as Window & typeof globalThis);
|
||||
// @ts-expect-error test global
|
||||
window.__player.renderSeek(2);
|
||||
|
||||
expect(calls).toEqual([2]);
|
||||
// @ts-expect-error test global
|
||||
delete window.__player;
|
||||
el.remove();
|
||||
});
|
||||
|
||||
it("does not throw when neither seek global exists", () => {
|
||||
expect(() =>
|
||||
installPositionEditsSeekReapply(window as Window & typeof globalThis),
|
||||
).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -154,7 +154,7 @@ export function applyPositionEditToElement(el: HTMLElement, opts?: { force?: boo
|
||||
* Apply all pending position edits in the document. Returns the number of
|
||||
* elements updated.
|
||||
*/
|
||||
export function applyPositionEdits(doc: Document): number {
|
||||
export function applyPositionEdits(doc: Document, opts?: { force?: boolean }): number {
|
||||
const marked = doc.querySelectorAll(`[${EDIT_BASE_X_ATTR}], [${EDIT_BASE_Y_ATTR}]`);
|
||||
// Not `instanceof HTMLElement`: `doc` is frequently an iframe's document (the
|
||||
// SDK's edit preview, a host embedding a composition), and its elements are
|
||||
@@ -170,8 +170,80 @@ export function applyPositionEdits(doc: Document): number {
|
||||
? el instanceof RealmHTMLElement
|
||||
: typeof (el as HTMLElement).style?.setProperty === "function";
|
||||
if (!isStylable) continue;
|
||||
applyPositionEditToElement(el as HTMLElement);
|
||||
applyPositionEditToElement(el as HTMLElement, opts);
|
||||
applied += 1;
|
||||
}
|
||||
return applied;
|
||||
}
|
||||
|
||||
const SEEK_REAPPLY_WRAPPED = "__hfPositionEditsSeekReapplyWrapped";
|
||||
|
||||
type SeekWindow = Window &
|
||||
typeof globalThis & {
|
||||
__hf?: { seek?: (...args: unknown[]) => unknown };
|
||||
__player?: { renderSeek?: (...args: unknown[]) => unknown };
|
||||
};
|
||||
|
||||
/** Reapply SDK position edits after every render seek, including late-bound seeks. */
|
||||
export function installPositionEditsSeekReapply(win: Window & typeof globalThis): void {
|
||||
const target = win as SeekWindow;
|
||||
const reapply = (): void => {
|
||||
try {
|
||||
applyPositionEdits(target.document, { force: true });
|
||||
} catch {
|
||||
// A position edit must never break the render seek path.
|
||||
}
|
||||
};
|
||||
|
||||
const isWrapped = (fn: unknown): fn is (...args: unknown[]) => unknown =>
|
||||
typeof fn === "function" &&
|
||||
Boolean((fn as { [SEEK_REAPPLY_WRAPPED]?: boolean })[SEEK_REAPPLY_WRAPPED]);
|
||||
|
||||
const markWrapped = (fn: (...args: unknown[]) => unknown): void => {
|
||||
try {
|
||||
Object.defineProperty(fn, SEEK_REAPPLY_WRAPPED, { value: true });
|
||||
} catch {
|
||||
// Frozen functions cannot be marked; the wrapper itself is still valid.
|
||||
}
|
||||
};
|
||||
|
||||
const wrapOne = (
|
||||
get: () => unknown,
|
||||
set: (fn: (...args: unknown[]) => unknown) => void,
|
||||
): void => {
|
||||
const fn = get();
|
||||
if (typeof fn !== "function") return;
|
||||
if (isWrapped(fn)) return;
|
||||
const wrapped = function (this: unknown, ...args: unknown[]): unknown {
|
||||
const result = fn.apply(this, args);
|
||||
reapply();
|
||||
return result;
|
||||
};
|
||||
markWrapped(wrapped);
|
||||
set(wrapped);
|
||||
reapply();
|
||||
};
|
||||
|
||||
const wrapAll = (): void => {
|
||||
wrapOne(
|
||||
() => target.__hf?.seek,
|
||||
(fn) => {
|
||||
if (target.__hf) target.__hf.seek = fn;
|
||||
},
|
||||
);
|
||||
wrapOne(
|
||||
() => target.__player?.renderSeek,
|
||||
(fn) => {
|
||||
if (target.__player) target.__player.renderSeek = fn;
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
wrapAll();
|
||||
let remaining = 120;
|
||||
const interval = target.setInterval(() => {
|
||||
wrapAll();
|
||||
remaining -= 1;
|
||||
if (remaining <= 0) target.clearInterval(interval);
|
||||
}, 50);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import {
|
||||
EDIT_BASE_X_ATTR,
|
||||
EDIT_BASE_Y_ATTR,
|
||||
applyPositionEdits,
|
||||
installPositionEditsSeekReapply,
|
||||
} from "../src/runtime/positionEdits";
|
||||
|
||||
function start(): void {
|
||||
if (!document.querySelector(`[${EDIT_BASE_X_ATTR}], [${EDIT_BASE_Y_ATTR}]`)) {
|
||||
return;
|
||||
}
|
||||
applyPositionEdits(document);
|
||||
installPositionEditsSeekReapply(window);
|
||||
}
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", start, { once: true });
|
||||
} else {
|
||||
start();
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import { parseHTML } from "linkedom";
|
||||
import {
|
||||
collectExternalAssets,
|
||||
compileForRender,
|
||||
injectSdkPositionEditsRenderScript,
|
||||
detectRenderModeHints,
|
||||
detectShaderTransitionUsage,
|
||||
detectThreeDTransformUsage,
|
||||
@@ -19,6 +20,28 @@ import {
|
||||
} from "./htmlCompiler.js";
|
||||
import { validateNoSystemFonts } from "./render/planValidation.js";
|
||||
|
||||
describe("injectSdkPositionEditsRenderScript", () => {
|
||||
it("injects before </body> when SDK position-edit markers are present", () => {
|
||||
const html =
|
||||
'<html><body><h1 data-x="-231" data-y="-139" data-hf-edit-base-x="0" data-hf-edit-base-y="0">Hi</h1></body></html>';
|
||||
const out = injectSdkPositionEditsRenderScript(html);
|
||||
expect(out).toContain("<script>");
|
||||
expect(out.indexOf("<script>")).toBeLessThan(out.indexOf("</body>"));
|
||||
expect(out).toContain("data-hf-edit-base-x");
|
||||
});
|
||||
|
||||
it("appends the script when there is no </body> tag", () => {
|
||||
const out = injectSdkPositionEditsRenderScript('<div data-hf-edit-base-y="0"></div>');
|
||||
expect(out.startsWith('<div data-hf-edit-base-y="0"></div>')).toBe(true);
|
||||
expect(out).toContain("<script>");
|
||||
});
|
||||
|
||||
it("is a no-op for style/text-only HTML", () => {
|
||||
const html = '<html><body><h1 style="color:#f00">Hi</h1></body></html>';
|
||||
expect(injectSdkPositionEditsRenderScript(html)).toBe(html);
|
||||
});
|
||||
});
|
||||
|
||||
// ── collectExternalAssets ──────────────────────────────────────────────────
|
||||
|
||||
describe("collectExternalAssets", () => {
|
||||
|
||||
@@ -57,6 +57,7 @@ import {
|
||||
} from "./deterministicFonts.js";
|
||||
import { prepareAnimatedGifInputs } from "./animatedGifPrep.js";
|
||||
import { createStudioPositionSeekReapplyScript } from "@hyperframes/studio-server/manual-edits-render-script";
|
||||
import { getPositionEditsRenderScript } from "@hyperframes/core/runtime/position-edits-render";
|
||||
import { defaultLogger, type ProducerLogger } from "../logger.js";
|
||||
|
||||
export interface CompiledComposition {
|
||||
@@ -84,6 +85,16 @@ function parseSubCompHtmlForValidity(html: string): ParsableDocumentLike {
|
||||
return parseHTML(html).document as unknown as ParsableDocumentLike;
|
||||
}
|
||||
|
||||
export function injectSdkPositionEditsRenderScript(html: string): string {
|
||||
if (!html.includes("data-hf-edit-base-x") && !html.includes("data-hf-edit-base-y")) {
|
||||
return html;
|
||||
}
|
||||
const script = `<script>${getPositionEditsRenderScript()}</script>`;
|
||||
const bodyClose = html.search(/<\/body\s*>/i);
|
||||
if (bodyClose < 0) return `${html}${script}`;
|
||||
return `${html.slice(0, bodyClose)}${script}${html.slice(bodyClose)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown by {@link assertSubCompositionsUsable} when one or more
|
||||
* `data-composition-src` references resolve to a missing, empty, or
|
||||
@@ -1730,6 +1741,7 @@ export async function compileForRender(
|
||||
`<script>${createStudioPositionSeekReapplyScript()}</script></body>`,
|
||||
)
|
||||
: assembledHtml;
|
||||
const htmlWithSdkPositionScript = injectSdkPositionEditsRenderScript(htmlWithPositionScript);
|
||||
|
||||
// Download remote <video> and <audio> sources to compiledDir and rewrite the
|
||||
// src attributes so the renderer reads from localhost. Remote S3 URLs cause
|
||||
@@ -1737,7 +1749,7 @@ export async function compileForRender(
|
||||
// over the network; any that don't reach readyState >= 2 in time render as
|
||||
// blank black frames. Localising them eliminates the race.
|
||||
const { html: htmlWithLocalMedia, remoteMediaAssets } = await localizeRemoteMediaSources(
|
||||
htmlWithPositionScript,
|
||||
htmlWithSdkPositionScript,
|
||||
downloadDir,
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user