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

Next.js项目页面切换后摄像头无法停止的问题排查与解决

问题原因与解决方案

核心问题分析

  1. 流与video元素未彻底解绑:你虽然调用了track.stop(),但video元素的srcObject仍指向原MediaStream,部分浏览器会因为这个关联继续保留摄像头资源的占用。
  2. 异步竞态隐患:handleDraw是异步函数,如果组件在getUserMedia完成前就卸载,后续流初始化完成后会处于无管理状态,可能导致资源泄漏(你当前日志已打印stop stream,说明该场景不是本次问题,但仍需防范)。

修复后的代码

useEffect(() => {
  let animate: number | null = null;
  let stream: MediaStream | null = null;
  let isUnmounted = false; // 标记组件是否已卸载,处理异步竞态
  const video = videoRef.current;

  const handleDraw = async () => {
    if (!video || isUnmounted) return; // 已卸载则直接终止流程

    try {
      stream = await navigator.mediaDevices.getUserMedia({
        video: {
          width: { ideal: 4096 },
          height: { ideal: 2160 },
          facingMode: 'environment',
        },
      });
      
      if (isUnmounted) { // 异步过程中组件已卸载,直接清理流
        stream.getTracks().forEach(track => track.stop());
        return;
      }

      // 用once选项避免重复绑定事件
      video.addEventListener('loadedmetadata', () => video.play(), { once: true });
      video.srcObject = stream;

      const draw = () => {
        // some draw logic
        animate = requestAnimationFrame(draw);
      };
      animate = requestAnimationFrame(draw);
    } catch (err) {
      console.error('获取摄像头失败:', err);
    }
  };

  handleDraw();

  return () => {
    isUnmounted = true;
    if (animate) cancelAnimationFrame(animate);
    
    // 先切断video与流的关联,这是释放摄像头的关键
    if (video) {
      video.pause();
      video.srcObject = null;
      console.log('stop video');
    }
    
    // 停止所有活跃的媒体轨道
    if (stream) {
      stream.getTracks().forEach(track => {
        if (track.readyState === 'live') {
          track.stop();
        }
      });
      stream = null;
      console.log('stop stream');
    }
  };
}, []);

关键修复点

  • 强制解绑video的srcObject:清理时先设置video.srcObject = null,彻底切断元素与流的关联,让浏览器能及时释放摄像头资源。
  • 添加卸载状态标记:用isUnmounted变量拦截异步过程中组件已卸载的情况,避免流被初始化后无人管理。
  • 优化事件绑定逻辑:给loadedmetadata事件添加once选项,防止重复绑定导致的冗余操作。
  • 检查轨道活跃状态:只对活跃状态的轨道调用stop(),避免无效操作。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 14:43:00