ended事件是捕获音视频自然播放结束的唯一可靠方式,需用addeventlistener监听,且须在loadeddata或canplay后注册;ios safari自动播放受限时需同时监听error;audio与video行为一致,但loop=true会阻止ended触发。

play() 后怎么知道音频/视频播完了
浏览器原生 play() 不返回 Promise 的完成状态,也不触发回调,它只管“开始播放”。真正能捕获结束的,是元素自身的 ended 事件。
注意:这个事件只在自然播放到末尾时触发,用户手动暂停、网络中断、load() 失败或调用 pause()/stop() 都不会触发它。
-
ended是 DOM 事件,必须用addEventListener('ended', ...)监听,不能靠play().then()或 await - 如果媒体资源是动态设置的(比如先
src = 'a.mp3'再play()),确保在loadeddata或canplay之后再监听ended,否则可能漏掉事件 - 移动端 iOS Safari 对自动播放限制严格,
play()可能被静默拒绝,此时ended永远不会来——得同时监听error和检查error?.message
Audio 和 Video 的 ended 事件行为一致吗
一致。无论是 Audio 实例还是 video 元素,只要播放自然结束,都会触发 ended。但实际使用中差异来自加载方式和属性状态。
-
Audio构造后立即play(),若未设置src或资源未就绪,会抛DOMException: The element has no supported sources,而不是触发ended -
video元素若设置了loop="true",则永远不会触发ended——哪怕视觉上“播完一圈”,也要先loop = false才能监听到 - 两者都受
preload影响:设为'none'时,ended可能延迟触发,甚至因缓冲不足提前中断而不触发
为什么 addEventListener('ended', ...) 有时不生效
最常见原因是监听时机太早:元素还没加载元数据,或者 src 是后来才赋值的,导致事件注册时播放器内部状态未初始化。
- 不要在
new Audio()后立刻监听,加一层audio.addEventListener('loadedmetadata', () => { audio.addEventListener('ended', ...) }) - 如果用
srcObject(比如 MediaStream),ended不适用——它只针对文件/URL 媒体;此时应监听inactive事件或检查readyState === 0 - React/Vue 等框架中,组件卸载后忘记
removeEventListener,会导致内存泄漏,且下次挂载时重复绑定,出现“一次播放、多次执行回调”
有没有更可靠的替代方案(比如轮询 currentTime)
不推荐轮询。虽然可以读 currentTime 和 duration 判断是否接近结尾,但有明显缺陷:
-
duration在加载完成前可能是NaN或Inf,尤其对流媒体或无 CORS 的跨域资源 - 某些编码下
currentTime在结尾处跳变(如 MP3 的 ID3 尾部),导致判断失准 - 轮询增加不必要的 CPU 占用,且需自己处理节流、清理定时器
- 唯一合理用轮询的场景是:你明确知道
ended被禁用(如某些 WebRTC 播放器封装层),且无法改底层代码
真要兜底,优先检查 onended 属性是否被意外覆盖,或用 getEventListeners(audio)(Chrome DevTools)确认事件监听器是否真实注册成功。
复杂点在于:不同格式、不同来源(本地文件 / HLS / MSE)、不同平台(iOS / Android WebView)对 ended 的触发时机和条件有细微差别,上线前得实机测一遍主流机型和系统版本。










