"use client"; import { useEffect, useState, type RefObject } from "react"; import { blankingInsets, findAspectPreset, isFullFrame, letterboxRect, type BlankingMode, } from "@/lib/aspect-overlays"; interface AspectOverlayProps { /** Preset id, or "off" — anything unknown draws nothing. */ presetId: string; mode: BlankingMode; videoRef: RefObject; } /** * Delivery blanking drawn over the video. Purely visual: it never touches the * video element, sits under the annotation canvas, and passes every pointer * event through so click-to-play still works underneath it. */ export function AspectOverlay({ presetId, mode, videoRef }: AspectOverlayProps) { const preset = findAspectPreset(presetId); const [box, setBox] = useState<{ top: number; bottom: number; left: number; right: number; rect: { left: number; top: number; width: number; height: number }; } | null>(null); useEffect(() => { const video = videoRef.current; if (!video || !preset) { setBox(null); return; } const measure = () => { const rect = letterboxRect( video.clientWidth, video.clientHeight, video.videoWidth, video.videoHeight ); // Before metadata lands videoWidth is 0 and there is nothing to measure. if (!rect) { setBox(null); return; } const insets = blankingInsets(rect, preset.ratio); setBox(isFullFrame(insets) ? null : { ...insets, rect }); }; measure(); // The picture rect moves on layout changes (panel resize, fullscreen) and // on metadata, which is when intrinsic dimensions first exist. const observer = new ResizeObserver(measure); observer.observe(video); video.addEventListener("loadedmetadata", measure); return () => { observer.disconnect(); video.removeEventListener("loadedmetadata", measure); }; }, [preset, videoRef]); if (!preset || !box) return null; const { rect } = box; const barClass = mode === "solid" ? "absolute bg-black" : "absolute bg-black/60 ring-1 ring-inset ring-white/25"; // Each bar is positioned against the picture rect, not the element box, so // blanking lands on the image itself whatever shape the player is. const horizontal = box.top > 0; return ( // No z-index: the annotation canvas is also absolutely positioned and comes // later in the DOM, so it stays on top and marks drawn over a blanked area // remain visible.