Files
v10/internal/design/spf/features/drm-support.md
T

21 KiB

status, date, definition
status date definition
draft 2026-05-20 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: 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 (cluster D); setting up the chosen key system is owned by this feature.

Tracked via GitHub issue #1411 ("Feature: DRM Support") with three per-key-system sub-issues: #1412 Widevine, #1413 PlayReady, #1414 FairPlay. Milestone: GA. Prior art: videojs-contrib-eme (Video.js v8 plugin), Mux Player DRM integration (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'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): 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), PlayReady (#1413), FairPlay (#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'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 and hevc-variant-selection. Probing extends capability-probing'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. 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: 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, 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) 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.
  • capability-probing (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 — DRM gates MSE setup; encrypted-event flow on mediaElement triggers session creation. Once keys are delivered, segment append proceeds unchanged.
  • capability-probing — also relevant for changeType() probing if mid-stream codec changes interact with DRM (uncommon but possible).
  • source-replacement — MediaKeys / MediaKeySession cleanup on source change. Standard resolved/ unresolved cascade with DRM additions.
  • live-stream-support (not yet implemented) — DRM + live combines naturally; license-renewal cadence during long live sessions is the open question.
  • dvr-event-stream-support (not yet implemented) — DRM + DVR with back-seek through history; key delivery for back-seek-fetched segments needs verification.
  • video-abr / hevc-variant-selection — output-protection-aware variant filtering when drm-security- levels lands.

See also