Audio & Video 高级 20m
使用 HLS Blocking Playlist Reload 降低延迟
Reduce latency with HLS Blocking Playlist Reload
2020年6月26日
一句话判断
传统 HLS 的”轮询等待新数据”模型是延迟的主要来源——Blocking Playlist Reload 把轮询变成了长连接推送,这是低延迟直播的关键一环。
这场 Session 讲了什么
这场 Session 深入讲解了 Low-Latency HLS 的 Blocking Playlist Reload 机制。在标准 HLS 中,播放器通过定期重新请求 playlist 来发现新内容。播放器加载 playlist 后,需要等待 target duration 的一半时间才能再次请求。这种轮询模式在直播场景下造成了两方面的浪费:一是延迟(等了太长时间才拿到新数据),二是带宽(请求了太多次拿到的是旧数据)。
Blocking Playlist Reload 的解决方案是:当播放器重新请求 playlist 时,如果服务器判断 playlist 还没有更新,它不会立即返回旧数据,而是保持 HTTP 连接打开,等到 playlist 真正更新后再返回。这样播放器不需要猜测轮询间隔,新数据产生后第一时间就能拿到。
演讲者详细讲解了 #EXT-X-SERVER-CONTROL 标签中的 CAN-BLOCK-RELOAD=YES 声明,以及客户端如何通过 _HLS_msn 和 _HLS_part 查询参数指定自己期望的 Media Sequence Number。服务端收到请求后,如果指定的 MSN 还没准备好,就 blocking 等待。Session 还讨论了超时处理、错误恢复和多码率场景下的 playlist 同步问题。
值得深挖的点
- MSN 和 Part 编号的精确控制:客户端不是盲目请求 playlist,而是明确告诉服务器”我要的是 Media Sequence Number X 的 Part Y”。这种确定性请求让服务端可以精确判断是否需要 blocking。如果客户端请求的 MSN 已经存在,服务端立即返回;如果还没生成,服务端 blocking 等待。这个设计比简单的”有更新就告诉我”要精确得多。
- 多码率同步的挑战:在 ABR(自适应码率)场景下,不同码率的 variant playlist 是独立的。如果客户端在一个 variant 上 blocking 等待,另一个 variant 可能已经更新了。Apple 建议客户端在每个活跃的 variant 上都维护独立的 blocking 连接。
代码片段
# Blocking Playlist Reload 的请求方式
# 播放器在 playlist URL 后添加 MSN 和 Part 参数:
# GET /live/playlist.m3u8?_HLS_msn=42&_HLS_part=3
# 服务端行为:
# - 如果 MSN 42 Part 3 已存在:立即返回 playlist
# - 如果尚未生成:保持连接,等待生成后返回
# - 如果超过超时时间:返回当前最新 playlist
# 服务端在 playlist 中声明支持 blocking:
# #EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,PART-HOLD-BACK=3.0
# 服务端实现 blocking 的简化逻辑
handle_playlist_request(msn, part) {
current_msn = get_current_msn()
if msn <= current_msn:
# 请求的 MSN 已存在,立即返回
return full_playlist()
else:
# 等待新的 MSN 生成
timeout = get_part_hold_back() # 通常 3 秒
wait_for_msn(msn, timeout)
if msn_ready(msn):
return full_playlist()
else:
# 超时,返回当前最新状态
return current_playlist()
}
// AVPlayer 原生支持 Blocking Playlist Reload
// 只要 playlist 包含 CAN-BLOCK-RELOAD=YES 声明
// AVPlayer 会自动使用 blocking 模式请求 playlist
// 开发者可以通过 AVPlayerItemAccessLog 监控效果
let playerItem = AVPlayerItem(url: liveStreamURL)
playerItem.addObserver(
self, forKeyPath: "accessLog",
options: .new, context: nil
)
// 在 KVO 回调中查看请求详情
if let accessLog = playerItem.accessLog() {
for event in accessLog.events {
print("请求间隔: \(event.numberOfSegmentsDownloaded)")
print("指示比特率: \(event.indicatedBitrate)")
}
}
最佳实践
- 服务端 blocking 超时建议设为
PART-HOLD-BACK值,通常 3 秒,避免连接占用时间过长 - CDN 必须支持 HTTP/2 的长连接,否则 blocking 请求会被中间代理截断
- 监控服务端的并发 blocking 连接数,这是容量规划的核心指标
- 客户端在网络切换(Wi-Fi 到蜂窝)后应重置 blocking 状态,重新请求完整 playlist
- 如果 blocking 请求超时,客户端应回退到传统轮询模式作为降级策略
还有什么值得关注
- Blocking Playlist Reload 和 Delta Updates 可以同时使用,既降低延迟又节省带宽
- 这场 Session 是 Low-Latency HLS 系列的深度技术讲解之一,建议按顺序观看整个系列
- Apple 在 GitHub 上提供了参考的服务端实现,可以帮助理解 blocking 逻辑
WWDC 2020