License Wrapping

Applications sometimes need to communicate extra information to or from its license server. EME, the API that browsers provide for DRM, does not offer a direct way to include extra information in a license request or extract extra information from a license response. To pass extra information, applications must "wrap" requests and "unwrap" responses as they pass through JavaScript.

Please note that the license server we are using in this tutorial is a Widevine license server, so you will need to use Chrome to follow along.

Wrapping License Requests

If your application needs to communicate complex information to the license server along with the request, the solution is to "wrap" the platform's license request and "unwrap" it at the license server. In Shaka Player, this is accomplished with a network request filter.

In practice, you may wrap the request in any format that can be constructed on the client and parsed on the server. For simplicity, the server used in this tutorial expects a JSON format that looks like this:

{
  "rawLicenseRequestBase64":
      "VGhlIHJhdyBsaWNlbnNlIHJlcXVlc3QgZ2VuZXJhdGVkIGJ5IHRoZSBDRE0=",
  ...
}

To start, we're going to use the code from Basic Usage, but use this manifest and license server:

var manifestUri =
    '//storage.googleapis.com/shaka-demo-assets/sintel-widevine/dash.mpd';
var licenseServer = '//cwip-shaka-proxy.appspot.com/wrapped_request';

We'll also need to configure the player to use this license server before it loads the manifest:

  player.configure({
    drm: {
      servers: { 'com.widevine.alpha': licenseServer }
    }
  });

  // Try to load a manifest.
  player.load(manifestUri).then(function() {
    // The video should now be playing!
  }).catch(onError);

This license server is expecting a wrapped request, so if we try to play now, we will see Error code 6007, which means LICENSE_REQUEST_FAILED. To wrap the license request, we must register a request filter:

  player.getNetworkingEngine().registerRequestFilter(function(type, request) {
    // Only manipulate license requests:
    if (type == shaka.net.NetworkingEngine.RequestType.LICENSE) {
      // This is the raw license request generated by the Widevine CDM.
      var rawLicenseRequest = new Uint8Array(request.body);

      // Create the wrapped request structure.
      var wrapped = {};

      // Encode the raw license request in base64.
      // The server we are using in this tutorial expects this field and this
      // encoding for the raw request.
      wrapped.rawLicenseRequestBase64 =
          btoa(String.fromCharCode.apply(null, rawLicenseRequest));

      // Add whatever else we want to communicate to the server.
      // None of these values are read by the server we are using in this
      // tutorial.
      // In practice, you would send what the server needs and the server would
      // react to it.
      wrapped.favoriteColor = 'blue';
      wrapped.Beatles = ['John', 'Paul', 'George', 'Ringo'];
      wrapped.bestBeatleIndex = 1;  // Paul, of course.
      wrapped.pEqualsNP = false;  // maybe?

      // Encode the wrapped request as JSON.
      var wrappedJson = JSON.stringify(wrapped);
      // Convert the JSON string back into a Uint8Array to replace the request
      // body.
      request.body = new Uint8Array(wrappedJson.length);
      for (var i = 0; i < wrappedJson.length; ++i) {
        request.body[i] = wrappedJson.charCodeAt(i);
      }
    }
  });

Load the page again, and the license request will succeed.

Wrapping License Responses

If your license server needs to communicate complex information back to the application, the solution is very similar to what we just did above. We can "wrap" the license itself in the server and "unwrap" it in the client. In Shaka Player, this is accomplished with a network response filter.

Similar to the guideline for license requests, you may wrap the response in any format that can be constructed on the server and parsed by the client. We will again use JSON for simplicity. The server will send a response with a format that looks like this:

{
  "rawLicenseBase64":
      "VGhlIHJhdyBsaWNlbnNlIGZyb20gdGhlIGxpY2Vuc2Ugc2VydmVyIGJhY2tlbmQ=",
  ...
}

Change the license server to:

var licenseServer =
    '//cwip-shaka-proxy.appspot.com/wrapped_request_and_response';

This license server is sending a wrapped response, so if we try to play now, we will see Error code 6008, which means LICENSE_RESPONSE_REJECTED. The Widevine CDM does not understand this wrapped format, so we must unwrap it first using a request filter:

  player.getNetworkingEngine().registerResponseFilter(function(type, response) {
    // Only manipulate license responses:
    if (type == shaka.net.NetworkingEngine.RequestType.LICENSE) {
      // This is the wrapped license.
      var wrappedArray = new Uint8Array(response.data);
      // Convert it to a string.
      var wrappedString = String.fromCharCode.apply(null, wrappedArray);
      // Parse the JSON string into an object.
      var wrapped = JSON.parse(wrappedString);

      // This is a base64-encoded version of the raw license.
      var rawLicenseBase64 = wrapped.rawLicenseBase64;
      // Decode it to a string.
      var rawLicenseString = atob(rawLicenseBase64);
      // Convert that string into a Uint8Array and replace the response data
      // to feed it to the Widevine CDM.
      response.data = new Uint8Array(rawLicenseString.length);
      for (var i = 0; i < rawLicenseString.length; ++i) {
        response.data[i] = rawLicenseString.charCodeAt(i);
      }

      // Read additional fields from the server.
      // The server we are using in this tutorial does not send anything useful.
      // In practice, you could send any license metadata the client might need.
      // Here we log what the server sent to the JavaScript console for
      // inspection.
      console.log(wrapped);
    }
  });

Load the page again, and the license response will be accepted by the Widevine CDM. Open the JavaScript console to see what the server sent back.

Continue the Tutorials

Next, check out Plugins and Customizing the Build.