Skip to content
Anti-Bot

What Is Codec Fingerprinting?

Pim

Pim · Scrappey Research

July 30, 2026 4 min read

On this page

Codec fingerprinting reads which audio and video formats a browser claims it can decode, and uses that matrix to identify the build, its licensing, and the host operating system. The list is queried through canPlayType(), MediaSource.isTypeSupported(), and MediaCapabilities.decodingInfo(). It carries only moderate entropy on its own, but it is unusually good at one specific job: separating an officially branded browser build from an open-source build of the same engine, because the difference between them is largely a licensing question.

Quick facts

Read throughcanPlayType(), MediaSource.isTypeSupported(), MediaCapabilities.decodingInfo()
Key splitLicensed codecs (H.264, AAC) ship with branded builds, often not with open builds
OS componentSome formats are decoded by the platform, so support varies by operating system
Coherence useA Chrome User-Agent without the licensed codec set is a contradiction
Structural checkThe same MIME type must get the same answer from a video and an audio element

Why the codec matrix tracks the build, not the engine

Codec support is not purely a function of the rendering engine. It is a function of what the person who compiled the browser was licensed and willing to ship. H.264 video and AAC audio are covered by patent licensing, so official vendor builds include the decoders while open-source builds compiled from the same source frequently do not, or defer to whatever the operating system provides.

This produces a reliable structural signal. A browser presenting a Chrome User-Agent implies an official build, and an official build implies the licensed codec set. A request claiming Chrome that reports no H.264 and no AAC support is far more likely to be an open build of Chromium wearing a Chrome identity, since a genuine Chrome installation that cannot decode the most common video format on the web is close to nonexistent.

A second layer comes from the operating system. Some containers and codecs are decoded by platform frameworks rather than by the browser, so the supported matrix shifts with the OS and even with which media packs are installed. That makes the codec list a weak but genuine platform oracle, corroborating the stronger ones described in engine and OS oracle fingerprinting. Related digital-rights capabilities behave the same way: the presence and baseline behaviour of the encrypted-media stack is another build-level property that branded and open builds do not share.

Three APIs that must give one answer

The browser exposes codec capability through several APIs that were added at different times, which makes the surface a natural cross-API coherence target.

  • canPlayType() - the oldest, returning the empty string, maybe, or probably. Critically it is specified to be element-agnostic: a video element and an audio element must return the same answer for the same MIME type, because the question is about the browser, not the element. A browser where the two disagree is answering from something other than a real codec table.
  • MediaSource.isTypeSupported() - used for streaming, returning a strict boolean. Anything canPlayType reports as probably should generally be supported here too.
  • MediaCapabilities.decodingInfo() - the modern API, returning not just support but whether decoding is smooth and power-efficient. Its supported flag must agree with the other two.

There is also a parser check that needs no reference data at all. Codec strings follow a defined syntax, so a browser should reject malformed ones and handle valid but obscure ones correctly. A capability table that answers optimistically to strings a real parser rejects was not produced by a parser - a straightforward spec invariant violation.

What this means in practice

Codec fingerprinting will not identify an individual. Its entropy is low, it groups visitors into broad buckets, and those buckets shift slowly. What it does well is test whether a declared browser identity is backed by the build characteristics that identity implies - which is why it appears in detection scripts far more often than its entropy would justify.

The practical consequence for anyone running browsers at scale is that the choice of build matters more than the User-Agent string attached to it. Running an open-source build compiled without licensed decoders while presenting a branded identity creates a contradiction that no header change addresses, because the decoders are either compiled in or they are not. Matching the claim to the binary - or simply presenting the build you are actually running - avoids the entire issue.

For teams whose goal is public web data rather than browser engineering, this is one more argument for not assembling the stack by hand. A managed web data API runs complete, conventional browser builds, so the codec matrix matches the identity presented without anyone auditing a capability table.

Code example

javascript
// The codec matrix, and the coherence checks that make it useful.
const TYPES = [
  'video/mp4; codecs="avc1.42E01E"',   // H.264 - licensed
  'audio/mp4; codecs="mp4a.40.2"',     // AAC   - licensed
  'video/webm; codecs="vp9"',          // VP9   - royalty-free
  'video/mp4; codecs="hvc1.1.6.L93.B0"' // HEVC - platform dependent
];

const video = document.createElement('video');
const audio = document.createElement('audio');

const matrix = TYPES.map(t => ({
  type: t,
  canPlay: video.canPlayType(t),
  mse: window.MediaSource ? MediaSource.isTypeSupported(t) : null,
  // Spec: canPlayType is element-agnostic, so these must match
  elementAgnostic: video.canPlayType(t) === audio.canPlayType(t)
}));

// Coherence: a Chrome-branded UA implies a build with licensed decoders
const claimsChrome = /Chrome\//.test(navigator.userAgent) && !/Firefox/.test(navigator.userAgent);
const hasLicensed = video.canPlayType('video/mp4; codecs="avc1.42E01E"') !== ''
                 && video.canPlayType('audio/mp4; codecs="mp4a.40.2"') !== '';

console.log({
  matrix,
  brandingCoherent: !claimsChrome || hasLicensed,
  agnosticViolations: matrix.filter(m => !m.elementAgnostic).map(m => m.type)
});

Related terms

Concept map

Concept map

How Codec Fingerprinting connects

The terms most directly tied to this one. Hover a node to see its neighbours, click to preview, drag to rearrange.

0 terms · 0 connections
You are here · Anti-Bot
Building map…

Frequently asked questions

Why does a Chrome User-Agent without H.264 support look suspicious?

Because H.264 and AAC decoders are covered by patent licensing, and official vendor builds ship them while open-source builds compiled from the same source often do not. A genuine Chrome installation that cannot decode the most widely used video format on the web is essentially nonexistent, so the combination points to an open build presenting a branded identity.

How much entropy does codec support actually provide?

Not much - it sorts visitors into broad buckets defined by build, licensing, and operating system rather than distinguishing individuals. Its value is corroborative: it tests whether a declared identity is backed by the build characteristics that identity implies, which is a different and often more actionable question than uniqueness.

What does it mean if a video and an audio element disagree about the same MIME type?

It means the answer is not coming from a real codec table. The specification defines canPlayType as a question about the browser rather than the element, so the same type must produce the same answer from both. A disagreement indicates the capability response is being generated per element rather than read from the browser build.

Last updated: 2026-07-30 · Facts last verified: 2026-07-30