The DRM handshake fails more than you think

May 20, 2026

The spinner that never becomes a picture

The user pressed play on a premium channel. The spinner turns for two seconds, then a dialog: Playback error 0x80070005. They don't know what a license server is. They know the free channels work and this one doesn't, and they blame the app.

DRM is the part of a video player most likely to fail in the living room and the part users understand least. Every protected stream has an invisible handshake between the player and a license server that must succeed before a single frame decodes, and on old TV hardware that handshake fails far more often than the happy-path demo suggests. The difference between a good player and a bad one is almost entirely in how it reacts when that handshake fails.

What actually happens before the first frame

Under EME (Encrypted Media Extensions) the flow is roughly this. The stream's init data — the pssh box passed to the player as initData — says which keys the content needs. The player hands that to the CDM, the Content Decryption Module baked into the TV: Widevine or PlayReady on most sets, FairPlay on Apple. The CDM produces a license request, a signed blob the player POSTs to the license server. The server checks who you are and what you're entitled to, and if it's happy it returns a license carrying the content keys. The CDM installs the keys, and only then does the encrypted media decode.

Four parties, one network round-trip, and a signed exchange that can go wrong at every step. Treating all of those failures the same way is the mistake.

Retryable versus terminal: the only distinction that matters

Split every license failure into two buckets, because they need opposite reactions.

Retryable failures are about the pipe, not the permission. A license-request timeout, a 503 from an overloaded server, a transient CDN blip, a connection dropped halfway through. The request was fine; the network wasn't. These you retry.

Terminal failures are answers, not accidents. A 403 means the server understood you and said no. Region and entitlement denials — wrong country, subscription doesn't include this channel — are also a firm no. Output-protection refusals are terminal too: the CDM asked for HDCP on the HDMI output, didn't get it (a cheap cable, a non-compliant display, a splitter in the path) and refused to release keys for high-value content. Retrying any of these changes nothing. The answer will be the same no.

Never retry a 403. This is the one people get wrong. A 403 is not a hiccup; it is the server telling you the request is unauthorized. Retrying it three times with backoff just makes the user wait three times as long for the same rejection, and hammers a license server that is working exactly as designed. Read the status code before you decide to retry, not after.

Backoff, but only around the retryable stuff

For the genuinely transient failures, wrap the license POST in a bounded retry with exponential backoff and jitter — something like 200ms, 400ms, 800ms, then stop. Two or three attempts, not ten. The jitter matters more than it looks: when a CDN or license server recovers from an outage, thousands of TVs retry at once, and synchronized retries just re-DDoS the thing that's trying to come back up. Spread them out.

And cap the total. A user will forgive a one-second recovery. They will not sit through fifteen seconds of silent retries — past a couple of seconds, a fast honest failure beats a slow hopeful one.

The old-stack problems nobody warns you about

Fresh hardware handshakes cleanly. The long tail of TVs already in the field does not, and two failure modes are specific to age.

Provisioning. A Widevine CDM needs a per-device certificate before it can make license requests at all, fetched from a provisioning server on first use. On an old or factory-reset set that provisioning can be missing or stale, and the symptom shows up as a license error even though the license server was never involved. If you see CDM errors before the request even leaves the device, suspect provisioning.

Certificates and the clock. PlayReady and Widevine both lean on certificates and, indirectly, on the device clock. A TV whose clock is years off — dead RTC battery, never synced to network time — can fail license acquisition or reject a server certificate as expired. It looks like DRM; it's really Date.now() returning 2016.

Robustness levels decide what can even play

Some failures aren't failures to fix — some content simply won't play on some hardware, by design. Widevine has security levels: L1 means keys and decode live in the hardware TEE; L3 means it's all in software. PlayReady draws the same line with SL3000 versus SL2000. Studios gate their best content — 4K, early release windows — behind hardware robustness. On an L3 or software-only device the license server will refuse the high-value keys, and no retry logic can change that. The right move is to know the device's level up front and request a stream tier it can actually play, so you never trigger a refusal you can't recover from.

Pre-acquire and persist, carefully

The handshake is a round-trip you can often pay ahead of time. While a channel is playing and the box is idle, you can pre-acquire licenses for the likely-next channels — the neighbours in the zap order — so the key is already installed when the user arrives. Where the scheme and the license policy allow it, a persistent license held in a persistent-license MediaKeySession survives across sessions, so a channel the user watches daily doesn't re-handshake every morning.

Two cautions. Persistent licenses have their own expiry and their own revocation, so you still handle the case where the stored key is dead on arrival. And pre-acquisition burns license-server quota, so bound it the way you bound prefetch — one or two neighbours, cancelled the moment the user moves.

Tell the user something true, quietly

When you've exhausted the retryable path and you're genuinely stuck, the last decision is UX — and a hex code is the wrong answer. 0x80070005 tells the user nothing and makes them feel the box is broken.

Map the terminal cases to plain language instead. Entitlement: "This channel isn't part of your plan." Region: "This channel isn't available in your area." HDCP: "This screen or cable doesn't support protected playback" — because that one is often literally fixable by swapping a cable. Keep it a small inline message, not a full-screen modal, and never blame the user. The failure is real, but a calm sentence they can act on beats a scary dialog with a number in it every single time.