--- status: draft date: 2026-05-20 definition: coarse --- # DRM support Digital Rights Management for protected content via the W3C Encrypted Media Extensions (EME) specification. Covers MediaKeys / MediaKeySession lifecycle, license-server interaction, key delivery, and the per-key-system specifics for Widevine, PlayReady, and FairPlay. The cluster F foundation that consumer-facing protected- playback features (e.g., a Mux Player `drm-token` integration) build on. A **Media-src feature** in the framing from [clusters.md § Feature classification axes](./clusters.md#feature-classification-axes): without it, DRM-protected sources don't play. The engine has to set up MediaKeys, negotiate a MediaKeySession, fetch and respond to licenses, and deliver keys before the browser will decrypt segments. *Which* key systems are supported is owned by [capability-probing](./capability-probing.md) (cluster D); *setting up* the chosen key system is owned by this feature. Tracked via **[GitHub issue #1411](https://github.com/videojs/v10/issues/1411)** ("Feature: DRM Support") with three per-key-system sub-issues: [#1412 Widevine](https://github.com/videojs/v10/issues/1412), [#1413 PlayReady](https://github.com/videojs/v10/issues/1413), [#1414 FairPlay](https://github.com/videojs/v10/issues/1414). Milestone: GA. Prior art: [videojs-contrib-eme](https://github.com/videojs/videojs-contrib-eme) (Video.js v8 plugin), [Mux Player DRM integration](https://www.mux.com/docs/guides/protect-videos-with-drm) (Widevine + PlayReady + FairPlay via `drm-token` attribute). ## Status - **Composition:** not implemented in `createSimpleHlsEngine`. No DRM-related code in `packages/spf/src/` today. `#EXT-X-KEY` and `#EXT-X-SESSION-KEY` tags are not recognized by `parseMediaPlaylist`. - **Definition depth:** coarse — scope identified from GitHub issue + prior art; SPF touchpoints sketched at the cluster level; no implementation. The three sub-issues each carry per-key-system detail. - **Hard prerequisite:** [capability-probing](./capability-probing.md)'s "Key-system capability probing" phase. The probe must resolve before this feature commits to a key system, sets up MediaKeys, or fetches a license. Crisp boundary per [clusters.md § Encrypted media (DRM)](./clusters.md#encrypted-media-drm): probing answers "can we?"; this feature answers "set it up." ## Phases of complexity Scope slices around the implementation layers. Per-key-system specifics are listed within a single phase row that points at the three sub-issues, rather than as separate phases. | Phase | What | Notes | |---|---|---| | EME setup pipeline | Capability-probing's key-system verdict drives `navigator.requestMediaKeySystemAccess(...)`, which produces a `MediaKeys` instance. `mediaElement.setMediaKeys(mediaKeys)` is called **before MediaSource attachment** per W3C EME (setMediaKeys must complete before encrypted content appends; pre-attachment is the spec-safe ordering). `encrypted` event on the media element fires when init data is appended → `MediaKeySession.generateRequest(initDataType, initData)` starts a session | Shared infrastructure regardless of key system. The generic ordering is settled by spec; the SPF composition question is how to express the gate without DRM-awareness leaking into the standard `setupMediaSource` behavior — see Likely cross-cutting impact for the variant-composition framing | | License flow | Per-source license-server configuration (consumer-provided URL + optional headers / auth tokens). `MediaKeySession.message` event → fetch license from server → `MediaKeySession.update(licenseResponse)`. Pluggable hooks for consumer policies (custom headers, body transformation, response transformation) | Consumer-facing config surface lives here. Mux Player's `drm-token` attribute is one consumer of this; videojs-contrib-eme's per-key-system config is another precedent | | Per-key-system specifics | Widevine ([#1412](https://github.com/videojs/v10/issues/1412)), PlayReady ([#1413](https://github.com/videojs/v10/issues/1413)), FairPlay ([#1414](https://github.com/videojs/v10/issues/1414)). Per-system: init-data format (PSSH for Widevine, PRO box for PlayReady, content-id derivation for FairPlay), license URL conventions, license body format, server-certificate handshake (FairPlay), browser-API quirks. **FairPlay-AirPlay is a distinct key system from standard FairPlay** (see [capability-probing](./capability-probing.md)'s four-key-system enumeration) — active when content streams via AirPlay; entering/exiting AirPlay mid-playback is a *runtime state change*, not a compose-time variant, raising an open question on runtime-switching shape (see Open questions) | Three sub-issues. The shared pipeline + license flow above handle most of the machinery; each key system adds its own init-data + license-format adapters. FairPlay-AirPlay sits as a runtime-switchable variant of FairPlay specifically | | Key delivery and `keystatuschange` reactivity | Browser receives keys via `MediaKeySession.update()`; encrypted segments decrypt automatically. `MediaKeySession.keystatuses` Map tracks per-key status (`usable`, `expired`, `output-restricted`, `released`, etc.); `keystatuschange` event fires on changes. Engine reacts to status transitions (e.g., expired key → re-request) | Tier 2-ish: engine can ignore non-`usable` statuses initially (key expiry surfaces as a playback failure); richer handling is consumer-policy-driven | | Security-level capability and constraint filtering | Probe device security level (Widevine L1 hardware-backed / L2 hybrid / L3 software-only; PlayReady SL150 / SL2000 / SL3000; FairPlay key-duration / persistent-vs-streaming model) via `MediaKeySystemAccess.getConfiguration()`. HDCP output-protection requirements similarly probed. Match against per-rendition security-level requirements (e.g., 4K HDR HEVC often requires L1 Widevine) and license-server policy. Write a `deviceSecurityLevel` constraint slot read by ABR / variant selection; renditions exceeding the device's level filter out, or the engine surfaces a failure when no compatible rendition remains | Constraint+filter pattern parallel to [rendition-selection-caps](./rendition-selection-caps.md) and [hevc-variant-selection](./hevc-variant-selection.md). Probing extends [capability-probing](./capability-probing.md)'s key-system probe with security-level configuration. Borderline classification (Media-src for "play protected content correctly"; Player for customer-policy caps) — current scope leans Media-src | | Parser surface for key tags | `parseMediaPlaylist` recognizes `#EXT-X-KEY` and `#EXT-X-SESSION-KEY` from media playlists; multivariant parser surfaces session keys at presentation resolution. Init-data flows from the parsed-track output through MSE setup to the EME pipeline | Parser-side change. Today neither tag is recognized; both are silently passed through | | Encrypted-event handling on `SourceBuffer` / mediaElement | `encrypted` event on `mediaElement` triggers session creation via init-data. Once keys are delivered, segment-append proceeds normally; the engine doesn't intervene per-segment | Cross-cluster MSE concern; segment-append flow is unchanged for encrypted streams aside from the key-readiness gate | ## What's in scope vs out of scope **In scope:** - All seven phases above for HLS protected content with EME-supported key systems (Widevine, PlayReady, FairPlay) - MediaKeys / MediaKeySession lifecycle management (per-source setup, source-change cleanup) - License fetcher with consumer-pluggable URL / headers / body transformation hooks - Parser surface for `#EXT-X-KEY` and `#EXT-X-SESSION-KEY` tags - `keystatuschange` event reactivity baseline (surface failures) - Engine-composition variant for DRM-required content - Security-level probing extension to capability-probing's key-system probe (returns supported security levels per key system via `MediaKeySystemAccess.getConfiguration()`) - Security-level constraint slot (`deviceSecurityLevel`) + filter- pattern integration with rendition selection (parallel to `userVideoTrackSelection` in video-abr.md and per-cap slots in rendition-selection-caps.md) - HDCP output-protection requirement detection and gating **Out of scope (separate Media-src candidate features):** - **Key-system capability probing** — owned by [capability-probing](./capability-probing.md). Crisp boundary: probing answers "can we?"; this feature answers "set it up." **Out of scope (different architectural layer):** - Adapter-layer customer-facing API surfaces (e.g., a Mux Player `drm-token` attribute, a consumer-passed license-server URL config). The SPF feature owns the engine-side license fetcher with pluggable hooks; the adapter / consumer provides the actual URL, headers, authentication tokens. videojs-contrib-eme's `keySystems` config shape is one reference for the consumer surface. - License-server hosting and DRM token signing. Service-side concerns. - Per-device key-system installation, content-decryption-module updates. Browser / OS responsibilities. - DRM error UX (license expired, output not permitted) above the engine. Engine surfaces failures; adapter renders UX. ## Likely cross-cutting impact Things this feature probably forces decisions on, not just additions: - **Gate chain extension on MSE setup — composition-variant placement.** Per W3C EME, `mediaElement.setMediaKeys()` must complete before encrypted content is appended to a SourceBuffer; the spec-safe ordering is pre-MediaSource-attachment. Today's MSE gates per [mse-mms-pipeline.md](./mse-mms-pipeline.md): MediaElement + presentation URL + `'open'` readyState gate `setupMediaSource`; resolved track + open MediaSource gate `setupSourceBuffers`. Adding a "MediaKeys attached" gate between these is straightforward in isolation; the SPF composition question is how to do it *without* DRM-awareness leaking into the standard `setupMediaSource` (which today, and in DRM-free engine variants, has no need to know about MediaKeys). Per the failure-mode catalog's composition- variant entry: variant-specific behaviors compose into DRM-required engine variants, not as runtime branches in always-on behaviors. Two likely shapes: - **(a)** DRM-required engine variant composes a *different* `setupMediaSource` that gates on a `mediaKeysReady` signal before attaching; standard `setupMediaSource` composes into non-DRM engines unchanged. - **(b)** A new `setupMediaKeys` behavior writes to a generic "ready-to-attach" gate slot that `setupMediaSource` reads; standard engines provide a default-true writer for the slot. Option (a) is cleaner composition-variant discipline (DRM-free engine's `setupMediaSource` is genuinely unchanged); option (b) introduces a slot whose primary purpose is DRM. Lean: (a). Beyond setMediaKeys, additional DRM gates fire downstream: capability-probing's key-system verdict (Tier 1 gate, fires once per source); per-session license obtained + `keystatuschange` confirms at least one `usable` key (fires on key delivery, decryption is async beyond this gate). - **Composition variant for DRM-required content.** DRM-required engine variants compose additional behaviors (MediaKeys setup, license fetcher, encrypted-event handler) atop the standard composition; DRM-free engines don't carry the machinery. Same shape as the live / DVR / LL-HLS variant pattern. The decision point — does the consumer opt into a DRM variant upfront, or does the engine detect DRM from `#EXT-X-KEY` / `#EXT-X-SESSION-KEY` parser output and route accordingly — is open. Adapter-upfront is simpler; detect-and-route is more adaptive. - **State slots for DRM lifecycle.** New slots likely include: `drmReady` (key system probed + MediaKeys attached + license obtained), `mediaKeysReady` (intermediate gate), and possibly per-session state for license-renewal scenarios. Multi-writer characterization is open until the slot family solidifies. - **Encrypted-segment buffer behavior.** Once keys are delivered, the MSE pipeline appends encrypted segments unchanged. The encrypted-event flow happens *before* steady-state appending. No per-segment decrypt overhead from the engine's perspective; the browser handles decryption transparently. The MSE codec-change check does not fire — DRM doesn't change codec. - **Source-replacement cascade under DRM.** When the consumer changes `presentation.url`, the existing MediaKeys / MediaKeySession tear down; the new source's DRM setup runs fresh. Standard resolved/unresolved cascade per [source-replacement.md](./source-replacement.md), with MediaKeys cleanup as an additional in-place cleanup target. - **License-fetcher composability.** License fetching is the most consumer-customizable surface (custom URLs per content, custom headers / auth tokens per session, custom body transformations for Mux's DRM token format vs raw EME, etc.). The engine should expose pluggable hooks rather than hardcoded behavior. videojs-contrib-eme's `keySystems.*.getLicense()` callback is one shape; a more general request/response-transformation interface is another. - **Per-key-system browser-API differences.** Widevine, PlayReady, FairPlay all have spec-compliant EME surfaces, but the init-data formats, license-message formats, server-certificate handshakes, and key-status semantics differ. Per-key-system adapter modules (one per sub-issue) handle the system-specific logic; the shared EME pipeline calls into them via a uniform interface. ## Open questions - **Variant-decision signal source.** Adapter-upfront opt-in (consumer knows the source is DRM-protected, instantiates a DRM-capable engine variant) vs detect-from-parser (engine sees `#EXT-X-KEY` and routes to DRM-capable composition). Adapter-upfront is simpler; detect-and-route is more adaptive but adds composition-time- decision complexity. - **Composition-variant shape for the setMediaKeys gate.** Per the cross-cutting note: variant-specific `setupMediaSource` (option a) vs generic ready-to-attach slot (option b). The spec-required ordering (`setMediaKeys` before MediaSource attachment) is not in question; only the SPF composition shape is. Resolving this constrains how DRM-required engine variants are composed and whether non-DRM engines remain genuinely unchanged. Lean: (a) for composition-variant discipline. - **FairPlay-AirPlay runtime switching.** AirPlay session state is a *runtime* condition (user can enter/exit AirPlay during playback), not a compose-time variant. This breaks the standard composition-variant discipline (compose-time variants for compose- time conditions). Two open shapes: (a) a middle-pattern behavior monitors AirPlay session state (`mediaElement.remote.state` or equivalent) and writes an `airplaySessionActive` slot; a DRM-variant behavior reads the slot and reacts (re-request key system, re-create MediaKeys, potentially flush buffer + re-fetch license). MediaKeys-recreation mid-source is non-trivial — the standard source-replacement cascade tears MediaKeys down on `presentation.url` change; an AirPlay-triggered recreation would need a narrower reset (MediaKeys only, not the rest of the source state). (b) Defer the switch entirely: standard FairPlay key system is used for both local and AirPlay playback, accepting any degraded behavior or playback errors during AirPlay sessions. Likely (a) but the implementation is substantial; this open question may itself motivate a follow-on feature doc once FairPlay implementation ([#1414](https://github.com/videojs/v10/issues/1414)) lands. - **License-fetcher hook shape.** Single `getLicense(message)` callback vs separate request-transformation + response-transformation hooks vs a full fetch-wrapper interface. videojs-contrib-eme's shape vs more general request/response interceptors. Consumer-policy flexibility vs engine-side complexity trade-off. - **MediaKeys re-use across sources.** When the consumer changes sources within the same key system + license server, should the engine re-use the existing MediaKeys instance or tear down and recreate? Re-use saves the `requestMediaKeySystemAccess` cost but complicates lifecycle. videojs-contrib-eme tears down per-source; worth verifying whether SPF wants the same default. - **`keystatuschange` reactivity policy.** Engine-baseline behavior on key-status transitions (expired, output-restricted, released, etc.). Surface as a state-error slot? Trigger automatic re-request? Defer to consumer? Defaulting matters because keystatus changes can fire mid-playback. - **Init-data extraction location.** Parser-side (extract during `parseMediaPlaylist`) vs segment-side (extract from segment init data via `encrypted` event). Both are valid; parser-side is more predictable, segment-side is more reactive. May not be either-or per the spec. - **Cross-feature: DRM + live / DRM + DVR.** Live and DVR streams with DRM are valid use cases. The reload-loop interacts with key renewal cadence (if licenses expire during a long live session, the reload-loop + license-fetcher may both need re-trigger logic). Cross-cluster A + F open question; resolution likely after both clusters have implementation work. - **Output-protection-aware ABR coordination.** Renditions tagged with security-level / HDCP requirements interact with video-ABR and hevc-variant-selection. ABR's candidate set should be filtered by the `deviceSecurityLevel` constraint slot before bandwidth- driven selection runs. Filter ordering: capability filter (physics) → policy caps → security-level → bandwidth-driven selection. Per-rendition requirement tagging is open: parser- surfaced HLS extension attributes vs runtime-probed via license- server policy vs both. Server-side conventions vary. - **Per-rendition security-level tag surface.** HLS doesn't have a spec-defined attribute for "this rendition requires L1 Widevine." Providers commonly encode the requirement in proprietary attributes (`URI-SECURITY-LEVEL`, etc.) or imply it from `RESOLUTION` thresholds (4K+ requires hardware DRM by convention on many platforms). Parser needs an extension axis for surfacing the requirement; license- server policy is the orthogonal source. ## Related features - **[capability-probing](./capability-probing.md)** *(hard prerequisite)* — owns key-system probing; this feature consumes the verdict. Crisp boundary: probing = "can we?"; this feature = "set it up." - **`[fairplay-airplay-workaround]`** *(candidate, this session)* — Apple-specific FairPlay quirks during AirPlay sessions. Consumes this feature's FairPlay setup as the baseline. - **[mse-mms-pipeline](./mse-mms-pipeline.md)** — DRM gates MSE setup; encrypted-event flow on `mediaElement` triggers session creation. Once keys are delivered, segment append proceeds unchanged. - **[capability-probing](./capability-probing.md)** — also relevant for `changeType()` probing if mid-stream codec changes interact with DRM (uncommon but possible). - **[source-replacement](./source-replacement.md)** — MediaKeys / MediaKeySession cleanup on source change. Standard resolved/ unresolved cascade with DRM additions. - **[live-stream-support](./live-stream-support.md)** *(not yet implemented)* — DRM + live combines naturally; license-renewal cadence during long live sessions is the open question. - **[dvr-event-stream-support](./dvr-event-stream-support.md)** *(not yet implemented)* — DRM + DVR with back-seek through history; key delivery for back-seek-fetched segments needs verification. - **[video-abr](./video-abr.md)** / **[hevc-variant-selection](./hevc-variant-selection.md)** — output-protection-aware variant filtering when `drm-security- levels` lands. ## See also - [GitHub issue #1411 — Feature: DRM Support](https://github.com/videojs/v10/issues/1411) — the tracking epic; per-key-system sub-issues [#1412 Widevine](https://github.com/videojs/v10/issues/1412), [#1413 PlayReady](https://github.com/videojs/v10/issues/1413), [#1414 FairPlay](https://github.com/videojs/v10/issues/1414) - [clusters.md § Encrypted media (DRM)](./clusters.md#encrypted-media-drm) — cluster F description; this feature is the foundation - [clusters.md § Capability probing](./clusters.md#capability-probing) — cluster D; the probing prerequisite this feature consumes - [clusters.md § Feature classification axes](./clusters.md#feature-classification-axes) — Media-src feature framing - [capability-probing.md](./capability-probing.md) — hard prerequisite; key-system probing - [videojs-contrib-eme](https://github.com/videojs/videojs-contrib-eme) — Video.js v8 prior art; key-system detection in [`src/cdm.js`](https://github.com/videojs/videojs-contrib-eme/blob/main/src/cdm.js) - [Mux Player DRM integration](https://www.mux.com/docs/guides/protect-videos-with-drm) — adapter-layer prior art; `drm-token` attribute on `` - [W3C Encrypted Media Extensions](https://www.w3.org/TR/encrypted-media/) — EME spec; [`MediaKeySystemAccess.getConfiguration()`](https://www.w3.org/TR/encrypted-media/#dom-mediakeysystemaccess-getconfiguration) is the security-level probing surface - [HDCP specification (DCP LLC)](https://www.digital-cp.com/) — output-protection requirements; the protection-level data this feature gates on for high-resolution / premium content - [HLS Spec — `EXT-X-KEY` / `EXT-X-SESSION-KEY`](https://datatracker.ietf.org/doc/html/rfc8216bis)