DRM (Digital Rights Management)

To access these features, Upgrade Now

Overview

JW Player supports industry standard DRM (Digital Rights Management) standards to help you protect your content. JW Player supports DRM providers such as Apple FairPlay, Microsoft PlayReady, Google Widevine, and the open ClearKey standard. AES 128 bit encryption is also supported as a basic way to protect your content.

DRM solutions are available for both HLS and MPEG-DASH adaptive streaming technologies and are configurable via the JW Player Javascript API.

Compatibility

One of the main problems with DRM is the lack of cross-platform compatibility. Most DRM technologies are proprietary and are only supported on OEM-specific products. For example, Apple FairPlay is only compatible with the Apple Safari web browser and other Apple hardware and software products.

Below is a list of DRM technologies that are supported in JW Player and which browsers support them. Note that our support for each DRM is constrained by what the DRM makers themselves support. For example, Apple supports Fairplay in Safari on Mac OS, but not in Safari on iOS.

DRM Browser & Mobile OS Supported
Google Widevine Chrome 58+ Page must be served over HTTPS
Chrome 35+ (Desktop and Android)
Firefox 47+ (Windows, Mac only)

Android v4.3+ (via native JW Player SDK)
Microsoft Playready Internet Explorer 11+ (Windows 8.1+ only)
Microsoft Edge 12+ (Windows desktop and Phone)
Apple FairPlay Streaming (FPS) Safari 8+ on macOS 10.11+ (El Capitan)

Apple iOS v8+ (via native JW Player iOS SDK)
Clearkey Chrome 35+
Firefox 47+
Android v4.3+
AES 128bit Chrome 35+
Firefox 47+
Internet Explorer 11+
Microsoft Edge 12+
Safari 8+

Android v4.3+
Apple iOS v8+

Note: ClearKey is not supported in Microsoft Edge or in Microsoft IE11.

Configuration & Setup

NOTE: The use of DRM can only be configured when a stream is being used with an Enterprise license.

DRM (including AES 128bit encryption) can be used either with HLS or MPEG-DASH adaptive streaming technologies served over HTTP(S), it cannot be used with other formats or delivery technologies.

DRM Providers

JW Player has recommended partners to provide encryption and license serving services. You will need to talk with these partners independently to get setup with your DRM workflow and license capabilities.

Recommended partners for DRM services include:

EZDRM
Vualto

In a typical DRM workflow, you use a DRM provider to encrypt content and serve licenses. A DRM multi-platform provider abstracts the encryption process of cross-platform DRM technologies by providing a single interface for encrypting via Widevine, Fairplay, etc. Some providers also provide a storage solution to store your encrypted assets and can also provide delivery services.


Figure A - Example DRM workflow using JW Player and third-party DRM vendor (workflows and capabilities of each DRM provider vary so please inquire about the particular provider you intend to use).

DRM-specific Setup

Specific DRM configuration options are then nested inside of the drm block. It is possible to configure multiple forms of DRM within a particular source, which will be chosen according to the particular browser.

Custom http headers are sometimes required to be added to the license requests. These values are specific to the DRM provider that you are working with. The below example is using a header with name of customData set equal the value provided. These headers offer extra security and without them the license request will not be approved. Note that not all DRM providers require these custom header pairs. More information can be found on our developer site.

A typical workflow for DRM includes configuring multiple DRM technologies to cover the most common playback environments. These DRM technologies should be embedded inside of a particular source, to allow the player to fall back to the correct technology. An example of a fully nested DRM block is shown below. This method chooses the appropriate DRM technology based on the browser/client that is requesting the content.

jwplayer('myElement').setup({
  playlist: [{
    sources: [{
      file: '//www.website.com/media/videofile.mpd',
        drm: {
          widevine: {
          url: '//widevine-proxy.appspot.com/proxy',
            headers:[{
              name:”customData”,
              value  : "abcdefg1234567hijklmn89101112opqrs98765tuvwxyz"
            }]
          }
        }
    },{
    file: '//www.website.com/media/videofile.mpd',
      drm: {
        playready: {
          url: '//playready-proxy.appspot.com/proxy',
            headers:[{
              name:”customData”,
              value  : "abcdefg1234567hijklmn89101112opqrs98765tuvwxyz"
            }]
          }
      }
    },{
    file: 'http://www.website.com/media/videofile.m3u8',
      drm: {
        fairplay: {
          processSpcUrl: 'path to key server that provides ckc',
          certificateUrl: 'path to certificate',
        }
      }
    },{
    file: "clearkey-manifest.mpd"
      drm: {
        clearkey: {
          key: "xldkjfa9a38hfa98hsadf0a89h",
          keyId: "1234-5678-91011"
        }
      }
    }]
  }]
});

AES Decryption

The Enterprise edition of JW Player has the ability to decrypt stream segments that are encrypted with AES-128 in HLS or MPEG-DASH streaming packages. When encryption is used, the manifest playlist file needs to reference the corresponding key file so that the JW Player can retrieve the keys for decryption. The using the aestoken configuration, the player can also pass a token to the key request URI, enhancing the security of AES.

JW Player supports three modes for decoding encrypted segments:

  1. The key can rotate per fragment or be the same.
  2. The key can be hosted externally or be embedded within the index file.
  3. Custom initialization vectors (IVs) can be used.

Note: JW Player does not support SAMPLE-AES in non-Safari browsers.

Here is an example of a playlist file with a custom IV:

#EXTM3U
#EXT-X-TARGETDURATION:10
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-PLAYLIST-TYPE:VOD
#EXTINF:10,	
#EXT-X-KEY:METHOD=AES-128,URI="key",IV=0x1c341b1db8ff5399501511a99c8c7d14
fileSequence0.ts
#EXTINF:10,	
fileSequence1.ts
#EXT-X-ENDLIST

On embed of the JW Player, a token can be set in the configuration value aestoken. When the JW Player requests the key, the token is appended as a url parameter called token. This allows the key server that is providing the AES decryption key to authenticate whether or not the request is valid. JW Player will check to make sure the ? is present on the key uri and add one if necessary.

An example of how this setup would look:

jwplayer("myElement").setup({
	file: 'sample_aes_stream.m3u8',
	aestoken: 'EXAMPLE_AES_TOKEN'
});

Note: aestoken is not supported in Safari on desktop or mobile.

Apple FairPlay Streaming

Safari 9 and higher supports DRM through Apple FairPlay Streaming (FPS). The most basic setup of FPS is achieved by providing JW Player with the certificate path URL (certificateUrl) and the URL to the server playback context (processSpcUrl). These configuration options are based on default functions that handle the transaction between the browser and the license server. These include options to set license request headers, extract the content id, and extract the key from the content key context (ckc). Certain DRM packagers require their own custom functions to handle these transactions.

Simple Setup

In this example JW Player has built in the code necessary to request the server playback context and to request the content provider’s application certificate. This information should be placed in the appropriate source that contains the Fairplay M3U8 manifest.

        drm: {
          fairplay: {
            certificateUrl: 'http://license.com/fairplay/cert',
            processSpcUrl: 'http://license.com/fairplay/ckc'
          }
        }

These functions are built into the player and are not needed to be defined when the response from the license server is formatted as an arraybuffer instead of text. Note that Apple’s reference example uses a text response instead of an arraybuffer.

Configuring a Custom FairPlay Integration

Apple has provided flexibility to key providers and DRM packagers in how they handle through JavaScript the key, certificate, and content requests. Since there is no single set of configuration options to handle all scenarios, JW Player has the following configuration options that allow custom functions to be passed into the player.

certificateUrl
Required. The path to the certificate which is part of the session data used to initialize the keySession.certificateUrl

processSpcUrl
Required. The path to the license server (server playback context) which provides the ckc. Expects a direct url to the server. If the url needs to be constructed dynamically, a custom function can be passed to this configuration option which returns the url.

extractContentId
Optional. Expects a function that receives the initData uri (converted to a string) from the needkey event, and returns the contentId which is part of the session data used to initialize the keySession.

By default this returns the text following “skd://” in the initData uri:

function(initDataUri) {
    return initDataUri.split('skd://')[1];
}

licenseRequestHeaders
Optional. Expects an Array of Objects containing header “name” and “value” properties to be included in the request to the license server.

licenseResponseType
Optional. Specifies the data type returned by the XHR request to the license server. The default value is 'arraybuffer'. Other options include 'blob', 'json', and 'text'. This option impacts how “licenseRequestMessage” will be processed.

licenseRequestMessage
Optional. Expects a function that receives the license key message and returns the message to be sent to the license server. With the default “licenseResponseType” of ArrayBuffer this function passes through keymessage event message property without any changes.

extractKey
Optional. Expects a function that receives the ckc returned by the license server and returns the key used to update the active key session. If the key can only be extracted asynchronously (for example reading bytes from a ‘blob’ response), this function can return a promise.

By default this returns a Uint8Array view of the default 'arraybuffer' response.

function(ckc) {
    return new Uint8Array(ckc);
}

Here is a summary of all defaults set for optional FairPlay DRM options:


        extractContentId: function(initDataUri) {
            return initDataUri.split('skd://')[1];
        },
        licenseRequestHeaders: [],
        licenseResponseType: 'arraybuffer',
        licenseRequestMessage: function(message) {
           return message;
        },
        extractKey: function(ckc) {
            return new Uint8Array(ckc);
        }
    

Apple FairPlay Reference Implementation

Apple’s reference implementation specifies the response from the license server to be formatted as text instead of as an arraybuffer. Though it is easier to manage when it is an arraybuffer, this is supported in JW Player with the following custom functions.

 fairplay: {
            certificateUrl: 'path/fps_certificate.der',
            extractContentId: function(initDataUri) {
              var link = document.createElement('a');
              link.href = initDataUri;
              return link.hostname;
            },
            processSpcUrl: 'path/keyservermodule',
            licenseResponseType: 'text',
            licenseRequestHeaders: [
              { name: 'Content-type', value: 'application/x-www-form-urlencoded' }
            ],
            licenseRequestMessage: function(message, session) {
              return 'spc='+base64EncodeUint8Array(message)+'&assetId='+encodeURIComponent(session.contentId);
            },
            extractKey: function(ckc) {
              var base64EncodedKey = ckc.trim();
              if (base64EncodedKey.substr(0, 5) === '' && base64EncodedKey.substr(-6) === '') {
                base64EncodedKey = base64EncodedKey.slice(5,-6);
              }
              return base64EncodedKey;
            }
          }

The following function is also required to base64 encode the key string. This does not need to be part of the player setup but needs to be on the page alongside the player for the above example.


    function base64EncodeUint8Array(input) {
        var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
        var output = "";
        var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
        var i = 0;

        while (i < input.length) {
          chr1 = input[i++];
          chr2 = i < input.length ? input[i++] : Number.NaN; // Not sure if the index
          chr3 = i < input.length ? input[i++] : Number.NaN; // checks are needed here

          enc1 = chr1 >> 2;
          enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
          enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
          enc4 = chr3 & 63;

          if (isNaN(chr2)) {
            enc3 = enc4 = 64;
          } else if (isNaN(chr3)) {
            enc4 = 64;
          }
          output += keyStr.charAt(enc1) + keyStr.charAt(enc2) +
                  keyStr.charAt(enc3) + keyStr.charAt(enc4);
        }
        return output;
      }

UpLynk Custom Functions

Verizon UpLynk requires customization to generate the SPC url. JW Player accepts either a function or a direct url within the processSpcUrl configuration option to accommodate this need.

The following is an example implementation within JW Player’s DRM block to integrate with UpLynk provided FPS streams.

      jwplayer('uplynk').setup({
      playlist: [{
        sources: [{
              file: 'content-url.m3u8',
                drm: {
                  fairplay: {
                    certificateUrl: 'path/certificate.der',
                    extractContentId: function(initDataUri) {
                      var link = document.createElement('a');
                      link.href = getSPCUrl(initDataUri);
                      var query = link.search.substr(1);
                      var id = query.split("&");
                      var item = id[0].split("=");
                      var cid = item[1];
                      return cid;
                    },
                    processSpcUrl: getSPCUrl,
                    licenseResponseType: 'json',
                    licenseRequestMessage: function(message, session) {
                      var payload = {};
                      payload.spc = base64EncodeUint8Array(message);
                      payload.assetId = session.contentId;
                      return JSON.stringify(payload);
                    },
                    extractKey: function(response) {
                      return response.ckc;
                    }
                  }
                }
              }]
            }]
      });

      function getSPCUrl(initDataUri) {
        var spcurl = initDataUri.replace('skd://', 'https://');
        spcurl = spcurl.substring(1, spcurl.length);
        return spcurl;
      }

      function base64EncodeUint8Array(input) {
        var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
        var output = "";
        var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
        var i = 0;

        while (i < input.length) {
          chr1 = input[i++];
          chr2 = i < input.length ? input[i++] : Number.NaN; // Not sure if the index
          chr3 = i < input.length ? input[i++] : Number.NaN; // checks are needed here

          enc1 = chr1 >> 2;
          enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
          enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
          enc4 = chr3 & 63;

          if (isNaN(chr2)) {
            enc3 = enc4 = 64;
          } else if (isNaN(chr3)) {
            enc4 = 64;
          }
          output += keyStr.charAt(enc1) + keyStr.charAt(enc2) +
                  keyStr.charAt(enc3) + keyStr.charAt(enc4);
        }
        return output;
      }

EZDRM Example

EZDRM also requires customization to extract content id and the key from the server key context.

jwplayer('ezdrm').setup({
      playlist: [{
        sources: [{
         file: 'content-path.m3u8',
           drm: {
            fairplay: {
              certificateUrl: 'certificate-path.cer',
              extractContentId: function(initDataUri) {
                var uriParts = initDataUri.split('://', 1);
                var protocol = uriParts[0].slice(-3);
                uriParts = initDataUri.split(';', 2);
                var contentId = uriParts.length > 1 ? uriParts[1] : '';
                return protocol.toLowerCase() == 'skd' ? contentId : '';
              },
              processSpcUrl: 'http://fps.ezdrm.com/api/licenses/09cc0377-6dd4-40cb-b09d-b582236e70fe' + '?p1=' + Date.now(),
              licenseResponseType: 'blob',
              licenseRequestHeaders: [
                { name: 'Content-type', value: 'application/octet-stream' }
              ],
              licenseRequestMessage: function(message) {
                return new Blob([message], {type: 'application/octet-binary'});
              },
              extractKey: function(response) {
                return new Promise(function(resolve, reject) {
                  var reader = new FileReader();
                  reader.addEventListener('loadend', function() {
                    resolve(new Uint8Array(reader.result));
                  });
                  reader.addEventListener('error', function() {
                    reject(reader.error);
                  });
                  reader.readAsArrayBuffer(response);
                });
              }
            }
        }
      }]  
    }]
  });
   

Did you find this article helpful?

Please log in to rate this article.