HTML 媒体元素

深入理解 HTML5 音视频元素的完整 API、兼容性处理、无障碍支持、以及流媒体集成方案。

HTML 媒体元素(HTML Media Elements)

一、HTML5 之前的媒体嵌入历史

1.1 从插件时代到原生支持

在 HTML5 之前,在网页中嵌入音视频是一个痛苦的过程。Flash 是最流行的解决方案,但存在严重的安全问题、性能消耗和移动设备兼容性问题。苹果在 2010 年决定 iPhone 不支持 Flash,这成为了 Flash 衰落的转折点。

HTML5 引入了原生的 <audio><video> 元素,终于让开发者不需要插件就能在网页中嵌入丰富的媒体内容。

1.2 媒体元素继承关系

HTML5 的 audio 和 video 元素继承自 HTMLMediaElement 接口,这个接口定义了所有媒体元素共有的属性、方法和事件:

EventTarget
    └── Node
        └── Element
            └── HTMLElement
                └── HTMLMediaElement
                    ├── HTMLAudioElement (<audio>)
                    └── HTMLVideoElement (<video>)

理解这个继承关系有助于理解媒体元素可以使用的 DOM API 和事件机制。


二、video 元素的深度解析

2.1 基本用法与属性

<video src="video.mp4" controls width="800" height="450">
  您的浏览器不支持 video 元素。
</video>

核心属性

属性 说明
src 视频文件的 URL
controls 显示内置播放控制条
width/height 视频显示尺寸
poster 加载时显示的封面图片
autoplay 自动播放(通常不推荐)
loop 循环播放
muted 静音
preload 预加载策略(none/metadata/auto)

2.2 多源格式支持

不同浏览器支持不同视频格式,通常需要提供多种格式以确保兼容性:

<video controls poster="poster.jpg">
  <!-- WebM 格式:现代浏览器最佳选择 -->
  <source src="video.webm" type="video/webm">

  <!-- MP4/H.264:兼容性最好 -->
  <source src="video.mp4" type="video/mp4">

  <!-- OGG/Theora:老式浏览器支持 -->
  <source src="video.ogv" type="video/ogg">

  <!-- 回退内容 -->
  <p>您的浏览器不支持 video 元素。</p>
  <a href="video.mp4">下载视频</a>
</video>

常见视频格式与浏览器支持

格式 MIME type 浏览器支持
MP4/H.264 video/mp4 所有现代浏览器
WebM/VP8 video/webm Chrome, Firefox, Opera
WebM/VP9 video/webm Chrome, Firefox, Opera
OGG/Theora video/ogg Chrome, Firefox, Opera(已弃用)

2.3 内置播放控制

<video id="myVideo" src="video.mp4" controls></video>

<script>
const video = document.getElementById('myVideo');

// 播放控制
video.play();      // 播放
video.pause();     // 暂停
video.paused;      // 是否暂停(只读)
video.ended;       // 是否播放结束

// 音量和静音
video.volume = 0.5;        // 0.0 到 1.0
video.muted = true;        // 静音
video.defaultMuted = true; // 默认静音

// 播放位置
video.currentTime = 10;    // 跳到 10 秒
video.duration;            // 视频总时长(秒)
video.currentTime;         // 当前播放位置

// 播放速率
video.playbackRate = 1.5; // 1.5 倍速播放

// 播放范围(用于流媒体)
video.played;    // TimeRanges,已播放范围
video.buffered;  // TimeRanges,已缓冲范围
video.seekable;  // TimeRanges,可跳转范围
</script>

三、audio 元素详解

3.1 基本用法

<audio controls>
  <source src="audio.mp3" type="audio/mpeg">
  <source src="audio.ogg" type="audio/ogg">
  您的浏览器不支持 audio 元素。
</audio>

3.2 常用属性和方法

<audio id="myAudio" src="audio.mp3" controls preload="metadata"></audio>

<script>
const audio = document.getElementById('myAudio');

// 播放控制
audio.play();
audio.pause();

// 循环设置
audio.loop = true;

// 播放位置
audio.currentTime = 30;  // 跳到 30 秒
audio.duration;          // 音频总时长
audio.currentTime;      // 当前播放位置
</script>

3.3 音频可视化基础

虽然 audio 元素本身不提供可视化 API,但可以配合 Web Audio API 实现:

const audioContext = new AudioContext();
const analyser = audioContext.createAnalyser();

// 将 audio 元素连接到音频分析器
const source = audioContext.createMediaElementSource(audio);
source.connect(analyser);
analyser.connect(audioContext.destination);

// 设置分析器参数
analyser.fftSize = 256;
const bufferLength = analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);

function draw() {
  requestAnimationFrame(draw);
  analyser.getByteFrequencyData(dataArray);

  // 绘制频谱可视化
  // dataArray 包含每个频率桶的振幅值
}

audio.play();
draw();

四、track 元素与字幕系统

4.1 WebVTT 格式

WebVTT(Web Video Text Tracks)是 HTML5 标准的字幕格式:

WEBVTT

00:00:00.000 --> 00:00:05.000
欢迎来到本教程

00:00:05.000 --> 00:00:10.000
今天我们将学习 HTML5 媒体元素

00:00:10.000 --> 00:00:15.line:80%
带有样式的字幕

4.2 在视频中使用字幕

<video src="video.mp4" controls>
  <track kind="subtitles" label="简体中文" srclang="zh" src="subs-zh.vtt" default>
  <track kind="subtitles" label="English" srclang="en" src="subs-en.vtt">
  <track kind="captions" label="英语字幕(对话)" srclang="en" src="captions-en.vtt">
  <track kind="descriptions" label="音频描述" srclang="zh" src="descriptions-zh.vtt">
  <track kind="chapters" label="章节" srclang="zh" src="chapters-zh.vtt">
</video>

track 的 kind 属性

用途
subtitles 翻译字幕
captions 对话字幕(包括音效描述)
descriptions 视频内容的文字描述
chapters 章节标题
metadata 用于脚本访问,不显示

4.3 字幕的 CSS 样式

/* 字幕样式 */
video::cue {
  background-color: rgba(0, 0, 0, 0.8);
  color: white;
  font-family: sans-serif;
  font-size: 1.2em;
}

/* 特定轨道样式 */
::cue(voice[lang="zh"]) {
  color: #FFD700;
}

::cue(.character-name) {
  color: #00BFFF;
  font-weight: bold;
}

五、媒体元素的事件模型

5.1 完整的事件列表

const video = document.getElementById('myVideo');

video.addEventListener('loadstart', () => console.log('开始加载'));
video.addEventListener('loadedmetadata', () => console.log('元数据加载完成'));
video.addEventListener('loadeddata', () => console.log('首帧数据加载完成'));
video.addEventListener('canplay', () => console.log('可以开始播放'));
video.addEventListener('canplaythrough', () => console.log('可以流畅播放'));

video.addEventListener('play', () => console.log('开始播放'));
video.addEventListener('pause', () => console.log('暂停'));
video.addEventListener('ended', () => console.log('播放结束'));

video.addEventListener('waiting', () => console.log('等待更多数据'));
video.addEventListener('playing', () => console.log('正在播放'));

video.addEventListener('timeupdate', () => console.log('播放位置更新', video.currentTime));
video.addEventListener('durationchange', () => console.log('时长变化'));
video.addEventListener('volumechange', () => console.log('音量变化'));

video.addEventListener('error', () => console.error('播放错误', video.error));
video.addEventListener('stalled', () => console.log('数据获取停滞'));
video.addEventListener('suspend', () => console.log('加载暂停'));

5.2 事件时序图

用户点击播放
    ↓
loadstart(开始加载)
    ↓
loadedmetadata(获取到视频尺寸、时长等信息)
    ↓
loadeddata(首帧数据加载完成)
    ↓
canplay(可以播放,但可能卡顿)
    ↓
play(播放方法被调用)
    ↓
playing(真正开始播放)
    ↓
timeupdate(播放中,持续触发)
    ↓
pause/ended(播放结束或暂停)

5.3 错误处理

video.addEventListener('error', (e) => {
  switch (video.error.code) {
    case video.error.MEDIA_ERR_ABORTED:
      console.error('加载被中断');
      break;
    case video.error.MEDIA_ERR_NETWORK:
      console.error('网络错误导致加载失败');
      // 可以尝试重新加载
      video.src = video.src;
      video.load();
      break;
    case video.error.MEDIA_ERR_DECODE:
      console.error('解码错误');
      break;
    case video.error.MEDIA_ERR_SRC_NOT_SUPPORTED:
      console.error('媒体格式不支持');
      break;
  }
});

六、自定义播放控制器

6.1 创建自定义控制条

<div class="video-container">
  <video id="video" src="video.mp4"></video>

  <div class="custom-controls">
    <button id="playBtn">播放</button>
    <input type="range" id="progress" value="0" min="0" max="100">
    <span id="timeDisplay">00:00 / 00:00</span>
    <button id="muteBtn">静音</button>
    <input type="range" id="volume" min="0" max="1" step="0.1" value="1">
    <button id="fullscreenBtn">全屏</button>
  </div>
</div>

6.2 完整实现

class VideoPlayer {
  constructor(videoId) {
    this.video = document.getElementById(videoId);
    this.playBtn = document.getElementById('playBtn');
    this.progress = document.getElementById('progress');
    this.timeDisplay = document.getElementById('timeDisplay');
    this.muteBtn = document.getElementById('muteBtn');
    this.volumeSlider = document.getElementById('volume');
    this.fullscreenBtn = document.getElementById('fullscreenBtn');

    this.bindEvents();
  }

  bindEvents() {
    this.playBtn.addEventListener('click', () => this.togglePlay());
    this.video.addEventListener('click', () => this.togglePlay());
    this.video.addEventListener('timeupdate', () => this.updateProgress());
    this.progress.addEventListener('input', (e) => this.seek(e.target.value));
    this.video.addEventListener('loadedmetadata', () => this.updateDuration());
    this.muteBtn.addEventListener('click', () => this.toggleMute());
    this.volumeSlider.addEventListener('input', (e) => this.setVolume(e.target.value));
    this.fullscreenBtn.addEventListener('click', () => this.toggleFullscreen());
  }

  togglePlay() {
    if (this.video.paused) {
      this.video.play();
      this.playBtn.textContent = '暂停';
    } else {
      this.video.pause();
      this.playBtn.textContent = '播放';
    }
  }

  updateProgress() {
    const percent = (this.video.currentTime / this.video.duration) * 100;
    this.progress.value = percent;
    this.timeDisplay.textContent =
      `${this.formatTime(this.video.currentTime)} / ${this.formatTime(this.video.duration)}`;
  }

  seek(percent) {
    this.video.currentTime = (percent / 100) * this.video.duration;
  }

  updateDuration() {
    this.progress.max = 100;
    this.timeDisplay.textContent =
      `00:00 / ${this.formatTime(this.video.duration)}`;
  }

  formatTime(seconds) {
    const mins = Math.floor(seconds / 60);
    const secs = Math.floor(seconds % 60);
    return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
  }

  toggleMute() {
    this.video.muted = !this.video.muted;
    this.muteBtn.textContent = this.video.muted ? '取消静音' : '静音';
  }

  setVolume(value) {
    this.video.volume = value;
  }

  toggleFullscreen() {
    if (!document.fullscreenElement) {
      this.video.parentElement.requestFullscreen();
    } else {
      document.exitFullscreen();
    }
  }
}

const player = new VideoPlayer('video');

七、HLS 与 DASH 流媒体集成

7.1 HLS(HTTP Live Streaming)

HLS 是苹果主导的流媒体协议,通过 .m3u8 播放列表和 .ts 切片文件实现流媒体播放:

<!-- 使用 hls.js 在不支持 HLS 的浏览器中播放 HLS -->
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>

<video id="video" controls></video>

<script>
const video = document.getElementById('video');

if (Hls.isSupported()) {
  const hls = new Hls({
    startLevel: -1,  // 自动选择最佳质量
    maxBufferLength: 30,  // 最大缓冲时长(秒)
    maxMaxBufferLength: 60,
  });

  hls.loadSource('https://example.com/video.m3u8');
  hls.attachMedia(video);

  hls.on(Hls.Events.MANIFEST_PARSED, () => {
    video.play();
  });

  hls.on(Hls.Events.ERROR, (event, data) => {
    if (data.fatal) {
      switch (data.type) {
        case Hls.ErrorTypes.NETWORK_ERROR:
          console.error('网络错误,尝试恢复...');
          hls.startLoad();
          break;
        case Hls.ErrorTypes.MEDIA_ERROR:
          console.error('媒体错误,尝试恢复...');
          hls.recoverMediaError();
          break;
        default:
          console.error('致命错误');
          hls.destroy();
          break;
      }
    }
  });
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
  // 原生支持 HLS(Safari)
  video.src = 'https://example.com/video.m3u8';
  video.addEventListener('loadedmetadata', () => {
    video.play();
  });
}
</script>

7.2 DASH(Dynamic Adaptive Streaming over HTTP)

DASH 是基于 MPEG-DASH 标准的自适应流媒体协议:

<!-- 使用 dash.js -->
<script src="https://cdn.dashjs.org/latest/dash.all.min.js"></script>

<video id="video" controls></video>

<script>
const player = dashjs.MediaPlayer().create();
player.initialize(document.querySelector('#video'), 'https://example.com/video.mpd', true);
</script>

参考资料

延展阅读