不能直接拿到帧的像素数据——requestvideoframecallback仅提供videoframe对象,需在回调内同步调用copyto()或planes提取数据,否则帧被释放导致invalidstateerror。

requestVideoFrameCallback 能不能直接拿到帧的像素数据?
不能。它只提供 VideoFrame 对象,不是 ImageData 或 Uint8Array;你得手动转换,而且必须在 callback 内完成读取,否则 VideoFrame 会立刻被释放,再调 copyTo() 就报 "InvalidStateError: Frame is no longer available"。
常见错误是把 frame.copyTo(canvas) 放在异步操作后、或试图跨帧复用 frame —— 这类操作一律失败。
- 必须在
requestVideoFrameCallback回调函数内同步完成所有读取逻辑 - 推荐用
frame.copyTo(imageBitmap)→createImageBitmap()→ 绘制到离屏canvas,再用ctx.getImageData()提取像素 - 若只需 YUV 数据(如做轻量运动检测),可直接用
frame.planes获取原始平面,避免 RGB 转换开销
为什么 requestVideoFrameCallback 比 onloadeddata + drawImage 更低延迟?
因为它是浏览器原生视频管线的“帧就绪”通知机制,不依赖渲染循环或事件队列调度。而 onloadeddata 只触发一次,drawImage() + getImageData() 必须等下一帧渲染完成才可靠,中间至少卡一个 requestAnimationFrame 周期(通常 16ms)。
requestVideoFrameCallback 在解码器输出帧的瞬间就回调,实测端到端延迟比 canvas 抓帧低 20–40ms(取决于设备和分辨率)。
- 必须配合
video.playbackRate = 1和video.muted = true,否则部分浏览器会降频或静音抑制帧回调 - 不要在回调里做耗时计算(如 full CV model inference),否则会阻塞后续帧,导致丢帧甚至回调停止
- Chrome 117+ 支持
requestVideoFrameCallback在OffscreenCanvas环境中使用,适合 Worker 中处理
如何安全地把 VideoFrame 传给 Web Worker 做实时分析?
不能直接传递 VideoFrame 对象——它不可序列化,且跨线程后生命周期无法保证。正确做法是:在主线程 callback 内,用 frame.copyTo(transferable) 输出 ImageBitmap 或 Transferable 数组缓冲区,再 postMessage(... , [transferable])。
例如提取 YUV 平面:
video.requestVideoFrameCallback((frame) => {
const yPlane = new Uint8ClampedArray(frame.planes[0].byteLength);
frame.planes[0].copyTo(yPlane); // 同步复制
worker.postMessage({ y: yPlane }, [yPlane.buffer]);
});
-
frame.planes是VideoFramePlane,支持copyTo(TypedArray),但不支持直接 transfer - 如果要用
ImageBitmap,需先frame.createImageBitmap(),再transferToImageBitmap()(注意:后者仅 Chrome 支持,Firefox 不可用) - 务必检查
frame.timestamp,避免 Worker 处理过期帧;建议加简单滑动窗口丢弃 timestamp 差距 > 100ms 的帧
移动端兼容性与 fallback 方案怎么写才不翻车?
目前仅 Chromium 内核(Chrome / Edge / Samsung Internet)和 Safari 17.4+ 支持 requestVideoFrameCallback;Firefox 完全不支持,iOS 17.3 及更早也无。
别用 if ('requestVideoFrameCallback' in HTMLMediaElement.prototype) 就完事——Safari 17.4 才加,但早期 17.x 版本会返回 true 却抛 NotSupportedError。
- 真实检测要 try/catch:
try { video.requestVideoFrameCallback(() => {}); } catch (e) { useFallback(); } - fallback 推荐用
canvas.captureStream().getVideoTracks()[0]+MediaStreamTrack.getSettings().frameRate估算间隔,再配setTimeout模拟,虽然延迟高,但至少能跑 - 低端安卓机上,即使支持该 API,
frame.duration可能为 0,此时应 fallback 到performance.now()计算帧间隔
真正难处理的是 Safari 17.4 的限制:只允许在 play() 成功后的第一个 callback 触发前注册,且不允许在 seeking 期间调用;错过时机就只能 reload video 元素重试。










