最稳排查路径是确认格式真实编码与服务端响应头是否匹配:用ffprobe查音频编码(如fltp需转s16p),严格设置Content-Type(如.mp3→audio/mpeg),避免VBR,iOS需预置且声明codecs,MediaError{code:4}表明解码失败而非网络问题。

canPlayType() 返回空字符串,MediaError {code: 4},控制台干干净净但没声音——这不是代码写错了,是浏览器在解码层直接拒收。最稳的排查路径不是改 HTML,而是先确认「格式真实编码」和「服务端响应头」是否匹配。
查真实编码别信文件后缀
文件名叫 a.mp3 不代表它就是标准 MP3;Safari 对含 fltp(浮点 PCM)或非 AAC-LC 编码的音频会静默失败。Firefox 可能加载 OGG 但无声,只因实际是 Opus 编码而非 Vorbis。
- 用
ffprobe a.mp3看输出里Audio: mp3 (mp3), 44100 Hz, stereo, fltp, 128 kb/s这一行:若含fltp,换用s16p(16 位整型)重编码 - OGG 文件必须确认含
Audio: vorbis,不是opus或flac;可用ffprobe a.ogg验证 - MP3 避免 VBR(可变比特率),旧版 Safari 和 Android WebView 解码不稳定;转码时加
-q:a 2(CBR 模式)更稳妥
检查服务器 Content-Type 响应头
即使 URL 正确、文件存在,若响应头是 text/plain 或缺失,Chrome/Firefox 会跳过该 <source></source>,连请求都不发。这不是前端能绕过的逻辑,是 HTTP 层拦截。
- 打开 DevTools → Network → 点击音频请求 → 查看 Response Headers 中的
Content-Type - 必须严格匹配:
.mp3→audio/mpeg,.ogg→audio/ogg,.m4a→audio/mp4; codecs="aac" - Apache 用户加
AddType audio/mpeg .mp3;Nginx 在types块中补audio/mpeg mp3; - 本地开发用
file://协议?Chrome 直接禁用音频加载,必须起服务:python3 -m http.server 8000
验证 canPlayType() 的返回值含义
canPlayType() 不是布尔判断,它只返回 "probably"、"maybe" 或空字符串 ""。iOS 上常误判为 "probably",结果 play() 仍抛 DOMException: Unable to decode audio data。
把任意文章或文档内容转换成精美的全屏翻页 HTML 演示文件(幻灯片风格)。当用户发来一篇文章、文档链接、Markdown 内容、或粘贴的文本,并且希望生成演示用的 HTML 时触发。支持任意内容来源:iWiki 链接、KM 链接、本地文件路径、直接粘贴的文本。生成的 HTML 文件保存到本地工作区,并通过企微直接发送给用户。触发词示例:帮我把这篇文章做成PPT、转成演示文档、生成HTML幻灯片、做个演示、转成可以演示的格式、article to deck、make a slide。
立即学习“前端免费学习笔记(深入)”;
- 不要只传
audio/mp4:Safari 要求明确声明codecs="aac",否则大概率返回"" - 顺序很重要:iOS Safari 不支持
audio/ogg,但它仍会按顺序发起每个<source></source>请求;若 OGG 放前面且 404,可能触发资源节流,导致后续 MP3 的play()被静默丢弃 - 动态插入
<source></source>无效:iOS 要求所有<source></source>在<audio></audio>首次解析时就存在,JS 后续 append 不会被识别
区分 MEDIA_ERR_SRC_NOT_SUPPORTED 和加载失败
控制台报 MediaError {code: 4} 是明确信号:文件已成功下载,但浏览器解码器当场拒绝。这和 404、CORS、跨域没配 crossorigin 属性完全不同。
-
code: 4=MEDIA_ERR_SRC_NOT_SUPPORTED,不是网络问题,是编码不兼容 - 若 Network 面板里音频请求状态是 200 但没声音,优先查编码和 MIME 类型,别在路径上浪费时间
- CORS 报错会明确写
Blocked by CORS policy;而code: 4出现时,Network 面板里请求是绿的,只是解不了 - 微信 iOS 端更特殊:即使
muted+autoplay,也得等WeixinJSBridgeReady事件,否则play()静音返回,连 Promise 都不触发
真正卡住人的从来不是“怎么写 audio 标签”,而是“为什么明明返回 200 却没声音”——问题藏在 ffmpeg 输出参数、服务器配置、iOS 加载节流这些看不见的链路上。每次改完格式,务必用真实设备抓一次 Network 面板,看响应头和实际编码是否对得上。


















