HTML5 audio /video 完整事件监听模板

`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 渲染,建议节流。

  1. `loop = true` 循环播放时,不会触发 `ended`,`media.ended` 也不会变成 `true` 。

  2. `play()` 返回 Promise,浏览器**自动播放策略**会报错,必须捕获 `catch` 。

  3. `play` vs `playing`:

  • `play`:调用 play () 那一刻触发,还不一定开始出声

  • `playing`:真正开始播放出声

5.`pause` 只有暂停才触发; **播放完毕触发 ended,不会触发 pause**。

  1. `seeking` / `seeked`:拖动进度条跳转时触发,timeupdate 也会同步更新时间。
相关推荐
IT_陈寒1 小时前
Vite热更新失效?你可能也踩了这个坑
前端·人工智能·后端
程序员大阳2 小时前
副队长CSS教程(30)–CSS Grid 实战:实现复杂自适应表单布局
前端·css·自适应·grid·表单
Csvn2 小时前
微前端实现
前端
Chrischaan3 小时前
做 AI 塔罗小程序时,我为什么不让大模型自己抽牌:一次上线复盘
前端
骑着蜗牛撵大象3273 小时前
LangChain 工具调用从声明到闭环:给智能体接上真实的手脚
前端
数据掘金4 小时前
百度统计后台页面数据异常?从手动调用时机开始排查
前端
hunteritself4 小时前
10 层 4K 实时剪辑,国产工作站进入广电制作核心
大数据·前端·人工智能·深度学习·transformer
踩着两条虫4 小时前
我在浏览器里“盖”了一个村:Vue + Three.js 大屏开发踩坑记
前端·vue.js·three.js
mbshqqb4 小时前
TypeScript基础教程之同步生成器
前端·javascript·typescript