Next.js项目页面切换后摄像头无法停止的问题排查与解决
问题原因与解决方案
核心问题分析
- 流与video元素未彻底解绑:你虽然调用了
track.stop(),但video元素的srcObject仍指向原MediaStream,部分浏览器会因为这个关联继续保留摄像头资源的占用。 - 异步竞态隐患:
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
相关产品推荐
相关产品推荐

