m3u8 格式视频播放(权限解密播放)

11,572 阅读5分钟

前言

公司业务技术上为实现视频资源的体验优化,服务端团队针对视频点播方面采用了 HLS .m3u8 格式文件技术方案,并且服务端在处理 m3u8 格式文件时进行了加密

这要求在 Web 端在进行播放时需要先进行权限认证(解密)。特此借助此片文章来记录这一过程。

下面,我们先简单了解视频点播新方案的几个概念。

HLS

HTTP Live Streaming(缩写 HLS)是一个由苹果公司提出的基于 HTTP 的流媒体网络传输协议。工作原理是把整个流分成一个个小的基于 HTTP 的文件来下载。

HLS 将视频作为一系列小文件发送,通常每段小文件持续时间约为 6 秒,称为媒体段文件。 索引文件或播放列表提供媒体段文件的 URL 的有序列表。 HLS 的索引文件保存为 M3U8 播放列表,客户端访问索引文件的 URL,然后客户端按顺序请求索引文件。

M3U8

M3U8 是苹果公司使用的 HTTP Live Streaming(HLS) 协议格式的基础,实质是一个媒体播放列表(Media Playlist),其内部信息记录的是一系列媒体片段资源,顺序播放该片段资源,即可完整展示多媒体资源。其格式如下所示:

#EXTM3U     # M3U8文件头
#EXT-X-VERSION:3    # M3U8版本号
#EXT-X-TARGETDURATION:10  # 每个分片TS的最大的时长
#EXT-X-MEDIA-SEQUENCE:1   # 第一个TS分片的序列号
#EXT-X-PLAYLIST-TYPE:VOD  # 表明流媒体类型,VOD 表示点播
#EXT-X-KEY:METHOD=AES-128,URI="https://xxx.key",IV=0x00000000000000000000000000000001   # 是否加密解析
#EXTINF:10,     # 分片TS的信息,如时长,带宽等;
index480p_00001.ts
#EXT-X-KEY:METHOD=AES-128,URI="https://xxx.key",IV=0x00000000000000000000000000000002
#EXTINF:10,
index480p_00002.ts
#EXT-X-KEY:METHOD=AES-128,URI="https://xxx.key",IV=0x00000000000000000000000000000003
#EXTINF:10,
...
#EXT-X-ENDLIST      # 结束,也可用来区分是直播或点播

简单来说,m3u8 是一个 ts 切片列表文件,它记录视频的每个切片的时长与顺序。在播放 M3U8 文件时,并不会将全部 ts 列表文件都请求执行,而是根据视频进度执行对应的 ts 分片文件。

在 M3U8 文件中有一个关键信息 EXT-X-KEY,如果存在此信息,说明视频是进行过加密解析的,如果不进行解密,ts 分片文件将无法正常播放。 而获取解析密钥的方式是访问 EXT-X-KEY.URI 地址来获取密钥 key,有了密钥 key,才可以解密 ts 分片文件,使得视频正常播放。

当然,在执行 M3U8 ts 切片列表文件时,每个 ts 切片文件在执行前都会执行一次获取密钥 key 的 URI 地址。

下面,我们看看在 Web 端拿到服务端下发的 m3u8 文件后,如何进行视频播放。

video.js

在 Web 端使用 HTML video 标签播放 m3u8 文件时,即使设置 type 为 application/x-mpegURL 也无法正常播放。如下:

<video id="my-video" width="320" height="240" controls>
  <source src="https://xxx/index.m3u8"  type="application/x-mpegURL">
  您的浏览器不支持 HTML5 video 标签。
</video>

不过,video.js 提供了解析播放 m3u8 文件的能力,下面我们引入 video 相关资源即可正常播放:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <link href="https://cdn.jsdelivr.net/npm/video.js@7.2.4/dist/video-js.css" rel="stylesheet">
  <script src="https://cdn.jsdelivr.net/npm/video.js@7.4.1/dist/video.js"></script>
</head>
<body>
  <video id="my-video" width="320" height="240" controls>
    <source src="https://xxx/index.m3u8"  type="application/x-mpegURL">
    您的浏览器不支持 HTML5 video 标签。
  </video>

  <script>
    const player = videojs('my-video');
  </script>
</body>
</html>

播放加密 m3u8 ts 片段文件

上面我们提到,如果 m3u8 是一个经过加密后的文件,在下发的文本内容中会有 EXT-X-KEY 字段信息,如:

#EXT-X-KEY:METHOD=AES-128,URI="https://xxx.key",IV=0x00000000000000000000000000000001   # 是否加密解析

METHOD 表示加密方式为 AES-128,其中 URI 是用于获取密钥进行解密的 api 接口,这个地址由服务端提供。

而对于这里的设计,我们采用了用户权限校验。也就是说,当访问 URI 获取密钥 key 时,如果用户有权限访问视频,则下发正确的密钥,否则认为无权限,视频无法正常播放,这很好的实现了视频防盗链。

而用户权限校验,可以是在访问 URI 获取密钥 key 时,将用户的登录信息(如:token)作为 queryString 参数传给 URI 做后续处理。

但是,问题来了,在 Web 端如何能够监听 m3u8 文件内下发的 URI 地址,在被访问时为其添加 queryString 参数呢?

videojs 团队提供了一个库 http-streaming,可以用来监听每一个 xhr 请求,本质上 获取密钥 key 的 URI 地址发起也属于 xhr。

http-streaming 为每个 xhr 提供了 beforeRequest 函数,它接收一个参数 option,我们可以通过改写 option.uri 来实现 queryString 传参。

代码实现参考如下:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <link href="https://cdn.jsdelivr.net/npm/video.js@7.2.4/dist/video-js.css" rel="stylesheet">
  <script src="https://cdn.jsdelivr.net/npm/video.js@7.4.1/dist/video.js"></script>
  <script src="https://unpkg.com/@videojs/http-streaming@2.14.2/dist/videojs-http-streaming.min.js"></script>
</head>
<body>
  <video id="my-video" width="320" height="240" controls>
    <source src="https://xxx/index.m3u8"  type="application/x-mpegURL">
    您的浏览器不支持 HTML5 video 标签。
  </video>

  <script>
    const player = videojs('my-video');
    setTimeout(() => {
      player.tech().vhs.xhr.beforeRequest = function(options) {
        console.log('options: ', options);
        // 这里可以加判断,只为获取密钥的 `URI` xhr 改写 options.uri
        options.uri = `${options.uri}?token=xxx`;
        return options;
      };
    }, 0);
  </script>
</body>
</html>

上面方式是监听目标 video 播放下的 xhr 请求,如果想监听全局 video xhr 请求时(当涉及多个 video 时),可以采用:

videojs.Vhs.xhr.beforeRequest = function(options) {
  options.uri = `${options.uri}?token=xxx`;
  return options;
};

参考

视频加密
videojs 团队(Github)
video.js(Github)
http-streaming(Github)