initial code (#2)

* feat: initial code port from hyperframes-internal

Port all OSS-ready packages from the internal monorepo:
- @hyperframes/core — shared types, HTML generation, GSAP utilities, runtime
- @hyperframes/cli — CLI for creating, previewing, and rendering compositions
- @hyperframes/engine — framework-agnostic rendering engine (BeginFrame + FFmpeg)
- @hyperframes/producer — video rendering pipeline (Puppeteer + FFmpeg)
- @hyperframes/ui-player — browser-based video player component
- @hyperframes/studio — composition editor (React frontend + Hono backend)

Includes regression test suite with Docker-based test harness.

All HeyGen-internal references, deployment infrastructure, and
proprietary assets have been removed. Package names migrated
from @app/* to @hyperframes/*.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: scrub internal codenames and stale references from OSS port

- Replace static.heygen.ai runtime URLs in test fixtures
- Remove internal CDN publish script (publish-hyperframe-runtime.ts)
- Replace sandbox-studio, sandbox-interceptor, __magicEditRuntime
  with neutral names (studio, hyperframe-runtime, __hyperframeRuntime)
- Fix stale Vault API / localhost references in docs
- Remove broken deprecated_studio link

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: remove remaining internal codenames and stale references

- Delete stale producer README.md and PIPELINE.md (referenced nonexistent files)
- Replace "Cerberus" codename with "HyperFrames" in test design reviews
- Replace magic-edit postMessage identifiers with hf-preview/hf-parent
- Rename debug-magic-edit-timeline.ts to debug-timeline.ts
- Replace "Motion Cut" with "HyperFrames" in Timeline comments
- Fix studio/CLI references to nonexistent archive package
  (use local data/projects/ dir, stub render proxy)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Vance Ingalls
2026-03-21 22:43:56 -07:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 10621e7903
commit 9f8e5ba5a1
401 changed files with 54545 additions and 2 deletions
@@ -0,0 +1,465 @@
import { useRef, useCallback } from "react";
import { usePlayerStore, liveTime, type TimelineElement } from "../store/playerStore";
import { useMountEffect } from "../lib/useMountEffect";
interface PlayerAPI {
play: () => void;
pause: () => void;
seek: (time: number) => void;
getTime: () => number;
getDuration: () => number;
isPlaying: () => boolean;
}
interface TimelineLike {
play: () => void;
pause: () => void;
seek: (time: number) => void;
time: () => number;
duration: () => number;
isActive: () => boolean;
}
interface ClipManifestClip {
id: string | null;
label: string;
start: number;
duration: number;
track: number;
kind: "video" | "audio" | "image" | "element" | "composition";
tagName: string | null;
compositionId: string | null;
parentCompositionId: string | null;
compositionSrc: string | null;
assetUrl: string | null;
}
interface ClipManifest {
clips: ClipManifestClip[];
scenes: Array<{ id: string; label: string; start: number; duration: number }>;
durationInFrames: number;
}
type IframeWindow = Window & {
__player?: PlayerAPI;
__timeline?: TimelineLike;
__timelines?: Record<string, TimelineLike>;
__clipManifest?: ClipManifest;
};
interface PlaybackAdapter {
play: () => void;
pause: () => void;
seek: (time: number) => void;
getTime: () => number;
getDuration: () => number;
isPlaying: () => boolean;
}
function wrapPlayer(p: PlayerAPI): PlaybackAdapter {
return {
play: () => p.play(),
pause: () => p.pause(),
seek: (t) => p.seek(t),
getTime: () => p.getTime(),
getDuration: () => p.getDuration(),
isPlaying: () => p.isPlaying(),
};
}
function wrapTimeline(tl: TimelineLike): PlaybackAdapter {
return {
play: () => tl.play(),
pause: () => tl.pause(),
seek: (t) => {
tl.pause();
tl.seek(t);
},
getTime: () => tl.time(),
getDuration: () => tl.duration(),
isPlaying: () => tl.isActive(),
};
}
function normalizePreviewViewport(doc: Document, win: Window): void {
if (doc.documentElement) {
doc.documentElement.style.overflow = "hidden";
doc.documentElement.style.margin = "0";
}
if (doc.body) {
doc.body.style.overflow = "hidden";
doc.body.style.margin = "0";
}
win.scrollTo({ top: 0, left: 0, behavior: "auto" });
}
function autoHealMissingCompositionIds(doc: Document): void {
const compositionIdRe = /data-composition-id=["']([^"']+)["']/gi;
const referencedIds = new Set<string>();
const scopedNodes = Array.from(doc.querySelectorAll("style, script"));
for (const node of scopedNodes) {
const text = node.textContent || "";
if (!text) continue;
let match: RegExpExecArray | null;
while ((match = compositionIdRe.exec(text)) !== null) {
const id = (match[1] || "").trim();
if (id) referencedIds.add(id);
}
}
if (referencedIds.size === 0) return;
const existingIds = new Set<string>();
const existingNodes = Array.from(doc.querySelectorAll<HTMLElement>("[data-composition-id]"));
for (const node of existingNodes) {
const id = node.getAttribute("data-composition-id");
if (id) existingIds.add(id);
}
for (const compId of referencedIds) {
if (compId === "root" || existingIds.has(compId)) continue;
const host =
doc.getElementById(`${compId}-layer`) || doc.getElementById(`${compId}-comp`) || doc.getElementById(compId);
if (!host) continue;
if (!host.getAttribute("data-composition-id")) {
host.setAttribute("data-composition-id", compId);
}
}
}
function unmutePreviewMedia(iframe: HTMLIFrameElement | null): void {
if (!iframe) return;
try {
iframe.contentWindow?.postMessage(
{ source: "hf-parent", type: "control", action: "set-muted", muted: false },
"*",
);
// Fallback for CDN runtime that still uses the old source name
iframe.contentWindow?.postMessage(
{ source: "hf-parent", type: "control", action: "set-muted", muted: false },
"*",
);
} catch {
/* ignore */
}
}
export function useTimelinePlayer() {
const iframeRef = useRef<HTMLIFrameElement>(null);
const rafRef = useRef<number>(0);
const probeIntervalRef = useRef<ReturnType<typeof setInterval> | undefined>(undefined);
const pendingSeekRef = useRef<number | null>(null);
const isRefreshingRef = useRef(false);
// ZERO store subscriptions — this hook never causes re-renders.
// All reads use getState() (point-in-time), all writes use the stable setters.
const { setIsPlaying, setCurrentTime, setDuration, setTimelineReady, setElements, reset } =
usePlayerStore.getState();
const getAdapter = useCallback((): PlaybackAdapter | null => {
try {
const win = iframeRef.current?.contentWindow as IframeWindow | null;
if (!win) return null;
if (win.__player && typeof win.__player.play === "function") {
return wrapPlayer(win.__player);
}
if (win.__timeline) return wrapTimeline(win.__timeline);
if (win.__timelines) {
const keys = Object.keys(win.__timelines);
if (keys.length > 0) return wrapTimeline(win.__timelines[keys[keys.length - 1]]);
}
return null;
} catch {
return null;
}
}, []);
const startRAFLoop = useCallback(() => {
const tick = () => {
const adapter = getAdapter();
if (adapter) {
const time = adapter.getTime();
const dur = adapter.getDuration();
liveTime.notify(time); // direct DOM updates, no React re-render
if (time >= dur && !adapter.isPlaying()) {
setCurrentTime(time); // sync Zustand once at end
setIsPlaying(false);
cancelAnimationFrame(rafRef.current);
return;
}
}
rafRef.current = requestAnimationFrame(tick);
};
rafRef.current = requestAnimationFrame(tick);
}, [getAdapter, setCurrentTime, setIsPlaying]);
const stopRAFLoop = useCallback(() => {
cancelAnimationFrame(rafRef.current);
}, []);
const applyPlaybackRate = useCallback((rate: number) => {
const iframe = iframeRef.current;
if (!iframe) return;
// Send to runtime via bridge (works with both new and CDN runtime)
iframe.contentWindow?.postMessage({ source: "hf-parent", type: "control", action: "set-playback-rate", playbackRate: rate }, "*");
iframe.contentWindow?.postMessage({ source: "hf-parent", type: "control", action: "set-playback-rate", playbackRate: rate }, "*");
// Also set directly on GSAP timeline if accessible
try {
const win = iframe.contentWindow as IframeWindow | null;
if (win?.__timelines) {
for (const tl of Object.values(win.__timelines)) {
if (tl && typeof (tl as unknown as { timeScale?: (v: number) => void }).timeScale === "function") {
(tl as unknown as { timeScale: (v: number) => void }).timeScale(rate);
}
}
}
} catch { /* cross-origin */ }
}, []);
const play = useCallback(() => {
const adapter = getAdapter();
if (!adapter) return;
if (adapter.getTime() >= adapter.getDuration()) {
adapter.seek(0);
}
unmutePreviewMedia(iframeRef.current);
applyPlaybackRate(usePlayerStore.getState().playbackRate);
adapter.play();
setIsPlaying(true);
startRAFLoop();
}, [getAdapter, setIsPlaying, startRAFLoop, applyPlaybackRate]);
const pause = useCallback(() => {
const adapter = getAdapter();
if (!adapter) return;
adapter.pause();
setIsPlaying(false);
stopRAFLoop();
}, [getAdapter, setIsPlaying, stopRAFLoop]);
const togglePlay = useCallback(() => {
if (usePlayerStore.getState().isPlaying) {
pause();
} else {
play();
}
}, [play, pause]);
const seek = useCallback(
(time: number) => {
const adapter = getAdapter();
if (!adapter) return;
adapter.seek(time);
liveTime.notify(time); // Direct DOM updates (playhead, timecode, progress) — no re-render
stopRAFLoop();
// Only update store if state actually changes (avoids unnecessary re-renders)
if (usePlayerStore.getState().isPlaying) setIsPlaying(false);
},
[getAdapter, setIsPlaying, stopRAFLoop],
);
// Convert a runtime timeline message (from iframe postMessage) into TimelineElements
const processTimelineMessage = useCallback(
(data: { clips: ClipManifestClip[]; durationInFrames: number }) => {
if (!data.clips || data.clips.length === 0) return;
// Show only root-level clips: those with no parentCompositionId (direct children of root).
// Sub-composition children (parentCompositionId !== null) belong to the drill-down view.
const els: TimelineElement[] = data.clips
.filter((clip) => !clip.parentCompositionId)
.map((clip) => {
const entry: TimelineElement = {
id: clip.id || clip.label || clip.tagName || "element",
tag: clip.tagName || clip.kind,
start: clip.start,
duration: clip.duration,
track: clip.track,
};
if (clip.assetUrl) entry.src = clip.assetUrl;
if (clip.kind === "composition" && clip.compositionId) {
entry.compositionSrc = clip.compositionSrc || `compositions/${clip.compositionId}.html`;
}
return entry;
});
setElements(els);
},
[setElements],
);
const onIframeLoad = useCallback(() => {
unmutePreviewMedia(iframeRef.current);
let attempts = 0;
const maxAttempts = 25;
if (probeIntervalRef.current) clearInterval(probeIntervalRef.current);
probeIntervalRef.current = setInterval(() => {
attempts++;
const adapter = getAdapter();
if (adapter && adapter.getDuration() > 0) {
clearInterval(probeIntervalRef.current);
adapter.pause();
const seekTo = pendingSeekRef.current;
pendingSeekRef.current = null;
const startTime = seekTo != null ? Math.min(seekTo, adapter.getDuration()) : 0;
adapter.seek(startTime);
setDuration(adapter.getDuration());
setCurrentTime(startTime);
if (!isRefreshingRef.current) {
setTimelineReady(true);
}
isRefreshingRef.current = false;
setIsPlaying(false);
try {
const doc = iframeRef.current?.contentDocument;
const iframeWin = iframeRef.current?.contentWindow as IframeWindow | null;
if (doc && iframeWin) {
normalizePreviewViewport(doc, iframeWin);
autoHealMissingCompositionIds(doc);
}
// Try reading __clipManifest if already available (fast path)
const manifest = iframeWin?.__clipManifest;
if (manifest && manifest.clips.length > 0) {
processTimelineMessage(manifest);
} else if (doc) {
// Fallback: parse data-start elements directly from DOM (raw HTML without runtime)
const rootComp = doc.querySelector("[data-composition-id]");
const nodes = doc.querySelectorAll("[data-start]");
const els: TimelineElement[] = [];
let trackCounter = 0;
const rootDuration = adapter.getDuration();
nodes.forEach((node) => {
if (node === rootComp) return;
const el = node as HTMLElement;
const startStr = el.getAttribute("data-start");
if (startStr == null) return;
const start = parseFloat(startStr);
if (isNaN(start)) return;
const tagLower = el.tagName.toLowerCase();
let dur = 0;
const durStr = el.getAttribute("data-duration");
if (durStr != null) dur = parseFloat(durStr);
if (isNaN(dur) || dur <= 0) dur = Math.max(0, rootDuration - start);
const trackStr = el.getAttribute("data-track-index");
const track = trackStr != null ? parseInt(trackStr, 10) : trackCounter++;
const entry: TimelineElement = {
id: el.id || el.className?.split(" ")[0] || tagLower,
tag: tagLower,
start,
duration: dur,
track: isNaN(track) ? 0 : track,
};
if (tagLower === "video" || tagLower === "audio" || tagLower === "img") {
const src = el.getAttribute("src");
if (src) entry.src = src;
}
// Detect sub-compositions
const compSrc = el.getAttribute("data-composition-src");
const compId = el.getAttribute("data-composition-id");
if (compSrc || (compId && compId !== rootComp?.getAttribute("data-composition-id"))) {
entry.compositionSrc = compSrc || `compositions/${compId}.html`;
}
els.push(entry);
});
if (els.length > 0) setElements(els);
}
// The runtime will also postMessage the full timeline after all compositions load.
// That message is handled by the window listener below, which will update elements
// with the complete data (including async-loaded compositions).
} catch {
// Cross-origin or DOM access error
}
return;
}
if (attempts >= maxAttempts) {
clearInterval(probeIntervalRef.current);
console.warn("Could not find __player, __timeline, or __timelines on iframe after 5s");
}
}, 200);
}, [getAdapter, setDuration, setCurrentTime, setTimelineReady, setIsPlaying, processTimelineMessage]);
/** Save the current playback time so the next onIframeLoad restores it. */
const saveSeekPosition = useCallback(() => {
const adapter = getAdapter();
pendingSeekRef.current = adapter ? adapter.getTime() : (usePlayerStore.getState().currentTime ?? 0);
isRefreshingRef.current = true;
stopRAFLoop();
setIsPlaying(false);
}, [getAdapter, stopRAFLoop, setIsPlaying]);
const refreshPlayer = useCallback(() => {
const iframe = iframeRef.current;
if (!iframe) return;
saveSeekPosition();
const src = iframe.src;
const url = new URL(src, window.location.origin);
url.searchParams.set("_t", String(Date.now()));
iframe.src = url.toString();
}, [saveSeekPosition]);
const togglePlayRef = useRef(togglePlay);
togglePlayRef.current = togglePlay;
const processTimelineMessageRef = useRef(processTimelineMessage);
processTimelineMessageRef.current = processTimelineMessage;
useMountEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.code === "Space" && e.target === document.body) {
e.preventDefault();
togglePlayRef.current();
}
};
// Listen for timeline messages from the iframe runtime.
// The runtime sends this AFTER all external compositions load,
// so we get the complete clip list (not just the first few).
const handleMessage = (e: MessageEvent) => {
const data = e.data;
if ((data?.source === "hf-preview" || data?.source === "hf-preview") && data?.type === "timeline" && Array.isArray(data.clips)) {
processTimelineMessageRef.current(data);
// Update duration only if the new value is longer (don't downgrade during generation)
if (data.durationInFrames > 0) {
const fps = 30;
const dur = data.durationInFrames / fps;
const currentDur = usePlayerStore.getState().duration;
if (dur > currentDur) usePlayerStore.getState().setDuration(dur);
}
}
};
window.addEventListener("keydown", handleKeyDown);
window.addEventListener("message", handleMessage);
return () => {
window.removeEventListener("keydown", handleKeyDown);
window.removeEventListener("message", handleMessage);
stopRAFLoop();
if (probeIntervalRef.current) clearInterval(probeIntervalRef.current);
reset();
};
});
return {
iframeRef,
play,
pause,
togglePlay,
seek,
onIframeLoad,
refreshPlayer,
saveSeekPosition,
};
}