diff --git a/apps/web/src/components/media/editor/Player.tsx b/apps/web/src/components/media/editor/Player.tsx new file mode 100644 index 0000000..82c3756 --- /dev/null +++ b/apps/web/src/components/media/editor/Player.tsx @@ -0,0 +1,210 @@ +import type { Edl } from "@rakazo/contracts"; +import { cn } from "@rakazo/ui-web"; +import { useCallback, useEffect, useImperativeHandle, useRef, useState } from "react"; +import { assetFileUrl, useMediaObjectUrl } from "./media-files"; +import { clipLengthMs, clipOutputStartMs, edlDurationMs, locateOutputTime } from "./timeline"; + +export interface PlayerHandle { + /** Jump to an output timestamp in ms. */ + seek(ms: number): void; +} + +/** What the source player currently shows, in source coordinates. */ +export interface SourceFrame { + outputMs: number; + clipId: string; + assetId: string; + sourceMs: number; +} + +/** Plays a finished render. Reports the playhead in output ms. */ +export function RenderVideo({ + src, + poster, + handleRef, + onTime, + className, + label, +}: { + src: string | null; + poster?: string | null; + handleRef?: React.Ref; + onTime?: (ms: number) => void; + className?: string; + label: string; +}) { + const video = useRef(null); + const pending = useRef(null); + useImperativeHandle(handleRef, () => ({ + seek(ms) { + const element = video.current; + if (!element || element.readyState < 1) { + pending.current = ms; + return; + } + element.currentTime = ms / 1000; + onTime?.(ms); + }, + })); + return ( + // biome-ignore lint/a11y/useMediaCaption: renders carry burned-in or sidecar captions. +