mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-11 14:50:02 +00:00
fix(engine,producer,lint): resolve <source> children for media extract and localize (#3238)
Parent src-only scans skipped multi-format <video>/<audio> markup, so those elements were never extracted, downloaded, or mixed and rendered blank/silent. Lint now accepts a child <source src> as a resolvable media src.
This commit is contained in:
@@ -217,6 +217,37 @@ describe("media rules", () => {
|
||||
expect(finding?.severity).toBe("error");
|
||||
});
|
||||
|
||||
it("accepts <source src> children in place of parent src", async () => {
|
||||
const html = `
|
||||
<html><body>
|
||||
<div id="root" data-composition-id="c1" data-width="1920" data-height="1080">
|
||||
<video id="rec" data-start="0" data-duration="4" muted playsinline>
|
||||
<source src="clip.mp4" type="video/mp4">
|
||||
<source src="clip.webm" type="video/webm">
|
||||
</video>
|
||||
</div>
|
||||
<script>window.__timelines = window.__timelines || {}; window.__timelines["c1"] = gsap.timeline({ paused: true });</script>
|
||||
</body></html>`;
|
||||
const result = await lintHyperframeHtml(html);
|
||||
expect(result.findings.some((f) => f.code === "media_missing_src")).toBe(false);
|
||||
});
|
||||
|
||||
it("reports error for <source>-only media with no data-start", async () => {
|
||||
const html = `
|
||||
<html><body>
|
||||
<div id="root" data-composition-id="c1" data-width="1920" data-height="1080">
|
||||
<video id="rec" muted playsinline>
|
||||
<source src="clip.mp4" type="video/mp4">
|
||||
</video>
|
||||
</div>
|
||||
<script>window.__timelines = window.__timelines || {}; window.__timelines["c1"] = gsap.timeline({ paused: true });</script>
|
||||
</body></html>`;
|
||||
const result = await lintHyperframeHtml(html);
|
||||
const finding = result.findings.find((f) => f.code === "media_missing_data_start");
|
||||
expect(finding).toBeDefined();
|
||||
expect(finding?.elementId).toBe("rec");
|
||||
});
|
||||
|
||||
it("reports error for media with src but no data-start", async () => {
|
||||
const html = `
|
||||
<html><body>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { LintContext, HyperframeLintFinding } from "../context";
|
||||
import type { LintContext, HyperframeLintFinding, OpenTag } from "../context";
|
||||
import { readAttr, readDecodedAttr, stripJsComments, truncateSnippet, isMediaTag } from "../utils";
|
||||
import { validateColorGradingContract } from "@hyperframes/parsers/color-grading-contract";
|
||||
|
||||
@@ -41,6 +41,20 @@ function hasAttrName(tagSource: string, attr: string): boolean {
|
||||
return new RegExp(`(?:^|\\s)${escaped}(?:\\s*=|\\s|/?>)`, "i").test(attrs);
|
||||
}
|
||||
|
||||
/** Parent `src`, else a descendant `<source src>` (matches engine resolveMediaElementSrc). */
|
||||
function mediaHasResolvableSrc(tag: OpenTag, tags: readonly OpenTag[]): boolean {
|
||||
if (readAttr(tag.raw, "src")) return true;
|
||||
const end = tag.closeIndex ?? tag.endIndex;
|
||||
if (end == null) return false;
|
||||
return tags.some(
|
||||
(child) =>
|
||||
child.name === "source" &&
|
||||
child.index > tag.index &&
|
||||
child.index < end &&
|
||||
Boolean(readAttr(child.raw, "src")),
|
||||
);
|
||||
}
|
||||
|
||||
function classNamesFromAttr(classAttr: string | null): string[] {
|
||||
if (!classAttr) return [];
|
||||
return classAttr.split(/\s+/).filter(Boolean);
|
||||
@@ -499,7 +513,7 @@ export const mediaRules: Array<(ctx: LintContext) => HyperframeLintFinding[]> =
|
||||
if (tag.name !== "video" && tag.name !== "audio") continue;
|
||||
const hasDataStart = readAttr(tag.raw, "data-start");
|
||||
const hasId = readAttr(tag.raw, "id");
|
||||
const hasSrc = readAttr(tag.raw, "src");
|
||||
const hasSrc = mediaHasResolvableSrc(tag, tags);
|
||||
if (hasSrc && !hasDataStart) {
|
||||
findings.push({
|
||||
code: "media_missing_data_start",
|
||||
@@ -538,9 +552,9 @@ export const mediaRules: Array<(ctx: LintContext) => HyperframeLintFinding[]> =
|
||||
findings.push({
|
||||
code: "media_missing_src",
|
||||
severity: "error",
|
||||
message: `<${tag.name} id="${hasId}"> has data-start but no src attribute. The renderer cannot load this media.`,
|
||||
message: `<${tag.name} id="${hasId}"> has data-start but no src (on the element or a <source> child). The renderer cannot load this media.`,
|
||||
elementId: hasId,
|
||||
fixHint: `Add a src attribute to the <${tag.name}> element directly. If using <source> children, the renderer still requires src on the parent element.`,
|
||||
fixHint: `Add src on the <${tag.name}> element, or a <source src="..."> child.`,
|
||||
snippet: truncateSnippet(tag.raw),
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user