瀏覽器能解碼哪些影像與影片格式,會洩露引擎、版本乃至 GPU。解析 Accept、canPlayType 與 decodingInfo 三種讀取方式。
各家瀏覽器能解碼哪些影像與影片格式,至今仍不統一。JPEG XL 就是最明顯的例子:Safari 從 Safari 17 起就能解碼它,而 Chromium 與 Firefox 在是否正式支援上反覆搖擺。只要兩個引擎對「能解碼什麼」的回答不一樣,「你支援哪些格式?」這個問題的答案就能反過來識別提問的瀏覽器。本文討論的正是這一指紋面:影像與媒體的解碼能力。它可以透過三種方式讀取,其中一種能深入到你的硬體。
重點速覽
- 格式支援在三個層面被讀取: HTTP
Accept請求標頭(不需要 JavaScript)、canPlayType()與isTypeSupported()這類能力查詢,以及MediaCapabilities.decodingInfo()——它額外回傳smooth與powerEfficient。 - 單獨使用時訊號較粗。 格式支援與瀏覽器家族和版本高度相關,基本只是把 User-Agent 又說了一遍,別指望靠它鎖定某個人。
- 它有兩種真正的用途: 一是與宣稱的 UA 做矛盾校驗,二是提供 UA 裡沒有的硬體解碼資訊。
powerEfficient是最有意思的欄位。 它表明某個編解碼器是否由硬體解碼,反映的是 GPU、驅動程式與作業系統——不需要 WebGL,也不需要權限彈窗。- 它的衰減曲線與其他訊號不同。 格式支援隨瀏覽器、系統與驅動程式更新而變化,與 canvas 或字型訊號的變化節奏並不相同。
讀取方式一:Accept 請求標頭
第一種讀取發生在任何腳本執行之前。請求頁面或圖片時攜帶的 Accept 標頭會列出瀏覽器接受的影像格式。較新的 Chromium 會送出類似 image/avif,image/webp,image/apng,image/svg+xml,image/* 的內容,其他引擎送出的清單則更短或順序不同。這就是伺服器在第一個請求上看到的東西,純 HTTP 指紋識別一文介紹了整組請求標頭如何在沒有 JavaScript 的情況下被讀取。格式清單能快速暴露引擎和大致版本,而你為封鎖腳本安裝的任何工具都碰不到它。
它隨時間變化的原因很簡單:瀏覽器發布一個解碼器,就會加入相應的標記。MDN 的影像檔案類型參考列出了各種格式及其 MIME 類型,Can I Use 的 AVIF 與 JPEG XL 頁面則展示了支援情況有多不均衡。AVIF 如今幾乎已普及;JPEG XL 仍然分裂,正因為各家尚未統一,它才成了一個有區分度的訊號。
讀取方式二:JavaScript 能力查詢
腳本可以直接詢問,而不必從請求標頭推斷。HTMLMediaElement.canPlayType() 接收 MIME 類型和可選的編解碼器字串,回傳 ""、"maybe" 或 "probably"。MediaSource.isTypeSupported() 對串流媒體管線做同樣的事,影像則可以透過載入每種格式的極小 data URI 樣本、看能否解碼來探測。
const probes = [
'video/mp4; codecs="avc1.42E01E"',
'video/mp4; codecs="hev1.1.6.L93.B0"',
'video/webm; codecs="vp9"',
'video/mp4; codecs="av01.0.05M.08"',
'audio/ogg; codecs="opus"',
];
const v = document.createElement('video');
const vector = probes.map((p) => v.canPlayType(p) || 'no').join('|');
得到的是一個短字串,其組合隨引擎、版本與作業系統而異,因為 HEVC 等專有編解碼器往往取決於平台的媒體框架,而不只是瀏覽器本身。注意 "maybe" 與 "probably" 的區別本身就是不同實作之間的差異,而不只是「行」與「不行」。
讀取方式三:MediaCapabilities 與硬體資訊
第三種讀取才是本文值得單獨成篇的原因。navigator.mediaCapabilities.decodingInfo() 接收完整設定——編解碼器、解析度、位元率、影格率——並回傳三個布林值:supported、smooth 與 powerEfficient。
const info = await navigator.mediaCapabilities.decodingInfo({
type: 'file',
video: {
contentType: 'video/mp4; codecs="hev1.1.6.L93.B0"',
width: 3840, height: 2160, bitrate: 20_000_000, framerate: 60,
},
});
// { supported: true, smooth: true, powerEfficient: true }
powerEfficient 實際上反映的是能否硬體解碼。GPU 能用硬體解碼 4K HEVC 或 AV1 的機器,與只能退回軟體解碼的機器,給出的答案不同,而答案取決於 GPU 世代、驅動程式與作業系統媒體堆疊。對一組編解碼器、尺寸與影格率的組合逐一探測,就能得到一個帶有硬體特徵的小向量,這是 User-Agent 不攜帶的資訊。它相當於 WebGPU 指紋識別中 GPU 訊號在媒體 API 裡的對應物,也能用於指紋與硬體不相符一文所說的交叉校驗,而且既不需要權限,也不需要 canvas。
實話實說:熵有多少
單獨看並不多。兩個人在同一作業系統上執行同一版本的 Chrome,Accept 清單與 canPlayType 向量幾乎一致,所以這一指紋面的軟體部分基本只是重述瀏覽器家族與版本。直說吧:格式支援單獨拿出來,並不是一個強識別碼。
它在兩種情況下變得有用:
- 矛盾校驗。 宣稱是 Safari 卻無法解碼 HEVC,或宣稱是新版 Chrome 卻缺少 AV1,都說不通。偵測系統會把它與無頭瀏覽器偵測(缺失編解碼器是經典破綻)搭配使用,也用於更廣泛的一致性檢查。
- 硬體資訊。 在一組探測上得到的
powerEfficient與smooth,能把瀏覽器版本相同但 GPU 不同的機器區分開,UA 做不到這一點。
有一個相鄰的話題需要區分:WebRTC 指紋識別讀取的是瀏覽器為即時通話宣告的編解碼器,而本文討論的是對儲存媒體、串流媒體與影像的解碼能力。
穩定性:不同的衰減曲線
canvas 與字型訊號會在你安裝字型或更新圖形堆疊時改變。格式支援則在瀏覽器發布或移除解碼器、作業系統更新媒體框架、或驅動程式更新開啟或關閉硬體解碼時變化。因此這個訊號呈階躍式變化,與更新週期綁定。對想維持穩定識別碼的追蹤方來說,這好壞參半:兩次更新之間很可靠,更新後卻會突然改變。對防禦追蹤的一方來說,更新確實會改變這部分特徵,但由於大量使用者幾乎同時更新,你多半只是換進了另一群人裡,而不會因此變得顯眼。
你能做什麼
這裡沒有開關。格式支援是功能性特性,偽造它會讓影片與圖片出問題。切實可行的做法:
- 使用主流且保持更新的瀏覽器,讓你的答案與一大群人一致,而不是一種罕見組合。
- 如有條件,優先使用能限制媒體能力 API 暴露資訊的抗指紋模式;它們會犧牲一部分功能,換來更小的暴露面。
- 對只偽造 UA 而不符合解碼特徵的工具保持警惕——這種不相符恰恰是這類偵測最擅長抓的。
想看看你的瀏覽器實際回報了什麼、與其他訊號是否一致,可以執行指紋檢測。
常見問題
編解碼器支援是強指紋嗎? 不是。它訊號較粗,與瀏覽器版本緊密相關,基本只是重述 User-Agent。它的價值在於矛盾校驗與硬體解碼資訊。
封鎖 JavaScript 能阻止它嗎?
只能阻止 JavaScript 那幾種讀取。Accept 標頭無論如何都會在第一個請求上送出。
為什麼 powerEfficient 比 supported 更有揭示性?
supported 跟隨瀏覽器建置,而 powerEfficient 跟隨你的 GPU、驅動程式與作業系統。


