iPad/iOS Safari字幕偏移因严格解析VTT时间戳:需统一HH:MM:SS.mmm格式、删注释、用ffmpeg重写并锚定PTS;Chrome/Firefox不显示则因track未被识别,须满足HTML结构、WEBVTT首行、MIME类型、srclang、kind等硬性条件。

为什么 iPad / iOS Safari 上字幕总是偏移几秒
iPad(尤其是 iOS 15+)对 track 的时间戳解析极其严格:它会强制将所有 cue 的起始时间按毫秒整数归一化,一旦 VTT 文件里存在时间格式不统一(比如 00:01:02.3 和 00:01:02.300 混用)、开头有空 cue、或用了 Windows 换行符(\r\n),就会从第一个 cue 开始累积误差,越往后偏得越明显。
解决方法不是调 JS 延迟,而是重校准时间轴本身:
- 所有时间戳必须写成
HH:MM:SS.mmm格式(三位毫秒,不足补零) - 删掉 VTT 文件里的
NOTE行和任何注释 - 用
ffmpeg重写字幕并锚定视频 PTS:ffmpeg -i video.mp4 -i subtitles.srt \ -map 0:v -map 0:a -map 1:s \ -c:v copy -c:a copy -c:s webvtt \ -metadata:s:s:0 language=zh \ -y synced.vtt
- 生成的
synced.vtt中每个start时间都真实对应视频帧显示时刻
Chrome / Firefox 字幕加载后不显示的常见原因
不是代码没写,而是浏览器根本没“认”这个 track——它可能已加载成功,但被静默忽略。典型表现是 DevTools Network 面板看到 .vtt 返回 200,但 video.textTracks 为空或 mode === "disabled"。
检查以下硬性条件是否全部满足:
立即学习“前端免费学习笔记(深入)”;
-
track必须是video的直接子元素,且**紧接在所有source之后**(source闭合标签后立刻跟track,中间不能有换行、空格、注释或div) - VTT 文件首行必须是顶格、全大写、无空格、无 BOM 的
WEBVTT - 服务器返回的 MIME 类型必须是
text/vtt(Nginx/Apache 需手动配置,file://协议下必失败) -
srclang必须是合法 BCP 47 码(如zh、en-US),不能写chinese或留空 -
kind必须为"subtitles"(不是"captions"或"descriptions")
JS 控制字幕显隐时 mode 总是设不生效
直接写 track.mode = "showing" 失败,大概率不是语法错,而是时机或状态不对。浏览器不会等你,也不会自动修复依赖关系。
必须确认两个前提:
-
track.readyState === 2(即TextTrack.LOADED),否则字幕内容还没进内存,设了也白设 -
track.mode !== "disabled"—— 如果该轨道从未被用户在播放器菜单中启用过(哪怕写了default),浏览器会拒绝将其设为"showing"
稳妥做法是监听 track.onload 或轮询 readyState,再结合 textTracks.onchange 做响应,而不是一上来就硬赋值。
流媒体(HLS/DASH)里 track 标签完全没反应
track 标签对 HLS(.m3u8)或 DASH(.mpd)根本无效——它只适用于 MP4/WebM 这类本地文件。流媒体的字幕轨道由 hls.js 或 dash.js 解析后,手动注入 video.textTracks 才能渲染。
这意味着:
-
<track src="xxx.vtt"></track>在 HLS 页面里只是个摆设,和流内字幕完全隔离 - 必须调用
hls.subtitleTrack = index或player.setConfig({caption: {enable: true}})显式启用 - 若流中字幕是 TTML 格式,得先 JS 解析成 WebVTT 字符串,再用
video.addTextTrack()注入 - 动态添加轨道必须等
hls实例hls.on(Hls.Events.MEDIA_ATTACHED, ...)后执行,否则addTextTrack()返回的对象无法被播放器识别
最易被忽略的一点:流媒体字幕常带 PTS 偏移(如 +200ms),不能靠设 mode 就完事,得监听 track.oncuechange,比对 activeCues[0].startTime 和 video.currentTime 才能真同步。



















