iOS Safari要求每个audio元素必须通过用户手势单独激活,需在touchstart/click中依次执行load()、play().catch()、pause()三步预热,且type属性和服务器MIME类型必须严格匹配。

苹果手机(iOS Safari)上 <audio> 标签不播、没声音、play() 报 NotAllowedError,根本不是代码写错了,而是它压根不给你“无交互就播”的权限——必须用用户手势激活每个音频元素,且激活后还得小心别把它弄失效。
为什么 iOS 上 audio.play() 会静默失败
iOS WebKit 要求每个 HTMLAudioElement 必须被独立“手势激活”:哪怕你刚点过按钮播了 audio1,再调 audio2.play() 依然会被拒。错误现象包括:
-
play()返回 Promise,但既不 resolve 也不 reject,控制台也无报错 -
audio.readyState === 0或1,audio.networkState === 0(NETWORK_EMPTY) - 写了
autoplay muted却仍无声——可能因为后续 JS 改了muted = false,而解除静音 ≠ 重新激活
必须做的三步预热(touchstart/click 中执行)
在用户首次点击或触摸时,对所有待用的 <audio> 元素统一执行以下操作,缺一不可:
- 调
audio.load():iOS 9+ 要求显式触发加载,否则play()不下载资源 - 立即调
audio.play().catch(() => {}):满足“手势触发”条件,启动下载 - 立刻接
audio.pause():中断播放流,避免杂音,同时保留已建立的播放上下文
示例代码(放在 touchstart 或 click 回调里):
立即学习“前端免费学习笔记(深入)”;
const audios = document.querySelectorAll('audio[data-preload]');
document.addEventListener('touchstart', () => {
audios.forEach(audio => {
audio.load();
audio.play().catch(() => {});
audio.pause();
});
}, { once: true });
type 属性写错等于白写 source
省略 type 或写错,iOS Safari 直接跳过该 <source>,连请求都不发。常见错误:
-
type="audio/mp3"❌ —— 正确是type="audio/mpeg" -
type="audio/ogg"✅,但确认文件是 Vorbis 编码(用ffprobe a.ogg看输出含Audio: vorbis),Opus 不行 - 只写一个
<source src="a.mp3">❌ —— 必须至少双源:<source src="a.mp3" type="audio/mpeg">+<source src="a.ogg" type="audio/ogg">
服务器 MIME 类型也得配对:Nginx 补 audio/mpeg mp3;,Apache 加 AddType audio/mpeg .mp3,否则响应头是 text/plain,浏览器当文本扔掉。
微信内嵌页要额外处理 WeixinJSBridgeReady
微信 iOS 客户端有自己的 WebView,即使用户已点过页面,WeixinJSBridgeReady 事件未触发前,play() 仍可能失败。需兜底:
- 监听
WeixinJSBridgeReady,在其回调中重复执行load()→play()→pause() - 若页面已交互过,但微信桥未就绪,可加延时 fallback(如
setTimeout(..., 300)),但不能脱离原始事件栈
关键点在于:iOS 的激活是“按元素、按上下文”计算的,一次漏掉一个 audio,它后续就永远播不了——不是配置问题,是权限没拿到。所以预热必须覆盖所有可能用到的 audio 实例,一个都不能少。



















