流式媒体播放的核心是“边收边解、边解边放”,通过fetch流与MediaSource协同实现无缝播放:fetch逐块拉取fMP4片段,MediaSource按时间轴精准写入并确保解码连续。

流式媒体播放的核心,不是“等全部下载完再播”,而是“边收边解、边解边放”。理解 fetch 流与 MediaSource 的无缝对接,关键在于把浏览器的网络层(fetch)和媒体层(MediaSource)看作两个协同运转的齿轮:一个负责持续吐出二进制数据块,另一个负责按时间轴精准喂给解码器——中间不卡顿、不跳帧、不重置解码状态。
fetch 流提供的是“可读、可控、可中断”的字节流
传统 fetch 返回的 arrayBuffer 是一次性加载整段数据,内存压力大、启动延迟高。而 fetch + ReadableStream 可以真正实现“流式拉取”:
- 用 response.body.getReader() 获取流式读取器,逐块读取 chunk(如每次 64KB)
- 每读到一块,就立即传给 SourceBuffer.appendBuffer(),无需等待整段加载完成
- 可监听 abort signal 主动中断读取(比如用户拖进度条或切换清晰度)
- 注意:chunk 必须是完整的 fMP4 片段(含 moof+mdat),不能截断在帧中间,否则 appendBuffer 会失败
MediaSource 要求的是“时间连续、结构合规、状态可控”的写入节奏
它不关心数据从哪来,只认三件事:格式对不对、时间戳对不对、当前能不能写。
- 必须用 fragmented MP4(fMP4),普通 MP4 的 moov 头在文件开头,无法分段流式写入
- sourceBuffer.mode 必须设为 'segments',并每次 append 前显式设置 timestampOffset 对齐总时间轴
- 只能在 sourceBuffer.updating === false 且 mediaSource.readyState === 'open' 时调用 appendBuffer()
- append 后要监听 updateend 事件,确认上一段写入完成,再触发下一段 fetch —— 这是衔接不丢帧的关键节拍点
两者对接的“无缝”体现在数据管道无缓冲断层
所谓无缝,不是视觉上绝对零延迟,而是播放器内部解码器不重启、不丢帧、不跳秒。这需要两端严格配合:
- fetch 流不能“堵住”:一旦上一段 append 开始,下一段就要开始 fetch,避免 sourceBuffer 空转导致卡顿
- MediaSource 不能“饿着”:通过监听 video 元素的 timeupdate 和 buffered 属性,预判即将播放位置,提前拉取后续片段
- 错误要即时拦截:比如 appendBuffer 报错(格式不符)、updateend 未触发(写入卡死)、fetch 中断(网络异常)—— 都需降级处理(如重试、切备用源、提示加载中)
- 首帧启动靠“初始化段”:第一段必须包含 moov(或单独提供 init segment),否则 sourceBuffer 不知道如何解码,会静音或报错
实际协作流程一句话概括
fetch 拿到响应体 → 用 reader 循环读 chunk → 每 chunk 经校验后传给 sourceBuffer.appendBuffer() → 等 updateend 触发 → 立即 fetch 下一段 → 同时根据当前播放时间动态更新 timestampOffset 和 mediaSource.duration → 直到所有片段加载完毕,调用 mediaSource.endOfStream()。










