流媒体协议:HLS、DASH、MSE 与低延迟直播的完整技术图谱

你的视频为什么不能直接用一个 MP4 链接播放了事?因为真实世界的视频业务需要面对网络波动、多端适配、码率切换、直播低延迟等复杂挑战。这篇文章把流媒体协议的完整技术栈从底层到应用层彻底讲透,包括 AI 视频边生成边播放的核心实现。


为什么需要流媒体协议?

传统的渐进式下载(Progressive Download)方式——把 MP4 文件的 URL 直接给 <video>src——在简单场景够用,但有三个根本性的局限:

第一,无法自适应网络。网络带宽是动态变化的,固定码率的视频要么在弱网下频繁卡顿,要么在强网下浪费画质。

第二,跳转效率低。用户跳到视频中间时,浏览器必须从服务器下载该位置的数据,而服务器必须支持 HTTP Range 请求,且文件的 moov 必须前置,否则根本无法跳转。

第三,无法直播。直播内容是实时生成的,没有完整文件可以提前下载,必须有一套协议来持续传输新生成的内容。

流媒体协议就是为解决这三个问题而生的。


第一部分:HLS — 苹果的流媒体标准

HLS 的工作原理

HLS(HTTP Live Streaming)是 Apple 在 2009 年推出的流媒体协议,核心思想极其简单:把视频切成一段一段的小文件,用一个文本索引文件(m3u8)描述这些片段的地址和顺序,播放器按顺序下载并拼接播放

整个系统由三个层次构成:

Master Playlist(主播放列表)
    ├── 1080p/playlist.m3u8  → [seg001.ts][seg002.ts][seg003.ts]...
    ├── 720p/playlist.m3u8   → [seg001.ts][seg002.ts][seg003.ts]...
    └── 360p/playlist.m3u8   → [seg001.ts][seg002.ts][seg003.ts]...

m3u8 文件结构完整解析

主播放列表(Master Playlist) — 描述所有可用的码率版本:

#EXTM3U
#EXT-X-VERSION:3
 
# 1080p 版本
#EXT-X-STREAM-INF:BANDWIDTH=5000000,RESOLUTION=1920x1080,CODECS="avc1.64002A,mp4a.40.2",FRAME-RATE=30
https://cdn.example.com/1080p/playlist.m3u8
 
# 720p 版本
#EXT-X-STREAM-INF:BANDWIDTH=2800000,RESOLUTION=1280x720,CODECS="avc1.4D401F,mp4a.40.2",FRAME-RATE=30
https://cdn.example.com/720p/playlist.m3u8
 
# 360p 版本(弱网兜底)
#EXT-X-STREAM-INF:BANDWIDTH=800000,RESOLUTION=640x360,CODECS="avc1.42E01E,mp4a.40.2",FRAME-RATE=30
https://cdn.example.com/360p/playlist.m3u8

媒体播放列表(Media Playlist) — 描述具体的分片列表:

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:6        ← 每个分片的最大时长(秒)
#EXT-X-MEDIA-SEQUENCE:0        ← 第一个分片的序号(直播时滚动更新)
 
#EXTINF:6.006,                 ← 该分片实际时长
seg000.ts
#EXTINF:6.006,
seg001.ts
#EXTINF:6.006,
seg002.ts
 
#EXT-X-ENDLIST               ← 点播视频有此标签;直播没有,播放器持续轮询

关键标签说明:

标签含义
#EXT-X-TARGETDURATION分片目标时长,通常 2~10 秒
#EXT-X-MEDIA-SEQUENCE直播时此值持续递增,播放器用它判断是否有新片段
#EXT-X-ENDLIST有此标签 = 点播(VOD);无此标签 = 直播(Live)
#EXT-X-KEY加密信息,用于 HLS 内容加密
#EXT-X-DISCONTINUITY标记分片之间的不连续点(如广告插入)

zhuanlan.zhihu.com zhuanlan.zhihu.com


TS 分片 vs fMP4 分片

HLS 传统上使用 .ts(MPEG-2 Transport Stream)作为分片格式,但 HLS v7+ 开始支持 fMP4 分片(.m4s),这也是 Apple WWDC 2016 推荐的现代做法。

fMP4 分片的优势:

  • 与 DASH 共用同一套分片,服务器只需存一份文件
  • 支持更多编解码器(VP9、AV1、HEVC)
  • 更小的容器开销
# 使用 fMP4 分片的 HLS 媒体播放列表
#EXTM3U
#EXT-X-VERSION:7                       ← 必须是 v7+
#EXT-X-TARGETDURATION:6
#EXT-X-MAP:URI="init.mp4"              ← 初始化片段(包含 moov 信息)
 
#EXTINF:6.000,
seg001.m4s
#EXTINF:6.000,
seg002.m4s
#EXT-X-ENDLIST

用 FFmpeg 生成 HLS

# 生成单码率 HLS
ffmpeg -i input.mp4 \
  -c:v libx264 -crf 23 -preset fast \
  -c:a aac -b:a 128k \
  -f hls \
  -hls_time 6 \              # 分片时长(秒)
  -hls_list_size 0 \         # 保留所有分片(0=全部,点播用)
  -hls_segment_filename "segments/seg%03d.ts" \
  output.m3u8
 
# 生成多码率 HLS(自适应码率的核心)
ffmpeg -i input.mp4 \
  -map 0:v -map 0:a -c:v libx264 -crf 28 -vf scale=640:-2  -c:a aac -b:a 96k  \
    -f hls -hls_time 6 -hls_list_size 0 \
    -hls_segment_filename "360p/seg%03d.ts" 360p/playlist.m3u8 \
  -map 0:v -map 0:a -c:v libx264 -crf 23 -vf scale=1280:-2 -c:a aac -b:a 128k \
    -f hls -hls_time 6 -hls_list_size 0 \
    -hls_segment_filename "720p/seg%03d.ts" 720p/playlist.m3u8 \
  -map 0:v -map 0:a -c:v libx264 -crf 20 -vf scale=1920:-2 -c:a aac -b:a 192k \
    -f hls -hls_time 6 -hls_list_size 0 \
    -hls_segment_filename "1080p/seg%03d.ts" 1080p/playlist.m3u8

然后手动编写 Master Playlist 将三个版本关联起来。


第二部分:MPEG-DASH — W3C 的开放标准

DASH 的核心概念

MPEG-DASH(Dynamic Adaptive Streaming over HTTP)是 W3C 国际标准,与 HLS 的核心思想相同(HTTP + 分片 + 自适应),但使用 XML 格式的 MPD(Media Presentation Description) 文件替代 m3u8,并原生支持更丰富的特性。

MPD 文件的层次结构从外到内是:

MPD(整个媒体描述)
  └── Period(时间段,可有多个,用于广告插入)
        └── AdaptationSet(媒体流类型:视频/音频/字幕)
              └── Representation(同类型不同码率/分辨率的版本)
                    └── Segment(实际的分片数据)

MPD 文件结构详解

<?xml version="1.0" encoding="UTF-8"?>
<MPD
  xmlns="urn:mpeg:dash:schema:mpd:2011"
  type="static"                          <!-- static=点播; dynamic=直播 -->
  mediaPresentationDuration="PT2M30S"    <!-- 总时长 2分30秒 -->
  minBufferTime="PT4S"                   <!-- 最小缓冲时间 -->
  profiles="urn:mpeg:dash:profile:isoff-on-demand:2011">
 
  <Period id="1" start="PT0S">
 
    <!-- 视频 AdaptationSet -->
    <AdaptationSet
      contentType="video"
      mimeType="video/mp4"
      segmentAlignment="true">
 
      <!-- 1080p 版本 -->
      <Representation
        id="v-1080p"
        bandwidth="5000000"
        width="1920" height="1080"
        codecs="avc1.64002A"
        frameRate="30">
        <SegmentTemplate
          initialization="1080p/init.mp4"
          media="1080p/seg$Number$.m4s"
          startNumber="1"
          duration="180000"
          timescale="30000"/>
      </Representation>
 
      <!-- 720p 版本 -->
      <Representation
        id="v-720p"
        bandwidth="2800000"
        width="1280" height="720"
        codecs="avc1.4D401F"
        frameRate="30">
        <SegmentTemplate
          initialization="720p/init.mp4"
          media="720p/seg$Number$.m4s"
          startNumber="1"
          duration="180000"
          timescale="30000"/>
      </Representation>
 
    </AdaptationSet>
 
    <!-- 音频 AdaptationSet -->
    <AdaptationSet
      contentType="audio"
      mimeType="audio/mp4"
      lang="zh">
      <Representation
        id="a-128k"
        bandwidth="128000"
        codecs="mp4a.40.2"
        audioSamplingRate="44100">
        <SegmentTemplate
          initialization="audio/init.mp4"
          media="audio/seg$Number$.m4s"
          startNumber="1"
          duration="192000"
          timescale="44100"/>
      </Representation>
    </AdaptationSet>
 
  </Period>
</MPD>

zhuanlan.zhihu.com blog.csdn.net


HLS vs DASH 选型对比

维度HLSMPEG-DASH
推出方AppleW3C / MPEG
标准性质私有(Apple 主导)开放国际标准
iOS 原生支持✅ Safari 原生❌ 需要 JS 库
索引文件格式文本(m3u8)XML(MPD)
编解码器限制早期限 H.264,v7+ 扩展无限制
DRM 支持FairPlayWidevine / PlayReady(更标准)
低延迟方案LL-HLSLL-DASH / CMAF
国内使用主流部分大厂使用

实践建议:如果需要 iOS Safari 原生支持,HLS 是必选;如果只面向 PC 和 Android 且需要 DRM,DASH 更灵活。很多大型平台会同时提供两种格式。


第三部分:自适应码率(ABR)算法

ABR 是流媒体协议最核心的价值——根据当前网络状况自动选择最合适的码率版本,让用户在网络好时享受高清,网络差时不卡顿。

ABR 的两种主流算法

算法一:基于吞吐量(Throughput-Based)

通过测量每个分片的下载时间来估算当前带宽,然后选择码率低于估算带宽的最高版本:

估算带宽 = 分片大小 / 下载时间
目标码率 = 估算带宽 × 安全系数(通常 0.7~0.9)

优点:响应迅速,带宽变化时快速切换。 缺点:对短暂的网络波动过于敏感,可能频繁切换导致画面质量抖动。

算法二:基于缓冲区(Buffer-Based)

根据当前缓冲区的大小(已缓冲但未播放的时长)来决定码率:

缓冲区 < 下限(如 8s)  → 降低码率
缓冲区 > 上限(如 20s) → 提高码率
缓冲区在中间           → 保持当前码率

优点:稳定,不会频繁切换,用户体验更平滑。 缺点:对带宽变化的响应有延迟。

现代播放器(如 hls.js、Shaka Player)通常使用两种算法的混合策略,同时考虑吞吐量和缓冲区状态,并加入"切换惩罚"机制避免频繁切换。 zhuanlan.zhihu.com blog.csdn.net


第四部分:MSE — 串联一切的底层 API

浏览器原生 &lt;video&gt; 无法直接播放 HLS 或 DASH(Safari 除外对 HLS 原生支持),需要借助 Media Source Extensions(MSE) 这个 W3C API 来实现。

MSE 的核心思想是:让 JavaScript 接管媒体数据的供给,把分片数据动态喂给 &lt;video&gt; 元素

MSE 完整工作流程

const video = document.querySelector('video');
const mediaSource = new MediaSource();
 
// 第一步:将 MediaSource 绑定到 video
video.src = URL.createObjectURL(mediaSource);
 
// 第二步:等待 sourceopen 事件,创建 SourceBuffer
mediaSource.addEventListener('sourceopen', async () => {
  // 创建视频 SourceBuffer(必须是 fMP4 格式)
  const videoBuffer = mediaSource.addSourceBuffer(
    'video/mp4; codecs="avc1.64002A"'
  );
  // 创建音频 SourceBuffer
  const audioBuffer = mediaSource.addSourceBuffer(
    'audio/mp4; codecs="mp4a.40.2"'
  );
 
  // 第三步:下载初始化片段(init.mp4,包含 moov 信息)
  const initData = await fetch('init.mp4').then(r => r.arrayBuffer());
 
  // 第四步:追加初始化数据
  await appendBuffer(videoBuffer, initData);
 
  // 第五步:持续下载并追加媒体分片
  for (let i = 1; i <= totalSegments; i++) {
    const segData = await fetch(`seg${i}.m4s`).then(r => r.arrayBuffer());
    await appendBuffer(videoBuffer, segData);
  }
 
  // 第六步:通知 MSE 数据已全部追加完毕(点播时)
  mediaSource.endOfStream();
});
 
// 辅助函数:等待 SourceBuffer 空闲后追加数据
function appendBuffer(sourceBuffer, data) {
  return new Promise((resolve, reject) => {
    if (sourceBuffer.updating) {
      // 等待上一次 append 完成
      sourceBuffer.addEventListener('updateend', () => {
        sourceBuffer.appendBuffer(data);
      }, { once: true });
    } else {
      sourceBuffer.appendBuffer(data);
    }
    sourceBuffer.addEventListener('updateend', resolve, { once: true });
    sourceBuffer.addEventListener('error', reject, { once: true });
  });
}

关键约束appendBuffer 是异步的,必须等待 updateend 事件触发后才能追加下一个片段,否则会抛出 InvalidStateError。这是 MSE 开发中最常见的错误来源。


第五部分:hls.js — 在浏览器中播放 HLS 的标准方案

hls.js 是目前最广泛使用的 HLS 播放库,内部基于 MSE 实现,支持所有现代浏览器。

基础集成

<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
<video id="video" controls></video>
 
<script>
const video = document.getElementById('video');
const hlsSrc = 'https://cdn.example.com/master.m3u8';
 
if (Hls.isSupported()) {
  // 大多数浏览器:使用 hls.js + MSE
  const hls = new Hls({
    // 核心配置
    maxBufferLength: 30,          // 最大缓冲时长(秒)
    maxMaxBufferLength: 60,       // 缓冲上限
    startLevel: -1,               // -1 = 自动选择起始码率
    abrEwmaDefaultEstimate: 500000, // 初始带宽估算(bps)
    lowLatencyMode: false,        // 低延迟模式(直播用)
  });
 
  hls.loadSource(hlsSrc);
  hls.attachMedia(video);
 
  hls.on(Hls.Events.MANIFEST_PARSED, () => {
    console.log('m3u8 解析完成,可用码率数:', hls.levels.length);
    video.play();
  });
 
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
  // iOS Safari:原生支持 HLS,直接赋值 src
  video.src = hlsSrc;
  video.play();
}
</script>

错误处理与自动恢复

hls.on(Hls.Events.ERROR, (event, data) => {
  console.error('[HLS Error]', data.type, data.details, data.fatal);
 
  if (data.fatal) {
    switch (data.type) {
      case Hls.ErrorTypes.NETWORK_ERROR:
        // 网络错误:尝试重新加载
        console.log('网络错误,尝试恢复...');
        hls.startLoad();
        break;
 
      case Hls.ErrorTypes.MEDIA_ERROR:
        // 媒体错误:尝试恢复解码
        console.log('媒体错误,尝试恢复...');
        hls.recoverMediaError();
        break;
 
      default:
        // 无法恢复的致命错误
        console.error('致命错误,销毁播放器');
        hls.destroy();
        showErrorUI('视频播放失败,请刷新页面');
        break;
    }
  }
});
 
// 监听码率切换事件
hls.on(Hls.Events.LEVEL_SWITCHED, (event, data) => {
  const level = hls.levels[data.level];
  console.log(`码率切换到: x @ kbps`);
});

第六部分:Shaka Player — HLS + DASH + DRM 的全能方案

Shaka Player 是 Google 开源的播放库,同时支持 HLS 和 DASH,并内置 Widevine/PlayReady DRM 支持,是需要版权保护场景的首选。

// 安装:npm install shaka-player
import shaka from 'shaka-player';
 
async function initShakaPlayer(videoEl, manifestUrl) {
  // 安装 polyfill
  shaka.polyfill.installAll();
 
  if (!shaka.Player.isBrowserSupported()) {
    console.error('浏览器不支持 Shaka Player');
    return;
  }
 
  const player = new shaka.Player();
  await player.attach(videoEl);
 
  // 配置
  player.configure({
    streaming: {
      bufferingGoal: 30,           // 目标缓冲时长
      rebufferingGoal: 2,          // 重新缓冲触发阈值
      bufferBehind: 30,            // 保留的已播放缓冲
    },
    abr: {
      enabled: true,               // 启用自适应码率
      defaultBandwidthEstimate: 1e6, // 初始带宽估算 1Mbps
    },
  });
 
  // 错误处理
  player.addEventListener('error', (event) => {
    console.error('Shaka Error:', event.detail.code, event.detail.message);
  });
 
  // 加载媒体(自动识别 HLS 或 DASH)
  try {
    await player.load(manifestUrl);
    await videoEl.play();
    console.log('播放器加载成功');
  } catch (err) {
    console.error('加载失败:', err);
  }
 
  return player;
}

第七部分:低延迟直播协议对比

直播场景对延迟极其敏感,不同协议的延迟差异可以从毫秒级到分钟级。

各协议延迟对比

协议典型延迟原理适用场景
WebRTC0.1 ~ 0.5sP2P + UDP,实时双向视频会议、互动直播
HTTP-FLV1 ~ 3sHTTP 长连接持续推送 FLV 数据低延迟直播(需 flv.js)
RTMP1 ~ 3sTCP 长连接,Flash 时代标准推流端(OBS → 服务器)
LL-HLS2 ~ 5s子分片(200~500ms)+ 阻塞重载苹果生态低延迟直播
LL-DASH2 ~ 5sCMAF Chunk + 分块传输编码跨平台低延迟直播
标准 HLS10 ~ 30s6~10s 分片 + 轮询点播、非实时直播

cnblogs.com

LL-HLS 的低延迟原理

标准 HLS 的高延迟来源于三个环节:分片生成(等待 6~10s 数据)+ 分片上传 + 客户端轮询。LL-HLS 通过两个机制解决这个问题:

部分片段(Partial Segments):将 6s 的分片进一步切分为 200~500ms 的子分片(Parts),子分片生成后立即推送,不等完整分片完成。

阻塞重载(Blocking Playlist Reload):客户端请求 m3u8 时可以附带 _HLS_msn_HLS_part 参数,服务器在指定的子分片就绪之前会挂起响应,不再需要客户端轮询。

标准 HLS 时间线:
生成 → [等待6秒] → [写入m3u8] → [客户端轮询发现] → [下载] → 播放
总延迟:约 15~30 秒

LL-HLS 时间线:
生成 → [200ms子分片就绪] → [立即推送] → [播放器收到] → 播放
总延迟:约 2~5 秒

blog.csdn.net juejin.cn


第八部分:AI 视频边生成边播放的完整实现

这是 AI 视频项目最核心的技术挑战——视频还在生成中,用户就要开始看。MSE + fMP4 分片推送是目前最成熟的方案。

架构设计

AI 生成引擎
    ↓ 每生成一个 fMP4 分片就推送
服务端(WebSocket / SSE / HTTP Chunked)
    ↓ 实时传输分片数据
浏览器 MSE
    ↓ appendBuffer() 追加到 SourceBuffer
<video> 元素
    ↓ 播放已缓冲的内容
用户看到画面

前端完整实现

class AIVideoStreamPlayer {
  constructor(videoEl) {
    this.video = videoEl;
    this.mediaSource = new MediaSource();
    this.sourceBuffer = null;
    this.queue = [];           // 待追加的分片队列
    this.isAppending = false;  // 防止并发 append
 
    this.video.src = URL.createObjectURL(this.mediaSource);
    this.mediaSource.addEventListener('sourceopen', () => this.onSourceOpen());
  }
 
  onSourceOpen() {
    this.sourceBuffer = this.mediaSource.addSourceBuffer(
      'video/mp4; codecs="avc1.64002A, mp4a.40.2"'
    );
    this.sourceBuffer.addEventListener('updateend', () => {
      this.isAppending = false;
      this.processQueue(); // 每次 append 完成后处理队列
    });
    this.sourceBuffer.addEventListener('error', (e) => {
      console.error('SourceBuffer 错误:', e);
    });
  }
 
  // 接收新的分片数据(由 WebSocket/SSE 调用)
  appendChunk(arrayBuffer) {
    this.queue.push(arrayBuffer);
    this.processQueue();
  }
 
  processQueue() {
    if (this.isAppending || this.queue.length === 0) return;
    if (!this.sourceBuffer || this.sourceBuffer.updating) return;
 
    const chunk = this.queue.shift();
    this.isAppending = true;
 
    try {
      this.sourceBuffer.appendBuffer(chunk);
    } catch (e) {
      if (e.name === 'QuotaExceededError') {
        // 缓冲区满,清理已播放的数据
        this.evictBuffer();
        this.queue.unshift(chunk); // 放回队列重试
      } else {
        console.error('appendBuffer 失败:', e);
      }
      this.isAppending = false;
    }
  }
 
  // 清理已播放的缓冲区(避免内存溢出)
  evictBuffer() {
    const currentTime = this.video.currentTime;
    if (currentTime > 30) {
      // 删除当前位置 30s 之前的缓冲数据
      this.sourceBuffer.remove(0, currentTime - 30);
    }
  }
 
    // 通知生成完毕(点播场景)
  finalize() {
    const checkAndEnd = () => {
      if (this.queue.length === 0 && !this.isAppending) {
        this.mediaSource.endOfStream();
        console.log('视频流已全部推送完毕');
      } else {
        // 还有数据待处理,等待后再检查
        setTimeout(checkAndEnd, 100);
      }
    };
    checkAndEnd();
  }
 
  destroy() {
    if (this.mediaSource.readyState === 'open') {
      this.mediaSource.endOfStream();
    }
    URL.revokeObjectURL(this.video.src);
  }
}

服务端推送策略

服务端生成 fMP4 分片有三种推送方式,各有适用场景:

方案一:HTTP Chunked Transfer(最简单)

// Node.js 服务端示例
app.get('/video-stream', async (req, res) => {
  res.setHeader('Content-Type', 'video/mp4');
  res.setHeader('Transfer-Encoding', 'chunked');
  res.setHeader('Access-Control-Allow-Origin', '*');
 
  // 先推送初始化片段(init.mp4)
  const initSegment = await generateInitSegment();
  res.write(initSegment);
 
  // 持续推送 AI 生成的媒体分片
  for await (const segment of aiVideoGenerator()) {
    res.write(segment);
  }
 
  res.end();
});
 
// 前端用 Fetch ReadableStream 接收
async function streamPlay(videoPlayer) {
  const response = await fetch('/video-stream');
  const reader = response.body.getReader();
 
  while (true) {
    const { done, value } = await reader.read();
    if (done) {
      videoPlayer.finalize();
      break;
    }
    videoPlayer.appendChunk(value.buffer);
  }
}

方案二:WebSocket(双向通信,适合需要控制生成进度的场景)

// 前端 WebSocket 接收
const ws = new WebSocket('wss://api.example.com/video-stream');
ws.binaryType = 'arraybuffer'; // 必须设置为 arraybuffer
 
const player = new AIVideoStreamPlayer(document.querySelector('video'));
 
ws.onmessage = (event) => {
  if (event.data instanceof ArrayBuffer) {
    // 二进制数据:视频分片
    player.appendChunk(event.data);
  } else {
    // 文本数据:控制消息
    const msg = JSON.parse(event.data);
    if (msg.type === 'done') player.finalize();
    if (msg.type === 'progress') updateProgressUI(msg.percent);
  }
};
 
ws.onerror = (err) => console.error('WebSocket 错误:', err);
ws.onclose = () => console.log('连接关闭');

方案三:SSE + 分片 URL 列表(适合已生成分片存储在 CDN 的场景)

// 服务端通过 SSE 推送新分片的 URL
const eventSource = new EventSource('/video-events');
 
eventSource.onmessage = async (event) => {
  const { segmentUrl, type } = JSON.parse(event.data);
 
  if (type === 'init') {
    const data = await fetch(segmentUrl).then(r => r.arrayBuffer());
    player.appendChunk(data);
  } else if (type === 'segment') {
    const data = await fetch(segmentUrl).then(r => r.arrayBuffer());
    player.appendChunk(data);
  } else if (type === 'done') {
    player.finalize();
    eventSource.close();
  }
};

第九部分:流媒体协议完整选型决策树

你的业务场景是什么?
│
├── 点播(VOD)
│   ├── 需要跨所有浏览器(含旧 iOS Safari)
│   │   └── HLS(m3u8 + TS)+ hls.js,Safari 原生支持
│   ├── 只需现代浏览器,需要 DRM 版权保护
│   │   └── MPEG-DASH + Shaka Player(内置 Widevine/PlayReady)
│   └── 简单场景,无自适应码率需求
│       └── 直接 MP4(moov 前置)+ <video src>
│
├── 直播(Live)
│   ├── 延迟 > 5s 可接受,需要最广兼容性
│   │   └── 标准 HLS(6\~10s 分片)
│   ├── 延迟 2\~5s,苹果生态
│   │   └── LL-HLS + hls.js lowLatencyMode
│   ├── 延迟 1\~3s,PC/Android 为主
│   │   └── HTTP-FLV + flv.js
│   └── 延迟 < 1s,强互动(连麦、电商带货)
│       └── WebRTC
│
└── AI 视频(边生成边播放)
    ├── 生成时间 < 30s,追求最简实现
    │   └── HTTP Chunked + MSE + fMP4
    ├── 需要进度控制、双向通信
    │   └── WebSocket + MSE + fMP4
    └── 生成后存 CDN,需要自适应码率
        └── HLS/DASH 分片 + hls.js/Shaka Player

第十部分:服务端流媒体架构要点

CDN 配置关键点

流媒体内容对 CDN 有特殊要求,以下几点是生产环境必须配置的:

# Nginx 流媒体服务配置示例
 
server {
  # 1. 必须允许 Range 请求(支持视频跳转)
  add_header Accept-Ranges bytes;
 
  # 2. CORS 配置(允许跨域加载 m3u8 和分片)
  add_header Access-Control-Allow-Origin *;
  add_header Access-Control-Allow-Methods "GET, OPTIONS";
  add_header Access-Control-Allow-Headers "Range";
 
  # 3. m3u8 文件不缓存(直播场景),分片文件长期缓存
  location ~* \.m3u8$ {
    add_header Cache-Control "no-cache, no-store";
    add_header Content-Type "application/vnd.apple.mpegurl";
  }
 
  location ~* \.(ts|m4s|mp4)$ {
    add_header Cache-Control "public, max-age=31536000, immutable";
  }
 
  # 4. 开启 gzip(对 m3u8 和 MPD 文本文件有效)
  gzip on;
  gzip_types application/vnd.apple.mpegurl application/dash+xml;
}

分片时长的选择

分片时长是影响延迟、性能和用户体验的关键参数:

分片时长延迟影响适用场景
1 ~ 2s低延迟,但服务器文件数量多,CDN 压力大直播
4 ~ 6s平衡,点播推荐值点播主流选择
8 ~ 10s延迟高,但分片少,CDN 友好长视频点播

完整技术栈速查

场景协议分片格式前端库服务端工具
点播自适应HLSTS / fMP4hls.jsFFmpeg + Nginx
点播 + DRMDASHfMP4Shaka PlayerShaka Packager
标准直播HLSTShls.jsSRS / Nginx-RTMP
低延迟直播HTTP-FLVFLVflv.jsSRS
超低延迟WebRTCRTP原生 WebRTC APIMediasoup / SRS
AI 流式播放MSE 直推fMP4原生 MSE APINode.js / Python

小结

流媒体协议的本质是把一个大文件拆成小片段,用索引文件描述顺序,让播放器按需下载。HLS 和 DASH 在这个框架上各自演进出了成熟的生态;MSE 则是让这一切在浏览器中运行的底层引擎。对于 AI 视频项目,边生成边播放的核心链路是 AI 引擎 → fMP4 分片 → WebSocket/HTTP Chunked → MSE appendBuffer → &lt;video&gt; 播放,掌握这条链路就掌握了 AI 视频流式播放的核心。

下一篇将深入 Media Source Extensions(MSE),把 MediaSourceSourceBuffer 的完整 API、缓冲区管理策略、时间戳对齐问题,以及与 HLS.js 等库的底层关系完整拆解。