import { useState, useRef, forwardRef, useEffect, KeyboardEvent, useCallback } from "react"; import Button from "../Button"; import { getIndex, getNearistIndex, parseSrt } from "../../subtitle"; import SubtitleDisplay from "./SubtitleDisplay"; type VideoPanelProps = { videoUrl: string | null; srtUrl: string | null; }; const VideoPanel = forwardRef(( { videoUrl, srtUrl }, videoRef ) => { videoRef = videoRef as React.RefObject; const [isPlaying, setIsPlaying] = useState(false); const [srtLength, setSrtLength] = useState(0); const [progress, setProgress] = useState(-1); const [autoPause, setAutoPause] = useState(true); const [spanText, setSpanText] = useState(''); const [subtitle, setSubtitle] = useState(''); const parsedSrtRef = useRef<{ start: number; end: number; text: string; }[] | null>(null); const rafldRef = useRef(0); const ready = useRef({ 'vid': false, 'sub': false, 'all': function () { return this.vid && this.sub } }); const togglePlayPause = useCallback(() => { if (!videoUrl) return; const video = videoRef.current; if (!video) return; if (video.paused || video.currentTime === 0) { video.play(); } else { video.pause(); } setIsPlaying(!video.paused); }, [videoRef, videoUrl]); useEffect(() => { const cb = () => { if (ready.current.all()) { if (!parsedSrtRef.current) { ; } else if (isPlaying) { // 这里负责显示当前时间的字幕与自动暂停 const srt = parsedSrtRef.current; const ct = videoRef.current?.currentTime as number; const index = getIndex(srt, ct); if (index !== null) { setSubtitle(srt[index].text) if (autoPause && ct >= (srt[index].end - 0.05) && ct < srt[index].end) { videoRef.current!.currentTime = srt[index].start; togglePlayPause(); } } else { setSubtitle(''); } } else { ; } } rafldRef.current = requestAnimationFrame(cb); } rafldRef.current = requestAnimationFrame(cb); return () => { cancelAnimationFrame(rafldRef.current); } }, [autoPause, isPlaying, togglePlayPause, videoRef]); useEffect(() => { if (videoUrl && videoRef.current) { videoRef.current.src = videoUrl; videoRef.current.load(); setIsPlaying(false); ready.current['vid'] = true; } }, [videoRef, videoUrl]); useEffect(() => { if (srtUrl) { fetch(srtUrl) .then(response => response.text()) .then(data => { parsedSrtRef.current = parseSrt(data); setSrtLength(parsedSrtRef.current.length); ready.current['sub'] = true; }); } }, [srtUrl]); const timeUpdate = () => { if (!parsedSrtRef.current || !videoRef.current) return; const index = getIndex(parsedSrtRef.current, videoRef.current.currentTime); if (!index) return; setSpanText(`${index + 1}/${parsedSrtRef.current.length}`) } const handleSeek = (e: React.ChangeEvent) => { if (videoRef.current && parsedSrtRef.current) { const newProgress = parseInt(e.target.value); videoRef.current.currentTime = parsedSrtRef.current[newProgress]?.start || 0; setProgress(newProgress); } }; const handleAutoPauseToggle = () => { setAutoPause(!autoPause); }; const next = () => { if (!parsedSrtRef.current || !videoRef.current) return; const i = getNearistIndex(parsedSrtRef.current, videoRef.current.currentTime); if (i != null && i + 1 < parsedSrtRef.current.length) { videoRef.current.currentTime = parsedSrtRef.current[i + 1].start; videoRef.current.play(); setIsPlaying(true); } } const previous = () => { if (!parsedSrtRef.current || !videoRef.current) return; const i = getNearistIndex(parsedSrtRef.current, videoRef.current.currentTime); if (i != null && i - 1 >= 0) { videoRef.current.currentTime = parsedSrtRef.current[i - 1].start; videoRef.current.play(); setIsPlaying(true); } } const restart = () => { if (!parsedSrtRef.current || !videoRef.current) return; const i = getNearistIndex(parsedSrtRef.current, videoRef.current.currentTime); if (i != null && i >= 0) { videoRef.current.currentTime = parsedSrtRef.current[i].start; videoRef.current.play(); setIsPlaying(true); } } const handleKeyDownEvent = (e: KeyboardEvent) => { if (e.key === 'n') { next(); } else if (e.key === 'p') { previous(); } else if (e.key === ' ') { togglePlayPause(); } else if (e.key === 'r') { restart(); } else if (e.key === 'a') { ; handleAutoPauseToggle(); } } return (
{spanText}
); }); VideoPanel.displayName = 'VideoPanel'; export default VideoPanel;