`audio` 和 `video` 的媒体事件完全通用,一份代码两者都能用。
包含:事件监听 + 状态变量 + 销毁解绑 + 注释说明
html
<!DOCTYPE html>
<html>
<body>
<video id="media" src="test.mp4"></video>
<!-- <audio id="media" src="test.mp3"> -->
<script>
const media = document.getElementById('media');
// 播放器状态对象
const playerState = {
// 基础状态
readyState: 0, // 媒体就绪状态 0~4
isPlaying: false, // 是否正在播放
isEnded: false, // 是否播放到末尾
currentTime: 0, // 当前播放时间 s
duration: 0, // 总时长 s
buffered: 0, // 已缓冲秒数
volume: 1, // 音量 0~1
muted: false, // 是否静音
error: null // 错误信息
};
// ===================== 媒体事件监听 =====================
// 1. loadstart:开始请求媒体资源
media.addEventListener('loadstart', () => {
console.log('loadstart - 开始加载资源');
});
// 2. loadedmetadata:元数据加载完成(拿到时长、宽高)
media.addEventListener('loadedmetadata', () => {
console.log('loadedmetadata - 元信息加载完成');
playerState.duration = media.duration;
playerState.readyState = media.readyState;
});
// 3. loadeddata:当前帧数据加载完成
media.addEventListener('loadeddata', () => {
console.log('loadeddata - 当前帧加载完毕');
});
// 4. canplay:可以播放,但可能会缓冲卡顿
media.addEventListener('canplay', () => {
console.log('canplay - 可播放,可能卡顿');
playerState.readyState = media.readyState;
});
// 5. canplaythrough:数据充足,预计能流畅播完
media.addEventListener('canplaythrough', () => {
console.log('canplaythrough - 缓冲充足,可完整播放');
playerState.readyState = media.readyState;
});
// 6. play:调用 play(),即将播放(paused 变为 false)
media.addEventListener('play', () => {
console.log('play - 触发播放动作');
playerState.isPlaying = true;
playerState.isEnded = false;
});
// 7. playing:实际正在播放(缓冲恢复后也会触发)
media.addEventListener('playing', () => {
console.log('playing - 媒体正在播放中');
});
// 8. pause:暂停触发
media.addEventListener('pause', () => {
console.log('pause - 已暂停');
playerState.isPlaying = false;
});
// 9. waiting:缺少数据,进入缓冲等待
media.addEventListener('waiting', () => {
console.log('waiting - 缓冲中,等待数据');
});
// 10. seeking:正在跳转进度(拖动进度条)
media.addEventListener('seeking', () => {
console.log('seeking - 正在跳转播放位置');
});
// 11. seeked:进度跳转完成
media.addEventListener('seeked', () => {
console.log('seeked - 进度跳转完成');
});
// 12. timeupdate:播放时间更新【高频事件!】
media.addEventListener('timeupdate', () => {
playerState.currentTime = media.currentTime;
// 获取缓冲结束位置
if (media.buffered.length > 0) {
playerState.buffered = media.buffered.end(media.buffered.length - 1);
}
// console.log('timeupdate', playerState.currentTime); // 不要高频打印!
});
// 13. ended:播放到末尾结束(loop=true 不会触发)
media.addEventListener('ended', () => {
console.log('ended - 播放结束');
playerState.isEnded = true;
playerState.isPlaying = false;
});
// 14. volumechange:音量/静音改变
media.addEventListener('volumechange', () => {
console.log('volumechange - 音量变化');
playerState.volume = media.volume;
playerState.muted = media.muted;
});
// 15. ratechange:播放速率改变
media.addEventListener('ratechange', () => {
console.log('ratechange - 播放倍速变化', media.playbackRate);
});
// 16. error:资源加载错误
media.addEventListener('error', () => {
console.log('error - 媒体加载出错', media.error);
playerState.error = media.error;
});
// readyState 变化(没有单独事件,可在上面事件里同步读取)
/*
HAVE_NOTHING 0 没有可用数据
HAVE_METADATA 1 拿到元数据
HAVE_CURRENT_DATA 2 当前位置有数据,但下一帧没有
HAVE_FUTURE_DATA 3 有后续数据,可以继续播放
HAVE_ENOUGH_DATA 4 数据足够
*/
// ===================== 常用操作方法 =====================
function playMedia() {
media.play().catch(err => {
console.log('播放失败(浏览器策略:需要用户交互)', err);
});
}
function pauseMedia() {
media.pause();
}
function setTime(val) {
media.currentTime = val;
}
function setVolume(val) {
media.volume = val;
}
// ===================== 销毁:移除所有监听(重要,防止内存泄漏) =====================
function destroyPlayer() {
const events = [
'loadstart','loadedmetadata','loadeddata','canplay','canplaythrough',
'play','playing','pause','waiting','seeking','seeked',
'timeupdate','ended','volumechange','ratechange','error'
];
events.forEach(evt => {
media.removeEventListener(evt, playerHandlers[evt]);
});
}
// 注意:如果要用 removeEventListener,不能直接传匿名函数!
// 上面代码如果需要销毁,建议把回调抽离成命名函数,示例:
/*
const playerHandlers = {
ended: function() {
console.log('ended');
}
}
media.addEventListener('ended', playerHandlers.ended);
media.removeEventListener('ended', playerHandlers.ended);
*/
</script>
</body>
</html>
⚠️ 重点踩坑提醒
1.`timeupdate` 高频触发(每秒几次) ,不要在里面做复杂 DOM 渲染,建议节流。
-
`loop = true` 循环播放时,不会触发 `ended`,`media.ended` 也不会变成 `true` 。
-
`play()` 返回 Promise,浏览器**自动播放策略**会报错,必须捕获 `catch` 。
-
`play` vs `playing`:
-
`play`:调用 play () 那一刻触发,还不一定开始出声
-
`playing`:真正开始播放出声
5.`pause` 只有暂停才触发; **播放完毕触发 ended,不会触发 pause**。
- `seeking` / `seeked`:拖动进度条跳转时触发,timeupdate 也会同步更新时间。