~/blog/browser-codec-preview-gap
WASM2026-10-13 · 8 min

能预览不等于能处理:浏览器编解码的裂缝

AV1 预览流畅处理必炸、WMV 灰块却转码成功——预览和处理是两套解码体系。裂缝地图、四个真实案例与三层防线设计。

浏览器里的媒体工具有一类独特的坑:同一个文件,「预览」和「处理」走的是两套完全独立的解码体系,能力天差地别。用户看到的是预览,判断的却是工具好不好用——于是「能预览但不能处理」和「能处理但不能预览」的文件都会被当成 bug 报上来。这篇笔记把这条裂缝完整拆开:两套体系各自由谁解码、常见的错位在哪、产品上怎么兜。

两套解码体系

预览走浏览器原生解码。<video>/<img> 能不能播,取决于你用的浏览器带没带这个 codec:Chrome/Edge 带 H.264,不带 HEVC(要装扩展或靠硬件);Firefox 连 HEVC 都拒绝支持;WMV(VC-1)所有浏览器都不带。

处理走 ffmpeg.wasm 的编译白名单。浏览器端跑 FFmpeg,能用哪些解码器看构建时的 build flags。我们用的 core 编入了 x264/x265/vpx/mp3lame/theora/vorbis/opus/webp/freetype/zlib/ass/zimg,没有 AMR、没有 AV1/aom——这是从失败日志的 build flags 里核实的,不是猜的。

两份清单不一样,裂缝就在这:AV1 文件浏览器自己能解(预览正常),塞进 wasm 处理必失败;反过来 WMV 预览是灰块,wasm 里却有 WMV 解码器,转码照样成功。

案例一:AV1——「能预览不能处理」陷阱

用户上传 AV1 编码的 MP4,预览流畅,点处理报「无法解析文件」。从用户视角这是灵异事件。正确的产品行为是在报错文案里点破这层:「此格式/编码不被浏览器端引擎支持」要翻译成可行动的路——先用其他工具转成 H.264 MP4。判定的关键证据来自失败日志:Error while opening decoder / Not yet implemented 这两类 stderr 指纹,对应的就是白名单缺口而非文件损坏。

AMR 音频是同族问题:手机录音常见的 .amr,浏览器解不了、wasm 也没编入,只能明确告知不支持。

案例二:WMV/HEVC——「能处理不能预览」

反方向的错位更有迷惑性。用户上传 WMV,预览灰块,「时好时坏」——其实从来不是时好时坏,是测试文件和灰块文件编码不同(H.264 测试文件能播,WMV 必灰)。追了半天 HMR、blob URL 生命周期,最后发现和代码毫无关系。

产品上的正确姿势分两步:一是 <video> onError 后把灰块换成明确文案「浏览器无法预览此编码(常见于 WMV、HEVC),不影响处理」——管理预期比修复重要,因为这在浏览器侧根本无解;二是文件选择后立刻给大文件预警,别让用户等 32MB 引擎下载完才知道文件超限。

HEVC 在线预览还有个进阶坑:有些源我们能解析出 HEVC 直链(如小红书原画),Chrome 无硬解在线播放就是黑屏,但下载后本地播放器没问题。这类源要明确提示「下载后播放」,而不是让用户对着黑屏怀疑工具坏了。

案例三:DASH 流没音轨——数据结构层面的坑

B站视频下载后没声音,追到 yt-dlp 层发现:B站的 DASH 流视频轨和音频轨是分开的,yt-dlp 抓到的视频流 acodec=none——不是 bug,是 DASH 的常态。桌面播放器会自动合流,但单直链场景没有这个机会。

解法是换通道:改走 html5 playurl 接口拿「单文件 mp4 含音轨」(durl 结构,视频音频同框)。顺带学到两个教训:请求 qn=64 实际回落 quality=16,返回值才可信;html5 直链只验浏览器 UA 不验 Referer——这决定了下载方案能不能走纯前端。

案例四:mime 不是装饰

预览判类还依赖一个容易被忽视的字段:File 的 type。CDN 返回 application/octet-stream 时(小红书图床就这么干),如果构造 File 时直接采用 blob 的 type,下游按 file.type.startsWith('video') 判类的组件会根本不渲染 <video>——预览区白屏,和编码无关。修复原则:octet-stream 或空 type 一律回落调用方声明的 mime。这个 bug 之前测不出来的原因也很典型:播种测试手动造的 File 是标准 video/mp4,恰好绕开了污染点。

测试环境的第四套体系

还有一层:自动化测试的 headless Chrome(Chrome for Testing)不带 H.264 解码器。E2E 用的测试视频必须是 VP8 WebM,用 mp4 会在 videoWidth=0 上卡死。于是实际上存在四套解码能力:用户浏览器、wasm 白名单、headless 测试环境、以及各不相同的移动端。写测试和写报错文案时都得记得自己在哪一套里。

产品收束:三层防线

把这些教训收束成三层防线,现在全站在用:

  1. 入口层:文件类型/大小/分片格式(.m3u8/.key)在选择后立即预检,超限和大文件预警前置,不让用户白等。
  2. 预览层:解码统一走双路兜底(createImageBitmap 失败回落 <img>),<video> 统一走带错误码透传的封装组件,onError 按错误码区分「编码不支持」和「加载失败」两种文案。
  3. 处理层:ffmpeg 失败按 stderr 指纹分类翻译,每类给可行动提示;「能预览不能处理」的 codec 缺口(AV1/AMR)在报错里明确指路。

「能预览 ≠ 能处理」不是要消灭的 bug——两套解码体系的裂缝是架构事实——而是要在产品的每一层都向用户诚实翻译的事实。

(本站全部工具在浏览器本地处理媒体文件,格式转换、抽帧、压缩——遇到不支持编码时会明确告诉你原因和出路。)

RELATED TOOLS · 把文章里的东西用起来