Video.js 博客

Garrett Singer2018-12-17

Bugpost:断开与重连

在我以前的公寓里,每当我用微波炉热一碗燕麦片时,我的WIFI就会断开。这很烦人,但我肚子饿了,公寓太小无法将路由器搬离微波炉更远,而且我也不想买任何在不同频率下运行的东西。所以我会启动微波炉,观看笔记本电脑上正在播放的视频直到缓冲区耗尽,然后看着加载指示器旋转直到微波炉发出三声提示音。

值得庆幸的是,视频播放器通常对网络连接(和不稳定的微波炉)等问题具有相当强的鲁棒性,并且在网络恢复后视频会继续播放。但最近我们发现,对于在 Video.js 中播放的某些内容,视频没有恢复播放。更糟糕的是,加载指示器直接消失了,屏幕上留下了一个冻结的画面。

您可以自己查看此行为:下载 Video.js 7.3.0,加载 https://dash.akamaized.net/akamai/bbb_30fps/bbb_30fps.mpd 作为源,然后使用 Chrome 开发者工具断开并重新连接网络。

以下是发生的情况以及我们如何修复它的快速回顾。

加载指示器

为了解决这个bug,我们从最明显的问题开始:加载指示器的消失。该指示器由 Video.js 管理。在代码中,当技术触发 waiting 事件时,vjs-waiting 类会添加到 HTML 元素中,并在下一个 timeupdate 事件时移除。

/**
 * Retrigger the `waiting` event that was triggered by the {@link Tech}.
 *
 * @fires Player#waiting
 * @listens Tech#waiting
 * @private
 */
handleTechWaiting_() {
  this.addClass('vjs-waiting');
  /**
   * A readyState change on the DOM element has caused playback to stop.
   *
   * @event Player#waiting
   * @type {EventTarget~Event}
   */
  this.trigger('waiting');
  this.one('timeupdate', () => this.removeClass('vjs-waiting'));
}

在记录了播放器发出的事件后,问题变得清晰起来。有些浏览器在waiting事件之后立即发出了timeupdate事件。

因此 timeupdate 事件本身并不可靠,那么还有什么可以用来更好地判断我们是否在等待呢?我们检查了 timeupdate 事件上的 player.currentTime(),发现当我们捕获到最后一个 timeupdate 事件,即 waiting 事件之后的那个时,播放器的时间与 waiting 事件之前的 timeupdate 事件的时间相同(也与 waiting 事件触发时的时间相同)。

`timeupdate` triggered, player.currentTime = 11
`timeupdate` triggered, player.currentTime = 11.250
`waiting` triggered, player.currentTime = 11.250
`timeupdate` triggered, player.currentTime = 11.250

修复方法相当简单。只需在播放器收到 waiting 事件时记录其时间,然后不在下一个 timeupdate 事件时移除 vjs-waiting 类,而是在下一个时间与我们记录的时间不同的 timeupdate 事件时移除 vjs-waiting 类。

您可以在此处查看相关的PR,并且它已在Video.js 7.4.0中提供。

断开连接后恢复播放

加载指示器恢复后,下一个问题是确定为什么在网络重新连接时视频没有恢复播放。首先,我们测试了几种不同的内容。我们测试的 HLS 源确实重新连接了,但 DASH 源没有。更糟糕的是,当使用 DASH 源时,Chrome 的调试控制台冻结了,因为它以计算机能处理的速度累积了请求和控制台错误。

控制台崩溃

想象一下您的控制台被以下两条消息重复填充数千次

GET https://dash.akamaized.net/akamai/bbb_30fps/bbb_a64k/bbb_a64k_8.m4a net::ERR_INTERNET_DISCONNECTED
VIDEOJS: WARN: Problem encountered with the current HLS playlist. Trying again since it is the final playlist.

当您的控制台冻结时,调试可能会很困难。这就像在微波炉工作时尝试看视频一样。因此我们首先解决了这个问题。

查看错误消息,我们发现它反复重试最后一个内容片段。这是预期行为,因为我们很久以前就将其添加到我们的 HLS 播放列表加载器中。然而,当我们添加 DASH 支持时,我们创建了一个不同的播放列表加载器,但我们从未为重新加载最终的 DASH 播放列表添加相同的逻辑。

添加节流之后,调试变得容易得多,我们得以再次使用控制台并深入研究为什么 HLS 能够重新连接而 DASH 不能。

多个请求,多个问题

最后一个问题被证明是最难调试的。最初,我们认为是解复用内容的问题,因为拥有一个单独的音频片段加载器从音频播放列表加载,可能会在主片段加载器尝试从视频播放列表加载时同时出现错误而导致问题。然而,HLS 也可以有解复用内容,而且在一个示例源证明可以正常恢复后,这种方法就被排除了。

通过检查 Chrome 的网络日志,DASH 内容在某个点之后停止请求视频片段,而只请求音频片段。但 DASH 的请求不止两个。视频片段本身包括两个请求:一个用于初始化片段,另一个用于视频数据。

VHS 中的请求由一个名为 media-segment-request 的模块管理,该模块负责请求一个片段所需的所有内容,无论是密钥、初始化片段、媒体数据,还是所有这些,然后才通知片段加载器其请求已完成。对于视频请求,media-segment-request 在断开连接后从未调用完成回调。

结果发现,如果我们收到一个错误,我们就会中止该组中的其他请求,并等待调用 done 回调,直到这些请求报告它们已被中止。然而,根据 XHR 标准,如果请求未发送,中止算法可能不会运行。在这种情况下,当网络断开连接时,第一个请求在第二个请求有机会发送之前就报告了错误。在我们中止第二个请求后,我们陷入了等待一个永远不会发生的错误的困境。

我们通过在遇到第一个错误时立即回调并返回错误来解决这个问题,而不是等待每个后续请求完成。

这个问题也应该存在于HLS中,但仅限于片段带有EXT-X-KEY或EXT-X-MAP标签以配合媒体请求的情况。在我们尝试的内容中,这两个标签都不存在,因此它似乎仅限于DASH内容。

Bug 已解决

我们希望这些内容对您有所帮助,能让您了解我们遇到的一些 bug、我们如何进行调查以及解决方案是什么样的。我们也乐于听取您的意见。您可以在 Video.js Slack 的 #playback 频道找到我们,或者在 https://github.com/videojs/http-streaming 上查看 Issues 和 PRs。

现在可以安心享用我的燕麦片和视频了,无需担心播放是否会恢复。

Garrett Singer2018-11-08

Video.js HTTP 流媒体 (VHS) 介绍

“如何让我的视频在 Video.js 中播放?”

这是我们在使用 Video.js 时最常遇到的问题之一。而且这是一个很好的问题。

如果有人下载 Video.js,他们的内容可能在一个浏览器中播放,而在另一个浏览器中无法播放。为了获得特定类型内容的跨浏览器支持,他们必须找到一个相关的源处理程序(例如,针对 HLS 内容的 videojs-contrib-hls)。只有包含该源处理程序后,他们的视频才能在所有主流浏览器中播放。

我们希望 Video.js 易于使用,但目前的设置并不简单。因此,我们决定是时候解决这个问题了。

这就是将 Video.js HTTP Streaming(昵称 VHS)默认集成到 Video.js 7 中的原因。

什么是 VHS?

VHS logoVHS 标志

VHS 是 videojs-contrib-hls 的继任者。它是从 videojs-contrib-hls 仓库派生出来的源处理程序。虽然 videojs-contrib-hls 最初是为了在所有浏览器上添加 HLS 播放而设计的,但我们意识到该引擎也能很好地播放其他格式。

为了证明这一点,我们添加了一个 DASH 清单解析器,经过一些小的改动,VHS 使用与 HLS 相同的代码库和 API 播放了 DASH 内容。

即使视频技术格局不断变化,Video.js 也将做好准备。如果一种新的流媒体格式流行起来,只需少量代码更改即可为 VHS 添加支持。一个引擎和 API 支持所有格式。

为什么 VHS 默认包含在 Video.js 中?

通过在 Video.js 中默认包含 VHS,您不再需要担心哪个浏览器支持哪种流媒体技术。

Video.js 的构建旨在抽象化网络浏览器之间视频 API 和功能的差异,创建一个尽可能接近网络标准的简单 API,并在可能的情况下弥补功能空白。随着时间的推移,浏览器在对不同媒体格式的支持方面出现了一个分歧点。一些浏览器可能支持 DASH 的原生播放,另一些支持 HLS,还有一些则两者都不支持。

以前,这需要通过包含源处理程序和插件来管理,但我们理解简单设置的重要性。通过在 Video.js 中默认包含 VHS,您不再需要担心哪个浏览器支持哪种流媒体技术。它应该可以直接工作。

如果我想选择不使用 VHS 怎么办?

如果您更愿意为 HLS 或 DASH 播放包含不同的源处理程序,Video.js 仍然允许您这样做。Video.js 核心 是您熟悉和喜爱的 Video.js,不包含 VHS。一切都应该像以前一样工作。

欢迎贡献和反馈

一如既往,我们欢迎您的贡献和反馈。就评论、问题、请求、贡献或仅仅是打个招呼而言,联系我们的最佳方式是 VHS GitHub 页面 以及 Video.js Slack 上的 #playback 频道。