创建跨浏览器视频播放器
本文档介绍了一个简单的 HTML 视频播放器,它使用 Media 和 Fullscreen API。除了全屏工作外,播放器还具有自定义控件,而不是仅使用浏览器默认控件。播放器控件本身不会进行任何超出基本要求的样式化,以使它们正常工作;播放器的完整样式将在以后的文章中处理。
工作示例
我们的示例视频播放器显示了来自名为 Tears of Steel 的开源电影的片段,并包含典型的视频控件。
HTML 标记
首先,让我们看一下构成播放器的 HTML。
视频
首先,定义了 <video>
元素,它包含在 <figure>
元素中,作为视频容器。对于熟悉 HTML 标记和 <video>
元素的人来说,这里应该没有什么让您感到惊讶的地方。
<figure id="videoContainer">
<video id="video" controls preload="metadata" poster="img/poster.jpg">
<source
src="video/tears-of-steel-battle-clip-medium.mp4"
type="video/mp4" />
<source
src="video/tears-of-steel-battle-clip-medium.webm"
type="video/webm" />
<source
src="video/tears-of-steel-battle-clip-medium.ogg"
type="video/ogg" />
<!-- Offer download -->
<a href="video/tears-of-steel-battle-clip-medium.mp4">Download MP4</a>
</video>
<figcaption>
© Blender Foundation |
<a href="http://mango.blender.org">mango.blender.org</a>
</figcaption>
</figure>
即使此播放器将定义自己的自定义控件集,controls
属性仍然被添加到 <video>
元素中,并且播放器的默认控件集稍后将使用 JavaScript 关闭。这样操作仍然允许 JavaScript 被禁用(出于任何原因)的用户仍然可以使用浏览器的原生控件。
为视频定义了一个海报图像,并且 preload
属性设置为 metadata
,这通知浏览器它最初应该只尝试加载视频文件中的元数据,而不是整个视频文件。这将为播放器提供诸如视频持续时间和格式之类的信息。
为播放器提供了三种不同的视频源:MP4、WebM 和 Ogg。使用这些不同的源格式提供了在所有支持 HTML 视频的浏览器中获得最佳支持的机会。有关视频格式和浏览器兼容性的更多信息,请参见 支持的媒体格式。
上面的代码将允许在大多数浏览器中播放视频,使用浏览器的默认控件集。下一步是定义一个自定义控件集,也在 HTML 中,它将用于控制视频。
控件集
大多数浏览器的默认视频控件具有以下功能
- 播放/暂停
- 静音
- 音量控制
- 进度条
- 快进
- 全屏
自定义控件集还将支持此功能,并增加了停止按钮。
再次,HTML 非常简单,使用一个无序列表,其中设置了 list-style-type:none
来包含控件,每个控件都是一个列表项,其中设置了 float:left
。对于进度条,可以使用 progress
元素。此列表插入在 <video>
元素之后,但在 <figure>
元素内部(这对全屏功能很重要,将在后面解释)。
<ul id="video-controls" class="controls">
<li><button id="playpause" type="button">Play/Pause</button></li>
<li><button id="stop" type="button">Stop</button></li>
<li class="progress">
<progress id="progress" value="0" min="0"></progress>
</li>
<li><button id="mute" type="button">Mute/Unmute</button></li>
<li><button id="volinc" type="button">Vol+</button></li>
<li><button id="voldec" type="button">Vol-</button></li>
<li><button id="fs" type="button">Fullscreen</button></li>
</ul>
每个按钮都被赋予了一个 id
,以便可以使用 JavaScript 方便地访问它。
控件最初使用 CSS display:none
隐藏,并将使用 JavaScript 启用。同样,如果用户禁用了 JavaScript,自定义控件集将不会出现,并且他们可以无阻碍地使用浏览器的默认控件集。
当然,此自定义控件集目前没有任何用处,并且什么也不做:让我们用 JavaScript 改进这种情况。
使用 Media API
HTML 带有一个 JavaScript Media API,允许开发者访问和控制 HTML 媒体。此 API 将用于使上面定义的自定义控件集真正起作用。此外,全屏按钮将使用 Fullscreen API,这是另一个 W3C API,它控制 Web 浏览器使用计算机全屏显示应用程序的能力。
设置
在处理各个按钮之前,需要进行一些初始化调用。
首先,最好先检查浏览器是否真正支持 <video>
元素,并且仅在支持的情况下设置自定义控件。这是通过检查创建的 <video>
元素是否支持 canPlayType()
方法 来完成的,任何支持的 HTML <video>
元素都应该支持该方法。
const supportsVideo = !!document.createElement("video").canPlayType;
if (supportsVideo) {
// set up custom controls
// …
}
一旦确认浏览器确实支持 HTML 视频,就该设置自定义控件了。需要指向 HTML 元素的变量
const videoContainer = document.getElementById("videoContainer");
const video = document.getElementById("video");
const videoControls = document.getElementById("video-controls");
如前所述,现在需要禁用浏览器的默认控件,并显示自定义控件
// Hide the default controls
video.controls = false;
// Display the user defined video controls
videoControls.style.display = "block";
完成之后,现在需要指向每个按钮的变量
const playpause = document.getElementById("playpause");
const stop = document.getElementById("stop");
const mute = document.getElementById("mute");
const volinc = document.getElementById("volinc");
const voldec = document.getElementById("voldec");
const progress = document.getElementById("progress");
const fullscreen = document.getElementById("fs");
使用这些句柄,现在可以将事件附加到每个自定义控件按钮上,使它们具有交互性。大多数这些按钮都需要添加一个简单的 click
事件监听器,以及在视频上调用的 Media API 定义方法和/或属性。
播放/暂停
playpause.addEventListener("click", (e) => {
if (video.paused || video.ended) {
video.play();
} else {
video.pause();
}
});
当在播放/暂停按钮上检测到 click
事件时,处理程序首先检查视频当前是否暂停或已结束(通过 Media API 的 paused
和 ended
属性);如果是,则使用 play()
方法播放视频。否则,视频必须正在播放,因此使用 pause()
方法暂停它。
停止
stop.addEventListener("click", (e) => {
video.pause();
video.currentTime = 0;
progress.value = 0;
});
Media API 没有 stop
方法,因此要模拟此方法,视频将被暂停,并且它的 currentTime
(即视频的当前播放位置)和 <progress>
元素的位置被设置为 0(有关 <progress>
元素的更多信息,请参见后面)。
静音
mute.addEventListener("click", (e) => {
video.muted = !video.muted;
});
静音按钮是一个简单的切换按钮,它使用 Media API 的 muted
属性来静音视频:这是一个布尔值,指示视频是否被静音。为了让它切换,我们将它设置为它自身的反值。
音量
volinc.addEventListener("click", (e) => {
alterVolume("+");
});
voldec.addEventListener("click", (e) => {
alterVolume("-");
});
定义了两个音量控制按钮,一个用于增加音量,另一个用于降低音量。创建了一个用户定义的函数 alterVolume(direction)
来处理此问题
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
和 min
,两者都被赋予了 0 的值。这些属性的功能不言而喻,min
表示 progress
元素的最小允许值,而 value
表示它的当前值。它还需要设置一个最大值,以便它可以正确地显示其范围,这可以通过 max
属性来完成,该属性需要设置为视频的最大播放时间。这是从视频的 duration
属性获得的,该属性也是 Media API 的一部分。
理想情况下,当 loadedmetadata
事件被触发时,视频的 duration
属性的正确值是可用的,该事件在视频的元数据被加载时发生
video.addEventListener("loadedmetadata", () => {
progress.setAttribute("max", video.duration);
});
不幸的是,在一些移动浏览器中,当 loadedmetadata
被触发时(如果它确实被触发了)——video.duration
可能没有正确的值,甚至可能根本没有值。因此,需要做一些其他事情。更多内容请参见下面。
另一个事件 timeupdate
会在视频播放过程中定期触发。此事件非常适合更新进度条的值,将其设置为视频的 currentTime
属性的值,该属性指示当前播放位置在视频中的位置。
video.addEventListener("timeupdate", () => {
progress.value = video.currentTime;
});
随着 timeupdate
事件的触发,progress
元素的 value
属性被设置为视频的 currentTime
。此跨度具有一个实心的 CSS 背景色,这有助于它提供与 <progress>
元素相同的视觉反馈。
回到上面提到的 video.duration
问题,当 timeupdate
事件被触发时,大多数移动浏览器中的视频 duration
属性应该已经有了正确的值。可以利用这一点,在 progress
元素的 max
属性当前未设置时对其进行设置。
video.addEventListener("timeupdate", () => {
if (!progress.getAttribute("max"))
progress.setAttribute("max", video.duration);
progress.value = video.currentTime;
});
注意: 更多关于进度条和缓冲反馈的信息和想法,请阅读 媒体缓冲、跳转和时间范围。
跳过
大多数浏览器默认视频控制集的另一个功能是,用户可以点击视频的进度条,以“跳过”到视频中的不同位置。这也可以通过向 progress
元素添加一个简单的 click
事件监听器来实现。
progress.addEventListener("click", (e) => {
const rect = progress.getBoundingClientRect();
const pos = (e.pageX - rect.left) / progress.offsetWidth;
video.currentTime = pos * video.duration;
});
这段代码使用点击位置来 (大致) 计算出用户点击了 progress
元素中的哪个位置,并通过设置视频的 currentTime
属性将其移动到该位置。
全屏
全屏 API 应该非常简单易用:用户点击一个按钮,如果视频处于全屏模式:取消它,否则进入全屏模式。
如果未启用全屏 API,则全屏按钮会隐藏。
if (!document?.fullscreenEnabled) {
fullscreen.style.display = "none";
}
全屏按钮需要真正做点什么。与其他按钮类似,它也附带了一个 click
事件处理程序,该处理程序调用用户定义的函数 handleFullscreen
。
fullscreen.addEventListener("click", (e) => {
handleFullscreen();
});
handleFullscreen
函数的定义如下:
function handleFullscreen() {
if (document.fullscreenElement !== null) {
// The document is in fullscreen mode
document.exitFullscreen();
setFullscreenData(false);
} else {
// The document is not in fullscreen mode
videoContainer.requestFullscreen();
setFullscreenData(true);
}
}
如果浏览器当前处于全屏模式,则必须退出,反之亦然。有趣的是,document
必须用于退出/取消全屏模式,而任何 HTML 元素都可以请求全屏模式,这里使用 videoContainer
,因为它也包含自定义控件,这些控件也应该与视频一起出现在全屏模式下。
另一个用户定义的函数 — setFullscreenData()
— 也被调用,它在 videoContainer
上设置 data-fullscreen
属性的值(这利用了 data-states
)。
function setFullscreenData(state) {
videoContainer.setAttribute("data-fullscreen", !!state);
}
这用于设置一些基本的 CSS,以改善自定义控件在全屏时的样式(有关详细信息,请参阅示例代码)。当视频进入全屏模式时,它通常会显示一条消息,指示用户可以按 Esc 键退出全屏模式,因此代码还需要监听相关事件,以便调用 setFullscreenData()
函数来确保控件样式正确。
document.addEventListener("fullscreenchange", (e) => {
setFullscreenData(!!document.fullscreenElement);
});
另请参阅
<video>
作为参考材料- 使用 HTML 音频和视频 以获取更多技巧
- HTML 音频和视频元素支持的媒体格式