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>