返回博客

HLS 流媒体是怎么工作的

用实践视角讲清 HLS、M3U8 播放列表、视频分片、Safari 原生播放、HLS.js,以及浏览器里测试流媒体时该看什么。

先把 HLS 想成一串普通 HTTP 请求

HLS 是 HTTP Live Streaming 的缩写,由 Apple 在 2009 年推出。它一开始就是为 iPhone 和 Safari 上的视频播放准备的,所以它没有走复杂的专用流媒体连接,而是把视频拆成播放列表和很多小文件,让浏览器通过普通 HTTP 一个个请求。

这个设计很朴素,也很耐用。CDN 可以缓存分片,播放器可以根据网络情况换清晰度,直播只要不断把新的分片写进播放列表,播放器就能继续追着播。

你在 Anoiona Tools 的 M3U8 播放器里粘贴一个地址时,浏览器先拿到的通常不是视频本体,而是一份 .m3u8 文本。很多播放失败的问题,其实就卡在这份文本、它引用的分片,或者浏览器是否允许请求这些资源。

M3U8 不是视频,它更像路线图

M3U8 是 UTF-8 编码的播放列表。文件通常从 #EXTM3U 开头,后面用 #EXT-X- 这一类标签描述流信息。

常见的第一种是 master playlist。它列出多个版本,比如 360p、720p、1080p,每个版本有自己的码率和子播放列表:

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-STREAM-INF:BANDWIDTH=800000,RESOLUTION=640x360
360p/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=2500000,RESOLUTION=1280x720
720p/index.m3u8

另一种是 media playlist。它直接列出视频分片,并告诉播放器每个分片大概多长:

#EXTM3U
#EXT-X-TARGETDURATION:6
#EXT-X-MEDIA-SEQUENCE:2048
#EXTINF:6.000,
segment-2048.ts
#EXTINF:6.000,
segment-2049.ts

点播视频通常会在末尾带上 #EXT-X-ENDLIST。直播不会这么早结束,播放器会隔几秒重新拉一次 media playlist,看有没有新的 segment 出现。如果 #EXT-X-MEDIA-SEQUENCE 一直不变,直播源可能已经停更了。

分片才是真正的视频数据

播放列表很小,真正占流量的是分片。老一些的 HLS 常见 .ts 分片,也就是 MPEG-TS。新的流可能使用 fMP4,先加载一个初始化段,再加载一段段 .m4s

分片时长会影响体验。6 秒是很常见的折中:请求数量不算太夸张,直播延迟也不至于特别大。分片太短,请求会变多,CDN 和播放器都更忙;分片太长,播放可能稳定,但切换清晰度和直播追赶会慢一些。

排查时要记住一点:播放列表能打开,不代表分片一定能打开。.ts.m4s 可能被 CORS 拦住,可能需要 Cookie,可能 URL 已经过期,也可能编码格式浏览器不支持。很多“播放器坏了”的问题,最后查出来是某个分片返回了 403

Safari 原生播放,其他浏览器通常靠 HLS.js

Safari 和 iOS 浏览器对 HLS 很友好,通常可以直接把 M3U8 地址交给 <video>

const video = document.querySelector("video");
video.src = "https://example.com/live/master.m3u8";
await video.play();

这种方式简单,性能也好,因为 HLS 解析、缓冲、解码都交给浏览器内部处理。缺点是前端能拿到的细节少,清晰度控制和错误诊断没有那么自由。

桌面版 Chrome、Edge、Firefox 一般不会直接播放 M3U8。我们的播放器会在这些环境里使用 HLS.js:它负责读取 playlist 和分片,再通过 Media Source Extensions 把媒体喂给 video 元素。

import Hls from "hls.js";

const hls = new Hls();
hls.loadSource("https://example.com/live/master.m3u8");
hls.attachMedia(video);

Anoiona Tools 的策略就是先试原生 HLS。浏览器支持时直接播放;不支持时,再走 HLS.js。这样 Safari 用户不需要多一层 JavaScript 管线,其他浏览器也能测试大多数授权流。

我们的播放器会额外处理哪些场景

正常情况很直接:校验 URL,加载 M3U8,解析清晰度层级,开始播放。如果 master playlist 里有多个版本,我们显示清晰度选项和 Auto;如果只有一个 media playlist,就不显示选择器,避免界面里出现没有意义的控件。

播放历史保存在 localStorage,不会上传到服务器。分享链接和 iframe 代码也是在浏览器里生成,用来复现测试场景,而不是替你托管视频。

还有一个比较实际的回退:TS 分片修复。有些 MPEG-TS 文件开头会混入多余字节,导致播放器找不到正确的 0x47 同步字节节奏。标准播放遇到致命的媒体或解析错误时,我们会重试一次,扫描分片前部,找到合理的 TS 同步位置后裁掉前缀。这个功能不是万能修复器,但对一些源站生成不太规范的测试流很有用。

测 HLS 时,我会先看这些地方

先看 M3U8 本身:

curl -I "https://example.com/live/master.m3u8"
curl "https://example.com/live/master.m3u8" | head

响应最好是 200,内容应该以 #EXTM3U 开头。相对路径要按播放列表所在目录解析,不是按你当前网页的目录解析。

接着挑一个分片直接请求:

curl -I "https://example.com/live/720p/segment-2049.ts"

如果这里是 403404、跳登录页,或者 CORS 不允许浏览器读取,播放器就算逻辑正确也播不起来。浏览器 DevTools 的 Network 面板很适合这一步,过滤 m3u8tsm4s,很快能看到到底哪一个请求出问题。

直播还要看播放列表是否继续前进。几次刷新后,#EXT-X-MEDIA-SEQUENCE 应该变大,新分片应该不断出现。它不动,播放器只能等。

HLS 的好处是简单,坏处也是简单:每个文本文件、每个分片、每个响应头都在链路里。找到断掉的那一环,问题通常就清楚了。