Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 | import { useRef } from "react"; import type { SubtitleCue } from "@/lib/types"; interface SubtitleOverlayProps { cues: SubtitleCue[]; position: number; // current MPV playback position in seconds delay?: number; // seconds; positive = show future cues, negative = show past cues fontSize?: number; // px, default 18 fontFamily?: string; // CSS font-family string, default system-ui posX?: number; // 0–100 %, default 50 posY?: number; // 0–100 %, default 88 editMode?: boolean; // enables drag + shows dashed border onPositionChange?: (x: number, y: number) => void; } export const SubtitleOverlay = ({ cues, position, delay = 0, fontSize = 18, fontFamily = "system-ui, sans-serif", posX = 50, posY = 88, editMode = false, onPositionChange, }: SubtitleOverlayProps) => { const ref = useRef<HTMLDivElement>(null); const lookupPos = position + delay; const active = cues.find((c) => lookupPos >= c.start && lookupPos <= c.end); if (!active && !editMode) return null; if (cues.length === 0 && !editMode) return null; const displayText = active?.text ?? "Sample subtitle text"; const handleMouseDown = (e: React.MouseEvent) => { if (!editMode || !onPositionChange) return; e.preventDefault(); const parent = ref.current?.parentElement; if (!parent) return; const rect = parent.getBoundingClientRect(); const startX = e.clientX; const startY = e.clientY; const startPosX = posX; const startPosY = posY; const handleMove = (me: MouseEvent) => { const newX = Math.min( 95, Math.max(5, startPosX + ((me.clientX - startX) / rect.width) * 100) ); const newY = Math.min( 97, Math.max(3, startPosY + ((me.clientY - startY) / rect.height) * 100) ); onPositionChange(newX, newY); }; const handleUp = () => { document.removeEventListener("mousemove", handleMove); document.removeEventListener("mouseup", handleUp); }; document.addEventListener("mousemove", handleMove); document.addEventListener("mouseup", handleUp); }; return ( <div ref={ref} onMouseDown={handleMouseDown} style={{ position: "absolute", left: `${posX}%`, top: `${posY}%`, transform: "translate(-50%, -50%)", pointerEvents: editMode ? "auto" : "none", zIndex: 30, cursor: editMode ? "grab" : "default", maxWidth: "80%", userSelect: "none", }} > <div style={{ fontSize: `${fontSize}px`, fontFamily, backgroundColor: "rgba(0,0,0,0.65)", color: "white", padding: "6px 16px", borderRadius: "4px", textAlign: "center", lineHeight: 1.4, textShadow: "0 1px 3px rgba(0,0,0,0.9)", border: editMode ? "2px dashed rgba(255,255,255,0.6)" : "none", opacity: !active && editMode ? 0.5 : 1, }} > {displayText.split("\n").map((line, i, arr) => ( <span key={i}> {line} {i < arr.length - 1 && <br />} </span> ))} </div> </div> ); }; |