/** * @file video-comp.tsx — DDR-148 video-comp canvas kind. * @scope apps/studio/video-comp.tsx (re-exported from canvas-lib.tsx as * part of the `@maude/canvas-lib` surface). * @purpose Mount a Remotion composition inside `@remotion/player`'s `` * for free scrub/preview, and expose the ONE deterministic seek * bridge (`window.__maude_seek__`) that the capture spine * (`bin/_video-playwright.mjs`) and the Timeline panel drive. * * A video-comp is deterministic BY CONSTRUCTION — every animated * value is a pure function of `useCurrentFrame()`, so seeking the * Player's `frame` renders one exact frame with no free-running * clock. The capture shim steps `__maude_seek__(0..N)` and * screenshots each frame; two runs to frame N are pixel-identical. * * Runtime wiring: `remotion` + `@remotion/player` are externalised * through the canvas importmap (RUNTIME_PACKAGES / DDR-148), so the * Player's provider stack and the composition's `useCurrentFrame()` * bind to the SAME `remotion` module instance (no dual-package * frozen-at-frame-0 hazard). */ import { Player, type PlayerRef } from '@remotion/player'; import { type ComponentType, useEffect, useMemo, useRef, useState } from 'react'; // ───────────────────────────────────────────────────────────────────────────── // Comp meta contract + the module-level seek registry /** The temporal contract a video-comp artboard declares. */ export interface VideoCompMeta { /** Frames per second (Remotion timebase). */ fps: number; /** Total composition length, in frames. */ durationInFrames: number; /** Composition pixel width. */ width: number; /** Composition pixel height. */ height: number; } /** A live registration — the Player handle + its meta, keyed by artboard id. */ interface CompEntry extends VideoCompMeta { id: string; /** The enclosing DCArtboard's id (`data-dc-screen`), resolved by DOM * containment at mount. The VideoComp's own `compId` is usually an * author-omitted `videocomp-N` mount slug, so this is the ONLY reliable way * for the shell to map "the artboard the user is looking at" → "the comp the * transport must drive" (rca/issue-timeline-drives-wrong-artboard). Null on a * comp mounted outside an artboard (bare preview / test harness). */ artboardId: string | null; /** The artboard's visible label — shown in the Timeline head so the user can * see WHICH artboard the rows + transport belong to. */ artboardLabel: string | null; ref: React.RefObject; /** The Remotion composition component + its props — carried so the export * capture's renderMediaOnWeb path (DDR-148 addendum, audio export) can * render the SAME component tree Remotion owns the timeline math for * (TransitionSeries offsets,