Enable DRM through the web player

Learn how to configure your player to generate a license request and authenticate your video playback using a valid streaming key from the license server.

👍

IMPORTANT

This feature requires an Enterprise license.


Digital rights management (DRM) enables you to protect your content. However, cross-platform compatibility is one of the challenges of using DRM. Most DRM technologies are proprietary and only supported on OEM-specific products. For example, Apple FairPlay Streaming is only compatible with the Apple Safari web browser and other Apple hardware and software products.

📘

GOALS

After completing this article, you should be able to answer the following questions:
  • Which DRM solutions are supported by JW Player's web player?
  • How can I implement DRM playback through the web player?



Workflow

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 through DRM solutions such as Widevine or Fairplay.

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).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).

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).

Some providers also provide a storage solution to store your encrypted assets and delivery services.



DRM License Providers

JW Player has recommended partners to provide encryption and license serving services:
  • EZDRM
  • Vualto

You will need to talk with these partners independently to get set up with your DRM workflow and license capabilities.



Compatibility

JW Player supports industry-standard DRM. The following table shows the DRM technologies that are supported in the web player and which browsers support those technologies.

DRMBrowser (OS)
Apple FairPlay Streaming (FPS)   • Safari 8+ on macOS 10.11+ (El Capitan)
   • Apple iOS 11.2+
Clearkey   • Chrome 35+
   • Firefox 47+
   • Android 4.3+
Google Widevine   • Chrome 62+ (Desktop and Android, must be served over HTTPS)
   • Firefox 47+ (Windows, Mac only)
   • Android 4.3+ (via native JW Player SDK)
Microsoft Playready   • Internet Explorer 11+ (Windows 8.1+ only)
   • Microsoft Edge 12+ (Windows desktop and Phone)

AES 128-bit encryption in HLS is also supported as a basic way to protect your content.



Implementation

Use the following steps to set up DRM playback in your web player.

📘

TIP

As an alternative to managing settings within the player settings, JW Player offers DRM through JW Stream which provides a simplified approach to protecting your content with industry-standard DRM:

  • Providing several configured DRM Policies
  • DRM license generation and management for Widevine, PlayReady, and Fairplay DRM solutions
  • License delivery services for content playback on any device

  1. In the jwplayer().setup(), create a playlist[].sources[] object in which you define DASH and HLS streams for the same content.

    The following example includes the following streams: DASH, HLS, and DASH manifest for ClearKey.
jwplayer('myElement').setup({
    "playlist": [{
        "sources": [{
            "file": "https://www.website.com/media/videofile.mpd"
        }, {
            "file": "https://www.website.com/media/videofile.m3u8"
        }, {
            "file": "https://www.website.com/media/clearkey_manifest.mpd"
        }]
    }]
});

  1. Add an empty drm object to each playlist[].sources[].file.
jwplayer('myElement').setup({
    "playlist": [{
        "sources": [{
            "file": "https://www.website.com/media/videofile.mpd",
            "drm": {}
        }, {
            "file": "https://www.website.com/media/videofile.m3u8",
            "drm": {}
        }, {
            "file": "https://www.website.com/media/clearkey_manifest.mpd",
            "drm": {}
        }]
    }]
});

  1. Define ClearKey, FPS, Playready, or Widevine objects with the information from the DRM providers.
"clearkey": {
    "key": "xldkjfa9a38hfa98hsadf0a89h",
    "keyId": "1234-5678-91011"
}
"fairplay": {
    "certificateUrl": "path/fps_certificate.der",
    "extractContentId": function(initDataUri) {
        var link = document.createElement('a');
        link.href = initDataUri;
        this.contentId = link.hostname;
        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(this.contentId);
    },
    "extractKey": function(ckc) {
        var base64EncodedKey = ckc.trim();
        if (base64EncodedKey.substr(0, 5) === '' && base64EncodedKey.substr(-6) === '') {
            base64EncodedKey = base64EncodedKey.slice(5, -6);
        }
        return base64EncodedKey;
    }
}
"playready": {
    "url": "https://playready-proxy.appspot.com/proxy",
    "headers": [{
        "name": "customData",
        "value": "abcdefg1234567hijklmn89101112opqrs98765tuvwxy"
    }]
}
"widevine": {
    "url": "https://widevine-proxy.appspot.com/proxy",
    "headers": [{
        "name": "customData",
        "value": "abcdefg1234567hijklmn89101112opqrs98765tuvwxy"
    }]
}

  1. Define each playlist[].sources[].file.drm object with the corresponding Widevine, Playready, FPS, or ClearKey object from the previous step.
jwplayer('myElement').setup({
    "playlist": [{
        "sources": [{
            "file": "https://www.website.com/media/videofile.mpd",
            "drm": {
                "widevine": {
                    "url": "https://widevine-proxy.appspot.com/proxy",
                    "headers": [{
                        "name": "customData",
                        "value": "abcdefg1234567hijklmn89101112opqrs98765tuvwxy"
                    }]
                },
                "playready": {
                    "url": "https://playready-proxy.appspot.com/proxy",
                    "headers": [{
                        "name": "customData",
                        "value": "abcdefg1234567hijklmn89101112opqrs98765tuvwxy"
                    }]
                }
            }
        }, {
            "file": "https://www.website.com/media/videofile.m3u8",
            "drm": {
                "fairplay": {
                    "certificateUrl": "path/fps_certificate.der",
                    ...
                    "processSpcUrl": "path/keyservermodule",
                    ...
                }
            }
        }, {
            "file": "https://www.website.com/media/clearkey_manifest.mpd",
            "drm": {
                "clearkey": {
                    "key": "xldkjfa9a38hfa98hsadf0a89h",
                    "keyId": "1234-5678-91011"
                }
            }
        }]
    }]
});

  1. (FPS only) On the same page and near the player code, add the following function to base64-encode the key string.
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;
}

License Wrapping

JW Player offers two methods required for DRM implementations that use license wrapping: licenseRequestFilter and licenseResponseFilter. These methods are available with FPS, PlayReady, or Widevine.

"widevine": {
    "widevineUrl": licenseServerUrl,
    "licenseRequestFilter": function(request, drmInfo) {
        request.headers["Content-Type"] = ["application/json"];
        request.body = JSON.stringify({
            desiredPayload: goesHere
        });
    },
    "licenseResponseFilter": function(response, drmInfo) {
        response.data = ArrayBufferfromBase64(wrappedLicense);
    },
}

FairPlay Special Use Cases

When using certain provider-supplied FPS streams, additional customization is needed:

  • EZDRM requires customization to extract the content ID and key from the server key context.
  • 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.

Examples of both integrations are shown below.

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);
                        });
                    }
                }
            }
        }]
    }]
});
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;
}


Did this page help you?