All files / src/components/player SubtitleOverlay.tsx

0% Statements 0/84
0% Branches 0/1
0% Functions 0/1
0% Lines 0/84

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>
	);
};