mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-10 22:20:14 +00:00
fix(studio): route rooted timeline media through preview (#3061)
This commit is contained in:
@@ -108,6 +108,42 @@ describe("useRenderClipContent", () => {
|
||||
if (isValidElement(content)) expect(content.type).toBe(AudioWaveform);
|
||||
});
|
||||
|
||||
it("routes root-relative iframe media back through the active project", () => {
|
||||
usePlayerStore.setState({ thumbnailMode: "adaptive" });
|
||||
const resolvedRootMedia = `${window.location.origin}/assets/clip.mp4`;
|
||||
const video = renderClipContent(
|
||||
{
|
||||
id: "video",
|
||||
tag: "video",
|
||||
start: 0,
|
||||
duration: 4,
|
||||
track: 0,
|
||||
src: resolvedRootMedia,
|
||||
},
|
||||
null,
|
||||
);
|
||||
const audio = renderClipContent({
|
||||
id: "audio",
|
||||
tag: "audio",
|
||||
start: 0,
|
||||
duration: 4,
|
||||
track: 1,
|
||||
src: resolvedRootMedia,
|
||||
});
|
||||
|
||||
expect(isValidElement<{ videoSrc: string }>(video)).toBe(true);
|
||||
expect(isValidElement<{ audioUrl: string; waveformUrl: string }>(audio)).toBe(true);
|
||||
if (isValidElement<{ videoSrc: string }>(video)) {
|
||||
expect(video.props.videoSrc).toBe("/api/projects/my-project/preview/assets/clip.mp4");
|
||||
}
|
||||
if (isValidElement<{ audioUrl: string; waveformUrl: string }>(audio)) {
|
||||
expect(audio.props).toMatchObject({
|
||||
audioUrl: "/api/projects/my-project/preview/assets/clip.mp4",
|
||||
waveformUrl: "/api/projects/my-project/waveform/assets/clip.mp4",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it("passes empty labels to thumbnail content so TimelineClip owns clip names", () => {
|
||||
usePlayerStore.setState({ thumbnailMode: "adaptive" });
|
||||
|
||||
|
||||
@@ -27,12 +27,21 @@ export function normalizeCompositionSrc(
|
||||
}
|
||||
|
||||
/** Resolve a media src to its project-relative preview path, or null. */
|
||||
function resolvePreviewRelative(src: string | undefined, pid: string): string | null {
|
||||
function resolvePreviewRelative(
|
||||
src: string | undefined,
|
||||
pid: string,
|
||||
origin: string,
|
||||
): string | null {
|
||||
if (!src) return null;
|
||||
if (!src.startsWith("http")) return src;
|
||||
const base = `/api/projects/${pid}/preview/`;
|
||||
const idx = src.indexOf(base);
|
||||
return idx !== -1 ? decodeURIComponent(src.slice(idx + base.length)) : null;
|
||||
try {
|
||||
const parsed = new URL(src, origin);
|
||||
const base = new URL(`/api/projects/${pid}/preview/`, origin).pathname;
|
||||
return parsed.pathname.startsWith(base)
|
||||
? decodeURIComponent(parsed.pathname.slice(base.length))
|
||||
: null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,14 +70,12 @@ function renderAudioClip(
|
||||
labelColor: string,
|
||||
context: TimelineClipRenderContext,
|
||||
): ReactNode {
|
||||
const srcRelative = resolvePreviewRelative(el.src, pid);
|
||||
const audioUrl = resolveMediaPreviewUrl(el.src ?? "", pid, window.location.origin);
|
||||
const srcRelative = resolvePreviewRelative(audioUrl, pid, window.location.origin);
|
||||
// Encode each path segment (spaces, parens, U+202F, unicode) so the URL matches
|
||||
// what the assets panel loads — a raw segment 404s. resolvePreviewRelative
|
||||
// returns the DECODED path, so it must be re-encoded here.
|
||||
const encodedRelative = srcRelative ? encodePreviewPath(srcRelative) : null;
|
||||
const audioUrl = encodedRelative
|
||||
? `/api/projects/${pid}/preview/${encodedRelative}`
|
||||
: (el.src ?? "");
|
||||
const waveformUrl = encodedRelative
|
||||
? `/api/projects/${pid}/waveform/${encodedRelative}`
|
||||
: undefined;
|
||||
@@ -184,7 +191,7 @@ export function useRenderClipContent({
|
||||
!/(backdrop|background|overlay|scrim|mask)/i.test(el.id);
|
||||
|
||||
if ((el.tag === "video" || el.tag === "img") && el.src) {
|
||||
const mediaSrc = resolveMediaPreviewUrl(el.src, pid);
|
||||
const mediaSrc = resolveMediaPreviewUrl(el.src, pid, window.location.origin);
|
||||
// Still images can't be decoded by VideoThumbnail's <video> extractor
|
||||
// (the error event fires and the shimmer never resolves) — render the
|
||||
// image itself as the strip.
|
||||
|
||||
Reference in New Issue
Block a user