媒体事件
媒体事件(Media Events)在音频、视频、流媒体等媒体资源播放过程中触发,包括加载、播放、暂停、结束、缓冲等状态变化。媒体事件是构建自定义播放器、处理流媒体加载状态和优化媒体播放体验的核心事件。
前置知识
阅读本节前,建议先了解:剪贴板事件
基础概念
媒体事件类型
| 事件 | 触发时机 | 说明 |
|---|---|---|
loadstart | 开始加载 | 媒体开始加载时 |
loadedmetadata | 元数据加载完成 | 获取到时长、尺寸等信息 |
loadeddata | 数据加载完成 | 首帧数据可用 |
canplay | 可以播放 | 已缓冲足够数据开始播放 |
canplaythrough | 可流畅播放 | 预计可以播放到结束 |
play | 开始播放 | play() 调用后 |
playing | 正在播放 | 实际播放中 |
pause | 暂停 | pause() 调用后 |
ended | 播放结束 | 播放到末尾 |
waiting | 等待缓冲 | 因缓冲而暂停 |
timeupdate | 播放位置更新 | currentTime 变化时 |
volumechange | 音量变化 | volume 或 muted 变化 |
error | 加载错误 | 资源加载失败 |
html
<!-- HTML 事件属性 -->
<video src="video.mp4"
onloadstart="showLoading()"
oncanplay="enablePlayBtn()"
ontimeupdate="updateProgress()"
onended="showReplay()">
</video>
<audio src="audio.mp3"
onplay="startVisualizer()"
onpause="stopVisualizer()">
</audio>语法
HTML 事件属性
html
<video controls
onloadedmetadata="onMetaLoaded()"
ontimeupdate="onTimeUpdate()"
onvolumechange="onVolumeChange()"
onerror="onMediaError(event)">
<source src="video.mp4" type="video/mp4">
<source src="video.webm" type="video/webm">
您的浏览器不支持 video 标签。
</video>JavaScript 绑定
javascript
const video = document.getElementById('my-video');
video.addEventListener('loadedmetadata', () => {
console.log('时长:', video.duration);
console.log('尺寸:', video.videoWidth, 'x', video.videoHeight);
});
video.addEventListener('timeupdate', () => {
const progress = (video.currentTime / video.duration) * 100;
progressBar.style.width = progress + '%';
});
video.addEventListener('ended', () => {
showReplayButton();
});详细说明
加载状态事件流程
loadstart → loadedmetadata → loadeddata → canplay → canplaythrough
↓ ↓ ↓ ↓ ↓
开始加载 获取元数据 首帧可用 可开始播放 可流畅播放常用媒体属性
| 属性 | 类型 | 说明 |
|---|---|---|
currentTime | number | 当前播放位置(秒) |
duration | number | 总时长(秒) |
paused | boolean | 是否暂停 |
ended | boolean | 是否播放结束 |
volume | number | 音量(0-1) |
muted | boolean | 是否静音 |
buffered | TimeRanges | 已缓冲的时间范围 |
played | TimeRanges | 已播放的时间范围 |
readyState | number | 就绪状态(0-4) |
videoWidth / videoHeight | number | 视频原始尺寸 |
实战示例
完整的自定义视频播放器
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>媒体事件示例</title>
<style>
.player {
max-width: 640px;
margin: 0 auto;
background: #000;
border-radius: 12px;
overflow: hidden;
}
.player video {
width: 100%;
display: block;
}
.controls {
padding: 12px 16px;
background: #1e293b;
display: flex;
flex-direction: column;
gap: 8px;
}
.progress-bar {
width: 100%;
height: 6px;
background: #334155;
border-radius: 3px;
cursor: pointer;
position: relative;
}
.progress-fill {
height: 100%;
background: #3b82f6;
border-radius: 3px;
width: 0%;
transition: width 0.1s linear;
}
.buffer-bar {
position: absolute;
top: 0;
left: 0;
height: 100%;
background: #475569;
border-radius: 3px;
width: 0%;
}
.btn-row {
display: flex;
align-items: center;
gap: 12px;
}
.btn {
background: none;
border: none;
color: white;
cursor: pointer;
font-size: 18px;
padding: 4px 8px;
border-radius: 4px;
}
.btn:hover { background: #334155; }
.time {
color: #94a3b8;
font-family: monospace;
font-size: 13px;
flex: 1;
text-align: center;
}
.volume-slider {
width: 80px;
accent-color: #3b82f6;
}
.status {
color: #94a3b8;
font-size: 12px;
text-align: center;
}
</style>
</head>
<body>
<div class="player">
<video id="video" preload="metadata">
<source src="sample.mp4" type="video/mp4">
</video>
<div class="controls">
<div class="progress-bar" id="progress-bar">
<div class="buffer-bar" id="buffer-bar"></div>
<div class="progress-fill" id="progress-fill"></div>
</div>
<div class="btn-row">
<button class="btn" id="play-btn">▶</button>
<button class="btn" id="mute-btn">🔊</button>
<input type="range" class="volume-slider" id="volume"
min="0" max="1" step="0.1" value="1">
<span class="time" id="time">0:00 / 0:00</span>
<button class="btn" id="fullscreen-btn">⛶</button>
</div>
<div class="status" id="status">加载中...</div>
</div>
</div>
<script>
const video = document.getElementById('video');
const playBtn = document.getElementById('play-btn');
const muteBtn = document.getElementById('mute-btn');
const volumeSlider = document.getElementById('volume');
const progressFill = document.getElementById('progress-fill');
const bufferBar = document.getElementById('buffer-bar');
const timeDisplay = document.getElementById('time');
const statusBar = document.getElementById('status');
const progressBar = document.getElementById('progress-bar');
// 格式化时间
function formatTime(seconds) {
const m = Math.floor(seconds / 60);
const s = Math.floor(seconds % 60);
return m + ':' + String(s).padStart(2, '0');
}
// 加载元数据
video.addEventListener('loadedmetadata', () => {
timeDisplay.textContent = '0:00 / ' + formatTime(video.duration);
statusBar.textContent = '就绪';
});
// 播放/暂停
playBtn.addEventListener('click', () => {
if (video.paused) {
video.play();
} else {
video.pause();
}
});
video.addEventListener('play', () => {
playBtn.innerHTML = '▮▮';
statusBar.textContent = '播放中';
});
video.addEventListener('pause', () => {
playBtn.innerHTML = '▶';
statusBar.textContent = '已暂停';
});
video.addEventListener('ended', () => {
playBtn.innerHTML = '▶';
statusBar.textContent = '播放结束';
});
// 更新进度
video.addEventListener('timeupdate', () => {
const progress = (video.currentTime / video.duration) * 100;
progressFill.style.width = progress + '%';
timeDisplay.textContent = formatTime(video.currentTime) + ' / ' + formatTime(video.duration);
});
// 缓冲进度
video.addEventListener('progress', () => {
if (video.buffered.length > 0) {
const buffered = (video.buffered.end(video.buffered.length - 1) / video.duration) * 100;
bufferBar.style.width = buffered + '%';
}
});
// 等待缓冲
video.addEventListener('waiting', () => {
statusBar.textContent = '缓冲中...';
});
video.addEventListener('playing', () => {
statusBar.textContent = '播放中';
});
// 点击进度条跳转
progressBar.addEventListener('click', (e) => {
const rect = progressBar.getBoundingClientRect();
const pos = (e.clientX - rect.left) / rect.width;
video.currentTime = pos * video.duration;
});
// 音量控制
volumeSlider.addEventListener('input', () => {
video.volume = volumeSlider.value;
});
video.addEventListener('volumechange', () => {
muteBtn.innerHTML = video.muted ? '🔇' : '🔊';
if (!video.muted) volumeSlider.value = video.volume;
});
muteBtn.addEventListener('click', () => {
video.muted = !video.muted;
});
// 错误处理
video.addEventListener('error', () => {
statusBar.textContent = '视频加载失败';
});
// 快捷键
document.addEventListener('keydown', (e) => {
if (e.target.tagName === 'INPUT') return;
switch (e.key) {
case ' ':
e.preventDefault();
video.paused ? video.play() : video.pause();
break;
case 'ArrowLeft':
video.currentTime = Math.max(0, video.currentTime - 5);
break;
case 'ArrowRight':
video.currentTime = Math.min(video.duration, video.currentTime + 5);
break;
case 'ArrowUp':
e.preventDefault();
video.volume = Math.min(1, video.volume + 0.1);
volumeSlider.value = video.volume;
break;
case 'ArrowDown':
e.preventDefault();
video.volume = Math.max(0, video.volume - 0.1);
volumeSlider.value = video.volume;
break;
case 'm':
case 'M':
video.muted = !video.muted;
break;
}
});
</script>
</body>
</html>注意事项
autoplay 限制
现代浏览器限制自动播放带音频的视频。需要满足以下条件之一:
- 用户已与页面交互
- 媒体已静音或
volume为 0 - 用户之前对该网站设置过允许自动播放
html
<!-- 推荐:静音自动播放 -->
<video autoplay muted src="video.mp4"></video>
<!-- 需要:先交互再播放 -->
<video id="video" src="video.mp4"></video>
<button onclick="document.getElementById('video').play()">播放</button>最佳实践
1. 监听多种加载状态
javascript
const events = ['loadstart', 'loadedmetadata', 'loadeddata', 'canplay', 'canplaythrough'];
events.forEach(event => {
video.addEventListener(event, () => {
console.log(event, '→ readyState:', video.readyState);
});
});下一节
继续学习:draggable 拖拽