React中用jsmpeg播放多RTSP流时出现Too many active WebGL contents问题求助
RTSP流渲染内存泄漏问题(Too many active WebGL contents)解决
问题本质
这个警告是浏览器提示当前页面活跃的WebGL上下文数量已接近上限。浏览器对单标签页的WebGL上下文数量有严格限制(一般在16-32个之间),你的25路流已经接近阈值,加上路由切换时旧的WebGL资源没彻底清理,就会触发该提示。这确实是内存泄漏问题,长期忽略会导致流无法渲染、页面卡顿甚至崩溃,必须修复。
现有代码的核心问题
- 全局
streams变量:跨组件/实例共享资源,容易导致残留引用无法被垃圾回收(GC),组件卸载后全局变量仍会保留空引用。 useEffect依赖缺失:挂载逻辑只执行一次,当preset(流列表/布局)变化时,旧播放器实例未被清理就创建新的,导致WebGL上下文堆积。elementsRef初始化逻辑错误:useRef的初始值不会随preset.partition变化更新,布局变化时canvas引用失效,旧canvas关联的WebGL资源无法释放。- 卸载清理不彻底:仅把
streams的值设为null,但对象键仍存在,残留空引用可能阻碍GC。
修复方案
1. 将全局streams移至组件内部
用useRef在组件内部存储播放器实例,避免全局变量的资源冲突:
// 替换全局streams,组件内部定义 const streams = useRef<Record<string, JSMpeg.Player | null>>({});
2. 修正canvas引用数组的更新逻辑
当布局分区数变化时,重新生成canvas引用数组,确保引用始终有效:
const elementsRef = useRef<(React.RefObject<HTMLCanvasElement>)[]>([]); useEffect(() => { // 分区数变化时重置引用数组 elementsRef.current = new Array(preset?.partition || 0).fill(0).map(() => createRef()); }, [preset?.partition]);
3. 监听preset变化,全生命周期管理播放器
修改核心逻辑,在preset变化时先清理旧播放器,再创建新的,同时确保卸载时彻底清理:
useEffect(() => { const { JSMpeg } = window; if (!preset?.stream) return; // 第一步:清理当前所有旧播放器 Object.values(streams.current).forEach(player => { if (player) { player.destroy(); } }); streams.current = {}; // 第二步:创建新播放器 const sortedStream = _.sortBy(preset.stream, ["position"]); sortedStream.forEach(v => { const canvas = elementsRef.current[v.position]?.current; if (!canvas) return; const player = new JSMpeg.Player(v.camera_monitoring.ws, { canvas }); streams.current[v.id] = player; }); // 组件卸载时的清理逻辑 return () => { Object.values(streams.current).forEach(player => { if (player) { player.destroy(); } }); streams.current = {}; }; }, [preset]); // 依赖preset,流列表或布局变化时自动执行
4. 彻底清理资源
卸载时不仅调用destroy(),还要清空streams对象,确保无残留引用阻碍垃圾回收。
5. 额外优化:降低WebGL上下文占用
25路流同时渲染已接近浏览器上限,可做以下优化:
- 按需播放:只渲染可见区域的流,不可见时调用
player.pause()暂停,需要时再恢复播放,减少活跃WebGL上下文数量。 - 资源复用:若JSMpeg支持,尝试复用WebGL上下文(单实例绑定一个canvas,复杂度较高,需结合业务场景评估)。
内容的提问来源于stack exchange,提问作者jerry
相关产品推荐
相关产品推荐

