背景:为什么 HLS 延迟这么高?

标准 HLS 的工作流程是:编码器输出 → 切成 6 秒一个 ts → 写入 m3u8 → 播放器拉取播放。播放器需要至少 3 个切片才能稳定起播,加上预加载和网络抖动缓冲,端到端延迟通常在 15~30 秒

导致高延迟的几个因素:

LL-HLS 是怎么做到低延迟的

Apple 在 HLS v7 规范中引入了 LL-HLS(Low-Latency HLS),核心思路有三:

  1. Partial Segments(部分切片):每个 ts 切片还可以再切成更小的 part(200ms~500ms),播放器边出边拉。
  2. Blocking Reload:客户端发起 playlist 请求时带 _HLS_msn / _HLS_part,服务端 hold 住连接直到新分片产生。
  3. Playlist Delta Updates:用 #EXT-X-SKIP 增量更新,减少 m3u8 体积。
效果:开启 LL-HLS 后,端到端延迟可压到 2 秒以内,接近 HTTP-FLV 体验,同时保留 HLS 的 CDN 友好特性。

SRS 服务端配置

SRS 5.0+ 已原生支持 LL-HLS,配置 conf/srs.conf

http_server {
    enabled on;
    listen 8080;
    dir /usr/local/srs/htdocs;
}

vhost __defaultVhost__ {
    hls {
        enabled on;
        hls_path /usr/local/srs/htdocs;
        hls_fragment 2s;            # 切片长度
        hls_window 6s;              # playlist 长度
        hls_ts_file live/test-%d.ts;
        hls_m3u8_file live/test.m3u8;

        # LL-HLS 关键参数
        hls_ctx 6s;
        hls_aof 2s;
        # 启用部分切片
        hls_partial on;
    }
}

推流测试:

ffmpeg -re -i input.mp4 -c:v libx264 -preset veryfast -tune zerolatency \
  -bf 0 -g 30 -keyint_min 30 -sc_threshold 0 \
  -c:a aac -b:a 128k \
  -f flv rtmp://127.0.0.1:1935/live/test
注意:必须关闭 B 帧(-bf 0),并强制 GOP 等于帧率,否则 LL-HLS 部分切片无法生成。

hls.js 客户端联调

hls.js 1.0+ 已支持 LL-HLS,开启低延迟模式:

<video id="v" controls autoplay></video>
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
<script>
  const v = document.getElementById('v');
  if (Hls.isSupported()) {
    const hls = new Hls({
      lowLatencyMode: true,           // 关键:开启 LL-HLS
      backBufferLength: 30,
      maxBufferLength: 6,
      maxMaxBufferLength: 6,
      liveSyncDuration: 1.5,
      liveMaxLatencyDuration: 5,
      enableWorker: true
    });
    hls.loadSource('http://example.com/live/test.m3u8');
    hls.attachMedia(v);
    hls.on(Hls.Events.MANIFEST_PARSED, () => v.play());
  } else if (v.canPlayType('application/vnd.apple.mpegurl')) {
    v.src = 'http://example.com/live/test.m3u8';
    v.play();
  }
</script>

打开浏览器 Network 面板,你会看到大量 test-1.ts?_HLS_part=0 这样的小请求,说明部分切片已生效。

总结

下次我们聊聊怎么用 WebRTC + SRS 进一步把延迟打到 500ms 以内,敬请关注。