Device capability diagnostic
Measuring what a device can actually decode instead of cataloging device profiles — and how that drives direct-play vs transcode.
How Airwave learns what a real TV can actually decode — by playing a matrix of short test clips and measuring what came out — instead of guessing from
canPlayType()or maintaining a database of device profiles. Plus how you override any verdict it gets wrong.
Measure, don't catalog
Every playback decision Airwave makes for a device comes down to one question: can this panel decode this container / codec / audio combination natively, or does the server have to transcode it first? Guess optimistically and you hand the TV a stream it renders as a black screen or silent audio; guess pessimistically and you burn CPU transcoding something that would have direct-played in full quality, HDR intact.
There are two obvious ways to answer, and Airwave rejects both:
- Ask the browser.
HTMLVideoElement.canPlayType()andMediaSource.isTypeSupported()reflect what the codec string claims, not what the panel's silicon actually has a decoder for — and the two disagree constantly on real TVs. The LG C2 reports"probably"for E-AC3/DTS/TrueHD yet throwsbufferAddCodecErrorthe moment hls.js feeds them through MSE.canPlayTypelies. - Keep a catalog. A table of "LG C2 → these codecs, Sony X90J → those codecs" is a losing game:
too many combinations of panel model, firmware, browser build, decode path (native
<video>vs hls.js/MSE vs mpv), and codec/container pairing to ever enumerate — and every entry drifts with the next firmware update.
So Airwave does neither. It measures the real device, once. On first sign-in the TV plays a matrix of ~49 short clips — one per container/codec/audio/feature combination worth knowing — through the exact playback element the app uses, and records for each whether frames actually decoded and whether audio actually came out. The union of what decoded becomes that device's real playback profile, stored server-side. The philosophy, in the words of the playback broker: "measure, don't guess."
How the result drives playback
The measured rows are raw evidence. getDeviceNativeCaps()
(packages/api/src/services/capabilities/native-caps.ts) resolves them into an effective codec set
through three layers — override ?? (measured && !quirk):
- Measured — a codec or container is credited only if a clip that actually decoded contained it.
- Known-issue quirks — a few codecs pass the isolated test but fail our real playback paths (VP9 on the C2, AV1 on Apple, DTS on LG), so they're dropped by default.
- Per-device overrides — manual toggles from the Device settings page win over both.
That effective profile becomes an X-Plex-Client-Profile-Extra string, so Plex direct-plays only
what the panel proved it can handle and transcodes the rest. It also feeds a native-first delivery
ladder (direct raw-file → http progressive → hls/MSE): the better the measurement, the more
often a device stays on rung one, HDR and HEVC untouched. The playback log stamps each tune with
capsSource: "measured" | "reported" | "default" so you can see which profile was used.
In this section
How it works
The on-device measurement flow — playing each test clip, reading the decode and audio signals, and the web vs native (mpv) differences.
Formats tested
The full CAP_MATRIX: every video codec, audio codec, container, HDR mode, and edge case — what ffmpeg fabricates vs needs a real sample, and what each maps to.
Device overrides
The per-device capability toggles, the sparse capabilityOverrides JSON, and the known-issue quirks (like AV1 off by default on Apple).
Source map
| Concern | File |
|---|---|
| The test matrix (single source of truth) | packages/api/src/services/capabilities/matrix.ts |
| Clip generator (ffmpeg, from the matrix) | apps/server/scripts/gen-capability-media.ts |
Serving clips + manifest (/caps/media/*) | apps/server/src/index.ts, packages/api/src/services/capabilities/service.ts |
| Probe screen — measure & record (web) | apps/tv-web/src/features/diagnostic/diagnostic.tsx |
| Probe screen — measure & record (mpv/native) | apps/tv-native/src/features/diagnostic/diagnostic.tsx |
| Result & device models | packages/db/prisma/schema/device-capability.prisma, .../tv-device.prisma |
| Measured → quirks → overrides → effective | packages/api/src/services/capabilities/native-caps.ts |
| Codec canonicalization + known-issue quirks | packages/api/src/services/capabilities/codecs.ts |
| Device settings view + override writes | packages/api/src/services/capabilities/device-settings.ts |
| Override UI (per-codec toggles, reset) | apps/tv-web/src/routes/_auth/settings/device.tsx, apps/tv-native/app/settings/device.tsx |
| Feeding the profile to Plex | packages/api/src/services/playback/broker.ts, packages/api/src/services/plex/quality.ts |
| First-run + per-server re-run flag | apps/tv-web/src/lib/device.ts, apps/tv-native/src/lib/device.ts |
Related
- Media sources — connecting the Plex server this profile is measured against and streamed from.
- Sessions — the play log that surfaces the per-tune delivery decision this profile drives.
Observability
Watching a workflow run — the Workflow SDK's inspector UI (pnpm workflow:ui) and Airwave's own in-app run pages backed by trace tables.
How it works
The on-device measurement flow — playing each test clip, reading the decode and audio signals, deriving the profile, and the web vs native (mpv) differences.
