返回知识中心
指纹

MIME 与编解码器指纹:媒体支持信号

说明 canPlayType、isTypeSupported 和 MediaCapabilities 如何报告媒体支持,结果为何因主机而异,以及如何对照同一个配置文件逐项核对。

BotBrowser Team

文档中心

想直接看维护中的产品文档?

这篇文章对应的主题已经有文档中心页面。需要规范流程、当前参数和长期参考时,优先看 docs。

媒体类型查询会暴露浏览器接受哪些容器和编解码器,答案来自宿主的构建版本、操作系统和显卡硬件。BotBrowser 可以让 canPlayType()、isTypeSupported() 和 decodingInfo() 报告已加载配置文件的媒体类型,但报告出的类型并不证明宿主真的能解码它。

观看视频

这段英文视频调查一个现象:同一个 MIME 字符串在两台机器上可能得到不同答案。视频按顺序排查四个嫌疑对象:三个应答 API(canPlayType()、MediaSource.isTypeSupported() 和 MediaCapabilities.decodingInfo())、MIME 字符串本身、底层宿主机,以及发起查询的上下文。本地实验页面声明一份固定的媒体类型列表,并在主页面、专用 Worker 和同源 iframe 中分别询问每个 API;答案在 Ubuntu 与 Windows 两台运行机的浏览器 DevTools Console 中读取。随后引用文章和 BotBrowser 文档说明 --bot-media-types 标志及其三种模式(expand、profile、real)。录屏中没有加载 BotBrowser 配置文件,所以这些模式只是引用而非实际运行。音轨为英文。

要点摘要

用同一份带显式 codecs 参数的固定 MIME 字符串列表,在主页面、Worker 和 iframe 中运行三个 API 并逐行比较。一致性检查用 --bot-media-types=profile,想知道宿主能解码什么用 real,更看重宿主播放时用 expand。

本文目录

三个媒体 API 对比配置文件、页面、Worker 和 iframe 的编解码器支持。

三个媒体 API 分别报告什么

HTMLMediaElement.canPlayType(type) 接收一个 MIME 类型字符串,可带 codecs 参数,返回空字符串、maybe 或 probably(见 MDN canPlayType)。空字符串表示浏览器无法播放该类型,probably 表示有把握,maybe 表示不实际尝试就无法判断。这个三值回答刻意保持粗略,所以几乎不反映画质或性能。

MediaSource.isTypeSupported(type) 是静态方法,针对带可选编解码器列表的 MIME 类型返回布尔值(见 MDN isTypeSupported)。它告诉播放器,浏览器是否可能接受该类型通过 Media Source Extensions 进行流式播放。返回 true 只是对支持情况的预测,并不证明某个具体的流一定能播放。

MediaCapabilities.decodingInfo(configuration) 是三者中最详细的(见 MDN decodingInfo)。配置用来描述一个流,包括内容类型,视频还包括尺寸、码率和帧率。返回的 Promise 会给出 supported、smooth 和 powerEfficient 三个标志。后两个描述预期的解码效果,因此比单纯的支持回答更依赖硬件以及浏览器自身的历史记录。

三者都从一个 MIME 类型字符串出发。单独的容器类型(例如 video/mp4)说明不了多少,因为同一种容器可以承载很多编解码器。可选的 codecs 参数写明真正的编解码器,例如 H.264 视频用 avc1.42E01E,AAC 音频用 mp4a.40.2,VP9 视频用 vp09.00.10.08,Opus 音频用 opus。编解码器字符串刻意写得很紧凑,其中编码了 profile 和 level,所以同一编解码器家族的两个字符串可能得到不同的回答。

decodingInfo() 配置里的 type 字段同样重要。file 描述对资源的普通播放,media-source 描述通过 Media Source Extensions 的流式播放,两条路径的结果不一定一致。用与你的播放器实际工作方式相符的类型来提问,可以让回答与任务相关,也能让每次提出的问题保持一致。

下面的调用并排展示了这三种形式。

const type = 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"';
document.createElement('video').canPlayType(type);
MediaSource.isTypeSupported(type);
await navigator.mediaCapabilities.decodingInfo({
  type: 'media-source',
  video: { contentType: 'video/mp4; codecs="avc1.42E01E"', width: 1280, height: 720, bitrate: 2500000, framerate: 30 },
});

同样的代码在两台电脑上可能打印出不同的回答,在同一台电脑上浏览器更新之后也可能不同。MIME 字符串只是一个问题,答案来自接收该问题的浏览器所拥有的解码器和策略。

加密媒体又增加了一层。decodingInfo() 还可以查询密钥系统,这属于 DRM,而不是单纯的编解码器支持。DRM 指纹讲的是这条边界,WebCodecs 能力讲的是更底层的编解码器接口,它有自己的支持查询。

为什么不同机器的回答会不同

用同一 MIME 字符串比较 canPlayType() 与 MediaSource.isTypeSupported();浏览器、操作系统、GPU 和管理策略都可能改变结果。

媒体回答是多个层次共同作用的结果,每一层都可能独立变化。浏览器构建决定内置或获得授权的编解码器,一些开源构建不带专有编解码器。操作系统决定通过其媒体服务能用到哪些解码器。图形硬件决定哪些编解码器可以硬件解码。在受管理的机器上,策略和设置还可能关闭某些功能。

  • 浏览器版本:新的编解码器和容器功能会陆续加入,某条代码路径也可能失去对旧格式的支持。
  • 操作系统:即使浏览器版本相同,一个系统里安装的解码器在另一个系统里也可能缺失。
  • 硬件:较新的图形硬件可以解码较旧设备只能用软件解码、甚至完全无法解码的编解码器。
  • 配置:企业策略、用户设置和命令行参数都可能改变浏览器报告的内容。

例子能让这个规律更具体。H.264 视频和 AAC 音频在主流桌面浏览器上广泛可用,VP9 和 Opus 也很常见。HEVC 往往依赖操作系统或图形硬件提供的解码器,AV1 则依赖浏览器内置的软件解码器或较新的硬件。这些只是倾向,不是规则。

你测试所处的环境同样会改变结果。虚拟机或远程桌面会话通常没有硬件解码,因此那里的 powerEfficient 结果和高分辨率回答,可能与同一操作系统的物理桌面大不相同。比较两台主机时,请记下每台是物理机还是虚拟机,因为这一个事实就能解释许多乍看费解的差异。

这种差异正是这些回答对兼容性有用的原因,也是它们携带主机信息的原因。一个站点如果询问十几个编解码器字符串,就会得到一个模式,而这个模式往往按操作系统家族和硬件世代聚类。这就是媒体查询会出现在隐私讨论中的全部原因。

有必要准确说明这个模式的分量。媒体支持向量只是众多信号之一。它不能识别某个人,任何单个结果都不能决定什么,而且很多不同的人共享同一个模式。它的实际作用是缩小组合信号所能描述的设备范围,因此它应当与浏览器报告的其他信号保持一致。

同一台机器上的回答也会变化。浏览器更新可能加入新的解码路径,驱动更新可能启用硬件解码,策略变更可能关闭某项功能。因此,已记录的回答只是对某个配置在某个时间点的观察,更新之后应当重新核对,而不是把它当作永久属性。

并排阅读这些回答

这三个接口彼此相关但不可互换,有用的核对会用同一组编解码器字符串来比较它们。每个接口的职责不同。

  • canPlayType() 针对媒体元素中的普通播放作答,返回三态字符串。
  • isTypeSupported() 针对通过 Media Source Extensions 的流式播放作答,返回布尔值。
  • decodingInfo() 针对所描述的流作答,并额外给出 smooth 和 powerEfficient。

由此得到两条一致性预期。第一,回答的方向应当一致:如果 decodingInfo() 对某个编解码器在中等分辨率下报告 supported 为 false,那么 isTypeSupported() 对同一类型返回 true 就需要解释。第二,一个编解码器家族对该平台而言应当显得自洽。对于主流桌面浏览器,如果报告里列出了非常新的、仅硬件支持的编解码器,却缺少 H.264 这样的基础编解码器,就很不寻常,而不寻常本身就是信息。

拼写和顺序也很重要。codecs 参数有多种写法,例如同一编解码器家族可以使用不同的 profile 和 level 代码,浏览器对每种写法的回答可能不同。请按页面实际发送的形式逐字比较,并让测试列表在多次运行之间保持固定,这样回答的变化反映的才是浏览器的变化,而不是问题的变化。

smooth 和 powerEfficient 需要格外小心。它们描述的是预期的解码效果和耗电情况,所以会随硬件以及浏览器的播放历史而变化。请把它们当作比 supported 更弱的证据,也不要期望两台不同主机上的两次运行会得到相同的结果。

执行顺序在实践中很重要。canPlayType() 和 isTypeSupported() 立即作答,而 decodingInfo() 返回 Promise,所以脚本必须等待每个结果后再记录。请从同一条代码路径发出所有调用,按固定顺序收集回答,并与你自己在同一台主机上得到的基线对照。从网页或另一台机器抄来的列表描述的是另一个环境,它误导核对的次数往往多于帮上忙的次数。

下面是一个典型的读法。假设 canPlayType() 对 MP4 中的 H.264 和 AAC 返回 probably,isTypeSupported() 对同一类型返回 true,decodingInfo() 对 720p 的流报告 supported 和 smooth。三者一致,说明该主机显然可以处理基础的网页视频。如果 decodingInfo() 对高分辨率 AV1 流报告 supported 但不是 powerEfficient,这可能意味着采用了软件解码,而不是出现了矛盾。

在多个上下文中核对同一个配置文件

媒体 API 并不只存在于主页面。navigator.mediaCapabilities 可以在 Workers 中访问,媒体元素和 MediaSource 也可以在 iframe 内使用。因此页面可以从多个上下文提出同一个问题,如果浏览器配置在主页面给出一个回答,在 Worker 或框架中给出另一个回答,即使每个回答单独看都合理,整体也显得不一致。

对于一致性核对来说,这意味着检查应当在多个位置执行。从主页面、从支持该 API 的专用 Worker、以及从同源 iframe 询问同一份固定的类型列表,然后逐行比较结果。差异指向配置问题。一致并不证明配置良好,但不一致显然说明某处有问题。

如果你的工作流嵌入了第三方播放器,请在比较中也加入一个跨源 iframe。嵌入的播放器往往会自行提出这些问题,通常发生在加载时,所以它们收到的回答决定了它们请求哪个流。同源测试框架更容易运行,但它无法替代在你的工作流实际使用的框架中进行的检查。

  1. 固定一份与你的工作流实际使用的媒体相符的类型字符串列表,并写明 codecs 参数。
  2. 在主页面对该列表运行这三个 API,并记录每个回答。
  3. 在专用 Worker 和同源 iframe 中重复(该 API 在那里可用时)。
  4. 逐行比较,然后把结果与已加载配置文件应当描述的内容对照。
  5. 对每个重要类型播放一小段样本,确认主机真正能解码什么。

不一致是需要解释的发现,而不是可以忽略的噪音。常见原因包括:配置只在某一个上下文中被更改,列表混用了 profile 和 level 代码不同的字符串,以及回答反映的是主机真实的解码器而不是配置文件。先修复原因,再把整份列表重新运行一遍,因为只重跑一部分可能掩盖新出现的差异。关于跨平台比较浏览器的更完整方法,请参阅评估跨平台浏览器一致性。

一份简短的书面记录能让结果可重复。记下加载的配置文件或配置、浏览器版本、主机操作系统和图形硬件、测试的类型列表,以及各上下文的回答。再加一行关于真实播放的说明:每个重要类型的样本是否确实在该主机上播放成功。支持回答描述的是浏览器报告了什么,而播放表明的是主机能解码什么,这是两个不同的事实。

什么时候这些回答真的矛盾

回答组合判定原因
canPlayType probably + isTypeSupported true兼容两条不同路径都给出肯定结果
canPlayType "" + isTypeSupported true需解释仅通过 MSE 支持是可能的但不常见
decodingInfo supported: false + isTypeSupported true矛盾同一个编解码器,一条路径声称无法解码
supported: true + smooth: false弱证据这是质量预期,不是支持性断言
powerEfficient: true 在一台主机上、false 在另一台上不可比该字段跟随硬件和播放历史

只有第三行是缺陷,而且只有当 decodingInfo 以 media-source 类型查询时才是;以 file 类型查询时,同样两个回答属于第二行,因为此时两个 API 描述的是不同的投递路径,而不是互相矛盾。第一行是正常情况,最后两行是前面段落描述过的较软信号。

把判定写在回答组合旁边,而不是记录原始字符串,因为记录下来的 true 和 false 不会说明它们属于五行中的哪一行。判定表也正是让两台主机可比的东西:同样的五行、同样的判定,以及一个显示哪一行换了类别的 diff。

两条规则让比较保持诚实。同一个编解码器字符串只跟它自己比,绝不跟 profile 或 level 不同的邻近字符串比,因为浏览器对每一个都可以有不同的回答。并且把 smooth 和 powerEfficient 当作某台具名主机上的观察,而不是可移植性断言,因为两者都跟随硬件和浏览器自身的播放历史。

媒体查询的隐私边界

只查询页面确实需要的格式,并把失败的 MIME 类型与浏览器版本一起记录;MediaCapabilities 不需要保存完整能力清单。

维护发布检查清单的团队可以为媒体支持增加一行。记录固定的类型列表、各上下文的回答和日期,并在浏览器更新或配置文件变更之后重新运行该列表。小而定期的检查能及早发现漂移的回答,并生成同事无需重复调查就能读懂的记录。

选择媒体类型模式

加载配置文件后,--bot-media-types 参数决定配置文件中的媒体类型与主机自身解码器之间的关系。它接受三个有文档说明的值(见 MIME 与编解码器文档)。

  • expand 是默认值。它优先使用本地解码器,并用配置文件定义的类型扩展列表,所以结果混合了主机的真实能力和配置文件的能力。
  • profile 只使用配置文件定义的媒体类型。它不混入主机解码器,因此适合把回答与已加载配置文件对照的一致性核对。
  • real 使用系统的实际媒体类型。它适合检查主机真正能解码什么,但可能与描述不同硬件的配置文件不一致。

请根据你要回答的问题选择模式。做一致性核对时,先用 profile,并在多个上下文中比较这三个 API。当主机上的播放比严格匹配更重要时改用 expand,想了解主机自身报告什么时使用 real。一次只改一项设置,并让类型列表在多次运行之间保持固定。

设想一个描述桌面系统的配置文件,而主机缺少该配置文件所列某个编解码器的解码器。在 profile 模式下,API 报告配置文件的列表,这正是一致性核对想看到的,但在这台主机上真实播放该编解码器仍会失败。在 real 模式下,回答跟随主机,会暴露缺失的解码器。两种模式都没有错,它们回答的是不同的问题,核对记录应当写明提出的是哪个问题。

模式行为可能在不同版本之间变化,所以在依赖本文的某个细节之前,请重新阅读文档,并在更新浏览器或配置文件之后重新运行你的固定列表。把新记录与上一份比较。更新之后立刻出现的差异,比数月之后才发现的差异容易解释得多。

BotBrowser 通过已加载的配置文件报告 MIME 类型和编解码器支持,并提供 --bot-media-types 参数,支持 expand(默认)、profile 和 real 三种模式,因此可以在主页面、Workers、Service Workers 和 iframe 之间核对 canPlayType()、isTypeSupported() 和 MediaCapabilities 的结果是否一致。这为你确认媒体回答与同一个配置文件相符提供了有文档依据的方法。BotBrowser 不能为主机添加其缺少的解码器,不能保证某个站点会接受所报告的媒体能力,也不能替代在目标工作流上检查真实播放。

相关延伸阅读:自动化浏览器中的 Widevine:EME 为何失败,如何检测。

相对参考文档新增的内容

参考页列出媒体模式。本节增加矩阵,用相同 MIME 字符串调用 canPlayType、isTypeSupported 和 decodingInfo,再比较页面、Worker 与媒体元素中的结果。不一致说明应排查上下文或主机解码器,不能据此认定某个 API 一定正确。

公开来源

实用结论

按要回答的问题选择媒体类型模式:与已加载配置文件对比用 profile,查宿主自身解码器用 real,默认混合用 expand。三个 API 之间或不同上下文之间的分歧是需要解释的发现,浏览器或配置文件更新后要重跑整份固定列表。对每个重要类型做一次真实播放,才能看到宿主实际解码的内容。

#Mime#Codec#Media#浏览器指纹识别#隐私#Mediacapabilities

本文属于指南: 什么是浏览器指纹?原理、风险、信号与隐私边界

让 BotBrowser 从研究走向生产

先用这些指南理解模型,再进入跨平台验证、隔离上下文和面向规模化的浏览器部署。