在 HTML5 视频中添加字幕

这篇文章也在 MDN 上提供

随着 <video> 和 <audio> 元素引入 HTML5,我们终于有了在网站上添加视频和音频的原生方式。我们还有 JavaScript API 允许我们以不同的方式与这种媒体内容交互,无论是编写我们自己的控件还是简单地查看视频文件的长度。作为负责任的网页开发者,我们也应该不断思考如何使我们的内容更易访问,这并不止于视频和音频内容。使我们的内容对所有人来说更易访问是一项重要步骤,无论对于听力障碍者还是不理解内容所用语言的人,包容性都至关重要。

幸运的是,HTML5 还通过 <track> 元素为我们提供了一种原生方式来使我们的媒体内容更易访问,方法是添加字幕。大多数主流浏览器都以不同程度的方式原生支持这一点,本文的第一部分对此进行了说明,但也提供了一个 JavaScript API,我们可以通过该 API 访问和使用可用的文本轨道(例如字幕)。本文还展示了如何使用此 API 来检测已添加到 HTML5 视频中的字幕,以及如何使用这些数据来构建可选择的可用文本轨道菜单,最终为各种浏览器提供更一致的界面。

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

带字幕的视频示例

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

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

注意:你可以在 Github 上找到源代码,还可以 查看示例的实时演示

HTML5 和视频字幕

在深入了解如何向视频播放器添加字幕之前,我们先提几点需要注意的事项。

字幕和旁白

字幕和旁白并非一回事:它们针对的受众截然不同,传递的信息也不同,如果你不确定它们的区别,建议你阅读一下。然而,它们在技术上以相同的方式实现,因此本文中的内容适用于两者。

在本文中,我们将参考显示为字幕的文本轨道,因为它们的内容针对的是有听力但难以理解电影语言的人,而不是聋人或听力障碍者。

<track> 元素

HTML5 允许我们使用 <track> 元素为视频指定字幕。该元素的各种属性允许我们指定诸如添加的内容类型、内容所用语言,以及当然,对包含实际字幕信息的文本文件的引用等。

WebVTT

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

视频提供商(例如 Blender 基金会)会以文本格式提供字幕,但通常以 SubRip Text (SRT) 格式提供。可以使用在线转换器(例如 srt2vtt)轻松地将它们转换为 WebVTT。

对 HTML 和 CSS 的修改

本节总结了对上一篇文章代码进行的修改,以便为视频添加字幕。如果你对这些内容不感兴趣,只想直接进入 JavaScript 和更相关的 CSS,请跳到 字幕实现 部分。

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

HTML 标记

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

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

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

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

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

<div id="video-controls" class="controls">
   <button id="playpause" type="button">Play/Pause</button>
   <button id="stop" type="button">Stop</button>
   <div class="progress">    
         <span id="progress-bar"></span>
   </div>
   <button id="mute" type="button">Mute/Unmute</button>
   <button id="volinc" type="button">Vol+</button>
   <button id="voldec" type="button">Vol-</button>
   <button id="fs" type="button">Fullscreen</button>
   <button id="subtitles" type="button">CC</button>
</div>

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 有关。与视频控件类似,如果浏览器支持 HTML5 视频字幕,将在原生控件集中提供一个按钮来访问它们。但是,由于我们定义了自己的视频控件,因此此按钮被隐藏,我们需要自己定义一个。

浏览器对所支持的功能存在差异,因此我们将尝试尽可能地为每个浏览器提供更统一的 UI。有关浏览器兼容性问题的更多信息,请参见后面的内容。

初始设置

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

var subtitles = document.getElementById('subtitles');

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

for (var i = 0; i < video.textTracks.length; i++) {
   video.textTracks[i].mode = 'hidden';
}

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

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

构建字幕菜单

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

我们已经添加了按钮,但在让它执行任何操作之前,我们需要为它构建菜单。此菜单是动态构建的,因此可以稍后通过简单地编辑视频标记中的 <track> 元素来添加或删除语言。

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

var subtitlesMenu;
if (video.textTracks) {
   var df = document.createDocumentFragment();
   var subtitlesMenu = df.appendChild(document.createElement('ul'));
   subtitlesMenu.className = 'subtitles-menu';
   subtitlesMenu.appendChild(createMenuItem('subtitles-off', '', 'Off'));
   for (var 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);
}

此代码创建一个 <a href=”https://mdn.org.cn/en-US/docs/Web/API/documentFragment” title=”The DocumentFragment interface represents a minimal document object that has no parent. It is used as a light-weight version of Document to store well-formed or potentially non-well-formed fragments of XML.” documentFragment,用于保存包含字幕菜单的无序列表。首先,添加一个选项允许用户关闭所有字幕,然后为每个文本轨道添加按钮,从每个轨道读取语言和标签。

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

var subtitleMenuButtons = [];
var createMenuItem = function(id, lang, label) {
   var listItem = document.createElement('li');
   var 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', function(e) {
      // Set all buttons to inactive
      subtitleMenuButtons.map(function(v, i, a) {
         subtitleMenuButtons[i].setAttribute('data-state', 'inactive');
      });
      // Find the language to activate
      var lang = this.getAttribute('lang');
      for (var 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';
            this.setAttribute('data-state', 'active');
         }
         else {
            video.textTracks[i].mode = 'hidden';
         }
      }
      subtitlesMenu.style.display = 'none';
   });
   subtitleMenuButtons.push(button);
   return listItem;
}

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

构建菜单后,它将被插入到视频容器底部的 DOM 中。

最初,菜单默认情况下是隐藏的,因此需要在字幕按钮上添加一个事件侦听器来切换它

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

字幕菜单 CSS

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

.subtitles-menu {
    display:none;
    position:absolute;
    bottom:14.8%;
    right:20px;
    background:#666;
    list-style-type:none;
    margin:0;
    padding: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 属性可以应用于文本提示

例如,要更改文本轨迹提示的文本颜色,可以编写以下内容:

::cue {
   color:#ccc;
}

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

0
00:00:00.000 --> 00:00:12.000
[Test]

那么这个特定的“语音”将像这样可样式化

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

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

浏览器兼容性

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

Internet Explorer

从 Internet Explorer 10+ 开始,字幕默认启用,默认控件包含一个按钮和一个菜单,提供与我们刚刚构建的菜单相同的功能。default 属性也受支持。

注意:除非设置 MIME 类型,否则 IE 将完全忽略 WebVTT 文件。这可以通过将包含 AddType text/vtt .vtt.htaccess 文件添加到适当的目录来轻松完成。

Safari

Safari 6.1+ 具有与 Internet Explorer 11 相似的支持,显示一个包含不同可用选项的菜单,并添加了一个“自动”选项,允许浏览器选择。

Chrome 和 Opera

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

Firefox

由于 错误,Firefox 的实现完全被破坏,导致 Mozilla 默认情况下关闭了 WebVTT 支持(您可以通过 media.webvtt.enabled 标志将其打开)。但是,此错误似乎已修复,并且从 Gecko 31 开始重新启用了 WebVTT 支持,因此对于 Firefox 最终发布用户来说,这将不再是一个问题(在撰写本文时,Gecko 29 存在此问题)。 从 Firefox 31 开始,此问题已修复,一切按预期工作。

插件

如果您在通读本文后,决定您不想费心去做所有这些事情,而是想让其他人为您做,那么有很多插件可以提供您可以使用的字幕和字幕支持。

playr
这个小型插件实现了字幕、说明和章节,以及 WebVTT 和 SRT 文件格式。
jwplayer
这款视频播放器非常强大,不仅仅支持视频字幕。它支持 WebVTT、SRT 和 DFXP 文件格式。
MediaElement.js
另一个完整的视频播放器,也支持视频字幕,但仅限于 SRT 格式。
LeanBack Player
另一个支持 WebVTT 字幕的视频播放器,并提供其他标准播放器功能。
SublimeVideo
这款播放器还通过 WebVTT 和 SRT 文件支持字幕。
Video.js
支持 WebVTT 视频字幕。

注意:您可以在 HTML5 视频播放器比较 中找到一份关于 HTML5 视频播放器及其当前状态的优秀列表。

关于 Ian Devlin

高级 Web 开发人员,就职于 pixolith。对所有 Web 相关事物感兴趣,尤其是 HTML5。爱尔兰人,但常驻德国杜塞尔多夫。

更多 Ian Devlin 的文章…

关于 Robert Nyman [资深编辑]

Mozilla Hacks 的技术布道者和编辑。发表关于 HTML5、JavaScript 和开放 Web 的演讲和博客。Robert 是 HTML5 和开放 Web 的坚定支持者,自 1999 年以来一直在从事 Web 前端开发工作,在瑞典和纽约市。他经常在 http://robertnyman.com 上发布博客,并且喜欢旅行和结识新朋友。

更多 Robert Nyman [资深编辑] 的文章…


7 条评论

  1. Eld

    Firefox 31 能最终支持新的 60fps YouTube 吗?

    2014 年 7 月 10 日 下午 05:09

    1. Robert Nyman [编辑]

      我不明白为什么它不能支持,但我还没有测试过。询问此类功能支持的最佳地方是 Firefox 支持

      2014 年 7 月 10 日 下午 10:41

  2. alexander farkas

    也许,您想添加 webshim 的媒体播放器,它不仅支持字幕、说明和章节,包括菜单,还支持 textTrack API。要添加,请阅读更改媒体元素的 textTracks。

    以下是一个使用字幕和章节的小示例
    http://afarkas.github.io/webshim/demos/demos/mediaelement/responsive-mediaelement.html

    以下是一个使用 textTrack API 的示例
    http://afarkas.github.io/webshim/demos/demos/mediaelement/track-demo.html

    2014 年 7 月 10 日 下午 08:17

    1. Robert Nyman [编辑]

      感谢您的建议!

      2014 年 7 月 11 日 上午 00:59

    2. Junior

      @alexander:太棒了…

      我没有安装 Flash,并且通过 HTML5 播放器观看 YouTube 视频… 太糟糕了… 我以为这是因为我的笔记本电脑配置一般,但显然错了… 您的示例在这里播放流畅…

      恭喜您完成这项工作!

      2014 年 7 月 12 日 下午 18:02

  3. alexander farkas

    关于您的代码的一些想法

    您最初将所有 textTracks 设置为隐藏,以防浏览器将其设置为显示。但正常模式被禁用。隐藏意味着它们不显示,但已处理。特别是,如果您有很多不同的轨道,这可能会给浏览器带来一些不必要的压力。也许最好将它们设置为禁用,或者仅在以前显示时才设置为隐藏。

    另一个问题,我一直讨厌那些视频播放器代码教程,其中脚本更改了播放按钮的视觉状态,因为播放按钮被点击。UI 应该反映视频/textTracks 的状态。并且仅根据状态和来自视频元素的事件更改视觉变化。在 textTracks 对象的情况下,有一个 change 事件,当 textTrack 的模式状态发生变化时会发出此事件。这样,菜单就会与正确的模式状态保持同步,即使模式由另一个脚本更改也是如此。

    2014 年 7 月 10 日 下午 19:17

    1. Robert Nyman [编辑]

      感谢您的反馈,这些都是需要考虑的有效问题!

      2014 年 7 月 11 日 上午 01:01

本文的评论已关闭。