m3u8 在线视频播放

2020/01/10 视频处理

自己有个需求,有时候希望能直接下载网站上的视频。以前另存一下就行,但后来发现很多视频网站都不能用这种方法下载了。仔细观察了一下,发现它们都有一个共同点,就是将视频分成大量切片,这种片段的格式也不是常见的, 其中在一种的 URL 都以.ts结尾,并且有个m3u8的链接跟这些.ts的 URL 有一定关系。于是就这样我就又开始愉快地玩了起来。

生成 ts 及 m3u8 文件

经过查阅一些资料,发现这些.ts文件是可以通过 mp4 等视频格式生成的。以下是ffmpeg生成.ts文件以及相关m3u8播放列表的方法,没错,m3u8本质上就是一个播放列表。ffmpeg的安装步骤就不多说了。

$ ffmpeg -i file.ext -c:v libx264 -strict -2 ./test.mp4 # 如果视频为 mp4,可省略该步骤。如果 file.ext 视频不是 mp4,则转为 mp4 格式
$ ffmpeg -y -i test.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb test.ts # 将 mp4 转为 ts 文件(转格式)
$ mkdir segments
$ ffmpeg -i test.ts -c copy -map 0 -f segment -segment_list segments/index.m3u8 -segment_time 10 segments/nxb-%04d.ts # 将 test.ts 文件进行切片,将这些切片文件存放到 segments 目录中,并以 nxb-%04d.ts 的格式命名,再将所有文件名存放到 segments 目录下的 index.m3u8 文件中。-segment_time 10 表示每个切片的时长为 10 秒

在用户上传完视频后,后台执行以上命令行,就生成了视频文件的多个切片nxb-%04d.ts以及这些切片的播放列表index.m3u8。在前端播放这个视频时,只需要请求index.m3u8即可。然后再根据index.m3u8的列表获取各个切片。以切片的方式获取视频,降低了网络延迟感,不需要等待完整的视频文件传输完毕后才能播放;转而根据视频的播放进度获取相应的片段,大大地提升了用户体验。

index.m3u8内容格式如下。

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-ALLOW-CACHE:YES
#EXT-X-TARGETDURATION:16
#EXTINF:15.875000,
nxb-0000.ts
#EXTINF:6.125000,
nxb-0001.ts
#EXTINF:10.291667,
nxb-0002.ts
#EXTINF:12.541667,
nxb-0003.ts
#EXTINF:7.000000,
nxb-0004.ts
#EXTINF:12.291667,
nxb-0005.ts
#EXTINF:5.708333,
nxb-0006.ts
#EXT-X-ENDLIST

前端获取 m3u8 视频

下面是前端使用hls.js请求m3u8视频资源的示例。

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/hls.js@0.13.1/dist/hls.min.js"></script>
</head>
<body>
<video id="v" controls></video>
<script>
    var video = document.getElementById('v');
    if (Hls.isSupported()) {
        var hls = new Hls();
        hls.loadSource('http://localhost/segments/index.m3u8');
        hls.attachMedia(video);
        hls.on(Hls.Events.MANIFEST_PARSED, function () {
           // video.play();
        });
    }

    // hls.js is not supported on platforms that do not have Media Source Extensions (MSE) enabled.
    // When the browser has built-in HLS support (check using `canPlayType`), we can provide an HLS manifest (i.e. .m3u8 URL) directly to the video element through the `src` property.
    // This is using the built-in support of the plain video element, without using hls.js.
    // Note: it would be more normal to wait on the 'canplay' event below however on Safari (where you are most likely to find built-in HLS support) the video.src URL must be on the user-driven
    // white-list before a 'canplay' event will be emitted; the last video event that can be reliably listened-for when the URL is not on the white-list is 'loadedmetadata'.
    else if (video.canPlayType('application/vnd.apple.mpegurl')) {
        video.src = 'http://localhost/segments/index.m3u8';
        video.addEventListener('loadedmetadata', function () {
           // video.play();
        });
    }
</script>
</body>
</html>

至于hls.js还没时间深入研究,但其基本原理也不难理解。但目前还是有以下问题还没想通:

  1. 在拖动视频进条时,不知道是不是本地的原因,不需要再次请求 ts,缓存?在视频网站貌似会重复请求的;
  2. video 的 src 的 blob 地址在播放时是固定的,不清楚是用哪个对象生成的 blob;

下载 m3u8 视频

下载 m3u8 视频同样用到 ffmpeg,大概的原理就是根据 m3u8 列表下载,最后合并多个 ts 文件。命令如下:

$ ffmpeg -i http://example.com/index.m3u8 "file.mp4"

以上命令可以将 index.m3u8 文件中的视频列表合并到 file.mp4 文件中。至于具体的解码、编码细节,就不是我熟悉的领域了。

Search

    Table of Contents