A premium streaming service that wants to play on Chrome, Edge, Safari, Android, iPhones, smart TVs and game consoles cannot pick one DRM system. Google's Widevine, Microsoft's PlayReady and Apple's FairPlay Streaming each own a slice of the device market, and each platform ships only its own owner's DRM in the browser or operating system. The practical job is therefore not choosing a DRM but running all three at once without encrypting, storing and caching your library three times.
This article is the side-by-side view. The general architecture of encrypted streaming, with license servers and the browser's Encrypted Media Extensions (EME), is covered in Video DRM architecture. Here we go one level down: what each system calls its security levels, what its license request looks like on the wire, how one Common Encryption package serves all three, how to set key policy per track so a weak client still plays something, and what breaks in production. Version-specific facts were checked in October 2026; DRM support changes with browser and OS releases, so confirm the current state on your own target devices.
Why there are three systems
A DRM system has two halves: a Content Decryption Module (CDM) on the device, which holds keys and decrypts media, and a license service, which hands keys to CDMs it trusts. Trust is the expensive part. The vendor has to certify device implementations, ship a CDM into the browser or OS, rotate device credentials and revoke broken ones. Each platform owner does that only for its own system.
The resulting map, at a level that has been stable for years: Widevine is in Chrome, Firefox, Android, ChromeOS, Chromecast and many smart TVs and set-top boxes. PlayReady is in Edge and Windows apps, Xbox, and many smart TVs and operator set-top boxes. FairPlay Streaming is on Apple platforms only: Safari on macOS, and iOS, iPadOS, tvOS and visionOS apps through AVFoundation. Some TVs carry both Widevine and PlayReady. No Apple device runs Widevine or PlayReady in Safari, and no non-Apple device runs FairPlay, so a service that wants every screen needs all three.
The shared layer: Common Encryption and PSSH boxes
What makes three systems affordable is ISO/IEC 23001-7, Common Encryption (CENC). It standardises how media samples are encrypted with AES-128 and how each sample's key ID and initialisation vector are signalled, while leaving key delivery to the DRM. The same encrypted segment can be decrypted by any CDM that obtains the key for its key ID. Two of the four CENC schemes matter in practice:
| Scheme | Cipher | Who needs it |
|---|---|---|
cenc | AES-CTR over the protected ranges of each sample | The long-standing default for DASH with Widevine and PlayReady |
cbcs | AES-CBC with a pattern: for video, 1 encrypted 16-byte block then 9 clear blocks, repeating | Required by FairPlay; supported by modern Widevine and by PlayReady 4.0 and later |
Because FairPlay only accepts cbcs, and current Widevine and PlayReady clients also accept it, a single cbcs CMAF package is now the usual target. The holdouts are older devices, typically TVs and set-top boxes whose CDMs predate cbcs. If your device analytics show many of those, you will keep a second cenc copy for them, and that doubles storage and halves CDN cache efficiency for the affected titles.
Each DRM finds its own data through a Protection System Specific Header, a pssh box in the init segment and optionally a ContentProtection element in the DASH manifest, tagged with a system ID:
| System | System ID | What its PSSH data carries |
|---|---|---|
| Widevine | edef8ba9-79d6-4ace-a3c8-27dcd51d21ed | A protobuf with key IDs and a content identifier |
| PlayReady | 9a04f079-9840-4286-ab92-e65be0885f95 | A PlayReady Object holding a WRMHEADER XML: key IDs, algorithm, optional license URL |
| FairPlay | 94ce86fb-07ff-4f43-adb8-93d2fa968ca2 | Usually unused: HLS signals FairPlay with an skd:// key URI instead |
| Common (W3C) | 1077efec-c0b2-4d02-ace3-3c1e52e2fb4b | Key IDs only, readable by any CDM |
Widevine: security levels, robustness and the opaque request
Widevine defines three security levels. At L1, decryption and all media processing happen inside a trusted execution environment, so decrypted frames never reach memory the main OS can read. At L2, cryptography runs in the TEE but video processing does not. At L3 everything runs in software, with obfuscation rather than hardware isolation; desktop Chrome and Firefox are L3. Services commonly cap L3 playback at a lower resolution than L1 for that reason.
In EME the key system string is com.widevine.alpha, and the player asks for a robustness level per track with one of SW_SECURE_CRYPTO, SW_SECURE_DECODE, HW_SECURE_CRYPTO, HW_SECURE_DECODE or HW_SECURE_ALL. The license request the CDM emits is an opaque signed blob: the player must not parse it, only send it to the license URL and hand the response back. The license server reads the client's security level and device identity from that blob, which is why the server, not the player, decides what the client may play.
A Widevine license carries the content keys plus a policy: rental and playback durations, whether the license may be persisted for offline use, renewal intervals and output protection, such as requiring HDCP on external displays. Browsers can be given a service certificate so that the request is encrypted to your license server and the device identity is not visible in transit. Production access comes through Google or a multi-DRM vendor; there is no self-signed path.
PlayReady: security levels, the header and the SOAP challenge
PlayReady uses numbered security levels. SL150 is for development and must not get production content. SL2000 is the production level for devices whose protections are typically software-based, with hardening. SL3000, introduced with PlayReady 3.0, requires hardware-backed protection in a TEE and is what services typically demand for UHD and HDR. Licenses can require a minimum level, and they carry output protection levels that, for example, demand HDCP on digital outputs.
The PlayReady header, a WRMHEADER XML document inside the PlayReady Object, lists the key IDs and can carry a default license acquisition URL. The CDM produces a license challenge as a SOAP XML document, which the client POSTs to the license server; the response is a SOAP envelope containing one or more licenses. In EME the key system strings are com.microsoft.playready and the newer com.microsoft.playready.recommendation; the string com.microsoft.playready.recommendation.3000 asks specifically for SL3000 hardware DRM and accepts cbcs as well as cenc for H.264, HEVC and AV1 according to the documentation checked for this article. Hardware PlayReady was long an Edge-on-Windows feature; vendor documentation now describes SL3000 in Chrome on Windows 11 with recent Chrome versions, so probe for it rather than assuming either way.
FairPlay Streaming: certificates, SPC and CKC
FairPlay Streaming is built around HLS. A media playlist marks protected content with a key tag whose URI uses the skd:// scheme and whose key format is com.apple.streamingkeydelivery:
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="skd://catalog/title-8812/video-hd",KEYFORMAT="com.apple.streamingkeydelivery",KEYFORMATVERSIONS="1"When the player meets that tag, the app fetches your FairPlay application certificate, and the FairPlay module on the device builds a Server Playback Context (SPC): an encrypted request containing the content ID from the skd URI and a session key. The app sends the SPC to your key server. The server's Key Security Module (KSM), using the credentials Apple issues to each content provider, decrypts the SPC, looks up the content key and returns a Content Key Context (CKC) that wraps the key for that device session. On native apps the exchange is driven through AVContentKeySession, which also supports persistable keys for offline playback; in Safari it is reachable through EME with the com.apple.fps key system, though many web players simply let Safari's native HLS play the stream and only supply the certificate and license calls.
Apple does not publish numbered security levels comparable to L1 or SL3000. Your levers are the license policy you encode in the CKC, such as lease and rental durations and offline persistence, and the HDCP requirement for external outputs. Credentials come from Apple after you request them, and the KSM must be your own implementation or a multi-DRM vendor's.
Side by side
| Widevine | PlayReady | FairPlay Streaming | |
|---|---|---|---|
| Owner and platforms | Google: Chrome, Firefox, Android, many TVs | Microsoft: Edge, Windows, Xbox, many TVs | Apple: Safari, iOS, iPadOS, tvOS, visionOS |
| EME key system | com.widevine.alpha | com.microsoft.playready.recommendation (and .3000) | com.apple.fps |
| Trust tiers | L1, L2, L3 | SL150, SL2000, SL3000 | Not exposed as levels |
| Manifest | DASH or HLS with PSSH | DASH, Smooth Streaming or HLS with PSSH | HLS with skd:// key URI |
| Scheme | cenc or cbcs | cenc; cbcs from PlayReady 4.0 | cbcs only |
| License request | Opaque signed blob | SOAP XML challenge | SPC, answered with a CKC |
Worked example: one cbcs package, three DRMs
Take a catalogue title packaged as CMAF with a five-rung video ladder and one audio track, served as both DASH and HLS from the same segments (see CMAF and HLS vs DASH). Use separate keys per track class, not one key for the title: one for audio, one for SD video, one for HD and one for UHD. A raw-key packaging run with Shaka Packager looks like this; flag names follow its documentation, so check them against the version you install:
packager \
in=audio.mp4,stream=audio,init_segment=a/init.mp4,segment_template=a/$Number$.m4s,drm_label=AUDIO \
in=v540.mp4,stream=video,init_segment=v540/init.mp4,segment_template=v540/$Number$.m4s,drm_label=SD \
in=v1080.mp4,stream=video,init_segment=v1080/init.mp4,segment_template=v1080/$Number$.m4s,drm_label=HD \
in=v2160.mp4,stream=video,init_segment=v2160/init.mp4,segment_template=v2160/$Number$.m4s,drm_label=UHD \
--enable_raw_key_encryption --protection_scheme cbcs \
--keys label=AUDIO:key_id=<kid_a>:key=<key_a>,label=SD:key_id=<kid_sd>:key=<key_sd>,label=HD:key_id=<kid_hd>:key=<key_hd>,label=UHD:key_id=<kid_uhd>:key=<key_uhd> \
--protection_systems Widevine,PlayReady,FairPlay \
--hls_key_uri "skd://catalog/title-8812" \
--mpd_output title.mpd --hls_master_playlist_output title.m3u8In production the packager gets keys from a key service over an API such as CPIX rather than on the command line, and those keys never appear in logs. The player side, for the EME browsers, tries key systems in order and requests the strongest robustness each one can give:
// encryptionScheme is per capability; browsers that predate the field ignore it
const video = { contentType: 'video/mp4; codecs="avc1.640028"', encryptionScheme: 'cbcs' };
const audio = { contentType: 'audio/mp4; codecs="mp4a.40.2"', encryptionScheme: 'cbcs' };
const candidates = [
['com.microsoft.playready.recommendation.3000', [{ ...video }]],
['com.widevine.alpha', [{ ...video, robustness: 'HW_SECURE_ALL' }]],
['com.widevine.alpha', [{ ...video, robustness: 'SW_SECURE_DECODE' }]],
['com.microsoft.playready.recommendation', [{ ...video }]],
];
async function pickKeySystem() {
for (const [keySystem, videoCapabilities] of candidates) {
try {
const access = await navigator.requestMediaKeySystemAccess(keySystem, [{
initDataTypes: ['cenc'], videoCapabilities, audioCapabilities: [audio],
}]);
return access; // report keySystem + robustness to analytics
} catch (_) { /* not supported here: try the next */ }
}
throw new Error('no usable DRM'); // Safari: fall back to native HLS + FairPlay
}The license proxy in front of the three servers holds the business rules. It authenticates the user, checks entitlement for the title, and asks the DRM server for keys appropriate to the client: all four key IDs for an L1 or SL3000 device, audio, SD and HD for software clients, perhaps audio and SD only for unknown devices. Because keys are per track, the player's ABR logic must also drop renditions it holds no key for, or it will switch up and stall.
Failure modes
| Symptom | Likely cause | Fix |
|---|---|---|
| Plays everywhere except older TVs | Device CDM predates cbcs | Keep a cenc variant only for those device models |
| Plays SD, freezes on upshift | Client has no key for the HD key ID and ABR selected it anyway | Filter renditions by key status in the player |
| Black screen on an external monitor | License requires HDCP the output cannot provide | Surface an explicit error; offer a lower tier with weaker output rules |
| Every Safari playback fails | Expired or wrong FairPlay certificate, or skd content ID not mapped in the KSM | Monitor certificate expiry and log the parsed content ID |
| License requests fail after an app update | Player started parsing or rewriting the opaque request | Forward CDM messages byte for byte |
| Offline downloads stop playing | Persistent license expired or the device clock moved | Renew licenses on reconnect; show expiry to the user |
Remember what DRM does not do. It stops unauthorised decryption and copying on trusted devices; it does not stop a camera pointed at a screen or a compromised L3 client. That is why services pair it with per-session forensic watermarking for leak tracing.
Operations: what to monitor
Log every license request with the key system, the reported security level or robustness, device model, title, the key IDs returned and the latency. Alert on license error rates per key system rather than overall, because a FairPlay certificate problem hides inside an average. Keep a device lab, or a device cloud, that covers every key system and level you claim to support: an L1 Android phone, an L3 desktop browser, a Windows machine with and without hardware DRM, an iPhone, an Apple TV and at least two TV platforms. Rerun it on every browser and OS release, since that is when DRM behaviour changes. Rotate license-server and FairPlay credentials on a calendar, and record which vendor or service holds each one.
What to do next
- Pull device analytics and list which platforms you must serve; that fixes which of the three systems you need.
- Package one
cbcsCMAF ladder with PSSH boxes for Widevine and PlayReady and an skd:// key URI for FairPlay, and keepcenconly for devices that prove they need it. - Split keys by track class (audio, SD, HD, UHD) and write the policy table that maps security level to allowed key IDs.
- Put one license proxy in front of all DRM servers for authentication, entitlement and policy, and forward CDM messages untouched.
- Make the player probe key systems in order, report the chosen robustness, and filter ABR renditions by key availability.
- Build the device test matrix and dashboards for license errors per key system before launch.
- Add forensic watermarking if leaks of premium content are a real risk.