ReactJS中Webcam直播时浏览器冻结问题排查求助
人脸识别应用浏览器无报错冻结问题排查
我正在开发一个简单的人脸识别应用,通过Webcam实时捕获帧并进行人脸检测,但运行一段时间后浏览器无报错冻结。请问该问题是否与Webcam流相关,或是存在其他问题?
相关源码
import './App.css'; import React, { useEffect, useRef,useState } from 'react'; import * as faceapi from 'face-api.js'; import axios from 'axios'; function App() { const videoRef = useRef(); let isDetectionInProgress = false; const loadModels = async () =>{ try{ await faceapi.nets.tinyFaceDetector.loadFromUri('/model') }catch(error){ console.log(error) } } useEffect(() => { loadModels(); const init = async () => { try { const stream = await navigator.mediaDevices.getUserMedia({ video: true }); videoRef.current.srcObject = stream; } catch (error) { console.error('Error accessing webcam:', error); } }; if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { init(); } else { console.error('Error accessing webcam:'); } const intervalId = setInterval(() => { if (!isDetectionInProgress) { emitFrame(); } }, 500); const hardRefreshIntervalId = setInterval(() => { // This code will be executed every hour (3600000 milliseconds) window.location.reload(true); }, 3600000); return () => { if (videoRef.current.srcObject) { const tracks = videoRef.current.srcObject.getTracks(); tracks.forEach((track) => track.stop()); videoRef.current.srcObject = null; } clearInterval(intervalId); clearInterval(hardRefreshIntervalId) console.log("App unmounted") }; }, []); const emitFrame = async () => { isDetectionInProgress = true if (videoRef.current?.srcObject?.getVideoTracks()[0]) { let imageCapture = new ImageCapture(videoRef.current.srcObject.getVideoTracks()[0]); if (faceapi.nets.tinyFaceDetector.isLoaded) { let blob = null; try { blob = await imageCapture.takePhoto(); if ("size" in blob) { let imageUrl = null; let image = null; try{ imageUrl = URL.createObjectURL(blob); image = new Image(); image.src = imageUrl; const faces = await faceapi.detectAllFaces(image, new faceapi.TinyFaceDetectorOptions({})); if(faces && faces.length>0 && image){ const formData = new FormData(); formData.append('blob', blob, 'frame.png'); const now = new Date(); const isoFormattedTime = now.toISOString(); formData.append('isoFormattedTime', isoFormattedTime); axios .post('http://127.0.0.1:5000/backend/detect_face', formData,{ headers: { 'Content-Type': 'multipart/form-data', }, }) .then((response) => { if (response.status === 200) { const speechUtterance = new SpeechSynthesisUtterance(response.data?.message); window.speechSynthesis.speak(speechUtterance); console.log(response.data?.message); console.log(response.data?.request_time,response.data?.response_time); } else { console.log('No face Detected', response.data?.message); } }) .catch((error) => { console.error('Error during api response :', error); }); } }catch(error){ console.log(error) } finally{ URL.revokeObjectURL(imageUrl); image = null } } } catch (error) { console.log("imageCapture",imageCapture) console.error('Error during face detection:', error); } finally{ blob = null } } imageCapture = null } isDetectionInProgress = false }; return ( <div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', height: '100vh' }}> <h1 style={{ color: 'red', fontSize: '24px' }}>Ixora Solution Limited AI Front Desk Assistance</h1> <video ref={videoRef} autoPlay></video> </div> ); } export default App;
问题根源分析
冻结问题和Webcam流本身无关,核心是内存泄漏和异步任务管控失效:
- 任务并发失控:用普通变量
isDetectionInProgress控制并发,React组件重渲染时变量会被重置,导致多个emitFrame任务同时执行,异步操作堆积占满主线程。 - 内存泄漏风险:
axios请求无取消机制,组件卸载或请求堆积时会残留未完成请求;ImageCapture、blob等对象在异常场景下可能未被正确回收;URL.createObjectURL存在遗漏回收的可能。 - 定时器与异步任务冲突:500ms定时器固定触发任务,若某次检测或API请求耗时超过500ms,会导致任务持续堆积,最终拖垮浏览器。
修复方案
1. 用React状态管控并发
将普通变量改为useState,确保状态在组件生命周期内稳定:
const [isDetectionInProgress, setIsDetectionInProgress] = useState(false); const emitFrame = async () => { if (isDetectionInProgress) return; setIsDetectionInProgress(true); // 原有检测逻辑 setIsDetectionInProgress(false); };
2. 优化资源回收与请求管控
- 给
axios请求添加取消令牌,避免组件卸载后请求继续执行:
// 在useEffect中创建取消令牌 const source = axios.CancelToken.source(); // 请求时配置令牌 axios.post('http://127.0.0.1:5000/backend/detect_face', formData,{ headers: { 'Content-Type': 'multipart/form-data' }, cancelToken: source.token }) // 组件卸载时取消请求 return () => { source.cancel('组件卸载,取消未完成请求'); // 原有清理逻辑 };
- 确保图片加载完成后再执行检测:
image.onload = async () => { const faces = await faceapi.detectAllFaces(image, new faceapi.TinyFaceDetectorOptions({})); // 后续人脸处理逻辑 };
3. 改用requestAnimationFrame替代定时器
贴合浏览器渲染周期,避免任务堆积:
useEffect(() => { let animationId; const detectLoop = async () => { if (!isDetectionInProgress) { await emitFrame(); } animationId = requestAnimationFrame(detectLoop); }; detectLoop(); return () => { cancelAnimationFrame(animationId); // 原有清理逻辑 }; }, [isDetectionInProgress]);
4. 简化帧捕获逻辑
用canvas绘制video帧替代ImageCapture,兼容性更好:
const canvas = document.createElement('canvas'); canvas.width = videoRef.current.videoWidth; canvas.height = videoRef.current.videoHeight; const ctx = canvas.getContext('2d'); ctx.drawImage(videoRef.current, 0, 0, canvas.width, canvas.height); const blob = await new Promise(resolve => canvas.toBlob(resolve, 'image/png'));
内容的提问来源于stack exchange,提问作者sultan rayhan
相关产品推荐
相关产品推荐

