You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ReactJS如何在视频就绪时切换video标签以避免加载黑屏

在NextJS+TypeScript中实现VideoJS双播放器无缝切换消除黑屏

核心实现思路

通过维护两个VideoJS播放器实例,一个处于显示状态,另一个处于隐藏预加载状态:

  • 收到服务器socket的video_data事件时,更新隐藏播放器的视频源
  • 待隐藏播放器触发canplay事件(视频已加载至可播放状态),切换两者的可见性,彻底避免切换过程中的黑屏

完整代码实现

1. 安装依赖

先确保安装必要的包:

npm install video.js @types/video.js socket.io-client
# 或
yarn add video.js @types/video.js socket.io-client

2. 播放器组件代码

import React, { useState, useRef, useEffect } from 'react';
import videojs from 'video.js';
import type Player from 'video.js/dist/types/player';
import { io, Socket } from 'socket.io-client';
import 'video.js/dist/video-js.css';

const VideoPlayer: React.FC = () => {
  // 管理当前激活的播放器索引(0/1)
  const [activeIdx, setActiveIdx] = useState(0);
  // 存储两个VideoJS实例的引用
  const playerRefs = useRef<(Player | null)[]>([null, null]);
  // Socket连接实例引用
  const socketRef = useRef<Socket | null>(null);

  useEffect(() => {
    // 初始化两个播放器
    const initPlayers = [0, 1].map(idx => {
      const videoEl = document.getElementById(`video-player-${idx}`) as HTMLVideoElement;
      const player = videojs(videoEl, {
        controls: true,
        autoplay: false,
        preload: 'auto',
        fluid: true,
        muted: false, // 根据需求调整静音状态
      });

      // 监听预加载完成事件,触发切换
      player.on('canplay', () => {
        if (idx !== activeIdx) {
          setActiveIdx(idx);
          // 重置旧播放器,释放资源
          const oldPlayer = playerRefs.current[activeIdx];
          if (oldPlayer) {
            oldPlayer.pause();
            oldPlayer.src('');
          }
        }
      });

      return player;
    });

    playerRefs.current = initPlayers;

    // 初始化Socket连接(替换为你的本地服务器地址)
    socketRef.current = io('http://localhost:3001');
    socketRef.current.on('video_data', (videoData) => {
      // 获取当前未激活的播放器索引
      const inactiveIdx = activeIdx === 0 ? 1 : 0;
      const targetPlayer = playerRefs.current[inactiveIdx];

      if (targetPlayer) {
        // 更新隐藏播放器的视频源
        targetPlayer.src({
          src: videoData.url, // 假设videoData返回视频地址
          type: videoData.mimeType || 'video/mp4', // 根据实际视频类型调整
        });
        targetPlayer.load();
      }
    });

    // 组件卸载时清理资源
    return () => {
      playerRefs.current.forEach(player => player?.dispose());
      socketRef.current?.disconnect();
    };
  }, [activeIdx]);

  return (
    <div className="video-container">
      {[0, 1].map(idx => (
        <div
          key={idx}
          className={`player-wrapper ${idx === activeIdx ? 'active' : 'hidden'}`}
        >
          <video
            id={`video-player-${idx}`}
            className="video-js vjs-big-play-centered"
          />
        </div>
      ))}
    </div>
  );
};

export default VideoPlayer;

3. 配套CSS样式

在全局样式文件或组件内联样式中添加:

.video-container {
  position: relative;
  width: 100%;
  max-width: 1200px;
  margin: 0 auto;
  aspect-ratio: 16/9;
}

.player-wrapper {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

.player-wrapper.hidden {
  display: none;
}

.player-wrapper.active {
  display: block;
}

关键细节说明

  • 预加载时机:始终更新未激活的播放器源,确保切换时视频已完成预加载
  • 资源清理:切换后清空旧播放器的源,避免浏览器占用不必要的内存
  • Socket稳定性:组件卸载时断开Socket连接,防止内存泄漏

内容的提问来源于stack exchange,提问作者Beki

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 22:15:37