是否可以反向播放 HTML5 视频?

IT技术 javascript html html5-video
2021-03-13 18:59:10

HTML5<video>标签可以反向播放,还是我必须下载 2 个视频(向前和向后播放)?

我正在寻找避免用户下载 2 个视频的解决方案。

5个回答

甚至没有进入 HTML5 或 Javascript,许多视频格式是旨在向前播放的流格式。向后播放需要解码整个流,将每个原始帧存储在磁盘上以避免破坏内存,然后向后渲染帧。

不过,至少有一个人实际尝试过使用mplayer,因此可以做到,至少在原则上是这样。

@Arham,但恐怕在本地缓冲整个输入流以在客户端上反转它也不是一种选择(还)。
2021-04-18 18:59:10
@FrédéricHamidi。您可以将初始当前时间设置为等于视频持续时间,并使用 setInterval 减去每个间隔的当前时间,但您需要微调该间隔持续时间并减去当前时间。
2021-04-24 18:59:10
谢谢@Frédéric,现在我知道有 2 个单独的视频可以更好地解决我的问题。
2021-05-05 18:59:10
但是添加两个单独的视频需要加载多个播放器或更改当前播放器视频源。更改当前播放器视频源将花费加载时间。
2021-05-05 18:59:10
@Arham,这对前向流格式来说是谋杀。每次回溯到与关键帧不完全匹配的点时,都必须渲染前一个关键帧以及它与跟踪点之间的所有相关帧。即使是四年后,我怀疑大多数机器是否具有以可接受的性能做到这一点的原始能力。哦,您仍然必须先流式传输整个内容,然后才能播放。
2021-05-10 18:59:10

我设法在更新方法中做到了。每一帧我都将 video.currentTime 减少到经过的时间,到目前为止,这是我设法获得的最佳结果。

很高兴看到这个 jsfiddle。
2021-04-19 18:59:10
这是我开始工作的一个非常粗糙的过程,它有点不稳定,但您可以通过更改0.1100(即 0.1 秒等于 100 毫秒)来设置速率js var v = document.getElementById('video'); var reverse = setInterval(function() { v.currentTime = v.currentTime - 0.1; }, 100);
2021-04-19 18:59:10
抱歉,为了阻止它反向播放,你跑了 clearInterval(reverse);
2021-05-13 18:59:10
aMediaElement.playbackRate = -1;

UA 可能不支持这一点,尽管设置playbackRate为负值是有效的

根据我的测试,它仅在 Safari 中受支持。MDN 说“负值不会导致媒体反向播放。” developer.mozilla.org/en-US/Apps/Fundamentals/...)但是规范允许负值(dev.w3.org/html5/spec-preview/...
2021-04-23 18:59:10
请注意,Chrome 目前没有计划实施负重放率。code.google.com/p/chromium/issues/detail?id=33099
2021-04-27 18:59:10
Firefox 目前也不支持
2021-05-04 18:59:10
在 Safari 中使用 MP4,但非常笨重(视频根本不流畅)。
2021-05-07 18:59:10

这个片段只是展示了它是如何完成的,但是复制每一帧需要一些时间。这在很大程度上取决于硬件。

它为它必须播放的每一帧生成一个画布。当它打开 100% 时,播放直接开始并向后播放、向前播放......等等。原来的Video也是在生成后附上的,但是由于iframe规则,不会自动播放。

它适用于短视频并作为概念证明。

更新: 我更改了捕获部分的顺序,以便跳过最大持续时间的帧,但捕获 0 处的帧(之前忘记了)。

最大持续时间的帧在我尝试的每个视频上都会导致白色画布。

我还更改了播放,以便在到达最后一帧后立即以相反的顺序播放以进行无休止的播放。所以你很容易看到,与硬件加速视频相比,这种播放有点 CPU 密集型。

fetch('https://i.imgur.com/cSmeKr4.mp4')
  .then(res => res.blob())
  .then(blob => {
    return new Promise((res) => {
      const fr = new FileReader();
      fr.onload = e => res(fr.result);
      fr.readAsDataURL(blob);
    })
  }).then(async(base64str) => {
    const video = document.createElement("video");
    video.src = base64str;
    video.controls = true;
    video.className = "w-50";

    while (isNaN(video.duration))
      await new Promise((r) => setTimeout(r, 50));

    const FPS = 25;


    const status = document.createElement("div"),
      length = document.createElement("div");
    length.innerText = `Generating ${Math.ceil(video.duration / (1 / FPS))} frames for a ${FPS} FPS playback (Video Duration = ${video.duration})`;
    document.body.appendChild(length);
    document.body.appendChild(status);

    const frames = [],
      copy = () => {
        const c = document.createElement("canvas");
        Object.assign(c, {
          width: video.videoWidth,
          height: video.videoHeight,
          className: "w-50"
        });
        c.getContext("2d").drawImage(video, 0, 0);
        return c;
      };

    // first seek outside of the loop this image won't be copied
    video.currentTime = video.duration; 
    // this frame seems to be always white/transparent
    
    while (video.currentTime) {
      if (video.currentTime - 1 / FPS < 0)
        video.currentTime = 0;
      else
        video.currentTime = video.currentTime - 1 / FPS;
      await new Promise((next) => {
        video.addEventListener('seeked', () => {
          frames.push(copy());
          status.innerText = (frames.length / (Math.ceil(video.duration / (1 / FPS))) * 100).toFixed(2) + '%';
          next();
        }, {
          once: true
        });
      });      
    }


    /*
     * frames now contains all canvas elements created,
     * I just append the first image and replace it on
     * every tick with the next frame.
     * using last.replaceWith(frames[currentPos]) guarantees a smooth playback.
     */

    let i = 0, last = frames[0];

    document.body.insertAdjacentHTML('beforeend', `<div class="w-50">Captured</div><div class="w-50">Video</div>`);

    document.body.appendChild(frames[0]);

    const interval = setInterval(() => {
      if (frames[++i]) {
        last.replaceWith(frames[i]);
        last = frames[i];
      } else {
        frames.reverse();
        i=0;
      }
    }, 1000 / FPS);

    document.body.appendChild(video);
    // won't :(
    video.play();
  });
/* Just for this example */
.w-50 {
  width: 50%;
  display: inline-block;
}

* {
  margin: 0;
  padding: 0;
  font-family: Sans-Serif;
  font-size: 12px;
}

获取 HTMLMediaElement 的持续时间,然后设置一个间隔,该间隔将每秒运行一次并通过设置 .currentTime 播放媒体,并将该值每秒减少 1。媒体元素必须完全下载才能工作。我只在 30 秒的剪辑上测试过这个,不确定是否可以更快(小于 1 秒)间隔。请注意,此方法仍向前播放媒体。尝试提高媒体播放速率,使其感觉更加无缝。