把 SRT 转成 VTT 后,网页视频能播放,字幕却不出现。这时再转换一遍文件,未必能解决问题:字幕可能根本没有被下载,也可能下载成了网页,或者轨道已加载但尚未显示。
我是字幕轻转(Subtitle Light)的制作者。下面从一个小的原生 HTML 视频示例出发,拆开检查请求、轨道和时间。工具功能以 v1.1.0 为准;代码用于帮助定位问题,不是已经覆盖所有浏览器、CDN 或播放器的兼容性测试报告。
先用短字幕定位问题
准备一个时长超过 4 秒、浏览器可以播放的视频,并准备下面的 UTF-8 文件,命名为 check.zh.vtt:
WEBVTT
00:00:01.000 --> 00:00:03.000
字幕文件已加载。
这一行用于检查换行。
文件头与时间轴之间有空行,毫秒使用点。它只在视频的第 1 秒至第 3 秒之间有显示内容;暂停在第 0 秒或第 4 秒看不到文字,本身不是异常。
在你管理的网站上,假设视频位于 /media/demo.mp4,字幕位于 /media/check.zh.vtt,页面可以这样写:
<video id="demo-video" controls src="/media/demo.mp4">
<track id="zh-track" kind="subtitles"
src="/media/check.zh.vtt"
srclang="zh" label="中文字幕" default>
</video>
这些路径是示例,需要替换成你的真实资源地址。通过 HTTP 或 HTTPS 打开页面进行检查;直接双击本地 HTML 得到的 file:// 环境,可能有不同的资源访问限制,不能替代部署后的验收。
第一关:浏览器到底请求了哪个地址?
在开发者工具的 Network 面板中找到字幕请求,查看最终请求 URL、状态码、响应正文和响应头。如果没有请求,先检查 <track> 是否真正挂在目标 <video> 下、src 是否有效,再在播放器中选择该字幕轨道;处于禁用状态的轨道可能尚未触发加载。
路径要按页面实际地址理解:页面在 /lessons/player.html 时,src="check.zh.vtt" 通常解析为 /lessons/check.zh.vtt;src="/media/check.zh.vtt" 则从当前站点根路径开始。页面设置了 <base href> 时,相对地址还会受它影响。Network 中的实际请求比猜测文件放在哪里更可靠。
| 看到的现象 | 可以确认什么 | 接下来检查什么 |
|---|---|---|
| 404 | 本次请求没拿到目标资源 | 文件是否已发布、路径和大小写是否一致 |
| 401 或 403 | 本次请求没有获准读取 | 资源权限、鉴权方式或防盗链要求 |
| 200,但正文是 HTML | 请求成功返回了网页,不是字幕文件 | 是否命中首页回退、登录页或错误页 |
| 200,正文是 VTT,但报跨域错误 | 文件地址存在,页面仍可能没有读取权限 | CORS 响应头和视频元素的请求模式 |
| VTT 已加载,画面仍无字 | 网络成功还不足以证明正在显示 | 轨道模式、播放时间和播放器渲染方式 |
特别注意“200 但正文是 HTML”:一些单页站点会把不存在的路径回退到首页。Network 看似成功,响应却以 <!doctype html> 开头。改字幕内容不能修复这个路由问题,应让字幕 URL 返回真正的 VTT 文件。
第二关:响应类型和编码是否正确?
WebVTT 资源应使用 text/vtt 内容类型,文件内容应为 UTF-8。可对照这样的响应头:
Content-Type: text/vtt; charset=utf-8
扩展名为 .vtt 并不保证服务器给出了这个响应类型;响应头正确也不保证正文没有误传成 HTML。两项需要一起看。
若字幕文字已经乱码,先在编辑器中按原编码正确打开原文件,再另存为 UTF-8。把已经损坏的文字直接另存为 UTF-8,不能恢复原文。遇到格式错误可参考字幕排错指南。
第三关:轨道类型和语言是否表达正确?
kind="subtitles" 适合字幕、翻译等文字辅助;kind="captions" 用于包含对话以及必要声音信息的无障碍字幕,例如说话人、音乐或音效说明。两者都可能显示为文字轨道,但用途不同,不能靠把 subtitles 改成 captions 自动生成缺失的声音描述。
srclang="zh" 声明字幕内容的语言,不会执行翻译。对于 kind="subtitles",应提供 srclang;它使用语言标签,不是填写“中文”这样的界面名称。label="中文字幕" 才是供用户识别轨道的名称。
default 表示默认选择的候选轨道,不是强制打开字幕的开关。用户的语言和字幕偏好、播放器逻辑都可能影响实际选择;同一个视频不要给多条轨道同时加 default。它是布尔属性,写成 default="false" 仍表示属性存在,取消默认应移除该属性。先在播放器字幕菜单里明确选中目标轨道,再判断是否加载失败。
第四关:跨域时,文件能打开为什么还会失败?
网页与字幕 URL 的协议、主机名或端口不同,就涉及跨源访问。同一主域名下的不同子域名也可能是不同源。你能在另一个标签页直接打开字幕地址,不代表视频页面获准跨源读取它。
如果必须从另一来源加载字幕,需要按媒体元素与服务器的 CORS 规则配置。常见的无凭据场景是在父级 <video> 上设置 crossorigin="anonymous",并让字幕服务器允许页面的来源;不是只给 <track> 添一个同名属性。
例如页面来源是 https://www.example.com,字幕响应可以明确允许该来源:
Access-Control-Allow-Origin: https://www.example.com
这是配置示意,不是可直接套用到任何站点的服务器规则。crossorigin 也会影响视频媒体资源的加载方式:如果视频本身来自另一 CDN,还要检查那个视频来源是否提供匹配的跨域响应。应在资源开始加载前完成配置;涉及登录态或凭据的资源,需要按实际鉴权设计处理,不能简单改成通配符解决。
可先用同源视频和字幕缩小排错范围,确认基本轨道可用后,再逐项迁移到 CDN。每次只改变一处来源或配置,才能知道哪个变化引入了问题。
第五关:轨道已经加载,为什么仍然没有字?
原生文字轨道有三个常见模式:disabled 为禁用;hidden 可用于加载或处理字幕,但不由原生文字轨道渲染;showing 才表示显示模式。使用自定义播放器时,它可能以 hidden 读取字幕,再自行绘制,所以不能只凭这一字段断定应用有故障。
对上面的示例,在页面中选过中文字幕后,可在开发者工具控制台读取一份状态快照:
const video = document.querySelector('#demo-video');
const element = document.querySelector('#zh-track');
if (!video || !element) {
throw new Error('未找到示例的视频或字幕轨道,请核对元素 ID。');
}
console.table({
videoTime: video.currentTime,
subtitleURL: element.src,
readyState: element.readyState,
mode: element.track.mode,
cueCount: element.track.cues?.length ?? null,
activeCueCount: element.track.activeCues?.length ?? null
});
这是只读检查,不会自动修复或开启轨道。readyState 的 0、1、2、3 分别表示尚未加载、加载中、已加载、加载出错。读取太早、轨道禁用等情况下,字幕列表可能不可用;null 与“已成功解析但有 0 条字幕”不是同一个结论。
结合前面的短字幕:在轨道加载完成且启用后,查看视频约第 2 秒的状态。如果有已解析字幕,但没有当前活动条目,先核对时间轴与 currentTime;如果活动条目存在却看不到文字,再检查轨道显示模式、自定义播放器的渲染层和字幕样式。仅有 readyState=2 不能证明全部字幕都合法、与视频同步或已经被显示。
字幕轻转在这个流程中负责什么?
如果手上只有 SRT,可以用字幕轻转生成 WebVTT,再按以上步骤接入自己的播放器;基础操作见SRT 转 VTT 指南。
当前工具免费、免注册、免安装,支持 UTF-8 的常规 SRT 与 WebVTT 双向转换,一次处理一个文件或一份粘贴文本,上限为 1 MiB(1,048,576 字节)和 10,000 条字幕。它不会自动校准音画同步,也不托管视频或字幕,更不会替你修改服务器的路径、响应头、鉴权或 CORS 配置。完整边界见工具说明。
转换时字幕内容、文件名和结果在浏览器内处理,不上传给本站服务器;网站页面加载及可关闭的匿名统计仍会使用网络。将生成文件部署到你自己的服务器是另一个步骤,是否公开取决于你的配置。详情见数据处理说明。
格式和接口可继续查阅 WebVTT 规范、MDN 的 track 元素说明和TextTrack.mode 说明。具体播放器上线前,仍需用实际视频、目标浏览器和真实部署地址验收字幕显示与同步。
本文由字幕轻转制作者撰写,使用 AI 辅助整理,并对工具功能与示例作本地核对。