在 HTML 视频中添加字幕

在其他文章中,我们了解了如何使用构建跨浏览器视频播放器,使用HTMLMediaElementWindow.fullScreen API,以及如何设置播放器的样式。本文将使用相同的播放器,并展示如何使用WebVTT 格式<track>元素向其添加字幕。

带字幕的视频示例

在本文中,我们将参考带字幕的视频播放器示例。此示例使用Sintel 开源电影的摘录,由Blender 基金会创建。

Video player with stand controls such as play, stop, volume, and captions on and off. The video playing shows a scene of a man holding a spear-like weapon, and a caption reads "Esta hoja tiene pasado oscuro."

注意:您可以在GitHub 上找到源代码,也可以查看示例

HTML 和视频字幕

在深入探讨如何向视频播放器添加字幕之前,我们将首先提到一些事项,在开始之前您应该了解这些事项。

字幕与隐藏式字幕

字幕和隐藏式字幕不是一回事:它们的目标受众和传达的信息大不相同,建议您在不确定它们的区别时阅读一下。然而,它们在技术上以相同的方式实现,因此本文中的内容适用于两者。

在本文中,我们将把显示的文本轨道称为隐藏式字幕,因为其内容针对的是难以理解电影语言的听众,而不是聋哑或听力障碍者。

<track> 元素

HTML 允许我们使用<track>元素为视频指定隐藏式字幕。此元素的各种属性允许我们指定诸如我们添加的内容类型、其使用的语言,当然还有包含实际字幕信息的文本文件的引用等信息。

WebVTT

包含实际字幕数据的文件是遵循指定格式的简单文本文件,在本例中为Web 视频文本轨道 (WebVTT) 格式。WebVTT 规范仍在开发中,但其大部分内容已经稳定,因此我们今天就可以使用它。

视频提供商(例如Blender 基金会)以文本格式提供带有其视频的字幕和隐藏式字幕,但它们通常采用 SubRip Text (SRT) 格式。这些可以通过在线转换器轻松转换为 WebVTT。

对 HTML 和 CSS 的修改

本节总结了为方便向视频添加隐藏式字幕而对上一篇文章代码所做的修改。如果您对此不感兴趣,只想直接进入 JavaScript 和更相关的 CSS,请跳至字幕实现部分。

在本示例中,我们使用的是不同的视频Sintel,因为它确实包含一些语音,因此更适合说明隐藏式字幕的工作原理!

HTML 标记

如上所述,我们需要使用新的 HTML <track> 元素将我们的隐藏式字幕文件添加到 HTML 视频中。我们的隐藏式字幕实际上有三种不同的语言——英语、德语和西班牙语——因此我们将通过在 HTML <video> 元素内添加 <track> 元素来引用所有三个相关的 VTT 文件。

html
<video id="video" controls preload="metadata">
  <source src="video/sintel-short.mp4" type="video/mp4" />
  <source src="video/sintel-short.webm" type="video/webm" />
  <track
    label="English"
    kind="subtitles"
    srclang="en"
    src="captions/vtt/sintel-en.vtt"
    default />
  <track
    label="Deutsch"
    kind="subtitles"
    srclang="de"
    src="captions/vtt/sintel-de.vtt" />
  <track
    label="Español"
    kind="subtitles"
    srclang="es"
    src="captions/vtt/sintel-es.vtt" />
</video>

如您所见,每个 <track> 元素都设置了以下属性

  • kind 的值为 subtitles,表示文件包含的内容类型。
  • label 的值为指示该字幕集所属的语言——例如 EnglishDeutsch——这些标签将显示在用户界面中,以便用户轻松选择他们想要查看的字幕语言。
  • src 在每种情况下都分配了一个指向相关 WebVTT 字幕文件的有效 URL。
  • srclang 指示每个字幕文件的内容所使用的语言。
  • default 属性设置在英语 <track> 元素上,指示浏览器在字幕已打开且用户未进行特定选择时,这是要使用的默认字幕文件定义。

除了添加 <track> 元素之外,我们还添加了一个新的按钮来控制我们将构建的字幕菜单。因此,视频控件现在如下所示

html
<div id="video-controls" class="controls" data-state="hidden">
  <button id="playpause" type="button" data-state="play">Play/Pause</button>
  <button id="stop" type="button" data-state="stop">Stop</button>
  <div class="progress">
    <progress id="progress" value="0" min="0">
      <span id="progress-bar"></span>
    </progress>
  </div>
  <button id="mute" type="button" data-state="mute">Mute/Unmute</button>
  <button id="volinc" type="button" data-state="volup">Vol+</button>
  <button id="voldec" type="button" data-state="voldown">Vol-</button>
  <button id="fs" type="button" data-state="go-fullscreen">Fullscreen</button>
  <button id="subtitles" type="button" data-state="subtitles">CC</button>
</div>

CSS 更改

视频控件进行了一些细微的更改,以便为额外的按钮腾出空间,但这些更改相对简单。

字幕按钮不使用任何图像,因此其样式如下所示

css
.controls button[data-state="subtitles"] {
  height: 85%;
  text-indent: 0;
  font-size: 16px;
  font-size: 1rem;
  font-weight: bold;
  color: #666;
  background: #000;
  border-radius: 2px;
}

还有一些其他 CSS 更改特定于一些额外的 JavaScript 实现,但这些更改将在下面适当的地方提及。

字幕实现

我们用来访问视频字幕的大部分内容都围绕着 JavaScript。与视频控件类似,如果浏览器支持 HTML 视频字幕,则将在本机控件集中提供一个按钮来访问它们。但是,由于我们已定义了自己的视频控件,因此此按钮被隐藏,我们需要定义自己的按钮。

浏览器对支持的内容有所不同,因此我们将尽可能尝试为每个浏览器提供更统一的用户界面。稍后将详细介绍浏览器兼容性问题。

初始设置

与所有其他按钮一样,我们需要做的第一件事之一是存储字幕按钮的句柄

js
const subtitles = document.getElementById("subtitles");

我们还最初关闭所有字幕,以防浏览器默认打开任何字幕。

js
for (let i = 0; i < video.textTracks.length; i++) {
  video.textTracks[i].mode = "hidden";
}

video.textTracks 属性包含附加到视频的所有文本轨道的数组。我们循环遍历每个轨道并将它的 mode 设置为 hidden

注意:WebVTT API 使我们可以访问使用 <track> 元素为 HTML 视频定义的所有文本轨道。

构建字幕菜单

我们的目标是使用我们之前添加的 subtitles 按钮显示一个菜单,允许用户选择他们希望显示的字幕语言,或完全关闭它们。

我们已添加了按钮,但在使其发挥作用之前,我们需要构建与之关联的菜单。此菜单是动态构建的,以便以后可以通过编辑视频标记中的 <track> 元素来添加或删除语言。

我们需要做的就是遍历视频的 textTracks,读取其属性并从中构建菜单

js
let subtitlesMenu;
if (video.textTracks) {
  const df = document.createDocumentFragment();
  const subtitlesMenu = df.appendChild(document.createElement("ul"));
  subtitlesMenu.className = "subtitles-menu";
  subtitlesMenu.appendChild(createMenuItem("subtitles-off", "", "Off"));
  for (let i = 0; i < video.textTracks.length; i++) {
    subtitlesMenu.appendChild(
      createMenuItem(
        `subtitles-${video.textTracks[i].language}`,
        video.textTracks[i].language,
        video.textTracks[i].label,
      ),
    );
  }
  videoContainer.appendChild(subtitlesMenu);
}

此代码创建一个documentFragment,用于保存包含我们的字幕菜单的无序列表。首先添加一个选项以允许用户关闭所有字幕,然后为每个文本轨道添加按钮,从每个轨道读取语言和标签。

每个列表项和按钮的创建由 createMenuItem() 函数完成,该函数定义如下

js
const subtitleMenuButtons = [];
function createMenuItem(id, lang, label) {
  const listItem = document.createElement("li");
  const button = listItem.appendChild(document.createElement("button"));
  button.setAttribute("id", id);
  button.className = "subtitles-button";
  if (lang.length > 0) button.setAttribute("lang", lang);
  button.value = label;
  button.setAttribute("data-state", "inactive");
  button.appendChild(document.createTextNode(label));
  button.addEventListener("click", (e) => {
    // Set all buttons to inactive
    subtitleMenuButtons.forEach((button) => {
      button.setAttribute("data-state", "inactive");
    });

    // Find the language to activate
    const lang = button.getAttribute("lang");
    for (let i = 0; i < video.textTracks.length; i++) {
      // For the 'subtitles-off' button, the first condition will never match so all will subtitles be turned off
      if (video.textTracks[i].language === lang) {
        video.textTracks[i].mode = "showing";
        button.setAttribute("data-state", "active");
      } else {
        video.textTracks[i].mode = "hidden";
      }
    }
    subtitlesMenu.style.display = "none";
  });
  subtitleMenuButtons.push(button);
  return listItem;
}

此函数构建所需的<li><button>元素,并返回它们以便可以将它们添加到字幕菜单列表中。它还在按钮上设置了所需的事件侦听器以切换相关字幕集的打开或关闭。这是通过将所需的字幕的 mode 属性设置为 showing 并将其他字幕设置为 hidden 来完成的。

构建完菜单后,将其插入到 videoContainer 底部的 DOM 中。

最初,菜单默认隐藏,因此需要向我们的字幕按钮添加一个事件侦听器以切换它

js
subtitles.addEventListener("click", (e) => {
  if (subtitlesMenu) {
    subtitlesMenu.style.display =
      subtitlesMenu.style.display === "block" ? "none" : "block";
  }
});

字幕菜单 CSS

我们还为新创建的字幕菜单添加了一些基本的样式

css
.subtitles-menu {
  display: none;
  position: absolute;
  bottom: 14.8%;
  right: 20px;
  background: #666;
  list-style-type: none;
  margin: 0;
  width: 100px;
  padding: 10px;
}

.subtitles-menu li {
  padding: 0;
  text-align: center;
}

.subtitles-menu li button {
  border: none;
  background: #000;
  color: #fff;
  cursor: pointer;
  width: 90%;
  padding: 2px 5px;
  border-radius: 2px;
}

设置显示字幕的样式

WebVTT 的一个鲜为人知且支持的功能是能够通过CSS 扩展为单个字幕(称为文本提示)设置样式。

::cue 伪元素是用于定位单个文本轨道提示以进行样式设置的关键,因为它匹配任何已定义的提示。只有少数几个 CSS 属性可以应用于文本提示

例如,要更改文本轨道提示的文本颜色,您可以编写

css
::cue {
  color: #ccc;
}

如果 WebVTT 文件使用语音跨度,它允许将提示定义为具有特定的“语音”

0
00:00:00.000 --> 00:00:12.000
<v Test>[Test]</v>

那么这个特定的“语音”可以这样设置样式

css
::cue(v[voice="Test"]) {
  color: #fff;
  background: #0095dd;
}

注意:目前,使用 ::cue 对提示进行的一些样式设置在 Chrome、Opera 和 Safari 上有效,但在 Firefox 上尚不可用。

浏览器兼容性

WebVTT 和<track> 元素的浏览器支持 非常好,尽管一些浏览器在实现上略有不同。

Safari

在 Safari 6.1 及更高版本中,默认情况下启用字幕,并且默认控件包含一个按钮和一个菜单,该菜单提供与我们刚刚构建的菜单相同的功能,以及一个“自动”选项,允许浏览器进行选择。default 属性也受支持。

Chrome 和 Opera

这些浏览器也具有类似的实现:默认情况下启用字幕,并且默认控件集包含一个“cc”按钮,用于打开和关闭字幕。Chrome 和 Opera 忽略<track> 元素上的default 属性,而是尝试将浏览器的语言与字幕的语言匹配。

插件

还有许多开源和商业的 HTML 视频播放器插件提供字幕支持,您可以使用它们,而不是自己开发。您可以使用诸如“HTML 视频播放器插件”之类的搜索词在网络上搜索这些插件。