流媒体协议: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 选型对比
| 维度 | HLS | MPEG-DASH |
|---|---|---|
| 推出方 | Apple | W3C / MPEG |
| 标准性质 | 私有(Apple 主导) | 开放国际标准 |
| iOS 原生支持 | ✅ Safari 原生 | ❌ 需要 JS 库 |
| 索引文件格式 | 文本(m3u8) | XML(MPD) |
| 编解码器限制 | 早期限 H.264,v7+ 扩展 | 无限制 |
| DRM 支持 | FairPlay | Widevine / PlayReady(更标准) |
| 低延迟方案 | LL-HLS | LL-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
浏览器原生 <video> 无法直接播放 HLS 或 DASH(Safari 除外对 HLS 原生支持),需要借助 Media Source Extensions(MSE) 这个 W3C API 来实现。
MSE 的核心思想是:让 JavaScript 接管媒体数据的供给,把分片数据动态喂给 <video> 元素。
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;
}第七部分:低延迟直播协议对比
直播场景对延迟极其敏感,不同协议的延迟差异可以从毫秒级到分钟级。
各协议延迟对比
| 协议 | 典型延迟 | 原理 | 适用场景 |
|---|---|---|---|
| WebRTC | 0.1 ~ 0.5s | P2P + UDP,实时双向 | 视频会议、互动直播 |
| HTTP-FLV | 1 ~ 3s | HTTP 长连接持续推送 FLV 数据 | 低延迟直播(需 flv.js) |
| RTMP | 1 ~ 3s | TCP 长连接,Flash 时代标准 | 推流端(OBS → 服务器) |
| LL-HLS | 2 ~ 5s | 子分片(200~500ms)+ 阻塞重载 | 苹果生态低延迟直播 |
| LL-DASH | 2 ~ 5s | CMAF Chunk + 分块传输编码 | 跨平台低延迟直播 |
| 标准 HLS | 10 ~ 30s | 6~10s 分片 + 轮询 | 点播、非实时直播 |
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 秒
第八部分: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 友好 | 长视频点播 |
完整技术栈速查
| 场景 | 协议 | 分片格式 | 前端库 | 服务端工具 |
|---|---|---|---|---|
| 点播自适应 | HLS | TS / fMP4 | hls.js | FFmpeg + Nginx |
| 点播 + DRM | DASH | fMP4 | Shaka Player | Shaka Packager |
| 标准直播 | HLS | TS | hls.js | SRS / Nginx-RTMP |
| 低延迟直播 | HTTP-FLV | FLV | flv.js | SRS |
| 超低延迟 | WebRTC | RTP | 原生 WebRTC API | Mediasoup / SRS |
| AI 流式播放 | MSE 直推 | fMP4 | 原生 MSE API | Node.js / Python |
小结
流媒体协议的本质是把一个大文件拆成小片段,用索引文件描述顺序,让播放器按需下载。HLS 和 DASH 在这个框架上各自演进出了成熟的生态;MSE 则是让这一切在浏览器中运行的底层引擎。对于 AI 视频项目,边生成边播放的核心链路是 AI 引擎 → fMP4 分片 → WebSocket/HTTP Chunked → MSE appendBuffer → <video> 播放,掌握这条链路就掌握了 AI 视频流式播放的核心。
下一篇将深入 Media Source Extensions(MSE),把 MediaSource、SourceBuffer 的完整 API、缓冲区管理策略、时间戳对齐问题,以及与 HLS.js 等库的底层关系完整拆解。