'use client' import { useState, useRef, useEffect } from 'react' interface VideoPlayerModalProps { videoSrc: string onClose: () => void } export default function VideoPlayerModal({ videoSrc, onClose }: VideoPlayerModalProps) { const [isLoading, setIsLoading] = useState(true) const [isBuffering, setIsBuffering] = useState(false) const [loadProgress, setLoadProgress] = useState(0) const [isPlaying, setIsPlaying] = useState(false) const [currentTime, setCurrentTime] = useState(0) const [duration, setDuration] = useState(0) const [volume, setVolume] = useState(1) const [isMuted, setIsMuted] = useState(false) const [showControls, setShowControls] = useState(true) const [videoAspectRatio, setVideoAspectRatio] = useState(null) const [isHoveringProgress, setIsHoveringProgress] = useState(false) const [isDraggingProgress, setIsDraggingProgress] = useState(false) const [hoverTime, setHoverTime] = useState(0) const [hoverPosition, setHoverPosition] = useState(0) const videoRef = useRef(null) const controlsTimeoutRef = useRef(null) const containerRef = useRef(null) const progressBarRef = useRef(null) // 格式化时间显示 const formatTime = (seconds: number): string => { if (!isFinite(seconds) || isNaN(seconds)) return '0:00' const mins = Math.floor(seconds / 60) const secs = Math.floor(seconds % 60) return `${mins}:${secs.toString().padStart(2, '0')}` } // 隐藏控制栏的定时器 const resetControlsTimeout = () => { if (controlsTimeoutRef.current) { clearTimeout(controlsTimeoutRef.current) } setShowControls(true) if (isPlaying) { controlsTimeoutRef.current = setTimeout(() => { setShowControls(false) }, 3000) } } useEffect(() => { // 组件挂载时,视频开始加载 setIsLoading(true) setCurrentTime(0) setDuration(0) setLoadProgress(0) // 立即开始加载视频(浏览器会利用预加载的缓存) if (videoRef.current && videoSrc) { videoRef.current.load() } }, [videoSrc]) // 清理定时器和事件监听器 useEffect(() => { return () => { // 清理控制栏定时器 if (controlsTimeoutRef.current) { clearTimeout(controlsTimeoutRef.current) controlsTimeoutRef.current = null } // 清理视频引用,防止内存泄漏 if (videoRef.current) { videoRef.current.pause() videoRef.current.src = '' videoRef.current.load() } } }, []) const handleLoadStart = () => { console.log('[VideoPlayer] 开始加载视频') setIsLoading(true) setLoadProgress(0) } const handleLoadedMetadata = () => { console.log('[VideoPlayer] 视频元数据加载完成') if (videoRef.current) { const video = videoRef.current setDuration(video.duration) // 计算视频宽高比 if (video.videoWidth && video.videoHeight) { setVideoAspectRatio(video.videoWidth / video.videoHeight) } } setIsLoading(false) } const handleProgress = (e: React.SyntheticEvent) => { const video = e.currentTarget if (video.buffered.length > 0 && video.duration > 0) { const bufferedEnd = video.buffered.end(video.buffered.length - 1) const progress = (bufferedEnd / video.duration) * 100 setLoadProgress(progress) } } const handleTimeUpdate = () => { if (videoRef.current) { setCurrentTime(videoRef.current.currentTime) } } const handleCanPlay = () => { console.log('[VideoPlayer] 视频可以播放') setIsLoading(false) if (videoRef.current) { videoRef.current.play().catch(() => {}) } } const handleCanPlayThrough = () => { console.log('[VideoPlayer] 视频可以流畅播放') setIsLoading(false) setIsBuffering(false) } const handleWaiting = () => { console.log('[VideoPlayer] 视频缓冲中...') setIsBuffering(true) } const handlePlaying = () => { console.log('[VideoPlayer] 视频开始播放') setIsPlaying(true) setIsBuffering(false) } const handlePause = () => { setIsPlaying(false) setShowControls(true) } const handleEnded = () => { setIsPlaying(false) setCurrentTime(0) if (videoRef.current) { videoRef.current.currentTime = 0 } } const handleError = (e: React.SyntheticEvent) => { console.error('[VideoPlayer] 视频加载错误:', e) setIsLoading(false) } // 播放/暂停切换 const togglePlay = () => { if (videoRef.current) { if (isPlaying) { videoRef.current.pause() } else { videoRef.current.play().catch(() => {}) } } } // 进度条点击或拖拽 const handleProgressClick = (e: React.MouseEvent) => { if (videoRef.current && duration > 0 && progressBarRef.current) { const rect = progressBarRef.current.getBoundingClientRect() const clickX = e.clientX - rect.left const percentage = Math.max(0, Math.min(1, clickX / rect.width)) const newTime = percentage * duration videoRef.current.currentTime = newTime setCurrentTime(newTime) } } // 进度条鼠标按下(开始拖拽) const handleProgressMouseDown = (e: React.MouseEvent) => { e.preventDefault() setIsDraggingProgress(true) if (videoRef.current && duration > 0 && progressBarRef.current) { const rect = progressBarRef.current.getBoundingClientRect() const clickX = e.clientX - rect.left const percentage = Math.max(0, Math.min(1, clickX / rect.width)) const newTime = percentage * duration videoRef.current.currentTime = newTime setCurrentTime(newTime) } } // 进度条鼠标移动(拖拽中) useEffect(() => { const handleMouseMove = (e: MouseEvent) => { if (isDraggingProgress && videoRef.current && duration > 0 && progressBarRef.current) { const rect = progressBarRef.current.getBoundingClientRect() const clickX = e.clientX - rect.left const percentage = Math.max(0, Math.min(1, clickX / rect.width)) const newTime = percentage * duration videoRef.current.currentTime = newTime setCurrentTime(newTime) } } const handleMouseUp = () => { setIsDraggingProgress(false) } if (isDraggingProgress) { document.addEventListener('mousemove', handleMouseMove) document.addEventListener('mouseup', handleMouseUp) return () => { document.removeEventListener('mousemove', handleMouseMove) document.removeEventListener('mouseup', handleMouseUp) } } }, [isDraggingProgress, duration]) // 音量控制 const handleVolumeChange = (e: React.ChangeEvent) => { const newVolume = parseFloat(e.target.value) setVolume(newVolume) if (videoRef.current) { videoRef.current.volume = newVolume setIsMuted(newVolume === 0) } } // 静音切换 const toggleMute = () => { if (videoRef.current) { if (isMuted) { videoRef.current.volume = volume || 0.5 setIsMuted(false) } else { videoRef.current.volume = 0 setIsMuted(true) } } } // 全屏切换 const toggleFullscreen = () => { if (containerRef.current) { if (!document.fullscreenElement) { containerRef.current.requestFullscreen().catch(() => {}) } else { document.exitFullscreen().catch(() => {}) } } } // 节流鼠标移动事件,避免频繁调用导致性能问题 const mouseMoveThrottleRef = useRef(null) // 鼠标移动显示控制栏(节流处理) const handleMouseMove = () => { // 如果已经有待处理的调用,跳过 if (mouseMoveThrottleRef.current) { return } // 立即执行一次 resetControlsTimeout() // 设置节流,200ms内不再处理 mouseMoveThrottleRef.current = setTimeout(() => { mouseMoveThrottleRef.current = null }, 200) } // 鼠标离开隐藏控制栏(仅在播放时) const handleMouseLeave = () => { // 清理节流定时器 if (mouseMoveThrottleRef.current) { clearTimeout(mouseMoveThrottleRef.current) mouseMoveThrottleRef.current = null } if (isPlaying && controlsTimeoutRef.current) { clearTimeout(controlsTimeoutRef.current) controlsTimeoutRef.current = setTimeout(() => { setShowControls(false) }, 1000) } } // 清理节流定时器 useEffect(() => { return () => { if (mouseMoveThrottleRef.current) { clearTimeout(mouseMoveThrottleRef.current) } } }, []) const progressPercentage = duration > 0 ? (currentTime / duration) * 100 : 0 return (
e.stopPropagation()} onMouseMove={handleMouseMove} onMouseLeave={handleMouseLeave} > {/* 视频容器 - 根据视频实际宽高比动态调整 */}
) }