The 30-second mental model
Encrypted media + licence (with policy) + on-device CDM/TEE. Without all three you have either no DRM or no playback. The CDM does the enforcement; the licence server expresses the rules; the packager provides the encrypted content.
System IDs
- Widevine:
edef8ba9-79d6-4ace-a3c8-27dcd51d21ed - PlayReady:
9a04f079-9840-4286-ab92-e65be0885f95 - FairPlay:
94ce86fb-07ff-4f43-adb8-93d2fa968ca2
Security levels (Widevine)
| Level | Keys | Decode | Typical tier |
|---|---|---|---|
| L1 | TEE | TEE | UHD/HDR |
| L2 | TEE | Rich OS | HD (rare) |
| L3 | Software | Software | SD |
Robustness strings (EME)
SW_SECURE_CRYPTO≈ L3SW_SECURE_DECODE— software but secure pathHW_SECURE_CRYPTO≈ L2HW_SECURE_DECODE≈ L1 videoHW_SECURE_ALL= full L1
Encryption schemes
cenc— AES-CTR full sample. Older Widevine/PlayReady.cbcs— AES-CBC pattern (1:9). FairPlay-compatible. Modern multi-DRM default.
EME flow (web)
navigator.requestMediaKeySystemAccess('com.widevine.alpha', config)access.createMediaKeys()→video.setMediaKeys(keys)- Listen for
encryptedevent,createSession() - Listen for
messageevent, POST to licence URL session.update(response)with the licence- Handle later
messageevents with messageTypelicense-renewal
MediaDrm flow (Android, online)
new MediaDrm(WIDEVINE_UUID)→openSession()getKeyRequest(sessionId, initData, "video/mp4", KEY_TYPE_STREAMING, null)- POST request bytes; receive licence bytes
provideKeyResponse(sessionId, response)new MediaCrypto(WIDEVINE_UUID, sessionId)codec.configure(format, surface, crypto, 0)closeSession(sessionId)when done
MediaDrm key types
KEY_TYPE_STREAMING= 1 — online, in-memoryKEY_TYPE_OFFLINE= 2 — persistent; returns keysetIdKEY_TYPE_RELEASE= 3 — release a persistent licence
MediaDrm exceptions to know
NotProvisionedException— run getProvisionRequest, retryDeniedByServerException— server denied; check token / entitlementMediaDrmStateException— session in wrong stateUnsupportedSchemeException— Widevine not on this deviceResourceBusyException— no session slots; check leaks
Policy patterns
| Use case | license_duration | playback_duration | persist |
|---|---|---|---|
| VOD | ~6h | — | no |
| 48h rental | 30d | 48h | yes |
| EST own-it | long | — | yes |
| Live + rotation | short | — | no |
| UHD/HDR | session | — | no, HDCP 2.2+ |
Per-resolution KIDs
Premium content uses one KID per resolution tier. The licence server omits keys the device shouldn't be able to use. Concurrency limits and geo-blocking are application concerns, not policy fields.
Chrome debug
chrome://components— CDM version + check for updatechrome://media-internals— full pipeline + EME log- EME requires HTTPS or localhost (secure context).
- CDM runs in a sandboxed utility process, not the renderer.
DASH manifest landmarks
cenc:default_KID— KID for the AdaptationSetContentProtection schemeIdUri="urn:uuid:..."per DRMcenc:psshchild of ContentProtection — base64 PSSH box
Dates to know
- Widevine Cloud Licence Service ceases:
2027-04-13 - Your training:
2026-05-19–2026-05-21(London)
Provisioning
Provisioning gives a device its Widevine identity. Without it, no licence server will issue. Provisioning 3.0 introduces per-origin / per-app device certificates so leaks don't cross app boundaries.
Last-minute trap list
- EME without HTTPS → fails.
- Forgetting renewal handler → playback dies at expiry.
- Same KID for SD/HD/UHD → can't enforce per-resolution policy.
- Live without
setMultiSession(true)→ glitches at rotation. - Caching licence responses across devices → all bug.
- Trusting client-claimed device tier → use the CDM identity in the request.