媒体缓冲、查找和时间范围

有时了解 <audio><video> 已经下载了多少或可以立即播放多少内容会很有用——一个很好的例子就是音频或视频播放器的缓冲进度条。本文将讨论如何使用 TimeRanges 和媒体 API 的其他功能来构建一个缓冲/跳转条。

缓冲

buffered 属性将告诉我们媒体的哪些部分已被下载。它返回一个 TimeRanges 对象,该对象将告诉我们媒体的哪些块已被下载。这通常是连续的,但如果用户在媒体缓冲时跳转,它可能会包含空隙。

这适用于 <audio><video>;现在,让我们以音频为例

html
<audio id="my-audio" controls src="music.mp3"></audio>

我们可以像这样访问这些属性

js
const audio = document.getElementById("my-audio");
const bufferedTimeRanges = audio.buffered;

TimeRanges 对象

TimeRanges 是由起始时间和停止时间组成的一系列不重叠的时间范围。(了解更多关于 TimeRanges 的信息)。

一个 TimeRanges 对象包含以下属性:

  • length:对象中时间范围的数量。
  • start(index):时间范围的起始时间(以秒为单位)。
  • end(index):时间范围的结束时间(以秒为单位)。

在没有任何用户交互的情况下,通常只有一个时间范围,但如果你在媒体中跳转,可能会出现多个时间范围,如下面的可视化所示。这表示两个缓冲的时间范围——一个跨度为 0 到 5 秒,第二个跨度为 15 到 19 秒。

------------------------------------------------------
|=============|                    |===========|     |
------------------------------------------------------
0             5                    15          19    21

对于这个音频实例,相关的 TimeRanges 对象将具有以下可用属性:

js
audio.buffered.length; // returns 2
audio.buffered.start(0); // returns 0
audio.buffered.end(0); // returns 5
audio.buffered.start(1); // returns 15
audio.buffered.end(1); // returns 19

为了尝试和可视化缓冲时间范围,我们可以编写一些 HTML

html
<p>
  <audio id="my-audio" controls>
    <source
      src="https://cdn.freesound.org/previews/155/155386_326032-lq.mp3"
      type="audio/mpeg" />
  </audio>
</p>
<p>
  <canvas id="my-canvas" width="300" height="20"> </canvas>
</p>

以及一些 JavaScript

js
const audio = document.getElementById("my-audio");
const canvas = document.getElementById("my-canvas");
const context = canvas.getContext("2d");

context.fillStyle = "lightgray";
context.fillRect(0, 0, canvas.width, canvas.height);
context.fillStyle = "red";
context.strokeStyle = "white";

// Display TimeRanges
audio.addEventListener("seeked", () => {
  const inc = canvas.width / audio.duration;
  for (let i = 0; i < audio.buffered.length; i++) {
    const startX = audio.buffered.start(i) * inc;
    const endX = audio.buffered.end(i) * inc;
    const width = endX - startX;

    context.fillRect(startX, 0, width, canvas.height);
    context.rect(startX, 0, width, canvas.height);
    context.stroke();
  }
});

这在较长的音频或视频片段上效果更好,但按下播放并点击播放器进度条,你应该会看到红色的片段。每个红色填充的白色矩形代表一个时间范围。

可跳转

seekable 属性返回一个 TimeRanges 对象,并告诉我们媒体的哪些部分可以立即播放;这与该部分是否已下载无关。媒体的某些部分可能是可跳转但未缓冲的,如果服务器启用了字节范围请求。字节范围请求允许从服务器交付媒体文件的部分,因此几乎可以立即播放——因此它们是可跳转的。有关字节范围请求的更多信息,请参阅 HTTP 范围请求

js
const seekableTimeRanges = audio.seekable;

创建我们自己的缓冲反馈

如果我们想创建自己的自定义播放器,我们可能需要提供关于媒体已准备好播放多少的反馈。实际上,一种很好的方法是使用 seekable 属性,尽管如上所述,媒体的可跳转部分不一定是连续的——但它们通常是连续的,我们可以安全地近似这些信息来向用户指示哪些部分可以直接播放。我们可以使用以下代码行找到媒体中的这个点:

js
const seekableEnd = audio.seekable.end(audio.seekable.length - 1);

注意: audio.seekable.end(audio.seekable.length - 1) 实际上告诉我们最后一个可跳转时间范围的结束点(不是所有可跳转的媒体)。在实践中,这已经足够了,因为浏览器要么启用范围请求,要么不启用。如果不启用,audio.seekable 将等同于 audio.buffered,这将给出可跳转媒体结束的有效指示。如果启用了范围请求,这个值通常会几乎立即变成媒体的时长。

也许最好能指示媒体实际下载了多少——浏览器原生播放器似乎就是这样显示的。

所以,让我们来实现它。我们播放器的 HTML 如下:

html
<audio id="my-audio" preload controls>
  <source
    src="https://cdn.freesound.org/previews/155/155386_326032-lq.mp3"
    type="audio/mpeg" />
</audio>
<div class="buffered">
  <span id="buffered-amount"></span>
</div>
<div class="progress">
  <span id="progress-amount"></span>
</div>

我们将使用以下 CSS 来样式化缓冲显示:

css
.buffered {
  height: 20px;
  position: relative;
  background: #555555;
  width: 300px;
}

#buffered-amount {
  display: block;
  height: 100%;
  background-color: #777777;
  width: 0;
}

.progress {
  margin-top: -20px;
  height: 20px;
  position: relative;
  width: 300px;
}

#progress-amount {
  display: block;
  height: 100%;
  background-color: #559955;
  width: 0;
}

以下 JavaScript 提供了我们的功能:

js
const audio = document.getElementById("my-audio");

audio.addEventListener("progress", () => {
  const duration = audio.duration;
  if (duration > 0) {
    for (let i = 0; i < audio.buffered.length; i++) {
      if (
        audio.buffered.start(audio.buffered.length - 1 - i) < audio.currentTime
      ) {
        document.getElementById("buffered-amount").style.width = `${
          (audio.buffered.end(audio.buffered.length - 1 - i) * 100) / duration
        }%`;
        break;
      }
    }
  }
});

audio.addEventListener("timeupdate", () => {
  const duration = audio.duration;
  if (duration > 0) {
    document.getElementById("progress-amount").style.width = `${
      (audio.currentTime / duration) * 100
    }%`;
  }
});

当数据下载时,会触发 progress 事件,如果我们要显示下载或缓冲进度,这是一个很好的事件来响应。

当媒体播放时,timeupdate 事件每秒触发 4 次,我们可以在那里增加播放进度条。

这次,你应该会看到两种类型的段。浅灰色条表示缓冲进度,绿色条表示已播放进度。

关于已播放的快速说明

值得一提的是 played 属性——它告诉我们媒体中已播放的时间范围。例如:

js
const played = audio.played; // returns a TimeRanges object

这可能有助于确定你的媒体中最常收听或观看的部分。