From 67efe6733a0ffdd67d654df0a332aca894a16ca5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel?= Date: Thu, 30 Apr 2026 00:46:38 -0400 Subject: [PATCH] fix(studio): address review follow-ups --- packages/core/src/lint/utils.ts | 12 ++++++-- .../player/hooks/useTimelinePlayer.test.ts | 29 +++++++++++++++++++ .../src/player/hooks/useTimelinePlayer.ts | 4 ++- 3 files changed, 41 insertions(+), 4 deletions(-) diff --git a/packages/core/src/lint/utils.ts b/packages/core/src/lint/utils.ts index 7ff05ec5f1..cf8a7b121b 100644 --- a/packages/core/src/lint/utils.ts +++ b/packages/core/src/lint/utils.ts @@ -59,12 +59,18 @@ export function extractBlocks(source: string, pattern: RegExp): ExtractedBlock[] } export function findRootTag(source: string): OpenTag | null { - const bodyMatch = source.match(/]*>([\s\S]*?)<\/body>/i); - const bodyContent = bodyMatch ? (bodyMatch[1] ?? source) : source; + const bodyOpenMatch = /]*>/i.exec(source); + const bodyCloseMatch = /<\/body>/i.exec(source); + const bodyStart = bodyOpenMatch ? bodyOpenMatch.index + bodyOpenMatch[0].length : 0; + const bodyEnd = + bodyOpenMatch && bodyCloseMatch && bodyCloseMatch.index > bodyStart + ? bodyCloseMatch.index + : source.length; + const bodyContent = bodyOpenMatch ? source.slice(bodyStart, bodyEnd) : source; const bodyTags = extractOpenTags(bodyContent); for (const tag of bodyTags) { if (["script", "style", "meta", "link", "title"].includes(tag.name)) continue; - return tag; + return { ...tag, index: tag.index + bodyStart }; } return null; } diff --git a/packages/studio/src/player/hooks/useTimelinePlayer.test.ts b/packages/studio/src/player/hooks/useTimelinePlayer.test.ts index d5366af0d4..2c89583f23 100644 --- a/packages/studio/src/player/hooks/useTimelinePlayer.test.ts +++ b/packages/studio/src/player/hooks/useTimelinePlayer.test.ts @@ -195,6 +195,35 @@ describe("anonymous timeline identity", () => { expect(new Set(elements.map((element) => element.key)).size).toBe(2); expect(elements.map((element) => element.selectorIndex)).toEqual([0, 1]); }); + + it("reads media metadata from owner-window media elements", () => { + const doc = createDocument(` +
+
+ +
+
+ `); + const hostEl = doc.querySelector(".video-card"); + const video = hostEl?.querySelector("video"); + if (!hostEl || !video) throw new Error("missing video test fixture"); + Object.defineProperty(video, "defaultPlaybackRate", { + value: 1.5, + configurable: true, + }); + + const element = createTimelineElementFromManifestClip({ + clip: createClip({ kind: "video", tagName: "div" }), + fallbackIndex: 0, + doc, + hostEl, + }); + + expect(element.tag).toBe("video"); + expect(element.src).toBe("/clip.mp4"); + expect(element.sourceDuration).toBe(12); + expect(element.playbackRate).toBe(1.5); + }); }); describe("mergeTimelineElementsPreservingDowngrades", () => { diff --git a/packages/studio/src/player/hooks/useTimelinePlayer.ts b/packages/studio/src/player/hooks/useTimelinePlayer.ts index 5e872664bf..2cdcf0b2ed 100644 --- a/packages/studio/src/player/hooks/useTimelinePlayer.ts +++ b/packages/studio/src/player/hooks/useTimelinePlayer.ts @@ -95,7 +95,9 @@ function applyMediaMetadataFromElement(entry: TimelineElement, el: Element): voi const src = mediaEl.getAttribute("src"); if (src) entry.src = src; - if (!(mediaEl instanceof HTMLMediaElement)) return; + const win = mediaEl.ownerDocument.defaultView ?? window; + const MediaElementCtor = win.HTMLMediaElement ?? globalThis.HTMLMediaElement; + if (typeof MediaElementCtor === "undefined" || !(mediaEl instanceof MediaElementCtor)) return; const sourceDurationAttr = el.getAttribute("data-source-duration") ?? mediaEl.getAttribute("data-source-duration");