HTML 视频的每一帧都有事件?
Event for every frame of HTML Video?
我想构建一个事件处理程序来处理 HTML 5 视频元素的每个新帧。不幸的是,没有针对每个新视频帧触发的内置事件(timeupdate event 是最接近的,但会针对每次时间变化而不是每个视频帧触发)。
还有其他人 运行 遇到过同样的问题吗?有什么好的解决办法吗?
有一个 HTMLVideoElement.requestVideoFrameCallback()
方法仍在起草中,因此既不稳定,也没有广泛实施(它仅在基于 Chromium 的浏览器中),但它可以满足您的需求,并提供许多其他有关该帧的详细信息。
对于您的 Firefox 用户,此浏览器有一个非标准的 seekToNextFrame()
方法,您可以使用该方法取决于您想要做什么。不过,这并不完全是一个事件,它更像是一种方式,好吧……寻找下一帧。所以这将极大地影响视频的播放,因为它不会尊重每一帧的持续时间。
对于 Safari 用户来说,最接近的确实是 timeupdate 事件,但如您所知,这与显示的帧并不匹配。
(async() => {
const log = document.querySelector("pre");
const vid = document.querySelector("video");
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
if( vid.requestVideoFrameCallback ) {
await vid.play();
canvas.width = vid.videoWidth;
canvas.height = vid.videoHeight;
ctx.filter = "invert(1)";
const drawingLoop = (timestamp, frame) => {
log.textContent = `timestamp: ${ timestamp }
frame: ${ JSON.stringify( frame, null, 4 ) }`;
ctx.drawImage( vid, 0, 0 );
vid.requestVideoFrameCallback( drawingLoop );
};
vid.requestVideoFrameCallback( drawingLoop );
}
else if( vid.seekToNextFrame ) {
const requestNextFrame = (callback) => {
vid.addEventListener( "seeked", () => callback( vid.currentTime ), { once: true } );
vid.seekToNextFrame();
};
await vid.play();
await vid.pause();
canvas.width = vid.videoWidth;
canvas.height = vid.videoHeight;
ctx.filter = "invert(1)";
const drawingLoop = (timestamp) => {
log.textContent = "timestamp: " + timestamp;
ctx.drawImage( vid, 0, 0 );
requestNextFrame( drawingLoop );
};
requestNextFrame( drawingLoop );
}
else {
console.error("Your browser doesn't support any of these methods, we should fallback to timeupdate");
}
})();
video, canvas {
width: 260px;
}
<pre></pre>
<video src="https://upload.wikimedia.org/wikipedia/commons/2/22/Volcano_Lava_Sample.webm" muted controls></video>
<canvas></canvas>
请注意,编码帧和显示帧不一定是同一件事,浏览器可能根本不考虑编码帧速率。因此,根据您愿意做的事情,也许一个简单的 requestAnimationFrame
循环会在监视器的每次更新时触发可能会更好。
我想构建一个事件处理程序来处理 HTML 5 视频元素的每个新帧。不幸的是,没有针对每个新视频帧触发的内置事件(timeupdate event 是最接近的,但会针对每次时间变化而不是每个视频帧触发)。
还有其他人 运行 遇到过同样的问题吗?有什么好的解决办法吗?
有一个 HTMLVideoElement.requestVideoFrameCallback()
方法仍在起草中,因此既不稳定,也没有广泛实施(它仅在基于 Chromium 的浏览器中),但它可以满足您的需求,并提供许多其他有关该帧的详细信息。
对于您的 Firefox 用户,此浏览器有一个非标准的 seekToNextFrame()
方法,您可以使用该方法取决于您想要做什么。不过,这并不完全是一个事件,它更像是一种方式,好吧……寻找下一帧。所以这将极大地影响视频的播放,因为它不会尊重每一帧的持续时间。
对于 Safari 用户来说,最接近的确实是 timeupdate 事件,但如您所知,这与显示的帧并不匹配。
(async() => {
const log = document.querySelector("pre");
const vid = document.querySelector("video");
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
if( vid.requestVideoFrameCallback ) {
await vid.play();
canvas.width = vid.videoWidth;
canvas.height = vid.videoHeight;
ctx.filter = "invert(1)";
const drawingLoop = (timestamp, frame) => {
log.textContent = `timestamp: ${ timestamp }
frame: ${ JSON.stringify( frame, null, 4 ) }`;
ctx.drawImage( vid, 0, 0 );
vid.requestVideoFrameCallback( drawingLoop );
};
vid.requestVideoFrameCallback( drawingLoop );
}
else if( vid.seekToNextFrame ) {
const requestNextFrame = (callback) => {
vid.addEventListener( "seeked", () => callback( vid.currentTime ), { once: true } );
vid.seekToNextFrame();
};
await vid.play();
await vid.pause();
canvas.width = vid.videoWidth;
canvas.height = vid.videoHeight;
ctx.filter = "invert(1)";
const drawingLoop = (timestamp) => {
log.textContent = "timestamp: " + timestamp;
ctx.drawImage( vid, 0, 0 );
requestNextFrame( drawingLoop );
};
requestNextFrame( drawingLoop );
}
else {
console.error("Your browser doesn't support any of these methods, we should fallback to timeupdate");
}
})();
video, canvas {
width: 260px;
}
<pre></pre>
<video src="https://upload.wikimedia.org/wikipedia/commons/2/22/Volcano_Lava_Sample.webm" muted controls></video>
<canvas></canvas>
请注意,编码帧和显示帧不一定是同一件事,浏览器可能根本不考虑编码帧速率。因此,根据您愿意做的事情,也许一个简单的 requestAnimationFrame
循环会在监视器的每次更新时触发可能会更好。