使用 requestAnimationFrame 为 HTML 视频制作动画

Animating HTML Video with requestAnimationFrame

我想用 requestAnimationFrame 播放 HTML <video> 元素。这很有用,因为它可以更好地控制播放(例如可以播放某些部分、控制速度等)。但是,我 运行 遇到以下方法的问题:

function playAnimation() {
  window.cancelAnimationFrame(animationFrame);
  var duration = video.seekable.end(0);
  var start = null;

  var step = function(timestamp) {
    if (!start) start = timestamp;
    const progress = timestamp - start;
    const time = progress / 1000;

    video.currentTime = time;
    console.log(video.currentTime);

    if (time > duration) {
      start = null;
    }
    animationFrame = window.requestAnimationFrame(step);
  }

  animationFrame = window.requestAnimationFrame(step);
}

在GoogleChrome中,视频播放了一小会儿,然后就卡住了。在 Firefox 中,它冻结得更多。控制台显示视频的 currentTime 正在按预期更新,但未呈现新时间。此外,在视频冻结的情况下,即使 currentTime 正在更新,ontimeupdate 事件也不会触发。

可以在此处找到一个简单的演示:https://codepen.io/TGordon18/pen/bGVQaXM

知道发生了什么问题吗?

更新: 有趣的是,controlling/throttling animationFrame 在 Firefox 中实际上有帮助。

setTimeout(() => {
  animationFrame = window.requestAnimationFrame(step);
}, 1000 / FPS);

虽然这似乎不是正确的方法

视频的搜索通常比requestAnimationFrame的一帧慢。 requestAnimationFrame 的一个理想帧约为 16.6 毫秒(60 FPS),但搜索的持续时间取决于视频的编码方式以及您要搜索的视频位置。当在 step 函数中设置 video.currentTime 然后在下一帧做同样的事情时,之前的搜索操作很可能还没有完成。当您一遍又一遍地继续调用 video.currentTime 时,浏览器仍会尝试执行旧任务,直到它开始冻结为止,因为它已被任务数量淹没。它也可能影响它如何触发 timeupdate.

等事件

解决方案可能是显式等待搜索完成,然后才请求下一个动画帧。

video.onseeked = () => {
   window.requestAnimationFrame(step);    
}

https://codepen.io/mradionov/pen/vYNvyym?editors=0010

然而,由于搜索操作的工作原理,您很可能无法像在视频标签中那样实现流畅的实时播放。除非你愿意在前一帧还没有准备好时丢弃一些当前帧。

基本上为每个视频帧存储整个图像非常昂贵。核心视频压缩技术之一是仅在某些间隔内存储完整的视频帧,例如每 1 秒(它们称为 I 帧的关键帧)。中间的其余帧将存储与前一帧(P 帧)的差异,与整个图像相比,它非常小。当视频正常播放时,缓冲区中已经有前一帧,唯一需要做的就是将差异应用到下一帧。但是当你进行查找操作时,没有前一帧来计算差异。视频解码器必须找到最近的具有完整图像的关键帧,然后对所有后续帧应用差异,直到它最终到达您想要查找的帧为止。

如果您使用我的建议等待上一个搜索操作完成后再请求下一个搜索,您会看到视频开始时很流畅,但是当它接近 2.5 秒时它会越来越卡顿,直到它达到2.5s+,再次变得平滑。但话又说回来,它会在 5s 时开始卡顿,并在 5s+ 后再次变得平滑。这是因为此视频的关键帧间隔为 2.5 秒,您要查找的时间戳距离关键帧越远,所需的时间越长,因为需要解码的帧更多。