Skip to content
Stefano Gottardo edited this page Jul 22, 2024 · 30 revisions

To play DRM-protected content is required, in addition to the standard properties described on Integration page, the setting of some properties to configure the DRM to be used.

In some circumstances the configuration of DRM can be complex, usually when a video provider customises the HTTP request/response of the license by using data wrappers (e.g. JSON, BASE64, ...).

Supported DRM KeySystem's

These are the currently supported DRM KeySystems:

DRM Name KeySystem
Widevine * com.widevine.alpha
PlayReady (android only) com.microsoft.playready
Wiseplay (android only) com.huawei.wiseplay
ClearKey (from v.21.5.0 / Kodi 21) org.w3.clearkey

[*] WIDEVINE LIBRARY REQUIREMENT: For all operative systems other than Android is required the installation of Widevine library, not included with this add-on. To handle the library installation automatically we suggest to use InputStream Helper add-on.

NOTE: Currently only a single DRM can be configured at a time. Therefore manifests with multiple DRM support must be set appropriately according to the use cases and type of operating system.


There are two methods for configuring DRM:

🔵 Simplest method "drm_legacy" (from v.21.5.0 / Kodi 21) [click to expand]

This is the simplest way to set up a DRM (for cases where the provider does not require advanced configurations such as wrappers, certificate, etc...).

NOTE: This property was added as a future easy replacement for inputstream.adaptive.license_type / inputstream.adaptive.license_key. In the future, the intention is to full replace license_type/license_key (and others) properties with a new more advanced property.

The expected value is a string, which can be extended with 2 more optional pipes | as required:
[DRM KeySystem] | [License server URL or KeyId's] | [License server headers]

Template fields:

  • [DRM KeySystem]
    The KeySystem of the DRM to be used, use the string as shown in the table on top.

  • [License server URL or KeyId's]
    Some DRM's, usually with Widevine, you need to specify the URL of the license server.
    With DRM ClearKey can be, optionally, possible to specify KeyId's for decryption in the following format: kid1:key1,kid2:key2,kid3:key3,...

  • [License server headers]
    Allow you to add custom HTTP headers for the license HTTP request. With some providers it may be necessary to avoid a server rejection of the HTTP request.
    We suggest to URL encode the headers values. The headers string must follow the scheme: param1=value1&param2=value2

Example for Widevine:

# Flat string example
listitem.setProperty('inputstream.adaptive.drm_legacy', 'com.widevine.alpha|https://license.server.com/licenserequest|User-Agent=Mozilla%2F5.0+%28Windows+NT+10.0%3B+Win64%3B+x64%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F106.0.0.0+Safari%2F537.36')
# Constructed string example
license_headers = {
    'Content-Type': 'application/octet-stream',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/109.0',
}
from urllib.parse import urlencode
drm_config = {
    'DRM KeySystem': 'com.widevine.alpha'
    'License server url': 'https://license.server.com/licenserequest',
    'License headers': urlencode(license_headers)
}
listitem.setProperty('inputstream.adaptive.drm_legacy', '|'.join(drm_config.values()))

Example for PlayReady:

listitem.setProperty('inputstream.adaptive.drm_legacy', 'com.microsoft.playready')

Example for ClearKey:

# KeyId's provided by manifest:
listitem.setProperty('inputstream.adaptive.drm_legacy', 'org.w3.clearkey')
# KeyId's provided by property:
listitem.setProperty('inputstream.adaptive.drm_legacy', 'org.w3.clearkey|000102030405060708090a0b0c0d0e0f:00112233445566778899aabbccddeeff')
🔵 Advanced method [click to expand]

This method provides a more in-depth configuration of DRM, such as certificates, wrappers and more. Currently, multiple properties must be used according to the provider's needs.

inputstream.adaptive.license_type

[mandatory for DRM encrypted media]

Specify which DRM System must be used, set the value as shown in table on top.

listitem.setProperty('inputstream.adaptive.license_type', 'com.widevine.alpha')

inputstream.adaptive.license_key

[mandatory for some DRM encrypted media]

Can be used to specify how to handle the HTTP request / response of the DRM license.

NOTE: The PlayReady DRM do not support this property, because license-URL is part of the PlayReady WRM header (manifest data).

The license_key value must be constructed as a string with 4 template fields separated by 3 pipe | chars:

[license server URL] | [Headers] | [Post-Data] | [Response-Data] (without spaces)

NOTE: For HLS AES encrypted, the key URL is supported in the HLS playlist, and for this use case the license_key parameter works this way, taking into account only the first two template fields:

[parameters to append to the key URL] | [Key URL Headers] || (without spaces)

Examples:

# Constructed string example
license_headers = {
    'Content-Type': 'application/octet-stream',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/109.0',
    'Origin': 'https://www.somesite.com'
}
from urllib.parse import urlencode
license_config = {
    'License server url': 'https://license.server.com/licenserequest',
    'Headers': urlencode(license_headers),
    'Post data': 'R{SSM}',
    'Response data': 'R'
}
listitem.setProperty('inputstream.adaptive.license_key', '|'.join(license_config.values()))

# Flat string example - common case
listitem.setProperty('inputstream.adaptive.license_key', 'https:\\www.licserver.com\data\example\||R{SSM}|R')

Template fields:

[license server URL]
The license server URL where make the license HTTP request. In the URL is possible optionally inject the DRM Challenge by using following placeholders:

  • B{SSM} Inject to the URL the DRM Challenge as base64, URL encoded
  • {HASH} Inject to the URL the DRM Challenge hashed as MD5

[Headers]
Allow you to add the HTTP headers for the license HTTP request, the paramters will be URL encoded automatically. Leave blank if not required. The headers must be follow this scheme: param1=value1&param2=value2

[Post-Data]
You can use this field to make an HTTP POST request instead of HTTP GET. Optionally value can be URL encoded to keep customised data intact.
Add at least one placeholder with a single prefix:

  • [R/b/B/D]{SSM} placeholder to transport the DRM Challenge
    With it you can use also other optional placeholders as:
  • [R/b/B]{SID} placeholder to transport the DRM Session ID
  • [R/H]{KID} placeholder to transport the DRM Key ID
  • [b/B]{PSSH} placeholder to transport the initial PSSH (Android only)

Most of time a license server accept (key request) challenge as raw data R{SSM} only.

Prefixes summary:
R - The data will be kept as is raw
b - The data will be base64 encoded
B - The data will be base64 encoded and URL encoded
D - The data will be decimal converted (each char converted as integer concatenated by comma)
H - The data will be hexadecimal converted (each character converted as hexadecimal and concatenated)

[Response-Data]
Specify how handle the data of HTTP license response:

  • Not specified, or, R if the response payload is in binary raw format
  • B payload is wrapped with base64
  • J[license tokens] payload is wrapped with JSON format. You must specify the license tokens names to allow inputstream.adaptive searches for the license key and optionally the HDCP limit. The tokens must be separated by ;. The first token must be for the key license, the second one, optional, for the HDCP limit. The HDCP limit is the result of resolution width multiplied for its height. For example to limit until to 720p: 1280x720 the result will be 921600.
  • JB[license tokens] same meaning of J[license tokens] but the value contained in the JSON path is encoded as base64.
  • BJ[license tokens] same meaning of J[license tokens] but the JSON is encoded as base64 and the value contained in the JSON path is raw.
  • HB if the payload is after two return chars \r\n\r\n in binary raw format.

Most of time a license server return raw data R.

Default values when license_key property is not set:

  • Widevine: [license server URL] | Content-Type=application/octet-stream | R{SSM} | R
  • PlayReady: [license server URL] | Content-Type=text/xml&SOAPAction=http://schemas.microsoft.com/DRM/2007/03/protocols/AcquireLicense | R{SSM} | R
  • WisePlay: [license server URL] | Content-Type=application/json | R{SSM} | R

inputstream.adaptive.license_data

Allow to specify the PSSH initialization license data, that will be sent to the license server.

Can be used when a MPEG-DASH manifest do not specify the ContentProtection tag with schemeIdUri attribute to identify a content protection scheme for a specific DRM. The data provided must be encoded as base64.

The placeholder {KID} can be used to specify where to inject the default KID into the initialization data, it must also be base64 encoded with the data.
WARNING: Due to its limited and specific use case, moreover for a difficult future maintenances, the placeholder {KID} HAS BEEN REMOVED FROM Kodi v21. We discourage its use even on older versions of Kodi, as the KID obtained is specifically limited only to some DASH PlayReady ContentProtection tags.

listitem.setProperty('inputstream.adaptive.license_data', 'base64data')

WARNING: Currently there are undocumented mixed features of this property that some old add-on may use, these will be reviewed and changed in the future versions, so may break your addon. But genarally speaking the main purpose to set the initialization PSSH data will be kept.

inputstream.adaptive.server_certificate

Specifies a server certificate to be used to encrypt messages to the license server. Should be encoded as Base64.

listitem.setProperty('inputstream.adaptive.server_certificate', 'certificate_data')

inputstream.adaptive.license_flags

Can be used to force some behaviours.

Possible values are:

  • 'persistent_storage': To force the server certificate (available only on OS's other than Android)
  • 'force_secure_decoder': To force secure decoder

More than one flag can be set, you can separate by comma.

listitem.setProperty('inputstream.adaptive.license_flags', 'persistent_storage')

inputstream.adaptive.pre_init_data

Some VOD services works with licensed manifests. Compared to the standard manifests, this one also encloses the license data in the manifest.

Usually the request for these types of manifests are customised by the service, so a proxy managed by your add-on will have to act as an intermediary between ISAdaptive and your VOD service, in order to allow your add-on to generate an appropriate manifest request with the DRM data and then make it instead of ISAdaptive.

Enabling this feature you must provide a PSSH and KID. Both values must be provided encoded as Base 64.

listitem.setProperty('inputstream.adaptive.pre_init_data', 'PSSH encoded base 64|KID encoded base 64')

This will pre-initialize the DRM by opening a DRM session and will provide these data in the ISAdaptive HTTP manifest request:

  • challengeB64 - The Challenge (Key Request) value encoded as base 64, and URL encoded.
  • sessionId - The Session ID value in clear.

You can intercept the ISAdaptive manifest/license HTTP requests by implementing a proxy server in your add-on, as in the example How to provide custom manifest and license, will allow you to create the appropriate manifest data and manage the HTTP request through your add-on. When you will get the HTTP manifest response, you will also need to transfer the license data into the ISAdaptive license HTTP request.

WARNINGS:

  • Do not enable this feature without a proxy server in an add-on, otherwise the DRM data will not be managed.
  • This feature is currently intended for non-Android systems. Can works also on Android system, but to keep in mind that it is not possible to maintain the same DRM session, this mismatch will result in the license data may not working.
🔵 Advanced method - Full examples [click to expand]

DASH + WIDEVINE: Play encrypted video stream from add-on, a common DRM configuration.

listitem = xbmcgui.ListItem(path='https://www.videoservice.com/manifest.mpd')

# These two lines are needed to prevent the HTTP HEAD request from Kodi core, used to determine the mimetype
listitem.setMimeType('application/dash+xml')
listitem.setContentLookup(False)

listitem.setProperty('inputstream', 'inputstream.adaptive')
listitem.setProperty('inputstream.adaptive.manifest_type', 'mpd') # Deprecated on Kodi 21, removed on Kodi 22
listitem.setProperty('inputstream.adaptive.license_type', 'com.widevine.alpha')

license_headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/109.0',
    'Content-Type': 'application/octet-stream'
}
from urllib.parse import urlencode
license_config = { # for Python < v3.7 you should use OrderedDict to keep order
    'License server url': 'https://license.server.com/licenserequest/',
    'Headers': urlencode(license_headers),
    'Post data': 'R{SSM}',
    'Response data': 'R'
}
listitem.setProperty('inputstream.adaptive.license_key', '|'.join(license_config.values()))

xbmcplugin.setResolvedUrl(pluginhandle, True, listitem=listitem)

DASH + WIDEVINE: Play encrypted video stream from playlist file STRM / M3U8, a common DRM configuration.

#KODIPROP:inputstream=inputstream.adaptive
#KODIPROP:inputstream.adaptive.manifest_type=mpd
#KODIPROP:inputstream.adaptive.license_type=com.widevine.alpha
#KODIPROP:inputstream.adaptive.license_key=https:\\www.licserver.com\data\example\|User-Agent=Mozilla%2F5.0+%28Windows+NT+10.0%3B+Win64%3B+x64%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F106.0.0.0+Safari%2F537.36|R{SSM}|R
#KODIPROP:mimetype=application/dash+xml
https://www.videoservice.com/manifest.mpd

SMOOTHSTREAMING + PLAYREADY: Play encrypted video stream from add-on, a common DRM configuration.

listitem = xbmcgui.ListItem(path='https://www.videoservice.com/tearsofsteel_4k.ism/manifest')

# These two lines are needed to prevent the HTTP HEAD request from Kodi core, used to determine the mimetype
listitem.setMimeType('application/vnd.ms-sstr+xml')
listitem.setContentLookup(False)

listitem.setProperty('inputstream', 'inputstream.adaptive')
listitem.setProperty('inputstream.adaptive.manifest_type', 'ism') # Deprecated on Kodi 21, removed on Kodi 22
listitem.setProperty('inputstream.adaptive.license_type', 'com.microsoft.playready')
# inputstream.adaptive.license_key is not set, usually the playready data is embedded with manifest

xbmcplugin.setResolvedUrl(pluginhandle, True, listitem=listitem)

HLS + AES-128: Play encrypted video stream from add-on.

listitem = xbmcgui.ListItem(path='https://www.videoservice.com/master_manifest.m3u8')

# These two lines are needed to prevent the HTTP HEAD request from Kodi core, used to determine the mimetype
listitem.setMimeType('application/vnd.apple.mpegurl')
listitem.setContentLookup(False)

listitem.setProperty('inputstream', 'inputstream.adaptive')
listitem.setProperty('inputstream.adaptive.manifest_type', 'hls') # Deprecated on Kodi 21, removed on Kodi 22
# inputstream.adaptive.license_type is not set, since there is no DRM, AES-128 encryption only
# inputstream.adaptive.license_key is not set, usually is not required, just to handle special use cases for the HLS key request

xbmcplugin.setResolvedUrl(pluginhandle, True, listitem=listitem)

DRM CLEARKEY NOTE: Since this DRM has been implemented at later time of Kodi 21 can be configured only by using the "drm_legacy" property of the "simplest method". This choice was made because the current "advanced method" will in future be deprecated and removed in favour of more advanced functionality. We want to encourage the use of the new methods to limit future changes needed.


🔵 License URL too long - How to solve PVR binary API bug [click to expand]

Currently there is a know Kodi PVR API interface bug, that truncate ISAdaptive properties values to max 1024 chars, then if the server license URL is too long will trucate the data set on the InputStream properties (e.g. inputstream.adaptive.license_key), and the video playback will fails.

To workaround this bug, has been added these two properties that can be used to split the URL on two parts of 1024 chars:

  • inputstream.adaptive.license_url
  • inputstream.adaptive.license_url_append

How to do:

  1. Leave empty the field used to set the license URL (e.g. on inputstream.adaptive.license_key the [license server URL] template field)
  2. Take care to split the license server url in two parts of 1024 chars, then use license_url to specify the first 1024 chars, and license_url_append to add the last part of remaining url chars.

NOTE: On Kodi v20, has been introduced from ISA v20.3.15