What is codec fingerprinting?
Codec fingerprinting reads which audio and video formats a browser claims it can handle, and how it phrases the claim. Several APIs answer a version of that question: HTMLMediaElement.canPlayType (reachable through both an <audio> and a <video> element), MediaSource.isTypeSupported, MediaRecorder.isTypeSupported, and navigator.mediaCapabilities.decodingInfo. They are not interchangeable: MediaRecorder answers about encoding, and legitimately refuses formats the same browser decodes without trouble. The decode surfaces are the ones backed by a single platform registry, and that shared backing is what lets a detector demand agreement without ever knowing what the host really supports.
The first thing codec answers leak is the build. H.264 (avc1.42E01E) and AAC (mp4a.40.2) are licensed codecs compiled into official Chrome builds but stripped from many open-source Chromium builds and minimal headless images. Clearcote's open-source audit therefore scores its codec-support check only when the UA contains Chrome/ and window.chrome is present, and then requires both codecs to be supported — a User-Agent override does not reach the media stack. A sibling check reads enumerateDevices kinds: Chromium enumerates audiooutput, Gecko does not, so a Firefox-shaped device list under a Chromium UA is a contradiction between two independent surfaces. Same class of tell as headless browser detection.
The second thing it leaks is implementation shape. A real canPlayType is an RFC-6381 parser, not a dictionary, and it returns one of three strings: "", "maybe", or "probably". One check exploits that twice. Asking video/mp4; codecs=bogus must return "" — a lookup table keyed on literal MIME strings often misses the codecs parameter and answers "probably" for the mp4 container. And RFC-6381 quoting is transparent to a parser, so audio/wav; codecs=1 and audio/wav; codecs="1" must agree, while a string-keyed map sees two distinct keys and desyncs. Neither cell carries any information about real codec support, which is precisely why they discriminate. A separate check exploits placement: canPlayType is implemented once, on HTMLMediaElement.prototype, so a <video> element, an <audio> element and a saved prototype reference must all return identical strings for every MIME string asked.
The cross-API contradiction is narrower than it looks, and deliberately so. Because decodingInfo and canPlayType read the same registry, the audit compares them across six codecs and records a failure in only two hard directions: canPlayType returning "probably" while decodingInfo reports supported: false, or canPlayType returning "" — an explicit no — while decodingInfo reports supported: true. The vague middle answer "maybe" never fails, because the spec permits it to mean "cannot commit". A hard disagreement cannot arise from a real decoder; it means one surface was overridden and the other was not.
One reading that is routinely misread as a defect: decodingInfo also returns smooth and powerEfficient, and both legitimately come back false on VMs, cloud instances, and any host falling back to a software decoder — they are also specific to the exact resolution and bitrate probed, not general verdicts. That is the platform reporting honestly about itself, not a lie, and forcing them to true on a machine with no hardware decode path manufactures a contradiction where none existed. The general principle holds here as everywhere: partial patches are what get read, not values. See browser fingerprinting and how detection works.