Optimize live streams with HLS Playlist Delta Updates
Audio & Video 高级 20m

使用 HLS Playlist Delta Updates 优化直播流

Optimize live streams with HLS Playlist Delta Updates

2020年6月25日

在 Apple 官方观看视频

一句话判断

如果你在运营大规模直播服务,Playlist Delta Updates 可以将你的 CDN 带宽成本显著降低——每次 playlist 更新只传增量,不传全量。

这场 Session 讲了什么

这场 Session 聚焦于 Low-Latency HLS 的一个关键优化机制:Playlist Delta Updates。在传统 HLS 中,每次客户端请求 playlist 时,服务器都会返回完整的 playlist 文件。对于直播流来说,playlist 每隔几秒就更新一次,大量客户端同时请求完整的 playlist 会给 CDN 带来不小的带宽压力——尤其是 Low-Latency HLS 的 playlist 更新频率更高(每秒级别)。

Delta Updates 的思路很简单:客户端第一次请求时获取完整 playlist,后续请求只需要告诉服务器”我上次看到的 playlist 是什么状态”,服务器就只返回变化的部分。这通过 #EXT-X-DATERANGE_HLS_skip 参数实现——客户端在请求 playlist 时带上 _HLS_skip=YES 参数,服务器会跳过已删除的 segment 条目,只保留新增的 partial segment。

演讲者通过多个 playlist 示例展示了 Delta Updates 的工作流程,并详细讲解了服务端如何生成 Delta Playlist、客户端如何合并 Delta 和本地缓存的 playlist。Session 还讨论了 CDN 缓存策略——Delta Playlist 需要和完整 Playlist 分别缓存。

值得深挖的点

  • Delta Updates 的 CDN 缓存策略:这是最容易出错的地方。完整 playlist 和 Delta playlist 是同一个 URL 的不同版本(通过查询参数区分),CDN 必须根据 _HLS_skip 参数分别缓存。如果 CDN 把 Delta 响应当作完整 playlist 缓存了,所有后续请求都会拿到错误数据。
  • Playlist 滚动窗口的维护:直播 playlist 是一个滑动窗口,旧的 segment 会被移除。在 Delta 模式下,服务器不能直接移除旧条目——需要用 #EXT-X-DATERANGEEND-DATE 来标记过期。这要求服务端维护更复杂的状态。

代码片段

# 客户端请求 Delta Playlist
# 第一次请求获取完整 playlist:
# GET /live/playlist.m3u8

# 后续请求带 _HLS_skip 参数:
# GET /live/playlist.m3u8?_HLS_skip=YES

# 服务端返回的 Delta Playlist(只包含增量):
# #EXTM3U
# #EXT-X-TARGETDURATION:6
# #EXT-X-SERVER-CONTROL:CAN-SKIP-UNTIL=18.0
# #EXT-X-PART-INF:PART-TARGET=1.0
# #EXT-X-SKIP:SKIPPED-SEGMENTS=3
# #EXT-X-PART:DURATION=1.0,URI="part3.mp4"
# #EXT-X-PART:DURATION=1.0,URI="part4.mp4"
# 注意:前 3 个完整 segment 被 SKIPPED-SEGMENTS 替代
# 服务端生成 Delta Playlist 的逻辑
generate_delta_playlist() {
    # 读取完整 playlist
    full_playlist=$(cat /hls/live/playlist.m3u8)

    # 计算可跳过的 segment 数量
    skip_until=$((current_time - 18))  # CAN-SKIP-UNTIL = 18 秒

    # 只保留最近 18 秒内的完整 segment 和所有 partial segment
    delta_playlist=$(filter_old_segments "$full_playlist" "$skip_until")

    # 添加 SKIPPED-SEGMENTS 计数
    echo "#EXT-X-SKIP:SKIPPED-SEGMENTS=$skipped_count"
    echo "$delta_playlist"
}
// AVPlayer 自动支持 Delta Updates
// 只需在 playlist 中包含正确的服务器控制标签:
// #EXT-X-SERVER-CONTROL:CAN-SKIP-UNTIL=18.0

// AVPlayer 会自动在后续请求中添加 _HLS_skip=YES
// 开发者无需手动处理 Delta 合并逻辑

let asset = AVURLAsset(url: liveStreamURL)
let playerItem = AVPlayerItem(asset: asset)
let player = AVPlayer(playerItem: playerItem)
player.play()

最佳实践

  • CAN-SKIP-UNTIL 建议设为 target duration 的 3 倍(如 6 秒 target duration 则设 18 秒),在带宽节省和客户端恢复能力之间取得平衡
  • CDN 必须根据请求中的 _HLS_skip 参数区分缓存完整 playlist 和 Delta playlist
  • 监控 Delta Playlist 的平均大小,对比完整 Playlist,量化带宽节省效果
  • 客户端长时间暂停后恢复播放时,应请求完整 playlist 而非 Delta playlist,避免合并失败
  • 确保服务端的 Delta 生成逻辑是确定性的——同一时刻的相同请求应返回相同的 Delta

还有什么值得关注

  • Delta Updates 与 Blocking Playlist Reload 配合使用效果最佳,可以同时降低延迟和带宽
  • Apple 推荐使用 mediastreamvalidator 的 --validate-delta-playlists 选项来验证 Delta 实现
  • 这场 Session 是 Low-Latency HLS 系列的第三篇,搭配前两篇一起看效果最好
WWDC 2020