为什么 HLS 直播会越播越慢,以及“回到直播”按钮应该跳到哪里
分别测量播放列表边缘、墙上时钟延迟、播放器安全距离、过期清单和低延迟 HLS 行为,准确诊断 HLS 直播延迟。
两位观众观看同一场 HLS 直播时,可能处在不同位置,但两台播放器都显示“直播中”。进度条即使已经到达最右端,也可能落后信号源数秒。如果“回到直播”按钮跳到最后一个已公布的瞬间,而不是播放器支持的直播同步点,它甚至会让播放更不稳定。
第一步是不要再把延迟当成一个数字。需要分别看信号源时间、播放列表可用性、播放位置与播放列表边缘的距离、播放器选取的安全延迟,以及最终显示时间。请只测试自己拥有或获准检查的流,并从共享记录中删除签名 URL、Cookie、观众 ID、IP 地址和私有主机名。
*验证方法 — 2026 年 9 月 23 日:*我们使用 Node.js 24.12.0 对一个构造的媒体播放列表进行了确定性计算。该播放列表包含五个 6 秒分段,EXT-X-PROGRAM-DATE-TIME 从 UTC 16:00:00 开始。已公布窗口在 16:00:30 结束,观察时间为 16:00:32。媒体时间为 6 的播放位置落后播放列表边缘 24 秒,落后测试清单墙上时钟 26 秒。采用特意设定的 12 秒安全延迟后,受控“回到直播”目标为媒体时间 18:落后播放列表边缘 12 秒,落后测试清单时钟 14 秒。这只验证脚本的播放列表计算。它没有测量编码器、网络、CDN、浏览器媒体管线、设备时钟、真实播放或端到端服务目标。脚本位于 scripts/test-hls-live-latency-budget.cjs。
比较之前,先给三个位置准确命名
“直播边缘”常被用来指代不同位置。应在日志和监控面板中为每项观察使用不同名称。
| 位置或测量值 | 实用定义 | 证据来源 |
|---|---|---|
| 播放列表边缘 | 当前向该客户端公布的最新媒体结束位置 | 媒体播放列表以及分段或部分分段时长 |
| 可拖动终点 | 媒体元素当前报告的最晚可拖动时间轴位置 | video.seekable.end(...) |
| 直播同步点 | 播放器在边缘之后考虑安全策略选出的目标 | 播放器 API 或明确的应用配置 |
| 播放位置距离 | 播放列表或可拖动边缘减去当前播放位置 | 同一时间轴上的 currentTime 和对应边缘 |
| 墙上时钟延迟 | 观察时间减去播放位置对应的估算节目时间 | 可信时钟和一致的节目日期映射 |
| 采集到显示延迟 | 从信号源采集到观众屏幕呈现 | 同步的信号源与显示测量 |
这些数值不一致并不矛盾。播放器可能只落后其缓存播放列表两秒,但该播放列表本身已经远远落后源站。新鲜播放列表可以很接近信号源,而观众可能在 DVR 窗口内暂停了十分钟。节目日期标签能够标识内容时间,却不能单独证明摄像机何时采集了某一帧。
先检查播放器时间轴
在点击“回到直播”前后,记录全部 seekable 范围、当前播放时间、缓冲范围、时长、播放速率和就绪状态。
function mediaSnapshot(video) {
const copyRanges = ranges => Array.from(
{ length: ranges.length },
(_, index) => ({ start: ranges.start(index), end: ranges.end(index) })
);
return {
currentTime: video.currentTime,
duration: video.duration,
playbackRate: video.playbackRate,
seekable: copyRanges(video.seekable),
buffered: copyRanges(video.buffered),
readyState: video.readyState,
};
}
不要用 duration 代替当前直播边缘。MDN 指出,直播媒体可能失去对过期内容的访问,而设置 currentTime 后,实际位置仍可能被调整到媒体支持的位置。如果存在多个可拖动范围,应全部记录,不要假定最后一个范围能够代表所有轨道和呈现版本。
如果请求位置已经离开直播窗口,请使用HLS 拖动和 DVR 诊断指南。只有在当前时间轴已经明确后,才开始诊断延迟。
为什么普通 HLS 会落后最后一个已公布分段
直播客户端需要保留足够媒体,才能应对正常的播放列表重新加载和下载波动。RFC 8216 建议:对于尚未结束的播放列表,开始普通播放时不要选择起点距离末尾不足三个目标时长的分段,因为起得太靠后可能导致卡顿。这是协议时代的安全建议,并不是每台播放器都会精确保持三个分段延迟的承诺。
分段生成、播放列表发布、从源站传播到 CDN、播放列表重载时机、对象下载、缓冲策略、解码和渲染都会造成延迟。其中一些可以重叠,另一些则会累积。仅仅改变播放器起点,无法消除媒体出现在播放列表之前已经花掉的时间。
| 层级 | 用来隔离它的证据 | 常见错误结论 |
|---|---|---|
| 采集和编码 | 画面内时间码或独立观察到的信号源时间 | EXTINF 能说明采集延迟 |
| 打包和发布 | 分段/部分分段完成时间及播放列表可用时间 | CDN 响应时间等于打包时间 |
| CDN 和缓存 | Age、缓存状态、最终 URL、重复的播放列表正文 | HTTP 200 代表拿到了最新播放列表 |
| 播放器加载 | 播放列表/分段请求时序和所选起点 | 进度条右端就是信号源边缘 |
| 缓冲和恢复 | 缓冲范围、卡顿、播放速率变化 | 每一秒额外延迟都是有意的安全余量 |
| 解码和渲染 | 媒体事件加设备级测量 | 仅凭 currentTime 就能得到采集到显示延迟 |
先测量第一个尚未被证实的层级。如果连续多次请求的播放列表正文,在预期更新周期之外仍完全相同,应先诊断陈旧交付,再调整播放器。我们的陈旧播放列表与缺失分段指南给出了判断路径。
计算两种延迟,而不是一种
无需墙上时钟元数据,就能算出播放位置与播放列表边缘的距离:
播放位置距离 = 播放列表时间轴终点 - 当前播放位置
这有助于控制播放器,但无法说明播放列表边缘本身有多旧。如果有一致的 EXT-X-PROGRAM-DATE-TIME 映射和可信时钟,再估算第二部分:
播放列表边缘年龄 = 观察时间 - 播放列表边缘对应的节目时间
估算墙上时钟延迟 = 播放列表边缘年龄 + 播放位置距离
我们的测试清单把 2 秒的播放列表边缘年龄与 24 秒的播放位置距离分开,受控时间轴上的总和为 26 秒。这些数字是测试输入和输出,不是对 M3U8Online 或生产广播服务的测量。
RFC 8216 将 EXT-X-PROGRAM-DATE-TIME 定义为分段第一个采样与绝对日期时间之间的映射,同时提醒播放列表日期可以表示内容生成时间,也可以表示与播放无关的其他时间。发布延迟指标前,请验证时间戳的实际含义、时区处理、时钟同步,以及变体和不连续点之间的映射一致性。
“回到直播”应跳到同步点,而不是数学终点
可用时,可靠的“回到直播”操作应使用播放器文档明确说明的直播同步点。在 hls.js 中,liveSyncPosition 是直播边缘减去所配置的安全延迟;maxLatency 则描述播放器可以向前跳到该同步点的延迟阈值。因此,播放器 API 比从其他服务照搬的固定减法更可靠。
如果播放路径没有公开直播同步目标,请在当前可拖动范围内定义符合产品要求的目标,并在受支持设备上验证。不要简单执行 currentTime = seekable.end(last),然后假定最后一个已公布瞬间已经可以下载和解码。
按钮流程应当:
- 获取当前可拖动范围和播放器延迟值。
- 选择文档明确的播放器同步点,或经过验证、位于最新可拖动终点之前的目标。
- 将目标限制在当前可拖动范围内。
- 只请求一次拖动,不要与播放器自带的延迟控制器对抗。
- 记录
seeking、seeked、结果currentTime以及操作后的首批请求。 - 确认结果后再更新按钮状态。
在确定性测试清单中,所选目标仍落后播放列表边缘 12 秒。跳到该位置后,模型墙上时钟延迟从 26 秒降到 14 秒,并没有变为零。这正是保留 12 秒安全延迟和 2 秒边缘年龄的预期结果。
明确“直播中”标记代表什么
“直播中”是需要书面规定容差的产品决策。它可以表示观众接近播放器目标、接近播放列表边缘,或者接近可信节目时钟。这三种承诺并不相同。
| 界面状态 | 示例判断条件 | 用户操作 |
|---|---|---|
| 直播中 | 播放位置在直播同步点的验证容差内 | 无需操作 |
| 落后直播 | 当前位置有效,但超出该容差 | 提供“回到直播” |
| 已暂停 | 播放已经暂停,即使刚才仍在直播位置 | 分别提供继续播放和回到直播 |
| 正在重新加入 | 播放器正在加载校正后的目标 | 显示进度,暂不宣称直播中 |
| 无可靠时钟 | 已知边缘距离,但不知道墙上时钟延迟 | 不要显示凭空编出的精确延迟 |
请提供可访问的文字标签,不要只用红点。如果观众主动在 DVR 窗口内回看,不要自动把他们拉回前方,除非产品明确承诺低延迟观看并清楚说明这种行为。
诊断播放过程中持续增长的延迟
如果播放器开始时接近目标,随后却越来越落后,请把偏离开始的时间与以下观察进行比对:
- 播放列表重载迟到、内容不变,或来自意外陈旧的缓存。
- 分段下载时间接近或超过媒体时长。
- 缓冲恢复时从较旧位置继续,而不是直播同步点。
- 追赶策略改变播放速率后,速率一直低于 1。
- 移动标签页进入后台或应用暂停后,常规加载停止。
- 广告时段、时间戳不连续、轨道切换或变体切换改变了时间轴。
- 应用状态反复把旧的
currentTime写回播放器。 - 配置的最大延迟校正从未触发,或与自定义代码冲突。
把播放列表、网络、播放器和媒体元素事件记录在同一条单调时间线上。如果流反复卡顿,请先修复交付问题,再缩小缓冲;HLS 卡顿诊断会分别检查吞吐量、分段可用性、解码和播放器状态。
低延迟 HLS 是一种端到端模式
不能通过给播放列表改名或添加一个查询参数来启用低延迟 HLS。Apple 文档把部分分段、服务器控制、阻塞式播放列表重载、预加载提示、呈现版本报告,以及特定服务器/CDN 行为作为协同工作的组成部分。当前编写指南还对 Part Target Duration 和 PART-HOLD-BACK 提出约束。
应把编码器或打包器、源站、CDN 缓存行为、播放列表响应、播放器支持和回退路径放在一起验证。不支持低延迟特性的客户端可能采用不同的请求模式;即使清单包含低延迟标签,错误缓冲或缓存播放列表请求的 CDN 也会抵消预期收益。
不要仅根据 PART-TARGET 宣传延迟数字。使用同步时钟和受支持的生产路径,测量真实的信号源到显示行为。未测试的浏览器和设备必须标为未测试。
执行受控测试矩阵
对每条受支持播放路径记录:
- 原生 HLS 与 JavaScript/MSE(适用时两者都测)。
- 标准直播与真实的低延迟 HLS 配置。
- 冷启动、正常稳定播放、回到直播、暂停/继续、DVR 回看,以及卡顿后恢复。
- 新鲜源站响应、CDN 命中和有意构造的陈旧播放列表测试。
- 低码率和高码率变体,以及备用音频和字幕。
- 实体移动设备上的前台播放和后台恢复。
- 在正式支持的桌面、手机、平板和电视产品上播放同一条获准测试的流。
缩小桌面窗口并不等于测试移动端后台策略或解码器。应如实记录缺少的硬件。签名媒体 URL 和观众标识不得出现在截图与错误报告中。
用证据报告延迟预算
可执行的报告可以写成:“UTC 16:00:32 时,最新公布的节目时间为 16:00:30。播放位置映射到 16:00:06,因此播放列表边缘年龄为 2 秒,播放位置距离为 24 秒。点击‘回到直播’后,请求了媒体时间 18 的播放器同步点,并在该位置完成。”这样,打包、CDN 和播放器负责人就能看清每部分延迟属于哪个层级。
报告中应包含时钟来源与同步方式、节目日期语义、播放列表请求和缓存数据、播放列表边缘、可拖动范围、当前位置、播放器直播同步和最大延迟设置、校正后的首批请求、卡顿与播放速率变化,以及设备/播放器版本。如果时间戳映射或时钟未经验证,就不要给出墙上时钟延迟。
主要参考资料
- RFC 8216:HTTP Live Streaming
- Apple:启用低延迟 HLS
- Apple:Apple 设备 HLS 编写规范
- Apple:HTTP Live Streaming 概览
- hls.js API:latency、liveSyncPosition、targetLatency 和 maxLatency
- MDN:HTMLMediaElement currentTime
请分别测量播放列表边缘、边缘年龄和播放位置距离,再让“回到直播”跳到经过测试的同步点,而不是最后一个已公布瞬间。这样既能保留安全策略,又能暴露陈旧交付,并防止界面承诺一个从未真正测量的延迟数字。