diff --git a/components/top-navbar.tsx b/components/top-navbar.tsx
index ecbe41ff3..c21ef06bc 100644
--- a/components/top-navbar.tsx
+++ b/components/top-navbar.tsx
@@ -13,6 +13,7 @@ import {
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { ImageAnnotator } from "@/components/image-annotator";
+import { VideoPlayer } from "@/components/video-player";
type RankingItem = {
rank: number;
@@ -478,9 +479,9 @@ export function TopNavbar({
{/* 视频播放器 */}
-
- {currentTutorialVideo.videoUrl.includes('youtube.com') || currentTutorialVideo.videoUrl.includes('youtu.be') ? (
- // YouTube 嵌入 - 启用自动播放和性能优化
+ {currentTutorialVideo.videoUrl.includes('youtube.com') || currentTutorialVideo.videoUrl.includes('youtu.be') ? (
+ // YouTube 嵌入 - 启用自动播放和性能优化
+
- ) : currentTutorialVideo.videoUrl.includes('bilibili.com') ? (
- // Bilibili 嵌入
+
+ ) : currentTutorialVideo.videoUrl.includes('bilibili.com') ? (
+ // Bilibili 嵌入
+
- ) : (
- // 高性能原生视频播放器
-
- )}
-
+
+ ) : (
+ // 自定义高性能视频播放器 - 针对慢速网络优化
+
+ )}
{/* 视频描述 */}
{currentTutorialVideo.description && (
diff --git a/components/video-player.tsx b/components/video-player.tsx
new file mode 100644
index 000000000..2387548cb
--- /dev/null
+++ b/components/video-player.tsx
@@ -0,0 +1,622 @@
+"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)}
+ >
+ {/* 视频元素 */}
+
+
+ {/* 加载指示器 */}
+ {(isLoading || isBuffering) && !error && (
+
+
+
+
+ {isLoading ? "加载中..." : "缓冲中..."}
+
+ {loadProgress > 0 && (
+
+ )}
+ {/* 网络质量指示 */}
+
+ 网络:
+
+ {networkQuality === "good" ? "良好" : networkQuality === "medium" ? "一般" : "较慢"}
+
+
+
+
+ )}
+
+ {/* 错误提示 */}
+ {error && (
+
+ )}
+
+ {/* 大播放按钮(暂停时显示) */}
+ {!isPlaying && !isLoading && !error && (
+
+ )}
+
+ {/* 控制栏 */}
+
+ {/* 进度条 */}
+
+ {/* 缓冲进度 */}
+ {bufferedRanges.map((range, i) => (
+
+ ))}
+ {/* 播放进度 */}
+
+ {/* 进度条手柄 */}
+
+
+
+ {/* 控制按钮 */}
+
+
+ {/* 播放/暂停 */}
+
+
+ {/* 快退 */}
+
+
+ {/* 快进 */}
+
+
+ {/* 音量 */}
+
+
+
+
+
+ {/* 时间 */}
+
+ {formatTime(currentTime)} / {formatTime(duration)}
+
+
+
+
+ {/* 播放速度 */}
+
+
+ {showSpeedMenu && (
+
+ {playbackRates.map((rate) => (
+
+ ))}
+
+ )}
+
+
+ {/* 全屏 */}
+
+
+
+
+ {/* 标题 */}
+ {title && (
+
+ {title}
+
+ )}
+
+
+ );
+}