audio的source顺序必须按浏览器解码器执行路径严格排列:Opus→AAC→MP3,Safari旧版不支持Opus故不能置末,WAV仅限短提示音且需单独处理,动态换源须pause-load-play并捕获错误。

audio 的 顺序不是“随便排”,而是浏览器解码器的执行路径
浏览器对 <source></source> 是从上到下逐个调用 canPlayType(),一旦返回 "probably" 或 "maybe" 就立刻加载、停止后续尝试。顺序错一位,Safari 可能直接跳过所有源,duration 为 NaN,控件也不显示——连错误都不抛。
常见错误现象:
– 写了 <source src="a.ogg" type="audio/ogg"></source> 放第一位,Safari(尤其 iOS)静默失败
– 把 .wav 放最前,Firefox 能播但用户得等几 MB 下完才出声
– 漏掉 type 属性,浏览器发起无效请求,Network 面板里一堆 404
-
<source src="a.opus" type="audio/ogg; codecs=opus"></source>:Chrome/Firefox 优先识别,体积小、解码快;Safari 17.5+ 已支持,但旧版不认,所以不能放最后 -
<source src="a.m4a" type="audio/mp4; codecs=aac"></source>:AAC-LC 编码的 .m4a 在 iOS 全系稳定,比 MP3 更省带宽;注意别用 HE-AAC 或 ALAC -
<source src="a.mp3" type="audio/mpeg"></source>:兼容最广,但 Safari 对 VBR 编码偶有解码失败;必须放第三位,作 fallback 不是主力 - 删掉
<source src="a.wav" ...></source>:仅短提示音(
type 属性写错等于没写,浏览器只认精确 MIME + codecs
type="audio/ogg" 和 type="audio/ogg; codecs=opus" 是两回事。前者在 Chrome 中对 .opus 文件可能返回空字符串,后者才稳定返回 "probably"。Safari 根本不认 audio/ogg,但对 audio/mp4; codecs=aac 响应积极。
实操验证方法:
打开控制台,运行 Audio.canPlayType('audio/ogg; codecs="opus"'),返回非空字符串才算通过
立即学习“前端免费学习笔记(深入)”;
- MP3 必须用
type="audio/mpeg",不是audio/mp3(浏览器直接忽略) - Opus 封装在 .ogg 容器里,写
type="audio/ogg; codecs=opus";若封装在 .weba 里,用type="audio/weba"(但支持度低,不推荐) - .m4a 必须确认编码是 AAC-LC:
ffprobe file.m4a查codec_name是否为aac,不是aac_he或alac - 服务器返回的
Content-Type响应头必须和type值完全一致,比如audio/ogg; codecs=opus对应Content-Type: audio/ogg,不能是application/octet-stream
本地开发时顺序“看起来对”,上线就崩?检查三件事
你写的顺序没错,但浏览器根本没机会执行它——因为资源压根没加载进来。90% 的“顺序失效”其实卡在底层协议或服务配置上。
- 别双击 HTML 打开:
file://协议下 Chrome/Firefox 直接禁音频加载,Network 面板都看不到请求;必须用python3 -m http.server 8000或 VS Code Live Server - 路径写相对 URL,统一用
src="assets/audio/click.opus",别混用./、../或绝对file://路径 - 服务器没配 MIME 类型:Apache 加
AddType audio/ogg .opus,Nginx 加add_type audio/ogg .opus;;否则浏览器把 Opus 当文本扔掉,canPlayType()永远返回空 - 检查 Network 面板里音频请求的 Response Headers,确认
Content-Type正确,且有Accept-Ranges: bytes(preload="metadata"依赖 Range 请求)
多个 audio 元素共存时,顺序策略要单独处理
一个页面有背景音乐、按钮音效、语音播报三个 <audio></audio>?别指望“全局手势授权”共享。iOS 不认这个逻辑,每个元素都得独立触发一次播放上下文。
- 对每个
<audio></audio>都需绑定一次用户交互事件,例如:button.addEventListener('click', () => audio.play().catch(() => {}), { once: true }) - 不要复用同一个
<audio></audio>元素切换src播不同音效:iOS 会重置播放状态,play()可能被拒绝 - 如果必须动态换源,先
pause(),再设src,再load(),最后play();但依然要.catch()捕获NotAllowedError - 背景音乐类长音频建议用
preload="metadata",音效类短音频可设preload="auto",但前提是服务端支持 Range 请求
<source></source>,而是从用户点击那一刻起,JS 行为、服务端响应、浏览器解码器、设备策略这四层严丝合缝咬合的结果。漏掉任意一层,前面写的顺序再完美也白搭。



















