Skip to main content

Entitlement and DRM

What you learn​

You're instantiating DIVA Player in your Roku app and relying on DIVA Back Office as the video streaming source.

The goal of this article is to build the simplest Roku app to stream a video from the DIVA Back Office with entitlement check and DRM.

Before starting​

  • Ensure Diva Player lib and DIVA Back Office Adapter lib downloaded (Setup > Step 13).
  • Ask your video engineers team the <video id> and <settings URL>.
  • Ensure the settings file contains:
    • The entitlementCheck section:
      "entitlementCheck": {
      "entitlementUrl": "<yourEntitlementBaseURL>/tokenize",
      "heartbeatUrl": "<yourEntitlementBaseURL>/heartbeat",
      "heartbeatPollingInterval": 180000,
      "heartbeatSeekInterval": 10000,
      "other": "{<sessionId>}|{<platform>}",
      "fairPlayCertificateUrl": "<yourFairPlayCertificateBaseURL>/fairplay.cer",
      "queryParams": ["VideoSourceName", "VideoId", "VideoKind"],
      "data": {
      "sessionId": "{Run.SessionID}",
      "platform": "Roku",
      "idfa": "{Run.idfa}",
      "p.adsParams.paln":"{diva_pal_nonce}"
      }
      }
      Note: Consider other deprecated and replaced by data.
    • The videoPlatformsPriority section:
      "videoPlatformsPriority": {
      "default": [
      {
      "type": "DASH"
      "sourceName": "Desktop-DASH",
      "drmType": "playready"
      },
      {
      "type": "DASH",
      "sourceName": "Desktop-DASH",
      "drmType": "widevine"
      },
      {
      "type": "HLS",
      "sourceName": "Desktop-V4",
      "drmType": "fairplay"
      }
      ]
      }

Instantiation​

DRM code​

There's no code to add to the Basic instantiation code.

Entitlement code​

Enrich the Basic instantiation code with the entitlement configuration:

  1. Add the entitlementConfigurationRequest field observer to Diva Player lib for configuring the Entitlement:

        sub launchBOAdapter()
    m.dpUtilsNode = CreateObject("roSGNode", "DivaPlayerSDK:DPUtilsNode")
    m.boAdapterNode = CreateObject("roSGNode", "DivaBOAdapter:DivaBOAdapter")
    ...
    ' Get event from the Diva Player for configuration Entitlement Service
    m.dpUtilsNode.observeField("entitlementConfigurationRequest", "onEntitlementConfigurationHandler")
    ...

    m.dpUtilsNode.callFunc("runPlayer") 'Launching the Diva Player
    end sub

    sub onEntitlementConfigurationHandler()
    hbInterval = m.boAdapterNode.callFunc("getEntitlementHeartBeatInterval")
    m.dpUtilsNode.callFunc("setEntitlementConfiguration", {heartBeatInterval: hbInterval})
    end sub

    Note: Diva Player lib calls the entitlementConfigurationRequest field observer after launch.

  2. Add the entitlementRequest field observer to Diva Player lib. After calling the field entitlementRequest observer, get data from Diva Player lib and send to the entitlementRequest field in the DIVA Back Office Adapter lib:

    sub launchBOAdapter()
    ...
    ' Get a request from the Diva Player on making the Entitlement request to the BE
    m.dpUtilsNode.observeField("entitlementRequest", "onEntitlementHandler")
    ...
    end sub

    sub onEntitlementHandler(evt as dynamic)
    data = evt.getData()
    m.boAdapterNode.entitlementRequest = data
    end sub

    Note: Diva Player lib calls the field entitlementRequest observer when Diva Player lib sends a request to the Entitlement Service. The request is sent in two cases:

    • Case 1: When launching a stream playback.
    • Case 2: When calling the heartbeat.
  3. Implement the entitlementPayloadMap callback, which receives the entitlement payload and returns the same payload — possibly modified. This callback allows the integrator to modify the entitlement payload before sending it to the entitlement service.

    ...
    ' Register the entitlementPayloadMap callback from BO adapter'
    m.boAdapterNode.activateEntitlementPayloadMapCallback = true
    m.boAdapterNode.observeField("entitlementPayloadMap", "onEntitlementPayloadMapHandler")
    ...

    sub onEntitlementPayloadMapHandler(evt as dynamic)
    entitlementPayloadMap = evt.getData()
    ' Integrator can make changes in m.entitlementPayloadMap then send it back to the BO Adapter through "setEntitlementPayloadMap"

    ' Important: in case you activate Entitlement Payload Map Callback (activateEntitlementPayloadMapCallback = true)
    ' you need to observe on "entitlementPayloadMap" and then get data from this observer and send data back through the "setEntitlementPayloadMap" field in BO Adapter
    ' Without these actions, BO Adapted not make the Entitlement call. BO Adapter will wait for the Call Back from the Main Application
    m.boAdapterNode.setEntitlementPayloadMap = entitlementPayloadMap
    end sub

    3.1 Set activateEntitlementPayloadMapCallback to true to activate the callback.

    3.2 If needed, change entitlementPayloadMap based on the following callback input parameters:

    • requestType: It can value processUrl or heartbeat:
      • processUrl: It indicates the entitlement initialization call.
      • heartBeat: It indicates the entitlement check call.
    • payloadMap: Created every time DIVA Player calls the entitlement service: E.g.:
      {
      "Type": 1, // 1 = 'processUrl', 2 = 'heartbeat'
      "User": "<userID>", //user identifier
      "VideoId": "<videoID>",
      "VideoSource": "<video source>",
      "VideoKind": "replay", //possible values depend on the video production
      "AssetState": "3", //2 = 'live', 3 = 'VOD'
      "PlayerType": "<player type>",
      "VideoSourceFormat": "DASH",
      "VideoSourceName": "Desktop-DASH",
      "DRMType": "widevine",
      "AuthType": "Token",
      "ContentKeyData": "<content key data>",
      "SessionId": "<sessionID>",
      "PlaybackSessionId": "<PlaybackSessionId>",
      "Other": "{"<sessionId>"}|{"<platform>"}",
      "Data": "{"sessionId":"<sessionID>","platform":"roku","idfa":"{Run.idfa}","offsetFromLiveEdge":"0","p.adsParams.paln":"{diva_pal_nonce}"}",
      "Version": "1"
      }
      Note: If AssetState = "1" there's something wrong: "1" means 'scheduled', which is a not yet existing video that DIVA Player cannot manage.

Working sample code (.brs)​

sub init()
initScreen()
end sub

sub initScreen()
loadLibs()
end sub

sub loadLibs()
loadPlayer()

'Init of loading the BO Adapter lib
loadBOAdapter()
end sub

sub loadPlayer()
' Creates the ComponentLibrary (the DivaPlayerSDK in this case)
m.divaPlayerSDK = CreateObject("roSGNode", "ComponentLibrary")
m.divaPlayerSDK.id = "DivaPlayerSDK"
m.divaPlayerSDK.uri = "pkg:/resources/diva-roku-cl-sdk-5.7.0.zip"

' Adding the ComponentLibrary node to the scene will start the download of the library
m.top.appendChild(m.divaPlayerSDK)

m.divaPlayerSDK.observeField("loadStatus", "onDivaPlayerLoadStatusChanged")
end sub

sub loadBOAdapter()
' Creates the ComponentLibrary (the BO Adapter in this case)
m.divaBOAdapter = CreateObject("roSGNode", "ComponentLibrary")
m.divaBOAdapter.id = "DivaBOAdapter"
m.divaBOAdapter.uri = "pkg:/resources/diva-bo-adapter-cl-1.2.0.zip"

' Adding the ComponentLibrary node to the scene will start the download of the library
m.top.appendChild(m.divaBOAdapter)
m.divaBOAdapter.observeField("loadStatus", "onDivaPlayerLoadStatusChanged")
end sub

sub onDivaPlayerLoadStatusChanged()
if m.divaPlayerSDK.loadStatus = "ready"
if m.divaBOAdapter <> invalid and m.divaBOAdapter.loadStatus = "ready"
launchBOAdapter()
end if
end if
end sub

sub launchBOAdapter()
m.boAdapterNode = CreateObject("roSGNode", "DivaBOAdapter:DivaBOAdapter")

m.boAdapterNode.initData = {
"settingId": <video id>
"videoId": <video id>
"dictionaryId": "en_GB"
}

' Observe BO Adapter DiveLaunchParams
m.boAdapterNode.observeField("divaLaunchParams", "onBOAdapterDivaLaunchParamsHandler")

' Observe BO Adapter Dictionary
m.boAdapterNode.observeField("dictionary", "onBOAdapterDictionaryHandler")

' Observe BO Adapter VideoMetaDataNode
m.boAdapterNode.observeField("videoDataNode", "onBOAdapterVideoDataNodeHandler")

' Observe BO Adapter Entitlement Data
m.boAdapterNode.observeField("entitlementData", "onEntitlementDataHandler")

' Observe BO Adapter Settings
m.boAdapterNode.observeField("settingsNode", "onSettingsHandler")

' Observe BO Adapter when all necessary data for launching the Diva Player was loaded
' After getting this event you can launch the Diva Player
m.boAdapterNode.observeField("boAdapterReady", "onBOAdapterReady")

m.boAdapterNode.activateEntitlementPayloadMapCallback = true
m.boAdapterNode.observeField("entitlementPayloadMap", "onEntitlementPayloadMapHandler")

'Diva Player Utils Node
m.dpUtilsNode = getDivaPlayerUtilsNode()

' Observe Diva Player exit call
m.dpUtilsNode.observeField("divaPlayerExit", "onDivaPlayerExitHandler")

' Observe Diva Player actions
' Observe Diva Metadata update call
m.dpUtilsNode.observeField("metaDataUpdate", "onMetadataUpdateHandler")

' Get a request from the Diva Player on making the Entitlement request to the BE
m.dpUtilsNode.observeField("entitlementRequest", "onEntitlementHandler")
' Get event from the Diva Player for configuration Entitlement Service
m.dpUtilsNode.observeField("entitlementConfigurationRequest", "onEntitlementConfigurationHandler")

' Getting Diva Player Node
m.divaPlayer = m.dpUtilsNode.callFunc("getPlayer")
' Adding Diva Player for the visualisation
screensStack = m.getScreensStack()
screensStack.appendChild(m.divaPlayer)

m.divaPlayer.setFocus(true)
end sub

function getDivaPlayerUtilsNode() as dynamic
if NOT isValid(m.dpUtilsNode)
m.dpUtilsNode = CreateObject("roSGNode", "DivaPlayerSDK:DPUtilsNode")
end if
return m.dpUtilsNode
end function

sub onBOAdapterDivaLaunchParamsHandler(evt as dynamic)
data = evt.getData()
' Setup Diva Player launch parameters
m.dpUtilsNode.callFunc("setLaunchParams", data)
end sub

sub onBOAdapterDictionaryHandler(evt as dynamic)
data = evt.getData()
' Setup Diva Player dictionary
m.dpUtilsNode.callFunc("setDictionary", data)
end sub

sub onBOAdapterVideoDataNodeHandler(evt as dynamic)
data = evt.getData()
' Setup Diva Player Metadata
m.dpUtilsNode.callFunc("setMetaData", data)
end sub

sub onSettingsHandler(evt as dynamic)
data = evt.getData()
' Setup Diva Player Settings
m.dpUtilsNode.callFunc("setSettings", data)
end sub

sub onEntitlementDataHandler(evt as dynamic)
' Set to the Diva Player response from Entitlement Service
m.dpUtilsNode.callFunc("setEntitlementData", evt.getData())
end sub

sub onBOAdapterReady(evt as dynamic)
m.dpUtilsNode.callFunc("runPlayer")
end sub

sub onEntitlementPayloadMapHandler(evt as dynamic)
entitlementPayloadMap = evt.getData()
' Integrator can make changes in m.entitlementPayloadMap then send it back to the BO Adapter through "setEntitlementPayloadMap"

' Important: in case you activate Entitlement Payload Map Callback (activateEntitlementPayloadMapCallback = true)
' you need to observe on "entitlementPayloadMap" and then get data from this observer and send data back through the "setEntitlementPayloadMap" field in BO Adapter
' Without these actions, BO Adapted not make the Entitlement call. BO Adapter will wait for the Call Back from the Main Application
m.boAdapterNode.setEntitlementPayloadMap = entitlementPayloadMap
end sub

sub destroyBOAdapter()
if m.boAdapterNode <> invalid
m.boAdapterNode.unobserveField("error")
m.boAdapterNode.destroy = true
m.boAdapterNode = invalid
end if
end sub

sub onMetadataUpdateHandler(evt as dynamic)
data = evt.getData()
m.boAdapterNode.requestMetaDataUpdate = data
end sub

sub onEntitlementHandler(evt as dynamic)
data = evt.getData()
m.boAdapterNode.entitlementRequest = data
end sub

sub onEntitlementConfigurationHandler()
hbInterval = m.boAdapterNode.callFunc("getEntitlementHeartBeatInterval")
if hbInterval <> invalid and hbInterval > 0
m.dpUtilsNode.callFunc("setEntitlementConfiguration", {heartBeatInterval: hbInterval})
end if
end sub

sub onDivaPlayerExitHandler()
m.dpUtilsNode = invalid

'Destroy BO Adapter Node
destroyBOAdapter()
end sub