Browser codec detection — three APIs, one card per codec, no guessing
CodecProbe queries three browser APIs against each codec record across multiple containers (MP4, MKV, WebM, MOV) and streaming formats (HLS, DASH, CMAF) — then compares their responses side-by-side. The results reveal which codecs your device can actually decode, where the APIs disagree, and whether your media server needs to transcode or can direct-play.
Each tested codec includes education content explaining the codec string format, spec references, and platform-specific behavior — so the results are not just data, they're documentation.
v4.7.2: 91 codec records (HEVC, Dolby Vision, AV1, VP9, AVC/H.264) with the normalized v2 database. Complete design token system — every visual property flows through
:rootcustom properties for full theme adaptability. Purple error badges now correctly adapt per-theme. DRM testing usesdecodingInfo()+keySystemConfigurationwith full scenario configs. Every codec string is validated against codec-resolve before entering the test matrix.
Media servers decide whether to transcode based on browser codec API responses. The three browser APIs return different answers for the same codec string, containers affect results independently of the codec, and platform-level parser differences mean the same query can fail on one OS but succeed on another. CodecProbe tests all three APIs across multiple containers so you see where they disagree and why.
canPlayType()matches codec strings against an internal list —"maybe"does not mean the device can decode itMediaSource.isTypeSupported()has stricter requirements than native<video>playback — a codec can work in a video element but fail in HLS/DASH streamingmediaCapabilities.decodingInfo()reports hardware decode capability, HDR transfer functions, and power efficiency — but most media server apps don't query it- Safari's
canPlayType()returns""for all Dolby Vision codec strings, even on devices with DV hardware —mediaCapabilitieswithtransferFunction: 'pq'reveals the actual support thatcanPlayType()hides
A device can have HDR decode hardware but a display that cannot render PQ content. iPad panels below 1000 nits return supported: false for transferFunction: 'pq' even with DV Profile 5 hardware — the API reflects the display limitation, not the SoC. The same codec string produces different mediaCapabilities results on devices with identical chips but different displays.
- The same HEVC codec string returns supported in MP4 but unsupported in MKV on most browsers — the difference between a fast remux and a slow transcode
- Chrome's media parser rejects
video/quicktime(MOV) andvideo/x-matroska(MKV) MIME types entirely — the API returns unsupported before evaluating the codec string. Firefox and Safari parse those MIME types normally - Even with
video/mp4, Chrome on Linux rejects all Dolby Vision FourCC tags (dvh1,dvhe,dvc1,dvhp,dav1,dvav) and dual-codec supplemental strings (e.g.hvc1.2.4.L153.B0, dvhe.08.09) — its media stack has no DV parser on that platform - These parser-level failures are distinct from decode-level failures: "the browser doesn't recognize this codec string" is a different problem from "the browser parsed it but can't decode it"
Each codec is tested against all three APIs. Results are shown as numbered color-coded badges (green = supported, yellow = maybe/partial, red = unsupported, purple = API threw exception/timed out) so disagreements are visible at a glance.
| Badge | API | Returns | What it actually checks |
|---|---|---|---|
| 1 | canPlayType() |
"probably" / "maybe" / "" |
Codec string syntax against an internal list. No hardware awareness — "maybe" does not mean the device can decode it. |
| 2 | MediaSource.isTypeSupported() |
true / false |
Whether the codec can be fed through MSE for adaptive streaming. Stricter than badge 1, but still no hardware info. |
| 3 | mediaCapabilities.decodingInfo() |
{ supported, smooth, powerEfficient } |
Actual hardware decode capability, including HDR transfer function (pq/hlg) and color gamut (rec2020/p3). The most accurate API. |
When badges disagree, that's the signal. A red 1 with a green 3 means the oldest API is wrong. A green 1 with a red 2 means native playback works but MSE streaming won't.
Device-level DRM detection uses requestMediaKeySystemAccess() at startup to discover available key systems. Per-codec DRM tests then use mediaCapabilities.decodingInfo() with keySystemConfiguration — the same API as badge 3, extended with DRM config. Only device-confirmed systems are tested per codec.
| Key System | Typical Platform | What CodecProbe Reports |
|---|---|---|
| Widevine | Chrome, Android | Security level (L1 hardware / L3 software) |
| PlayReady | Edge, Xbox | Robustness level |
| FairPlay | Safari, iOS | Key system availability |
| ClearKey | All (W3C standard) | Unencrypted key delivery support |
DRM results show the full decodingInfo() config including keySystemConfiguration, along with { supported, smooth, powerEfficient } response and resolved security level.
91 codec records across 5 codec groups. Each record tests against multiple containers (file + streaming) and all three APIs per container. Streaming scenarios use type: 'media-source' for MSE validation.
| Codec | Records | Profiles/Variants | Containers |
|---|---|---|---|
| HEVC/H.265 | 15 | Main, Main 10, Main Still Picture, High Tier, Levels 3.1–6.1, SDR/HDR10/HLG | MP4, MKV, MOV |
| Dolby Vision | 29 | Profiles 4, 5, 7, 8.1, 8.2, 8.4, 9 (AVC), 10 (AV1), Levels 01–10, film framerates, supplemental dual-codec strings | MP4, MKV, MOV |
| AV1 | 12 | Main (P0), High (P1), Professional (P2), Film Grain, High Tier, Levels 3.0–6.0, SDR/HDR10/HLG | MP4, MKV, WebM, MOV |
| VP9 | 21 | Profiles 0–3, Levels 1.0–6.0, 8/10/12-bit, SDR/HDR10/HLG, full and limited range | MP4, MKV, WebM |
| AVC/H.264 | 14 | Baseline, Constrained Baseline, Main, High, Constrained High, Progressive High, High 10, High 4:2:2, Extended, avc3, Levels 3.0–5.2 | MP4, MKV, MOV, 3GP |
The following codec groups have empty v2 shells and are blocked on codec-resolve decoder support:
- Video: VVC/H.266, VP8, Legacy (MPEG-4 Part 2, H.263, Theora)
- Audio: Dolby (AC-3/E-AC-3/AC-4), DTS, Lossless (FLAC/ALAC/Opus/PCM), Standard (AAC/MP3/Vorbis), MPEG-H 3D Audio
- Progressive testing — cards appear immediately with PENDING status, results fill in as each test completes
- Batched execution — 10 codecs tested in parallel per batch, 2 retries with 1s timeout per test
- Search and filter — filter by support level (all/supported/unsupported/video), search by codec name or description
- JSON export — full results with device fingerprint, DRM info, and all three API responses per codec
- Three themes — Dark OLED (default), Light, Retro Terminal — persisted in localStorage
- Keyboard shortcuts —
/to focus search,Escto clear, standard navigation - Accessibility — ARIA labels, skip links, screen reader announcements,
prefers-reduced-motionsupport - Device detection — UAParser.js v2.x with Client Hints API (Chromium) and iPad-specific detection via
withFeatureCheck() - Offline PWA — service worker precaches all assets, works without network after first visit
- Zero runtime dependencies — everything bundled at build time, no CDN or external requests
- Fluid layout — CSS intrinsic sizing with
clamp()/min()/auto-fit, no hardcoded breakpoints - Design token system — CSS custom properties for typography (1.25 scale), spacing (4px grid), and border-radius — any visual change is a single token edit
- Education content — codec string breakdowns, platform notes, and cited spec references
| Badge | Meaning |
|---|---|
| SUPPORTED (green) | All queried APIs confirm support |
| PROBABLY (green) | Most APIs confirm, at least one disagrees |
| UNSUPPORTED (gray) | No API reports support |
| FAILED (purple) | Test threw an error or timed out after retries |
Each codec card shows three numbered badges matching the API table above. When they agree, the result is clear. When they disagree, click the card to expand API details — the response from each API is shown individually so you can see exactly which one differs and why.
Common patterns:
- 1 red, 3 green —
canPlayType()doesn't recognize the codec string, but hardware decode is available - 1 green, 2 red — native
<video>playback works, but the codec can't be used in MSE/adaptive streaming - 1 yellow —
"maybe"response, which means the browser parsed the codec string but makes no guarantee about playback
Codec support varies across platforms. These are factual observations from API responses, not judgments about platform quality.
- Dolby Vision Profile 8.1 hardware decode on webOS 6+
- webOS 25+ added MKV Dolby Vision support
- Native DTS-HD passthrough to audio receivers
- Dolby Vision Profile 5 hardware on A11+ chips (iPhone 8 and later)
canPlayType()returns""for DV codec strings —mediaCapabilitiesreflects actual support- Panels below 1000 nits:
transferFunction: 'pq'returnssupported: false(API reflects display capability, not SoC) - HLS HDR requires
VIDEO-RANGE=PQin the master playlist
- Codec support varies by manufacturer, SoC, and Android version
- Widevine level detected: L1 (hardware-backed) or L3 (software-only)
- HEVC Main 10 requires Android 7.0+ with a MediaCodec hardware decoder
- Chrome/Edge (Blink) — broadest codec and container coverage
- Safari (WebKit) — strongest HEVC and Dolby Vision support;
canPlayType()results differ frommediaCapabilitiesfor DV strings - Firefox (Gecko) — Dolby and DTS codecs unavailable (requires proprietary licenses not included in the open-source media stack)
Open codecprobe.dev or serve locally:
python -m http.server 8000 # Uses pre-compiled CSS
# Open http://localhost:8000git clone https://github.com/nofear0411/codecprobe.git
cd codecprobe
npm install # Install build tools
npm run build # Compile SCSS + minify JS + bundle deps
npm run dev # Dev server + SCSS file watcher- Fork the repository
- Enable GitHub Pages → select
mainbranch - GitHub Actions builds and deploys automatically
- Access at
https://YOUR_USERNAME.github.io/codecprobe/
codecprobe/
├── index.html # Single-page application
├── sw.js # Service worker (offline PWA)
├── manifest.json # PWA manifest
├── css/styles.css # Compiled from SCSS (all themes)
├── scss/
│ ├── styles.scss # Main stylesheet
│ └── _themes.scss # Theme definitions
├── js/
│ ├── codec-database-v2.js # Normalized codec database — 91 records, 13 groups
│ ├── codec-tester.js # Three-API testing with retry logic
│ ├── device-detection.js # UAParser.js v2.x integration
│ ├── drm-detection.js # DRM/EME system testing
│ ├── ui-renderer.js # Card rendering, filters, search, education panel
│ ├── theme-manager.js # Theme switching (Dark OLED / Light / Retro Terminal)
│ ├── url-state.js # URL state management
│ ├── main.js # Initialization orchestrator + PWA install
│ └── vendor/
│ └── ua-parser.min.js # Bundled UAParser.js v2.0.9
├── icons/ # Favicons + PWA icons (any + maskable)
├── screenshots/ # OG image + PWA install screenshots
├── scripts/
│ ├── build.js # Terser minification + UAParser bundling
│ ├── inject-versions.js # Cache-busting version hashes for deploy
│ ├── db-tool-v2.mjs # Database CLI — SQL verb-first dispatch (create, insert, update, etc.)
│ └── v2-audit.mjs # Database audit, validation, and coverage analysis
├── docs/
│ ├── BUILD.md # Build system documentation
│ └── SETUP.md # Deployment guide
├── CLAUDE.md # AI assistant context
└── README.md
Every codec string in the v2 database is validated against codec-resolve before it enters the test matrix. codec-resolve is a bidirectional codec string resolver and validator — it can both generate codec strings from content parameters (resolution, HDR format, bit depth) and decode existing strings back into their constituent fields.
The migration workflow:
- Decode —
python -m codec_resolve --decode hvc1.2.4.L153.B0parses the string, validates profile/level/constraint relationships, and flags semantic errors (wrong tier for level, incompatible chroma for profile, etc.) - Validate — 180 automated tests across HEVC, AV1, VP9, AVC, VP8, and Dolby Vision confirm that every codec string follows its spec (ITU-T, ISO/IEC, IETF, VP9-ISOBMFF Binding)
- Insert — only strings that pass validation are added to the v2 database via
scripts/db-tool-v2.mjs
This prevents invalid or malformed codec strings from polluting the test matrix. When a browser reports "unsupported" for a CodecProbe test, it means the codec string is spec-correct and the browser genuinely lacks support — not that we sent a malformed string.
codec-resolve currently supports:
- HEVC — 13 profiles, constraint flag validation, tier/level cross-checks
- AVC/H.264 — 8 profiles, 20 levels, hex triplet format, constraint flags, bitrate multipliers
- Dolby Vision — profiles 5/7/8/9/10/20, HEVC/AV1 hybrid cross-validation, HLS brand inference
- AV1 — profiles 0/1/2, tier/level, color parameter validation
- VP9 — profiles 0–3, 13 levels, chroma/depth orthogonality checks
- VP8 — bare tag validation
What the APIs report vs. reality:
canPlayType()returns "maybe" for codecs without hardware decoders — it checks codec string syntax, not capability- Safari's
canPlayType()returns""for Dolby Vision codec strings, even on devices with DV hardware.mediaCapabilitiesreveals the actual support - Firefox does not expose Dolby or DTS codecs — these require proprietary licenses not included in the browser's media stack
- Chrome's
MediaSource.isTypeSupported()results can differ from<video>element support because MSE has stricter codec requirements
Platform-specific behavior:
- iOS: Hardware capabilities exceed what APIs report. 500-nit iPad panels return
supported: falsefortransferFunction: 'pq'even with DV Profile 5 hardware - Android: Codec support varies across manufacturers, SoCs, and firmware versions — the same codec string can produce different results on different devices
Scope:
- Tests API responses, not actual file playback — "supported" means the API says yes, not that a specific file will play
- DRM tests check
decodingInfo()withkeySystemConfigurationfor per-codec DRM capability, not whether a license server will issue keys mediaCapabilitiesreportssmoothandpowerEfficientbooleans but this tool does not benchmark actual decode performance
Runtime: None. UAParser.js v2.x is bundled in js/vendor/ (35.3 KB minified).
Build tools: sass, terser, ua-parser-js
Every education entry in the codec database cites its sources. 38 specifications across 6 standards bodies are referenced inline — linked where freely available, cited by number for paywalled specs.
| Standards Body | Specifications |
|---|---|
| ITU-T | H.264 (AVC), H.265 (HEVC), H.266 (VVC) |
| ISO/IEC | 14496-3 (AAC), 14496-15 (codec packaging), 23008-2 (HEVC), 23008-3 (MPEG-H 3D Audio), 23091-2 (AV1 registration), 23094-1 (VVC), 23000-19 (CMAF), 23009-1 (DASH), 13818-1 (MPEG-TS), 11172-3 (MP3) |
| ETSI | TS 102 114 (DTS), TS 102 366 (Dolby AC-3/E-AC-3), TS 103 572 (Dolby Vision) |
| IETF | RFC 6386 (VP8), RFC 6716 (Opus), RFC 8216 (HLS), RFC 9639 (FLAC) |
| Industry | AV1 Bitstream & Decoding Process, AV1 ISOBMFF Binding, VP9 Bitstream & Decoding Process, VP9 ISOBMFF Binding, Vorbis I Specification, DASH-IF Implementation Guidelines |
| Vendor | Apple HLS Authoring Spec, webOS TV AV Formats, Android Supported Media Formats, Android ExoPlayer DASH, Android ExoPlayer HLS |
| Companion | codec-resolve — codec string resolver and validator used to generate and validate the test matrix (180 tests across HEVC, AVC, DV, AV1, VP9, VP8) |
Pull requests welcome — codec database contributions especially. See CONTRIBUTING.md for setup, code standards, CLI reference, and detailed guides for adding codec records and education content.
If you have a device that reports unexpected results, open an issue with your exported JSON — it helps expand coverage for everyone.
AGPL-3.0-or-later — see LICENSE.
If you modify CodecProbe and make it available over a network, you must share your source code under the same license. This protects the community-built codec database.
UAParser.js v2.x is bundled under the same AGPL-3.0 license.