抛出问题: MP4 在video里到底需不需要下载完再播答案是不一定。取决于两件事moov放在文件的哪里以及服务器是否支持 Range 请求。(moov是 MP4 的元数据容器, 下文会介绍)本文用实验, 逐一说清楚这两件事各自的作用——以及你可能一直误解的 FastStart 的真实价值。最小必要知识MP4 是什么?MP4 是视频的封装格式, 遵循 MPEG-4 Part 14 协议。MP4 的存储结构上有三个核心:ftyp、mdat、moov。ftyp: 声明格式, 兼容的规范, 内容对播放本身没影响, 但不能缺。mdat: 真正的音视频数据。是文件里最大的 box, 但它是一段裸数据(没有索引), 单独拿到**mdat** 浏览器不知道哪个字节是第几帧、时间戳是多少、从哪里开始解码。moov: 全局“目录”。记录所有视频的元信息(时长、轨道、编解码, 以及最关键的每一帧在mdat里的字节偏移量和时间戳)。浏览器要先拿到moov, 才知道怎么读**mdat**、从哪里开始解码。所以moov的位置非常重要, 理论上, 默认moov在文件尾浏览器不拿到结尾就不知道怎么解析前面的数据; 如果moov在文件头浏览器下载开头就能开始播为什么 moov 在末尾?传统上大部分 MP4 视频资源的moov都是在末尾的, 也是有一定的历史原因。采集设备(相机)内置编码器, 录制时实时压缩每一帧到mdat中,moov里记录的是是每一帧的字节偏移量和时间戳, 这些信息等所有帧都编码完毕, 才最终确定, 所以自然而然的放到最后一帧中。早期, 视频是本地播放的, 读文件可以随机寻址(系统调用层面直接跳到最后读moov信息)。moov放到哪里无所谓。Web 兴起后, HTTP 下载变成顺序读取,moov在末尾的代价才暴露出来。HTTP/1.0 200 和 HTTP/1.1 206 和 Range 请求头HTTP/1.0 1996年发布, 定义了响应成功的默认状态, 服务器返回 200 状态码 。HTTP/1.1 1997年发布, 定义了服务器处理 Range 请求头, 返回 206 状态码。(虽然是更新了0.1, 但实际上是个大版本迭代。)# Range请求头格式, 代表告知服务器返回文件的哪一部分(单位:字节) Range: unitrange-start- Range: unitrange-start-range-end Range: unitrange-start-range-end, …, range-startN-range-endN Range: unit-suffix-length早期, 服务器和 CDN 只实现了 HTTP/1.0 的部分200是默认行为Range 支持是需要额外实现的——这也解释了为什么至今还有服务器不支持 Range。本文的术语约定接下来, 我会将moov后置的 MP4 资源, 叫做“传统 MP4”, 将moov前置的 MP4 叫做“FastStart MP4”, 注意我后面还会提到结构完全不同的“fmp4(Fregment MP4)”, 注意不要混淆。本文中的快速起播指浏览器无需下载完整文件、拿到 moov 元数据后即可解码首帧开始播放。与之对应的是全量下载完后起播——视频数据必须完整到达才能播放。实验过程实验目标调查 MP4 在 是否是必须下载完才能播。实验准备一个约 60MB 的 MP4 视频资源的两个版本。传统MP4 :bigmp4-moov-end.mp4moov 在尾部。FastStart MP4 :bigmp4-moov-first.mp4moov 在头部, 由ffmpeg转码。一个 HTTP 服务, 可以提供 200 和 206 的 MP4 资源的响应, 由 node 提供。mp4box.js, 用于识别 MP4 封装格式内部的具体结构。一个 chromium 内核的浏览器。接下来实验的展开是: 两个变量 × 两种状态 四种组合的播放器行为200206 Range传统 MP4??FastStart MP4??为了简化实验和消除无关变量, 不再专门启动一个 HTML 页面挂video, 而是直接在地址栏输入 MP4 URL来代表这个过程。地址栏输入 MP4 URLChrome 会创建一个MediaDocument在内部构建一个真实的video元素用source src你的URL挂上去。走的是完全相同的HTMLVideoElement管线Range 请求、moov 解析行为与手写video src...没有任何区别。Chromium 源码 media_document.ccauto* media MakeGarbageCollectedHTMLVideoElement(*GetDocument()); media-setAttribute(html_names::kControlsAttr, g_empty_atom); media-setAttribute(html_names::kAutoplayAttr, g_empty_atom); auto* source MakeGarbageCollectedHTMLSourceElement(*GetDocument()); source-setAttribute(html_names::kSrcAttr, AtomicString(GetDocument()-Url()));// ← 输入的 URL实验一: 仅 200, 无 Range 支持, 传统 MP4 vs FastStart MP4Step1 : 传统 MP4: 下载完才能起播(2RTT)moov在末尾, 约在第 60M 字节的位置。服务不处理 Range, 直接返回 200 , 出现了两条请求:第一条请求试图请求全量 Range, 然后快速停止了。第二条请求开始从偏末尾位置请求Range, 属于一条持续下载的 200 请求视频无法播放, 直到下载完毕。Step2: FastStart MP4: 快速起播(1RTT)MP4 的moov位置在前面, 该测试文件中 moov 位于第32字节。服务器同样不处理 Range, 这次仅出现一条请求:浏览器请求全量的Range, 但是服务没处理, 一条持续下载的 200 请求, 但是却在刚下载前一段资源后就已经可以播放了实验一的初步结论GET 200传统 MP4资源(moov 后置)2次请求, 下载完再播FastStart MP4资源(moov 提前)1次请求, 快速起播 实验二: 206 Range 支持, 传统 MP4 vs FastStart MP4Step1: 传统 MP4: 快速起播(3RTT)实验现象: 浏览器触发三次不同的 Range 请求, 且能快速起播请求Range目的为什么结束①0-探测 box 结构;下载mdat数据解析box头发现 moov 在末尾abort②64290816-取 moov 元数据moov 读完正常结束③32768-从 32KB 对齐点恢复 mdat, 源码写死了固定值:chromium.org-32kb block持续下载播放进行中Step2: FastStart MP4: 快速起播(1RTT)实验现象: 仅有一条全量的Range的请求, 且能快速起播实验二的初步结论GET 206 Range传统 MP4资源(moov 后置)3次请求, 快速起播FastStart MP4资源(moov 提前)1次请求, 快速起播实验结论 在 chromium147(2026.04) 内核的浏览器上, 传统 MP4 和 FastStart MP4 的行为表现不同GET 200GET 206 Range传统 MP4资源(moov 后置)下载完才能播(2RTT)快速起播(3RTT) FastStart MP4资源(moov 提前)快速起播(1RTT) 快速起播(1RTT) 传统 MP4 资源只有在服务端仅支持 200 的情况下, 才会下载完才能播的情况(开篇的疑问结论已有答案 ✅)。而 FastStart MP4 , 做到 1 条请求快速起播。额外校验:我快速尝试了在 Safari 26.1 上检验上述结论, 得出“快速起播的组合关系在Safari上结论是一致的”, 只是请求 RTT 机制略有不同, 不过这对于生产选型已经够用了。在低版本 Chrome 40(2015 release) 上, 传统MP4 搭配上 206 Range 依然可以做到快速起播。(使用 Browserstack Window 8 真机验证)启发MP4 的生产最佳实践使用支持 206 和 Range 的服务端, 可以稳妥地做到快速起播。(eg: 现代CDN/自建服务本身等)而将传统 MP4 转化为 FastStart MP4的价值是**,**不仅能快速起播, 还能近一步节省 RTT。尤其是弱网场景下RTT收益更明显。以国内 4G 网络典型 RTT 约 50–100ms 为例moov 后置 Range 比 FastStart Range 多出 2 次额外请求理论增加起播延迟100–200ms弱网RTT 200ms下可达400ms 以上。探索 video 的边界上述研究只解决了一个具体问题moov 位置 服务端 Range 支持对起播体验的影响。video标签本身还有更根本的限制无法处理无限增长的流直播内容没有固定文件大小moov 探测逻辑从根上不适用无法运行时切换码率ABR 需要在不同清晰度分片之间动态切换video不暴露这个控制接口无法自定义网络策略预加载时机、P2P 分发、CDN 调度对video来说是黑盒所以, 企业生产实践上, 会有更多的场景需要处理, 所以解法N:fmp4(Fragment MP4) MSE将 moov 打散为分片配合 MediaSource API 增量推送HLS / DASH流媒体协议, 原理是将视频切成独立分片(fmp4/TS)分段请求支持 ABR 和直播FLVtag 结构天然流式每个 tag 自带类型和时间戳无需 moov可用于 HTTP-FLV 直播这些限制不是「等 Chrome 更新」能解决的它们是videoAPI 设计边界的一部分。实验工具 实验代码备注静态站, 演示206下的 FastStart mp4 和 传统MP4为了方便大家快速看到206下的不同 MP4 效果(见下图)mp4box.js online快速查看当前 MP4 文件的内部结构ffmpeg音视频转码工具, 传统 MP4 可以快速转化为 FastStart MP4 :ffmpeg -i input.mp4 -movflags faststart output.mp4Github 200/206 Node server极简的 200/206 服务参考资料https://mp4ra.org/https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Reference/Status/206https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Reference/Headers/Rangehttps://www.rfc-editor.org/rfc/rfc9110#name-206-partial-contenthttps://ossrs.io/lts/zh-cn/assets/files/ISO_IEC_14496-14-MP4-2003-9a3eb04879ded495406399602ff2e587.pdfhttps://www.flashedgecdn.com/blog/round-trip-time-rtt/https://mdn.org.cn/en-US/docs/Web/HTTP/Guides/Evolution_of_HTTP—— 研究于 2026.04.26