mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-07 10:06:21 +00:00
fix(video): hold final frame through composition
This commit is contained in:
@@ -11,6 +11,7 @@ import {
|
||||
runAndParseJsonEnvelope,
|
||||
} from "./deprecationTestHarness.js";
|
||||
import {
|
||||
auditClipDurations,
|
||||
extractCompositionErrorsFromLint,
|
||||
navigationTimeoutHint,
|
||||
raceMediaReady,
|
||||
@@ -49,6 +50,58 @@ vi.mock("../utils/producer.js", () => ({
|
||||
vi.mock("../utils/project.js", () => resolveProjectMock());
|
||||
vi.mock("../utils/lintProject.js", () => lintProjectFailureMock());
|
||||
|
||||
describe("auditClipDurations", () => {
|
||||
it("audits audio only because explicit video slots hold their final frame", async () => {
|
||||
let selector = "";
|
||||
const originalDocument = globalThis.document;
|
||||
const audio = {
|
||||
duration: 1,
|
||||
id: "voice",
|
||||
loop: false,
|
||||
tagName: "AUDIO",
|
||||
getAttribute: (name: string) =>
|
||||
name === "data-duration" ? "5" : name === "data-media-start" ? "0" : null,
|
||||
};
|
||||
const page = {
|
||||
evaluate: async (fn: (waitMs: number) => unknown, waitMs: number) => {
|
||||
Object.defineProperty(globalThis, "document", {
|
||||
configurable: true,
|
||||
value: {
|
||||
querySelectorAll: (query: string) => {
|
||||
selector = query;
|
||||
return [audio];
|
||||
},
|
||||
},
|
||||
});
|
||||
return fn(waitMs);
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const warnings = await auditClipDurations(
|
||||
page as never,
|
||||
({ slotSeconds, mediaSeconds }) => ({
|
||||
shortfallSeconds: slotSeconds - mediaSeconds,
|
||||
toleranceSeconds: 0.05,
|
||||
}),
|
||||
10,
|
||||
);
|
||||
expect(selector).toBe("audio[data-duration]");
|
||||
expect(warnings).toHaveLength(1);
|
||||
expect(warnings[0]?.text).toContain('Audio "voice"');
|
||||
} finally {
|
||||
if (originalDocument === undefined) {
|
||||
Reflect.deleteProperty(globalThis, "document");
|
||||
} else {
|
||||
Object.defineProperty(globalThis, "document", {
|
||||
configurable: true,
|
||||
value: originalDocument,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Regression for the validate audio-duration-probe timeout: a slow-loading
|
||||
// media element's duration was snapshotted once, at a fixed point in time,
|
||||
// and any element still mid-load was permanently misreported as unreadable.
|
||||
|
||||
@@ -127,8 +127,9 @@ export function raceMediaReady(
|
||||
}
|
||||
|
||||
/**
|
||||
* Flag `<video>`/`<audio>` clips whose source is meaningfully shorter than their
|
||||
* `data-duration` slot (the slot gets silently shortened in renders). Runs in
|
||||
* Flag `<audio>` clips whose source is meaningfully shorter than their
|
||||
* `data-duration` slot (the slot gets silently shortened in renders). Videos
|
||||
* intentionally hold their final frame through an explicit longer slot. Runs in
|
||||
* the live page to read each element's intrinsic `.duration`, which static lint
|
||||
* can't see.
|
||||
*/
|
||||
@@ -140,7 +141,7 @@ export async function auditClipDurations(
|
||||
// fallow-ignore-next-line complexity
|
||||
const clips = await page.evaluate(async (maxWaitMs: number) => {
|
||||
const nodes = Array.from(
|
||||
document.querySelectorAll("video[data-duration], audio[data-duration]"),
|
||||
document.querySelectorAll("audio[data-duration]"),
|
||||
) as HTMLMediaElement[];
|
||||
|
||||
// The caller's page-settle sleep is a flat, unconditional wait shared with
|
||||
|
||||
Reference in New Issue
Block a user