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

Next.js与React.js中Video.js加载大量视频时应用崩溃求助

解决大量Video.js播放器导致的应用崩溃问题

当一次性渲染120-140个Video.js播放器时,应用崩溃的核心原因是内存占用过高(过多DOM节点、播放器实例、事件监听器)以及不必要的资源加载。以下是针对性的优化方案:

1. 实现虚拟滚动,只渲染可视区域内的播放器

一次性渲染所有播放器会生成大量DOM节点,直接撑爆内存。使用虚拟滚动库(如react-window)只渲染当前视口内的播放器,其余区域用占位符代替,大幅减少DOM数量。

示例改造思路:

// 父组件中使用FixedSizeList
import { FixedSizeList as List } from 'react-window';

const VideoList = ({ videos }) => {
  const renderPlayer = ({ index, style }) => {
    const video = videos[index];
    return (
      <div style={style}>
        <LukPlayer 
          options={video.options} 
          data={video.data} 
          // 传递其他所需props
        />
      </div>
    );
  };

  return (
    <List
      height={window.innerHeight}
      itemCount={videos.length}
      itemSize={300} // 每个播放器容器的固定高度
      width="100%"
    >
      {renderPlayer}
    </List>
  );
};

2. 动态创建/销毁播放器实例,避免内存泄漏

当前组件在卸载时会调用dispose,但如果播放器不在可视区域,应该主动销毁实例、清空DOM,只保留占位元素;进入视口时再重新初始化播放器,释放闲置资源。

修改LukPlayer核心逻辑:

const LukPlayer = memo((props) => {
  const videoRef = useRef(null);
  const playerRef = useRef(null);
  const observerRef = useRef(null);
  const { options, onReady, data, commentFlag, flag } = props;

  // ... 保留原有状态、工具函数

  useEffect(() => {
    let isMounted = true;
    const videoNode = videoRef.current;

    const observerOptions = {
      root: null,
      rootMargin: '0px',
      threshold: 0.5,
    };

    observerRef.current = new IntersectionObserver(async (entries) => {
      entries.forEach(async (entry) => {
        if (!isMounted) return;
        
        if (entry.isIntersecting) {
          // 进入视口,初始化播放器
          if (!playerRef.current) {
            const videoElement = document.createElement('video-js');
            videoElement.classList.add('vjs-big-play-centered');
            videoRef.current.appendChild(videoElement);

            const validOptions = options?.sources?.[0]?.src?.includes('undefined') ? null : options;
            if (validOptions) {
              const player = videojs(videoElement, validOptions, () => onReady?.(player));
              playerRef.current = player;
              player.on('ended', onVideoEnd);
            }
          }
          await playerRef.current?.play();
          setCurrentPlaying(data);
        } else {
          // 离开视口,暂停并销毁播放器
          if (playerRef.current) {
            playerRef.current.pause();
            playerRef.current.dispose();
            playerRef.current = null;
            // 清空DOM节点,释放内存
            videoRef.current.innerHTML = '';
          }
        }
      });
    }, observerOptions);

    observerRef.current.observe(videoNode);

    return () => {
      isMounted = false;
      observerRef.current?.disconnect();
      playerRef.current?.dispose();
      playerRef.current = null;
    };
  }, [commentFlag, flag === 'create' && options, data]);

  // ... 保留其他逻辑与返回结构
});

3. 共享全局IntersectionObserver实例

每个播放器创建独立的Observer会导致大量实例占用内存,改为全局共享一个Observer监听所有播放器元素,减少资源消耗。

在全局文件(如App.js)创建共享Observer:

// App.js
export const videoObserver = new IntersectionObserver((entries) => {
  entries.forEach(async (entry) => {
    const { playerRef, videoData, videoOptions, setPlaying } = entry.target;
    if (entry.isIntersecting) {
      if (!playerRef.current) {
        const videoElement = document.createElement('video-js');
        videoElement.classList.add('vjs-big-play-centered');
        entry.target.appendChild(videoElement);

        const validOptions = videoOptions?.sources?.[0]?.src?.includes('undefined') ? null : videoOptions;
        if (validOptions) {
          const player = videojs(videoElement, validOptions);
          playerRef.current = player;
          player.on('ended', () => { /* 视频结束逻辑 */ });
        }
      }
      await playerRef.current?.play();
      setPlaying(videoData);
    } else {
      if (playerRef.current) {
        playerRef.current.pause();
        playerRef.current.dispose();
        playerRef.current = null;
        entry.target.innerHTML = '';
      }
    }
  });
}, { root: null, rootMargin: '0px', threshold: 0.5 });

在LukPlayer中注册元素到全局Observer:

// LukPlayer.js
import { videoObserver } from './App';

const LukPlayer = memo((props) => {
  const videoRef = useRef(null);
  const playerRef = useRef(null);
  const { options, data } = props;
  const [playing, setCurrentPlaying] = useState([]);

  useEffect(() => {
    const node = videoRef.current;
    // 绑定所需属性到DOM节点,供全局Observer调用
    node.playerRef = playerRef;
    node.videoData = data;
    node.videoOptions = options;
    node.setPlaying = setCurrentPlaying;

    videoObserver.observe(node);

    return () => {
      videoObserver.unobserve(node);
      playerRef.current?.dispose();
      playerRef.current = null;
    };
  }, [options, data]);

  // ... 保留其他逻辑与返回结构
});

4. 优化全局事件监听

当前每个播放器都绑定keydown事件到document,会导致重复绑定。改为全局只绑定一次,通过全局状态判断当前播放的播放器:

// 在全局状态(如Redux或App组件)中维护当前播放的视频ID
useEffect(() => {
  const handleKeyPress = async (event) => {
    if (event.code === 'Space') {
      event.preventDefault();
      const currentPlayingId = store.getState().currentPlayingId;
      // 用全局ref集合存储所有播放器实例,通过ID获取
      const player = playerInstances.current.get(currentPlayingId);
      if (player) {
        player.paused() ? await player.play() : player.pause();
      }
    }
  };

  document.addEventListener('keydown', handleKeyPress);
  return () => document.removeEventListener('keydown', handleKeyPress);
}, []);

5. 其他细节优化

  • 缓存iOS判断结果:用useMemo缓存check_iOS的结果,避免每次渲染重复执行:
    const ios = useMemo(() => {
      if (!isClient) return false;
      return [
        'iPad Simulator', 'iPhone Simulator', 'iPod Simulator', 'iPad', 'iPhone', 'iPod'
      ].includes(navigator.platform) || /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
    }, [isClient]);
    
  • 稳定props依赖:父组件传递options时用useMemo包裹,确保对象引用稳定,避免memo失效导致不必要的重渲染:
    // 父组件中
    const videoOptions = useMemo(() => ({
      sources: [{ src: videoUrl, type: 'video/mp4' }],
      autoplay: true,
      // 其他配置项
    }), [videoUrl]);
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 04:47:08