必须用 多格式兜底,单 src 属性等于放弃兼容;type 必须严格配对(如 audio/mpeg 而非 audio/mp3);顺序按 mp3→ogg→wav 排列;服务器 MIME 必须精确匹配;autoplay 需 muted 或用户手势触发;JS 播放须捕获 Promise 拒绝并监听 error 与 canplaythrough。

必须用 <source></source> 多格式兜底,单 src 属性等于放弃兼容
只写 <audio src="a.mp3"></audio>,90% 的兼容问题当场爆发:Firefox 可能跳过、iOS Safari 根本不触发 canplay、Safari 甚至返回 duration = NaN。浏览器不会报错,只是静默失败。真正起作用的是显式声明多个 <source></source>,并严格配对 type。
实操建议:
-
type值不能靠猜:audio/mp3是错的,必须是audio/mpeg;audio/ogg对应 Vorbis 编码(用ffprobe a.ogg确认输出含Audio: vorbis) - 顺序决定加载优先级:把兼容性最广的放最前,推荐顺序为
mp3 → ogg → wav;WAV 文件体积大、加载慢,仅作最后兜底 - 别加
.aac或.opus作主力——虽被 Chrome/Firefox 支持,但 Safari 仍不认audio/aac,且服务器 MIME 配置更易出错
服务器 MIME 类型不匹配,<source></source> 写得再全也白搭
即使 DOM 里 <source src="a.mp3" type="audio/mpeg"></source> 完全正确,只要响应头里 Content-Type 是 text/plain、空值或错配成 application/octet-stream,浏览器就当普通文本扔掉,连解码器都不调用。
检查方法:打开 DevTools → Network → 点击音频请求 → 查看 Response Headers 中的 Content-Type 是否精确匹配后缀和编码。
立即学习“前端免费学习笔记(深入)”;
配置建议:
- Apache 用户:在
.htaccess或虚拟主机配置中加AddType audio/mpeg .mp3、AddType audio/ogg .ogg - Nginx 用户:在
types块中补audio/mpeg mp3;、audio/ogg ogg; - 本地开发别双击 HTML:用
python3 -m http.server 8000启服务,file://协议下 Chrome/Firefox 直接屏蔽音频加载
autoplay 失效不是代码错,是浏览器策略强制拦截
写了 autoplay 却没声音?不是 JS 漏了 play(),而是现代浏览器(Chrome ≥70、Firefox ≥66、Safari ≥12.1)默认阻止有声自动播放。控制台会抛 The play() request was interrupted,Promise 被 reject。
可行路径只有两条:
- 静音自动播:必须同时写
autoplay muted,且确保 JS 没后续改audio.muted = false或audio.volume = 1 - 有声首次播:删掉
autoplay,改用用户手势触发,例如button.addEventListener('click', () => audio.play().catch(e => {})) - iOS Safari 更严:元素必须在视口内、未被
display: none或opacity: 0遮挡,否则即使点过也静音
JS 播放必须捕获拒绝,不能只监听 canplay
在用户点击后调 audio.play() 不等于一定能响。常见错误包括 NotAllowedError(自动播放被拒)、NotSupportedError(所有 <source></source> 均不支持)、AbortError(网络中断)。只监听 canplay 会误判——它在元数据加载完就触发,音频体可能还没到。
安全做法:
- 用
audio.play().catch(e => console.warn('play rejected:', e.name))捕获 Promise 拒绝 - 监听
error事件:当所有<source></source>都失败时,audio.error?.code === 4(即MEDIA_ERR_SRC_NOT_SUPPORTED) - 用
canplaythrough替代canplay判断首帧就绪,但注意它会显著延迟播放时机
最容易被忽略的一点:iOS Safari 对 currentTime 设置极敏感——未触发 canplay 就设 audio.currentTime = 5,后续 play() 会静音或失败。必须等 canplay 触发后再操作时间轴。



















