mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-07 01:56:04 +00:00
135 lines
4.3 KiB
TypeScript
135 lines
4.3 KiB
TypeScript
import { useEffect } from "react";
|
|
import { useCaptionStore } from "../captions/store";
|
|
import { acceptStudioRuntimeMessage } from "../player/lib/runtimeProtocol";
|
|
import { useCaptionSync } from "../captions/hooks/useCaptionSync";
|
|
import { parseCaptionComposition } from "../captions/parser";
|
|
|
|
interface UseCaptionDetectionParams {
|
|
projectId: string | null;
|
|
activeCompPath: string | null;
|
|
compIdToSrc: Map<string, string>;
|
|
captionEditMode: boolean;
|
|
captionHasSelection: boolean;
|
|
previewIframeRef: React.MutableRefObject<HTMLIFrameElement | null>;
|
|
captionSync: ReturnType<typeof useCaptionSync>;
|
|
setRightCollapsed: (collapsed: boolean) => void;
|
|
}
|
|
|
|
export function useCaptionDetection({
|
|
projectId,
|
|
activeCompPath,
|
|
compIdToSrc,
|
|
captionEditMode,
|
|
captionHasSelection,
|
|
previewIframeRef,
|
|
captionSync,
|
|
setRightCollapsed,
|
|
}: UseCaptionDetectionParams) {
|
|
// eslint-disable-next-line no-restricted-syntax
|
|
useEffect(() => {
|
|
if (!projectId) return;
|
|
|
|
let activating = false;
|
|
|
|
const tryActivateCaptions = () => {
|
|
if (useCaptionStore.getState().isEditMode || activating) {
|
|
return;
|
|
}
|
|
|
|
const iframe = previewIframeRef.current;
|
|
let doc: Document | null = null;
|
|
let win: Window | null = null;
|
|
try {
|
|
doc = iframe?.contentDocument ?? null;
|
|
win = iframe?.contentWindow ?? null;
|
|
} catch {
|
|
return;
|
|
}
|
|
if (!doc || !win) return;
|
|
|
|
const groups = doc.querySelectorAll(".caption-group");
|
|
if (groups.length === 0) return;
|
|
|
|
let captionSrcPath: string | null = null;
|
|
|
|
const compHosts = doc.querySelectorAll("[data-composition-src], [data-composition-file]");
|
|
for (const host of compHosts) {
|
|
const src =
|
|
host.getAttribute("data-composition-src") || host.getAttribute("data-composition-file");
|
|
if (src && src.includes("captions")) {
|
|
captionSrcPath = src;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!captionSrcPath) {
|
|
for (const [id, src] of compIdToSrc) {
|
|
if (id.includes("caption") || src.includes("caption")) {
|
|
captionSrcPath = src;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!captionSrcPath && activeCompPath?.includes("captions")) {
|
|
captionSrcPath = activeCompPath;
|
|
}
|
|
|
|
if (!captionSrcPath) {
|
|
const captionComp = doc.querySelector('[data-composition-id*="caption"]');
|
|
if (captionComp) {
|
|
const compId = captionComp.getAttribute("data-composition-id") || "";
|
|
captionSrcPath = compIdToSrc.get(compId) || null;
|
|
}
|
|
}
|
|
|
|
if (!captionSrcPath) return;
|
|
|
|
activating = true;
|
|
const srcPath = captionSrcPath;
|
|
fetch(`/api/projects/${projectId}/files/${encodeURIComponent(srcPath)}`)
|
|
.then((r) => r.json())
|
|
.then((data: { content?: string }) => {
|
|
if (!data.content || !doc || !win || useCaptionStore.getState().isEditMode) return;
|
|
const root = doc.querySelector("[data-composition-id]");
|
|
const w = parseInt(root?.getAttribute("data-width") ?? "1920", 10);
|
|
const h = parseInt(root?.getAttribute("data-height") ?? "1080", 10);
|
|
const dur = parseFloat(root?.getAttribute("data-duration") ?? "0");
|
|
const model = parseCaptionComposition(doc, win, data.content, w, h, dur);
|
|
if (!model) return;
|
|
const store = useCaptionStore.getState();
|
|
store.setModel(model);
|
|
store.setSourceFilePath(srcPath);
|
|
store.setEditMode(true);
|
|
captionSync.loadOverrides();
|
|
})
|
|
.catch(() => {})
|
|
.finally(() => {
|
|
activating = false;
|
|
});
|
|
};
|
|
|
|
const handleMessage = (e: MessageEvent) => {
|
|
const data = e.data;
|
|
if (data?.source === "hf-preview" && (data?.type === "state" || data?.type === "timeline")) {
|
|
if (!acceptStudioRuntimeMessage(data)) return;
|
|
tryActivateCaptions();
|
|
}
|
|
};
|
|
|
|
window.addEventListener("message", handleMessage);
|
|
tryActivateCaptions();
|
|
|
|
return () => {
|
|
window.removeEventListener("message", handleMessage);
|
|
};
|
|
}, [activeCompPath, projectId, compIdToSrc, captionSync, previewIframeRef]);
|
|
|
|
// eslint-disable-next-line no-restricted-syntax
|
|
useEffect(() => {
|
|
if (captionEditMode) {
|
|
setRightCollapsed(!captionHasSelection);
|
|
}
|
|
}, [captionHasSelection, captionEditMode, setRightCollapsed]);
|
|
}
|