WebRTC发送Canvas捕获流仅显示单帧问题排查求助
这种情况我之前也碰到过,对方只显示一帧就停,核心原因大概率是Canvas捕获的流没有持续生成新帧,或者浏览器对未挂载到DOM的video元素做了优化限制,导致视频帧更新中断。下面是具体的排查方向和解决办法:
1. 动态创建的video元素未挂载到DOM,浏览器限制帧更新
很多浏览器(比如Chrome)会对不在DOM树中的video元素做性能优化,即使你调用了play(),视频的帧解码和更新可能会被限制在“后台模式”——表面上本地Canvas预览正常,但实际上视频帧没有持续输出,导致WebRTC只能传输第一帧。
解决办法:把你动态创建的localCameraVideoStream添加到DOM中(可以隐藏起来,不影响页面布局):
this.localCameraVideoStream = document.createElement('video'); this.localCameraVideoStream.srcObject = stream; this.localCameraVideoStream.muted = true; // 隐藏元素并挂载到DOM this.localCameraVideoStream.style.position = 'fixed'; this.localCameraVideoStream.style.top = '-9999px'; this.localCameraVideoStream.style.left = '-9999px'; document.body.appendChild(this.localCameraVideoStream); // 等待视频播放就绪后再启动绘制 this.localCameraVideoStream.play().then(() => { this.redrawStreamToCanvas(); }).catch(err => { console.error('视频播放失败:', err); });
2. 未等待视频加载完成就启动绘制循环
video.play()是异步操作,如果你在视频还没加载好元数据和第一帧时就调用redrawStreamToCanvas(),可能会导致初始绘制后,后续的drawImage无法获取到新的视频帧——循环看似在跑,但实际没有更新Canvas内容。
解决办法:监听视频的loadedmetadata事件,或者等待play()的Promise完成后再启动绘制:
this.localCameraVideoStream.addEventListener('loadedmetadata', () => { this.localCameraVideoStream.play().then(() => { this.redrawStreamToCanvas(); }); });
3. Canvas捕获流未指定帧率,依赖内容变化触发帧生成
默认canvas.captureStream()的帧率是0,意思是只有当Canvas内容发生可见变化时才会生成新帧。如果浏览器认为两次绘制的内容完全相同(比如视频帧静止,或者绘制操作被优化),就不会生成新的流帧,导致WebRTC传输中断。
解决办法:给captureStream()指定固定帧率(比如30fps),强制持续生成帧:
// 指定每秒30帧 this.canvasStream = this.canvas.captureStream(30);
4. Angular Zone.js影响绘制循环的持续执行
Angular的Zone.js会跟踪异步操作,requestAnimationFrame如果在Zone内运行,可能会因为变更检测的影响导致循环意外停止。
解决办法:把绘制循环放到Angular Zone之外运行:
首先在组件构造函数中注入NgZone:
import { NgZone } from '@angular/core'; constructor(private ngZone: NgZone) {}
然后修改redrawStreamToCanvas方法:
private redrawStreamToCanvas(){ const ctx = this.canvas.getContext('2d'); const width = 1280; const height = 720; const combinedVideoStream = this.localCameraVideoStream; const drawVideo = () => { ctx.clearRect(0, 0, width, height); ctx.drawImage(combinedVideoStream, 0, 0, width, height); // 在Zone外调用requestAnimationFrame,避免触发不必要的变更检测 this.ngZone.runOutsideAngular(() => { requestAnimationFrame(drawVideo); }); }; this.ngZone.runOutsideAngular(() => { requestAnimationFrame(drawVideo); }); }
你可以先尝试前两个方案,这两个是最常见的原因,一般就能解决问题。
内容的提问来源于stack exchange,提问作者Maciej Wojcik

