创建跨浏览器视频播放器

本文介绍了一个使用 Media 和 Fullscreen API 的基本 HTML 视频播放器。除了支持全屏播放外,该播放器还具有自定义控件,而不是仅使用浏览器默认控件。播放器控件本身不会进行除了基本功能实现之外的样式设置;播放器的完整样式将在未来的文章中进行介绍。

我们示例的视频播放器展示了一部名为“Tears of Steel”的开源电影片段,并包含典型的视频控件。

HTML 标记

首先,让我们看一下构成播放器的 HTML。

视频

我们的整个播放器包含在一个 <figure> 元素中。

html
<figure id="videoContainer">

在内部,我们首先定义 <video> 元素。

html
<video
  id="video"
  controls
  preload="metadata"
  poster="/shared-assets/images/examples/tears-of-steel-battle-clip-medium-poster.jpg">
  <source
    src="/shared-assets/videos/tears-of-steel-battle-clip-medium.mp4"
    type="video/mp4" />
  <source
    src="/shared-assets/videos/tears-of-steel-battle-clip-medium.webm"
    type="video/webm" />
  <source
    src="/shared-assets/videos/tears-of-steel-battle-clip-medium.ogg"
    type="video/ogg" />
  <!-- Offer download -->
  <a href="/shared-assets/videos/tears-of-steel-battle-clip-medium.mp4"
    >Download MP4</a
  >
</video>

即使此播放器将定义自己的自定义控件集,但 controls 属性仍会添加到 <video> 元素中,并且播放器的默认控件集稍后会通过 JavaScript 关闭。采用这种方式,即使禁用了 JavaScript 的用户(无论出于何种原因)仍然可以访问浏览器的原生控件。

为视频定义了海报图像,并将 preload 属性设置为 metadata,这会告知浏览器它应该只尝试加载视频文件的元数据,而不是整个视频文件。这为播放器提供了视频时长和格式等数据。

为播放器提供了三种不同的视频源:MP4、WebM 和 Ogg。使用这些不同的源格式可以最大程度地提高所有支持 HTML 视频的浏览器上的兼容性。有关视频格式和浏览器兼容性的更多信息,请参阅选择视频编解码器

上面的代码允许在大多数浏览器中使用浏览器的默认控件集播放视频。下一步是也在 HTML 中定义一个自定义控件集,用于控制视频。

控件集

大多数浏览器的默认视频控件具有以下功能:

  • 播放/暂停
  • 静音
  • 音量控制
  • 进度条
  • 快进
  • 全屏

自定义控件集还将支持此功能,并增加一个停止按钮。

再次,HTML 非常简单,使用一个无序列表,并将 list-style-type:none 设置为包含控件,每个控件都是一个设置了 float:left 的列表项。对于进度条,利用了 progress 元素。此列表插入在 <video> 元素之后,但位于 <figure> 元素内部(这对于稍后解释的全屏功能很重要)。

html
<ul id="video-controls" class="controls" data-state="hidden">
  <li><button id="play-pause" type="button">Play/Pause</button></li>
  <li><button id="stop" type="button">Stop</button></li>
  <li class="progress">
    <progress id="progress" value="0"></progress>
  </li>
  <li><button id="mute" type="button">Mute/Unmute</button></li>
  <li><button id="vol-inc" type="button">Vol+</button></li>
  <li><button id="vol-dec" type="button">Vol-</button></li>
  <li><button id="fs" type="button">Fullscreen</button></li>
</ul>

每个按钮都赋予了一个 id,以便可以通过 JavaScript 轻松访问。

控件最初通过 CSS display:none 隐藏,并将通过 JavaScript 启用。同样,如果用户禁用了 JavaScript,自定义控件集将不会出现,他们可以不受干扰地使用浏览器的默认控件集。

当然,这个自定义控件集目前是无用的,什么也做不了:让我们用 JavaScript 来改进一下。

最后,我们使用一个包含版权信息的 <figcaption> 来关闭 <figure> 元素。

html
  <figcaption>
    &copy; Blender Foundation |
    <a href="http://mango.blender.org">mango.blender.org</a>
  </figcaption>
</figure>

使用 Media API

HTML 带有一个 JavaScript Media API,它允许开发者访问和控制 HTML 媒体。此 API 将用于使上面定义的自定义控件集真正起作用。此外,全屏按钮将使用 Fullscreen API,这是另一个 W3C API,用于控制 Web 浏览器以全屏模式显示应用程序的能力。

设置

在处理单个按钮之前,需要进行一些初始化调用。需要指向 HTML 元素的变量。

js
const videoContainer = document.getElementById("videoContainer");
const video = document.getElementById("video");
const videoControls = document.getElementById("video-controls");
const playPause = document.getElementById("play-pause");
const stop = document.getElementById("stop");
const mute = document.getElementById("mute");
const volInc = document.getElementById("vol-inc");
const volDec = document.getElementById("vol-dec");
const progress = document.getElementById("progress");
const fullscreen = document.getElementById("fs");

使用这些句柄,现在可以为每个自定义控件按钮附加事件,使其具有交互性。其中大多数按钮需要添加一个 click 事件监听器,并对视频调用/检查 Media API 定义的方法和/或属性。

如前所述,现在需要禁用浏览器的默认控件,并显示自定义控件。

js
// Hide the default controls
video.controls = false;
// Display the user defined video controls
videoControls.setAttribute("data-state", "visible");

播放/暂停

js
playPause.addEventListener("click", (e) => {
  if (video.paused || video.ended) {
    video.play();
  } else {
    video.pause();
  }
});

当在播放/暂停按钮上检测到 click 事件时,处理程序首先检查视频当前是否暂停或已结束(通过 Media API 的 pausedended 属性);如果是,则使用 play() 方法播放视频。否则,视频必须正在播放,因此使用 pause() 方法将其暂停。

停止

js
stop.addEventListener("click", (e) => {
  video.pause();
  video.currentTime = 0;
  progress.value = 0;
});

Media API 没有 stop 方法,因此为了模拟它,视频会被暂停,并且其 currentTime(即视频的当前播放位置)和 <progress> 元素的位置会被设置为 0(稍后将详细介绍 <progress> 元素)。

静音

js
mute.addEventListener("click", (e) => {
  video.muted = !video.muted;
});

静音按钮是一个切换按钮,它使用 Media API 的 muted 属性来静音视频:这是一个布尔值,指示视频是否静音。为了使其能够切换,我们将其设置为其自身的反值。

音量

js
volInc.addEventListener("click", (e) => {
  alterVolume("+");
});
volDec.addEventListener("click", (e) => {
  alterVolume("-");
});

定义了两个音量控制按钮,一个用于增加音量,另一个用于减小音量。创建了一个用户定义的函数 alterVolume(direction) 来处理此问题。

js
function alterVolume(dir) {
  const currentVolume = Math.floor(video.volume * 10) / 10;
  if (dir === "+" && currentVolume < 1) {
    video.volume += 0.1;
  } else if (dir === "-" && currentVolume > 0) {
    video.volume -= 0.1;
  }
}

此函数利用了 Media API 的 volume 属性,该属性保存视频的当前音量值。此属性的有效值是 0 和 1 之间的任何值。该函数检查 dir 参数,该参数指示音量是要增加(+)还是减小(-),并据此执行操作。该函数定义为以 0.1 的步长增加或减小视频的 volume 属性,确保其不超过 0 或 1。

进度

在上面 HTML 中定义 <progress> 元素时,只将 value 属性设置为 0。此属性指示进度元素的当前值。它还需要设置一个最大值,以便正确显示其范围,这可以通过 max 属性来完成,该属性需要设置为视频的最大播放时间。这可以从视频的 duration 属性中获得,该属性同样是 Media API 的一部分。

理想情况下,当触发 loadedmetadata 事件时(当视频元数据加载完成时),视频 duration 属性的正确值将可用。

js
video.addEventListener("loadedmetadata", () => {
  progress.setAttribute("max", video.duration);
});

不幸的是,在某些移动浏览器中,当 loadedmetadata 被触发时——即使它被触发了——video.duration 可能没有正确的值,甚至没有任何值。所以需要做些其他事情。下面将进一步介绍。

另一个事件 timeupdate 会在视频播放过程中定期触发。此事件非常适合更新进度条的值,将其设置为视频 currentTime 属性的值,该属性指示当前播放进度。视频播放的进度。

js
video.addEventListener("timeupdate", () => {
  progress.value = video.currentTime;
});

当触发 timeupdate 事件时,progress 元素的 value 属性被设置为视频的 currentTime。这个跨度有一个实心的 CSS 背景颜色,这有助于它提供与 <progress> 元素相同的视觉反馈。

回到上面提到的 video.duration 问题,当 timeupdate 事件被触发时,在大多数移动浏览器中,视频的 duration 属性现在应该具有正确的值。如果 progress 元素的 max 属性尚未设置,则可以利用这一点来设置它。

js
video.addEventListener("timeupdate", () => {
  if (!progress.getAttribute("max"))
    progress.setAttribute("max", video.duration);
  progress.value = video.currentTime;
});

注意:有关进度条和缓冲反馈的更多信息和想法,请阅读媒体缓冲、搜寻和时间范围

快进

大多数浏览器默认视频控件集的另一个功能是能够单击视频进度条以“跳到”视频中的不同点。也可以通过向 progress 元素添加 click 事件监听器来实现此功能。

js
progress.addEventListener("click", (e) => {
  if (!Number.isFinite(video.duration)) return;
  const rect = progress.getBoundingClientRect();
  const pos = (e.pageX - rect.left) / progress.offsetWidth;
  video.currentTime = pos * video.duration;
});

这段代码使用单击的位置(大致)来计算用户在 progress 元素上单击的位置,并通过设置其 currentTime 属性将视频移动到该位置。如果视频的持续时间是 NaNInfinity(当视频尚未加载时会发生这种情况),则它会避免设置 currentTime

全屏

Fullscreen API 应该很简单:用户单击按钮,如果视频处于全屏模式:取消全屏,否则进入全屏模式。

如果 Fullscreen API 未启用,则全屏按钮将被隐藏。

js
if (!document?.fullscreenEnabled) {
  fullscreen.style.display = "none";
}

全屏按钮需要真正起作用。与其他按钮一样,会附加一个 click 事件处理程序,该处理程序会切换全屏模式。

js
fullscreen.addEventListener("click", (e) => {
  if (document.fullscreenElement !== null) {
    // The document is in fullscreen mode
    document.exitFullscreen();
  } else {
    // The document is not in fullscreen mode
    videoContainer.requestFullscreen();
  }
});

如果浏览器当前处于全屏模式,则必须退出,反之亦然。有趣的是,必须使用 document 来退出/取消全屏模式,而任何 HTML 元素都可以请求全屏模式,此处使用 videoContainer,因为它也包含自定义控件,这些控件也应与视频一起显示在全屏模式下。

结果

本教程隐藏了 CSS 部分,但您可以点击“播放”查看完整源代码。在下一部分,视频播放器样式基础,我们将探讨这里使用的一些有趣的 CSS 技术,并添加新的 CSS 使播放器看起来更美观。

警告:示例视频可能会很大声!

另见