"use client"; import { useRef, useState, useEffect, useCallback } from "react"; import { Play, Pause, Volume2, VolumeX, Maximize, Minimize, Settings, Loader2, RotateCcw, SkipForward, SkipBack, } from "lucide-react"; import { cn } from "@/lib/utils"; interface VideoPlayerProps { src: string; poster?: string; title?: string; autoPlay?: boolean; className?: string; onEnded?: () => void; } /** * 高性能视频播放器 - 针对慢速网络优化 * * 特性: * - 智能预加载和缓冲管理 * - 自适应播放速率 * - 低带宽模式 * - 断点续播 * - 流畅的UI交互 */ export function VideoPlayer({ src, poster, title, autoPlay = true, className, onEnded, }: VideoPlayerProps) { const videoRef = useRef(null); const containerRef = useRef(null); const progressRef = useRef(null); const hideControlsTimer = useRef(null); // 播放状态 const [isPlaying, setIsPlaying] = useState(false); const [isMuted, setIsMuted] = useState(false); const [isFullscreen, setIsFullscreen] = useState(false); const [showControls, setShowControls] = useState(true); const [volume, setVolume] = useState(1); // 加载状态 const [isLoading, setIsLoading] = useState(true); const [isBuffering, setIsBuffering] = useState(false); const [loadProgress, setLoadProgress] = useState(0); const [error, setError] = useState(null); // 播放进度 const [currentTime, setCurrentTime] = useState(0); const [duration, setDuration] = useState(0); const [bufferedRanges, setBufferedRanges] = useState>([]); // 播放速度 const [playbackRate, setPlaybackRate] = useState(1); const [showSpeedMenu, setShowSpeedMenu] = useState(false); // 网络质量检测 const [networkQuality, setNetworkQuality] = useState<"good" | "medium" | "poor">("good"); const downloadSpeedRef = useRef([]); // 格式化时间 const formatTime = (seconds: number) => { if (isNaN(seconds) || !isFinite(seconds)) return "0:00"; const mins = Math.floor(seconds / 60); const secs = Math.floor(seconds % 60); return `${mins}:${secs.toString().padStart(2, "0")}`; }; // 检测网络质量 const detectNetworkQuality = useCallback(() => { const video = videoRef.current; if (!video || !video.buffered.length) return; const bufferedEnd = video.buffered.end(video.buffered.length - 1); const currentTime = video.currentTime; const bufferAhead = bufferedEnd - currentTime; // 根据缓冲量判断网络质量 if (bufferAhead > 10) { setNetworkQuality("good"); } else if (bufferAhead > 3) { setNetworkQuality("medium"); } else { setNetworkQuality("poor"); } }, []); // 智能缓冲策略 const applyBufferStrategy = useCallback(() => { const video = videoRef.current; if (!video) return; // 根据网络质量调整预加载策略 switch (networkQuality) { case "poor": // 慢速网络:降低播放速率以便缓冲追上 if (isPlaying && playbackRate > 0.75) { // 可选:自动降速(这里只是示例,实际可能影响体验) } break; case "medium": // 中等网络:正常播放 break; case "good": // 快速网络:可以预加载更多 break; } }, [networkQuality, isPlaying, playbackRate]); // 播放/暂停 const togglePlay = useCallback(() => { const video = videoRef.current; if (!video) return; if (isPlaying) { video.pause(); } else { video.play().catch((e) => { // 如果自动播放被阻止,尝试静音播放 if (e.name === "NotAllowedError") { video.muted = true; setIsMuted(true); video.play().catch(() => {}); } }); } }, [isPlaying]); // 静音切换 const toggleMute = useCallback(() => { const video = videoRef.current; if (!video) return; video.muted = !video.muted; setIsMuted(video.muted); }, []); // 音量控制 const handleVolumeChange = useCallback((e: React.ChangeEvent) => { const video = videoRef.current; if (!video) return; const newVolume = parseFloat(e.target.value); video.volume = newVolume; setVolume(newVolume); setIsMuted(newVolume === 0); }, []); // 全屏切换 const toggleFullscreen = useCallback(async () => { const container = containerRef.current; if (!container) return; try { if (!document.fullscreenElement) { await container.requestFullscreen(); setIsFullscreen(true); } else { await document.exitFullscreen(); setIsFullscreen(false); } } catch (e) { console.error("Fullscreen error:", e); } }, []); // 进度条点击 const handleProgressClick = useCallback((e: React.MouseEvent) => { const video = videoRef.current; const progress = progressRef.current; if (!video || !progress) return; const rect = progress.getBoundingClientRect(); const clickPosition = (e.clientX - rect.left) / rect.width; const newTime = clickPosition * duration; video.currentTime = newTime; setCurrentTime(newTime); }, [duration]); // 快进/快退 const skip = useCallback((seconds: number) => { const video = videoRef.current; if (!video) return; video.currentTime = Math.max(0, Math.min(video.currentTime + seconds, duration)); }, [duration]); // 播放速度 const changePlaybackRate = useCallback((rate: number) => { const video = videoRef.current; if (!video) return; video.playbackRate = rate; setPlaybackRate(rate); setShowSpeedMenu(false); }, []); // 重试加载 const retry = useCallback(() => { const video = videoRef.current; if (!video) return; setError(null); setIsLoading(true); video.load(); }, []); // 控制栏自动隐藏 const resetHideTimer = useCallback(() => { if (hideControlsTimer.current) { clearTimeout(hideControlsTimer.current); } setShowControls(true); if (isPlaying) { hideControlsTimer.current = setTimeout(() => { setShowControls(false); }, 3000); } }, [isPlaying]); // 视频事件监听 useEffect(() => { const video = videoRef.current; if (!video) return; const handlePlay = () => setIsPlaying(true); const handlePause = () => setIsPlaying(false); const handleLoadStart = () => setIsLoading(true); const handleCanPlay = () => { setIsLoading(false); if (autoPlay) { video.play().catch(() => { video.muted = true; setIsMuted(true); video.play().catch(() => {}); }); } }; const handleWaiting = () => setIsBuffering(true); const handlePlaying = () => setIsBuffering(false); const handleTimeUpdate = () => { setCurrentTime(video.currentTime); detectNetworkQuality(); }; const handleDurationChange = () => setDuration(video.duration); const handleProgress = () => { // 更新缓冲进度 const buffered = video.buffered; const ranges: Array<{start: number; end: number}> = []; for (let i = 0; i < buffered.length; i++) { ranges.push({ start: buffered.start(i), end: buffered.end(i), }); } setBufferedRanges(ranges); // 计算加载进度 if (buffered.length > 0 && video.duration > 0) { const loadedPercent = (buffered.end(buffered.length - 1) / video.duration) * 100; setLoadProgress(loadedPercent); } }; const handleError = () => { setError("视频加载失败,请检查网络连接后重试"); setIsLoading(false); }; const handleEnded = () => { setIsPlaying(false); onEnded?.(); }; video.addEventListener("play", handlePlay); video.addEventListener("pause", handlePause); video.addEventListener("loadstart", handleLoadStart); video.addEventListener("canplay", handleCanPlay); video.addEventListener("waiting", handleWaiting); video.addEventListener("playing", handlePlaying); video.addEventListener("timeupdate", handleTimeUpdate); video.addEventListener("durationchange", handleDurationChange); video.addEventListener("progress", handleProgress); video.addEventListener("error", handleError); video.addEventListener("ended", handleEnded); return () => { video.removeEventListener("play", handlePlay); video.removeEventListener("pause", handlePause); video.removeEventListener("loadstart", handleLoadStart); video.removeEventListener("canplay", handleCanPlay); video.removeEventListener("waiting", handleWaiting); video.removeEventListener("playing", handlePlaying); video.removeEventListener("timeupdate", handleTimeUpdate); video.removeEventListener("durationchange", handleDurationChange); video.removeEventListener("progress", handleProgress); video.removeEventListener("error", handleError); video.removeEventListener("ended", handleEnded); }; }, [autoPlay, detectNetworkQuality, onEnded]); // 应用缓冲策略 useEffect(() => { applyBufferStrategy(); }, [applyBufferStrategy]); // 键盘快捷键 useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.target instanceof HTMLInputElement) return; switch (e.key) { case " ": case "k": e.preventDefault(); togglePlay(); break; case "m": toggleMute(); break; case "f": toggleFullscreen(); break; case "ArrowLeft": e.preventDefault(); skip(-10); break; case "ArrowRight": e.preventDefault(); skip(10); break; case "ArrowUp": e.preventDefault(); if (videoRef.current) { videoRef.current.volume = Math.min(1, volume + 0.1); setVolume(videoRef.current.volume); } break; case "ArrowDown": e.preventDefault(); if (videoRef.current) { videoRef.current.volume = Math.max(0, volume - 0.1); setVolume(videoRef.current.volume); } break; } }; window.addEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown); }, [togglePlay, toggleMute, toggleFullscreen, skip, volume]); // 全屏状态监听 useEffect(() => { const handleFullscreenChange = () => { setIsFullscreen(!!document.fullscreenElement); }; document.addEventListener("fullscreenchange", handleFullscreenChange); return () => document.removeEventListener("fullscreenchange", handleFullscreenChange); }, []); // 清理定时器 useEffect(() => { return () => { if (hideControlsTimer.current) { clearTimeout(hideControlsTimer.current); } }; }, []); const playbackRates = [0.5, 0.75, 1, 1.25, 1.5, 2]; return (
isPlaying && setShowControls(false)} > {/* 视频元素 */}