5.4 字幕与轨道(textTrack)

5.4 字幕与轨道(textTrack)

WebVTT字幕系统

1. WebVTT文件格式规范

基础文件结构

WEBVTT

00:00:01.000 --> 00:00:04.000
这是第一句字幕

00:00:05.500 --> 00:00:08.750
这是第二句字幕
<v 张三>说话人标签

高级特性

STYLE
::cue {
  background-color: rgba(0,0,0,0.5);
  font-family: "Microsoft YaHei";
}

NOTE 这是注释行

00:00:10.000 --> 00:00:12.000 line:10% position:20% align:start
带定位的字幕

2. 字幕轨道集成

HTML实现

<video controls>
  <source src="video.mp4" type="video/mp4">
  <track default
         kind="subtitles"
         srclang="zh"
         label="中文"
         src="subtitles/chinese.vtt">
  <track kind="captions"
         srclang="en"
         label="English"
         src="subtitles/english.vtt">
</video>

轨道属性详解

属性 可选值 作用
kind subtitles/captions/descriptions/chapters/metadata 轨道类型
srclang ISO语言代码(zh/en/es等) 语言标识
label 任意字符串 用户可见的轨道名称
default 布尔属性 默认选中轨道

字幕动态控制

1. JavaScript轨道API

获取轨道实例

const video = document.querySelector('video');
const textTracks = video.textTracks; // TextTrackList对象

// 遍历所有文本轨道
for(let i = 0; i < textTracks.length; i++) {
  const track = textTracks[i];
  console.log(`轨道${i}:`, track.kind, track.language, track.label);
}

轨道状态控制

// 激活指定轨道
function activateTrack(trackIndex) {
  // 先禁用所有轨道
  Array.from(textTracks).forEach(track => {
    track.mode = 'disabled';
  });
  
  // 启用选定轨道
  if(trackIndex >= 0 && trackIndex < textTracks.length) {
    textTracks[trackIndex].mode = 'showing';
  }
}

// 监听轨道变化
const track = textTracks[0];
track.addEventListener('cuechange', () => {
  const activeCues = track.activeCues;
  if(activeCues && activeCues.length > 0) {
    console.log('当前字幕:', activeCues[0].text);
  }
});

2. 动态字幕加载

异步加载字幕

async function loadSubtitle(lang) {
  const response = await fetch(`subtitles/${lang}.vtt`);
  const subtitleText = await response.text();
  
  // 创建Blob URL
  const blob = new Blob([subtitleText], { type: 'text/vtt' });
  const subtitleUrl = URL.createObjectURL(blob);
  
  // 动态创建轨道
  const track = document.createElement('track');
  track.kind = 'subtitles';
  track.srclang = lang;
  track.label = lang === 'zh' ? '中文' : 'English';
  track.src = subtitleUrl;
  
  video.appendChild(track);
  
  // 轨道加载完成后启用
  track.onload = () => {
    track.mode = 'showing';
  };
}

字幕样式定制

1. CSS伪元素控制

基础样式设置

video::cue {
  color: white;
  background-color: rgba(0, 0, 0, 0.7);
  font-size: 16px;
  font-family: "Microsoft YaHei", sans-serif;
}

/* 说话人特定样式 */
video::cue(v[voice="张三"]) {
  color: #ffcc00;
}

/* 定位特定字幕 */
video::cue(c.position-top) {
  top: 15px;
}

动态样式切换

function changeSubtitleStyle(style) {
  const styleElement = document.getElementById('subtitle-style');
  
  switch(style) {
    case 'dark':
      styleElement.textContent = `
        video::cue {
          background-color: rgba(0,0,0,0.8);
          color: white;
        }
      `;
      break;
    case 'light':
      styleElement.textContent = `
        video::cue {
          background-color: rgba(255,255,255,0.8);
          color: black;
        }
      `;
      break;
  }
}

2. 自定义字幕渲染

替代默认渲染

<div class="custom-cue-display"></div>

<script>
  const cueDisplay = document.querySelector('.custom-cue-display');
  const track = video.textTracks[0];
  
  track.addEventListener('cuechange', () => {
    cueDisplay.innerHTML = '';
    
    if(track.activeCues) {
      Array.from(track.activeCues).forEach(cue => {
        const cueElement = document.createElement('div');
        cueElement.className = 'custom-cue';
        cueElement.textContent = cue.text;
        
        // 解析说话人标签
        if(cue.voice) {
          const voice = document.createElement('span');
          voice.className = 'voice';
          voice.textContent = cue.voice + ': ';
          cueElement.insertBefore(voice, cueElement.firstChild);
        }
        
        cueDisplay.appendChild(cueElement);
      });
    }
  });
</script>

高级轨道应用

1. 章节导航系统

章节轨道实现

WEBVTT

Chapter 1
00:00:00.000 --> 00:02:30.000
开场介绍

Chapter 2
00:02:30.000 --> 00:05:45.000
主要内容

JavaScript集成

function setupChapterNavigation() {
  const chaptersTrack = Array.from(video.textTracks)
    .find(track => track.kind === 'chapters');
  
  if(!chaptersTrack) return;
  
  const chapterMenu = document.getElementById('chapter-menu');
  
  chaptersTrack.oncuechange = () => {
    const activeChapter = chaptersTrack.activeCues[0];
    if(activeChapter) {
      highlightCurrentChapter(activeChapter.id);
    }
  };
  
  // 生成章节菜单
  chaptersTrack.cues && Array.from(chaptersTrack.cues).forEach(cue => {
    const button = document.createElement('button');
    button.textContent = cue.text;
    button.onclick = () => {
      video.currentTime = cue.startTime;
    };
    chapterMenu.appendChild(button);
  });
}

2. 元数据轨道交互

元数据轨道示例

WEBVTT - 元数据

00:00:05.000 --> 00:00:10.000
{
  "type": "product-info",
  "id": "P12345",
  "name": "示例产品"
}

元数据处理

const metadataTrack = Array.from(video.textTracks)
  .find(track => track.kind === 'metadata');

metadataTrack.addEventListener('cuechange', () => {
  const activeCues = metadataTrack.activeCues;
  if(activeCues && activeCues.length > 0) {
    try {
      const metadata = JSON.parse(activeCues[0].text);
      showRelatedContent(metadata);
    } catch(e) {
      console.error('元数据解析错误:', e);
    }
  }
});

多语言支持

1. 自动语言检测

function setBestSubtitleLanguage() {
  // 获取用户首选语言
  const userLanguage = navigator.language.split('-')[0];
  const availableLanguages = Array.from(video.textTracks)
    .map(track => track.language);
  
  // 尝试匹配精确语言代码
  let trackToEnable = Array.from(video.textTracks)
    .find(track => track.language === navigator.language);
  
  // 回退到主语言代码
  if(!trackToEnable) {
    trackToEnable = Array.from(video.textTracks)
      .find(track => track.language.split('-')[0] === userLanguage);
  }
  
  // 启用匹配的轨道
  if(trackToEnable) {
    trackToEnable.mode = 'showing';
  } else if(video.textTracks.length > 0) {
    // 默认启用第一条轨道
    video.textTracks[0].mode = 'showing';
  }
}

2. 实时翻译系统

async function translateSubtitles(targetLang) {
  const activeTrack = Array.from(video.textTracks)
    .find(track => track.mode === 'showing');
  
  if(!activeTrack) return;
  
  const translatedCues = [];
  
  // 遍历所有cue进行翻译
  for(let i = 0; i < activeTrack.cues.length; i++) {
    const cue = activeTrack.cues[i];
    const translation = await translateText(cue.text, targetLang);
    
    const translatedCue = new VTTCue(
      cue.startTime,
      cue.endTime,
      translation
    );
    
    translatedCues.push(translatedCue);
  }
  
  // 创建新轨道
  const newTrack = video.addTextTrack('subtitles', targetLang, targetLang);
  translatedCues.forEach(cue => newTrack.addCue(cue));
  newTrack.mode = 'showing';
}

async function translateText(text, targetLang) {
  // 实际项目中调用翻译API
  const response = await fetch(`/translate?text=${encodeURIComponent(text)}&lang=${targetLang}`);
  return await response.text();
}

无障碍优化

1. 屏幕阅读器集成

<video aria-describedby="caption-description">
  <track id="captions" kind="captions" srclang="en" src="captions.vtt">
</video>

<div id="caption-description" class="sr-only">
  本视频包含字幕,可通过播放器控件开启
</div>

2. 键盘导航支持

document.addEventListener('keydown', (e) => {
  if(document.activeElement === video) {
    switch(e.key) {
      case 'c':
        // 切换字幕
        toggleSubtitles();
        break;
      case 'm':
        // 切换元数据显示
        toggleMetadataDisplay();
        break;
    }
  }
});

function toggleSubtitles() {
  const tracks = video.textTracks;
  const currentTrack = Array.from(tracks).find(t => t.mode === 'showing');
  const nextIndex = currentTrack ? 
    (Array.from(tracks).indexOf(currentTrack) + 1) % tracks.length : 0;
  
  activateTrack(nextIndex);
}

问题排查与调试

1. 常见问题解决

字幕不显示检查清单

  1. 确认文件路径正确且服务器配置了正确的MIME类型(text/vtt)
  2. 检查轨道mode属性是否设置为showing
  3. 验证WebVTT文件格式是否正确
  4. 确保字幕时间轴与视频匹配
  5. 检查CORS策略是否允许加载字幕文件

2. 调试工具

控制台检查

// 检查加载的字幕轨道
console.log('可用字幕轨道:', Array.from(video.textTracks));

// 检查当前活动的cue
const activeTrack = Array.from(video.textTracks)
  .find(track => track.mode === 'showing');
  
if(activeTrack) {
  console.log('当前活动的cue:', 
    activeTrack.activeCues ? Array.from(activeTrack.activeCues) : null);
}

通过系统化地应用这些字幕与轨道技术,开发者可以为多媒体内容添加强大的文本支持,从基础的字幕显示到复杂的交互式元数据应用,全面提升视频内容的可访问性和用户体验。

#前端开发 分享于 2025-04-01

【 内容由 AI 共享,不代表本站观点,请谨慎参考 】