audio标签的loop属性无法实现真正无缝循环,因浏览器在末尾到开头间插入50–200ms静音间隙;需用js监听timeupdate,在audio.duration-0.02秒处pause、currenttime=0、play来逼近无缝,且preload必须设为auto以保障事件精度。

audio 标签默认 loop 属性无法真正无缝
直接写 <audio loop></audio> 看似能循环,但几乎所有浏览器(Chrome、Firefox、Safari)在音频末尾到开头之间都会插入 50–200ms 的静音间隙。这不是 bug,而是规范行为:loop 只保证播放器状态重置,不处理解码缓冲区和音频引擎的衔接。
真正无缝需要绕过浏览器原生循环机制,用 JavaScript 手动控制播放时机。关键点在于:在音频结束前几毫秒触发 currentTime = 0,并避免触发 seeking 或重缓冲。
用 timeupdate + pause + play 实现低延迟跳转
监听 timeupdate 事件,在到达末尾前 10–30ms 主动跳回起点,比等 ended 事件更及时。注意不能只靠 currentTime = 0,必须配合 pause() 和 play() 防止音频引擎卡顿。
-
audio.duration可能为NaN或Infinity初次加载时,需等loadedmetadata后再绑定逻辑 - 跳转阈值设为
audio.duration - 0.02(20ms),太早会提前中断当前帧,太晚就来不及 - 必须调用
audio.pause()再audio.currentTime = 0,否则部分浏览器(尤其是 Safari)会忽略跳转 - 最后用
audio.play()恢复播放,不要依赖自动继续
<audio id="seamless-audio" src="loop.mp3"></audio><script>
const audio = document.getElementById('seamless-audio');
audio.addEventListener('loadedmetadata', () => {
audio.addEventListener('timeupdate', () => {
if (audio.currentTime >= audio.duration - 0.02 && audio.duration > 0) {
audio.pause();
audio.currentTime = 0;
audio.play().catch(e => console.warn('play failed:', e));
}
});
});
</script>
preload="none" 会导致 timeupdate 触发不稳定
如果 preload 设为 none 或 metadata,音频数据可能未完全加载,timeupdate 事件频率下降甚至中断,导致跳转时机错乱。实测中,preload="auto" 是唯一能保障 timeupdate 持续触发的选项,尤其对短循环音频(如 2–5 秒音效)。
副作用是首次加载体积稍大,但换来的是可预测的定时精度。若必须懒加载,得改用 canplaythrough 事件后才启用循环逻辑,且要加 fallback:检测连续 3 次 timeupdate 间隔 > 200ms 就降级为原生 loop。
Safari 对 currentTime = 0 的兼容性最差
iOS / macOS Safari 经常在 currentTime = 0 后出现 1–2 帧黑屏或卡顿,即使加了 pause()。临时缓解方案是:跳转后等待 waiting 事件结束再 play(),或退而求其次,用两个 <audio></audio> 实例交叉播放——一个播放末段时,另一个从头加载并准备接续。
交叉播放代码复杂度高,仅当音频长度稳定、格式统一(如全为 AAC 编码 MP3)、且必须支持 iOS 时才值得投入。多数场景下,接受 Safari 上约 10ms 的微小间隙,比引入双实例状态管理更实际。
真正难处理的是音频文件本身:有编码头(ID3、APE 标签)或非标准采样率(如 48kHz 录音硬转 44.1kHz)的文件,无论怎么调 JS 都会出缝。先用 ffmpeg -i in.mp3 -acodec copy -fflags +genpts out.mp3 清除时间戳再试。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











