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.

  1. Encryption — which DRM system(s) and which AES mode.
  2. Muxing — which container the encrypted segments live in.
  3. 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:

ModeFairPlayWidevinePlayReady
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 (cmaf muxing) is not supported. Note the distinction: a CMAF-compatible output (streamFormat: CMAF) is fine and used in the examples, it's the chunked cmaf muxing type that isn't supported. Use fmp4.


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-CBC

Manifest — 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-CBC

Full 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