场景
2023年03月19日
一、获取视频帧
1.1 认识
开发者通常使用以下方法获取视频帧:
-
setInterval
: 无法精准同步视频帧,可能出现帧丢失或重复 -
requestAnimationFrame
: 无法精准同步视频帧,可能出现帧丢失或重复。requestAnimationFrame
不保证 每次都能精准对应视频帧,可能有丢帧或重复帧问题。 -
requestVideoFrameCallback
通过视频帧驱动回调,确保每一帧都能精准捕获,并提供额外的帧元数据(timestamp
、presentationTime
、expectedDisplayTime
)。而且requestVideoFrameCallback
保证只在新视频帧可用时触发,避免多余计算。因此,requestVideoFrameCallback
可以用于高效同步视频帧,不会错过或重复帧, 帧驱动 回调,更适用于 视频分析、AI
计算、滤镜渲染。比requestAnimationFrame
更精准,但需要 浏览器支持。
目前 requestVideoFrameCallback
仅在 Chrome
、Edge
、Opera
(基于 Chromium
)上支持,Safari
、Firefox
仍未实现。下面是一个完整的 优雅降级 方案,优先使用 requestVideoFrameCallback
,然后是 requestAnimationFrame
,最后降级到 setInterval
。
1.2 实现
<video id="video" src="./video.mp4" controls></video>
<canvas id="canvas"></canvas>
const video = document.getElementById("video");
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
function isVideoPlaying(video) {
return !video.paused && !video.ended;
}
function captureVideoFrame(video, callback) {
if (!video) {
return;
}
let requestFrame;
function processFrame(now, metadata) {
if (!isVideoPlaying(video)) {
return;
}
callback?.(video, now, metadata);
requestFrame(processFrame);
}
if ("requestVideoFrameCallback" in HTMLVideoElement.prototype) {
requestFrame = (cb) => video.requestVideoFrameCallback(cb);
} else if (window.requestAnimationFrame) {
requestFrame = (cb) => requestAnimationFrame(cb);
} else {
requestFrame = (cb) => {
setInterval(() => {
const now = performance.now();
cb(now);
}, 1000 / 30);
};
}
requestFrame(processFrame);
}
function drawVideoFrame(video, now, metadata) {
ctx.drawImage(video, 0, 0, video.offsetWidth, video.offsetHeight);
}
video.onloadeddata = () => {
canvas.width = video.offsetWidth;
canvas.height = video.offsetHeight;
};
video.onplay = () => {
captureVideoFrame(video, drawVideoFrame);
};
二、实现一个 2s 的动画
let start = null;
const box = document.getElementById("box");
function workLoop(timestamp) {
if (!start) {
start = timestamp;
}
let process = timestamp - start;
box.style.left = Math.min(process / 10, 100) + 'px';
if (process < 2000) {
window.requestAnimationFrame(workLoop);
}
}
window.requestAnimationFrame(workLoop);
三、实现一个无线循环的动画
const box = document.getElementById("circle");
let flag = false;
let width = box.clientWidth;
function loop() {
if (flag) {
if (width >= 400) {
flag = false;
}
width += 1;
} else {
if (width <= 100) {
flag = true;
}
width -= 1;
}
box.style.width = `${width}px`;
box.style.height = `${width}px`;
}
function animationFrame() {
loop();
window.requestAnimationFrame(animationFrame);
}
animationFrame();