01 · 预约帧回调
video.requestVideoFrameCallback(inspectFrame);浏览器准备把新视频帧交给合成器时调用函数。每次预约只对应一次回调。
播放或暂停片段,比较视频帧回调次数与 timeupdate 次数;两者服务于不同精度的任务。
video.requestVideoFrameCallback(inspectFrame);浏览器准备把新视频帧交给合成器时调用函数。每次预约只对应一次回调。
metadata.mediaTime这是视频时间轴中的秒数。now 则是页面高精度计时值,不能把它直接当视频进度。
video.requestVideoFrameCallback(inspectFrame);在回调内再次预约,形成连续观察;这不是同步的无限循环。
回调次数与视频总帧数一定相同吗?只想更新“已播放多少秒”,是否必须用这个接口?
不一定,繁忙时可能错过回调,不能用次数直接证明视频总帧数或流畅度。普通进度文本可使用 timeupdate;逐帧图像分析才更需要帧时间信息。
requestVideoFrameCallback 属于较新的跨浏览器媒体能力;能力检测失败时保留原生播放。Interop 2026 也包含 WebVTT 测试研究工作。
完整视频演示使用同目录中的 huangmei-opera.mp4。上方播放器可直接运行;下方隔离预览不加载外部媒体文件。
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>按视频帧观察,而不是把时间事件当帧事件</title>
<style>
body { margin:16px; color:#183347; background:white; font:20px/1.5 "Times New Roman",SimSun,serif; }
h1,h2 { font-family:"Times New Roman",SimHei,sans-serif; }
button,input,select { font:inherit; max-width:100%; }
button { margin:4px 0; cursor:pointer; }
pre { white-space:pre-wrap; overflow-wrap:anywhere; font:18px/1.5 Consolas,SimSun,monospace; }
:focus-visible { outline:3px solid #b56220; outline-offset:3px; }
video { max-width:100%; }
</style>
</head>
<body>
<video id="video" controls preload="metadata" playsinline width="420" src="huangmei-opera.mp4">
</video>
<p id="frame" role="status">尚无帧回调</p>
<p id="time">timeupdate 次数:0</p>
<script>
const video = document.querySelector('#video');
let frames = 0; let times = 0;
video.addEventListener('timeupdate', () => {
times += 1;
document.querySelector('#time').textContent = 'timeupdate 次数:' + times;
});
function inspectFrame(now, metadata) {
frames += 1;
document.querySelector('#frame').textContent = '回调次数:' + frames + ';帧时间:' + metadata.mediaTime.toFixed(3) + ' 秒';
video.requestVideoFrameCallback(inspectFrame);
}
if ('requestVideoFrameCallback' in video) video.requestVideoFrameCallback(inspectFrame);
else document.querySelector('#frame').textContent = '当前浏览器未提供视频帧回调;仍可观察 timeupdate。';
</script>
</body>
</html>