DRM for Live Encodings
A how-to for setting up DRM on Bitmovin Live encodings.
The three phases
DRM touches a live encoding in three places. Getting each one right is what makes playback work across devices.
- Encryption — which DRM system(s) and which AES mode.
- Muxing — which container the encrypted segments live in.
- Manifest — how players are told the content is protected.
1. Encryption phase
You have two ways to encrypt.
Vendor-specific DRM - encrypt separately per system:
- FairPlay (Apple)
- Widevine (Google)
- PlayReady (Microsoft)
CENC (AES) - encrypt once, deliver to all
A single encryption pass produces content that all three systems can consume. CENC comes in two AES modes:
- CBC (also referred to as CBCS)
- CTR
Which mode should I pick?
Pick CBC unless you have a specific reason not to, it's the only mode FairPlay accepts, so it's the one that covers all three systems from a single encryption:
| Mode | FairPlay | Widevine | PlayReady |
|---|---|---|---|
| CBC (CBCS) | ✅ | ✅ | ✅ |
| CTR | ❌ | ✅ | ✅ |
This is why, in the examples below, the CBC config carries fairPlay, widevine, and playReady keys, while the CTR config only carries widevine and playReady.
2. Muxing phase
Encrypted streams must be muxed into a supported container:
- TS
- fMP4 - CMAF-compatible, and the right default for DRM live.
Chunked CMAF (
cmafmuxing) is not supported. Note the distinction: a CMAF-compatible output (streamFormat: CMAF) is fine and used in the examples, it's the chunkedcmafmuxing type that isn't supported. Usefmp4.
3. Manifest phase
You can advertise protection in HLS, DASH, or both at once from the same encoding.
For DASH, the contentprotection tag can sit at three levels:
- Period
- Adaptation set
- Representation
The examples below place it at the adaptation-set level, which is the common case.
Examples
The snippets use Bitmovin Encoding Templates (YAML). Full runnable templates are provided at the end. Values in {{doubleBraces}} are placeholders — replace them with your own input URL, output details, and the key material from your DRM provider before running a template.
Muxing — CBC
An fmp4 muxing with a CENC DRM config in CBC mode, carrying all three DRM systems:
muxings:
fmp4:
video-600-fmp4:
properties:
streams:
- streamId: $/encodings/main-encoding/streams/video-600
segmentLength: 6
segmentNamingTemplate: 'media_video-{rand_chars:4}-%number%.m4s'
initSegmentNameTemplate: 'init_video-{rand_chars:4}.mp4'
drm:
cenc:
drm-CBC:
properties:
outputs:
- outputId: $/outputs/akamaiMsl/my-msl-output
outputPath: "{{outputPath}}"
key: "{{cencKey}}" # 16-byte content key, hex
kid: "{{cencKid}}" # key ID, hex
encryptionMode: CBC
ivSize: 16_BYTES
fairPlay:
iv: "{{fairPlayIv}}"
uri: "{{fairPlayUri}}" # e.g. skd://<key-id>
widevine:
pssh: "{{widevinePssh}}"
playReady:
pssh: "{{playReadyPssh}}"Muxing — CTR
Identical, except encryptionMode: CTR and no fairPlay block (FairPlay doesn't support CTR):
encryptionMode: CTR
widevine:
pssh: "{{widevinePssh}}"
playReady:
pssh: "{{playReadyPssh}}"Manifest — HLS
Start the live encoding pointing at the HLS manifest, then reference the muxing's DRM config from the stream via drmId:
live:
start:
properties:
streamKey: "{{streamKey}}"
manifestGenerator: V2
hlsManifests:
- manifestId: $/manifests/hls/main-hls
timeshift: 120
liveEdgeOffset: 5
manifests:
hls:
main-hls:
properties:
manifestName: playlist.m3u8
outputs:
- outputId: $/outputs/akamaiMsl/my-msl-output
outputPath: /
streams:
video-600-stream-info:
properties:
name: video-600-stream-info
uri: "{{outputPath}}/chunklist_video.m3u8"
segmentPath: ''
audio: AUDIO
forceFrameRateAttribute: true
encodingId: $/encodings/main-encoding
muxingId: $/encodings/main-encoding/muxings/fmp4/video-600-fmp4
streamId: $/encodings/main-encoding/streams/video-600
drmId: $/encodings/main-encoding/muxings/fmp4/video-600-fmp4/drm/cenc/drm-CBCManifest — DASH
Start the live encoding against the DASH manifest, and set drmId plus the adaptation-set-level contentprotection:
live:
start:
properties:
streamKey: "{{streamKey}}"
manifestGenerator: V2
dashManifests:
- manifestId: $/manifests/dash/dash-CBC
timeshift: 120
liveEdgeOffset: 5
manifests:
dash:
dash-CBC:
properties:
name: Dash Manifest CBC
manifestName: manifest.mpd
iso8601TimestampFormat: SHORT
outputs:
- outputId: $/outputs/akamaiMsl/my-msl-output
outputPath: "{{outputPath}}"
periods:
period1:
properties:
start: 0
adaptationsets:
video:
adaptation-set-H264:
properties: { }
representations:
fmp4:
drm:
video-600:
properties:
type: TIMELINE
encodingId: $/encodings/main-encoding
muxingId: $/encodings/main-encoding/muxings/fmp4/video-600-fmp4
segmentPath: ''
drmId: $/encodings/main-encoding/muxings/fmp4/video-600-fmp4/drm/cenc/drm-CBC
contentprotection:
properties:
encodingId: $/encodings/main-encoding
muxingId: $/encodings/main-encoding/muxings/fmp4/video-600-fmp4
drmId: $/encodings/main-encoding/muxings/fmp4/video-600-fmp4/drm/cenc/drm-CBCFull templates
- HLS - single CBC muxing → HLS manifest (FairPlay + Widevine)
metadata: type: LIVE name: live-drm-hls-example inputs: hls: my-hls-input: properties: name: "My HLS Input" url: "{{hlsInputUrl}}" adMarkersSource: SEGMENTS outputs: akamaiMsl: my-msl-output: properties: eventName: "{{mslEventName}}" streamId: "{{mslStreamId}}" streamFormat: CMAF mslVersion: MSL4 configurations: video: H264: 600-codec-config: properties: profile: main height: 360 bitrate: 600000 presetConfiguration: LIVE_LOW_LATENCY rate: 25 encodings: main-encoding: properties: name: live-drm-hls-example cloudRegion: GOOGLE_EUROPE_WEST_1 encoderVersion: STABLE streams: video-600: properties: inputStreams: - inputId: $/inputs/hls/my-hls-input inputPath: / selectionMode: AUTO codecConfigId: $/configurations/video/H264/600-codec-config muxings: fmp4: video-600-fmp4: properties: streams: - streamId: $/encodings/main-encoding/streams/video-600 outputs: [] segmentLength: 6 segmentNamingTemplate: 'media_video-{rand_chars:4}-%number%.m4s' initSegmentNameTemplate: 'init_video-{rand_chars:4}.mp4' drm: cenc: drm-CBC: properties: outputs: - outputId: $/outputs/akamaiMsl/my-msl-output outputPath: "{{outputPath}}/video/" key: "{{cencKey}}" kid: "{{cencKid}}" encryptionMode: CBC ivSize: 16_BYTES fairPlay: iv: "{{fairPlayIv}}" uri: "{{fairPlayUri}}" widevine: pssh: "{{widevinePssh}}" live: start: properties: streamKey: "{{streamKey}}" manifestGenerator: V2 hlsManifests: - manifestId: $/manifests/hls/main-hls timeshift: 120 liveEdgeOffset: 5 manifests: hls: main-hls: properties: manifestName: playlist.m3u8 outputs: - outputId: $/outputs/akamaiMsl/my-msl-output outputPath: "{{outputPath}}/" streams: video-600-stream-info: properties: name: video-600-stream-info uri: "chunklist_video.m3u8" segmentPath: "video/" forceFrameRateAttribute: true encodingId: $/encodings/main-encoding muxingId: $/encodings/main-encoding/muxings/fmp4/video-600-fmp4 streamId: $/encodings/main-encoding/streams/video-600 drmId: $/encodings/main-encoding/muxings/fmp4/video-600-fmp4/drm/cenc/drm-CBC - DASH - CBC and CTR muxings → two DASH manifests (Widevine + PlayReady)
metadata: type: LIVE name: live-drm-dash-example inputs: hls: my-hls-input: properties: name: "My HLS Input" url: "{{hlsInputUrl}}" adMarkersSource: SEGMENTS outputs: akamaiMsl: my-msl-output-cbc: properties: eventName: "{{mslEventNameCbc}}" streamId: "{{mslStreamIdCbc}}" streamFormat: CMAF mslVersion: MSL4 my-msl-output-ctr: properties: eventName: "{{mslEventNameCtr}}" streamId: "{{mslStreamIdCtr}}" streamFormat: CMAF mslVersion: MSL4 configurations: video: H264: 600-codec-config: properties: profile: main height: 360 bitrate: 600000 presetConfiguration: LIVE_LOW_LATENCY rate: 25 encodings: main-encoding: properties: name: live-drm-dash-example cloudRegion: GOOGLE_EUROPE_WEST_1 encoderVersion: STABLE streams: video-600: properties: inputStreams: - inputId: $/inputs/hls/my-hls-input inputPath: / selectionMode: AUTO codecConfigId: $/configurations/video/H264/600-codec-config muxings: fmp4: video-600-fmp4-cbc: properties: streams: - streamId: $/encodings/main-encoding/streams/video-600 outputs: [] segmentLength: 6 segmentNamingTemplate: 'media_video-{rand_chars:4}-%number%.m4s' initSegmentNameTemplate: 'init_video-{rand_chars:4}.mp4' drm: cenc: drm-CBC: properties: outputs: - outputId: $/outputs/akamaiMsl/my-msl-output-cbc outputPath: "{{outputPathCbc}}/video/" key: "{{cencKey}}" kid: "{{cencKid}}" encryptionMode: CBC ivSize: 16_BYTES widevine: pssh: "{{widevinePssh}}" playReady: pssh: "{{playReadyPssh}}" video-600-fmp4-ctr: properties: streams: - streamId: $/encodings/main-encoding/streams/video-600 outputs: [] segmentLength: 6 segmentNamingTemplate: 'media_video-{rand_chars:4}-%number%.m4s' initSegmentNameTemplate: 'init_video-{rand_chars:4}.mp4' drm: cenc: drm-CTR: properties: outputs: - outputId: $/outputs/akamaiMsl/my-msl-output-ctr outputPath: "{{outputPathCtr}}/video/" key: "{{cencKey}}" kid: "{{cencKid}}" encryptionMode: CTR ivSize: 16_BYTES widevine: pssh: "{{widevinePssh}}" playReady: pssh: "{{playReadyPssh}}" live: start: properties: streamKey: "{{streamKey}}" manifestGenerator: V2 dashManifests: - manifestId: $/manifests/dash/dash-CBC timeshift: 120 liveEdgeOffset: 5 - manifestId: $/manifests/dash/dash-CTR timeshift: 120 liveEdgeOffset: 5 manifests: dash: dash-CBC: properties: name: Dash Manifest CBC manifestName: manifest.mpd iso8601TimestampFormat: SHORT outputs: - outputId: $/outputs/akamaiMsl/my-msl-output-cbc outputPath: "{{outputPathCbc}}/" periods: period1: properties: start: 0 adaptationsets: video: adaptation-set-H264: properties: { } representations: fmp4: drm: video-600: properties: type: TIMELINE encodingId: $/encodings/main-encoding muxingId: $/encodings/main-encoding/muxings/fmp4/video-600-fmp4-cbc segmentPath: "video/" drmId: $/encodings/main-encoding/muxings/fmp4/video-600-fmp4-cbc/drm/cenc/drm-CBC contentprotection: properties: encodingId: $/encodings/main-encoding muxingId: $/encodings/main-encoding/muxings/fmp4/video-600-fmp4-cbc drmId: $/encodings/main-encoding/muxings/fmp4/video-600-fmp4-cbc/drm/cenc/drm-CBC dash-CTR: properties: name: Dash Manifest CTR manifestName: manifestCTR.mpd iso8601TimestampFormat: SHORT outputs: - outputId: $/outputs/akamaiMsl/my-msl-output-ctr outputPath: "{{outputPathCtr}}/" periods: period1: properties: start: 0 adaptationsets: video: adaptation-set-H264: properties: { } representations: fmp4: drm: video-600: properties: type: TIMELINE encodingId: $/encodings/main-encoding muxingId: $/encodings/main-encoding/muxings/fmp4/video-600-fmp4-ctr segmentPath: "video/" drmId: $/encodings/main-encoding/muxings/fmp4/video-600-fmp4-ctr/drm/cenc/drm-CTR contentprotection: properties: encodingId: $/encodings/main-encoding muxingId: $/encodings/main-encoding/muxings/fmp4/video-600-fmp4-ctr drmId: $/encodings/main-encoding/muxings/fmp4/video-600-fmp4-ctr/drm/cenc/drm-CTR