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

Posted

技术标签:

【中文标题】是否可以反向播放 HTML5 视频?【英文标题】:Is it possible to play HTML5 video in reverse? 【发布时间】:2011-07-13 17:36:42 【问题描述】:

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

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

【问题讨论】:

我强烈倾向于 NO,因为大多数压缩方案都假定您正在播放 Forward。 【参考方案1】:

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

不过,至少有一个人actually tried that 使用mplayer,所以它可以做到,至少原则上是这样。

【讨论】:

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

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

【讨论】:

很高兴看到这个的 jsfiddle。 这是我开始工作的一个非常粗略的过程,它有点不稳定,但您可以通过更改 0.1100 来设置速率(即 0.1 秒等于 100 毫秒):@987654323 @ 抱歉,要停止反向播放,请运行 clearInterval(reverse);【参考方案3】:
aMediaElement.playbackRate = -1;

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

【讨论】:

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

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

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

它适用于短视频和概念验证。

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

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

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

fetch('https://i.imgur.com/BPQF5yy.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;

【讨论】:

【参考方案5】:

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

【讨论】:

以上是关于是否可以反向播放 HTML5 视频?的主要内容,如果未能解决你的问题,请参考以下文章

使用 html5 视频标签从特定位置开始播放视频

HTML5视频标签,javascript检测播放状态?

滚动到时播放 HTML5 视频

在 IOS 9 上使用 AVPlayer 和 Swift 反向播放视频

HTML5 视频无法仅在 Chrome 中播放

HTML5 <video> 可以播放 .mkv 文件吗?