先把当时的情况摆出来。后端用 ffmpeg 生成 MP4 后直接上传对象存储,网页 video 标签要等整个文件下载完才出第一帧,8MB 的视频首帧等了 8 秒;下载到本地播放器却秒开,CDN 支持 range 请求。

这类报错背后常见的原因有几处,逐个排除就行。moov box 写在文件尾部,浏览器要先拿全文件。也可能是录制时用了默认 mp4 muxer,没开 faststart。

少数情况下是上传前又转码了一次,把 faststart 标志丢了。不容易想到的是,CDN 没透传 Range 头,range 请求退化成全量。

先看 moov 在文件里的位置

ffprobe -v trace input.mp4 2>&1 | grep -n -m3 'moov'

接着用 faststart 重新封装不重编码

ffmpeg -i input.mp4 -c copy -movflags +faststart output.mp4

再往下确认输出里 moov 已经前置

python -c "d=open('output.mp4','rb').read(4096);print(d.find(b'moov'))"

末了用 range 请求验证首字节返回

curl -s -r 0-1023 -o /dev/null -w '%{http_code} %{size_download}\n' https://cdn.example.com/output.mp4

恢复的判定其实很直接。moov 的偏移出现在文件前 4KB 内,网页 video 的首帧时间降到 1 秒以内,range 请求返回 206 和正确的字节数。

这里还有个坑。faststart 只是把索引搬到前面,不改变画质也不重新编码;录制链路和转码链路都要带上这个参数,中间任何一步丢掉都会退回尾部 moov。线上能稳定复现的问题都不算难,难的是只在大流量下才出现的那种。

作者 9823189