Files
shaka-player/docs/tutorials/fairplay.md
T
Jacob Trimble 7ae6fc7d93 Fix FairPlay encrypted event handling.
The 'webkitkeyneeded' and 'encrypted' events send similar data, but
they were incompatible with each other and our transform handling.
This makes our polyfill produce the same format as the browser for
cases where the browser may only fire the old event.  This also makes
our utilities work with the new format.

The 'webkitkeyneeded' event was a length-prefixed UTF-16 string while
the 'encrypted' event was just a UTF-8 string.

This also makes a breaking change in the transform callback to pass the
init data type.  This shouldn't break anyone that only uses the first
argument; the second argument was mainly added so we could have the
default transform work without knowing anything.

This change could also break people who use custom transform functions.
The init data format is changing, which could break people who read
it directly.  If they follow the tutorial and use our utilities, it
shouldn't break.  This also updates the tutorial to match the new format
and be more clear about the format.

Fixes #2214

Change-Id: I006382028e828e31e20e085114fd7fd85c0e1eaa
2020-04-07 16:59:27 +00:00

2.5 KiB

FairPlay Support

When using native src= playback, we support using FairPlay on Safari. Adding FairPlay support involves a bit more work than other key systems.

Server certificate

All FairPlay content requires setting a server certificate. This is set in the Player configuration:

const req = await fetch('https://example.com/cert.der');
const cert = await req.arrayBuffer();

player.configure('drm.advanced.com\\.apple\\.fps\\.1_0.serverCertificate',
                 new Uint8Array(cert));

Content ID

Some FairPlay content use custom signaling for the content ID. The content ID is used by the browser to generate the license request. If you don't use the default content ID derivation, you need to specify a custom init data transform:

player.configure('drm.initDataTransform', (initData, initDataType) => {
  if (initDataType != 'skd')
    return initData;

  // 'initData' is a buffer containing an 'skd://' URL as a UTF-8 string.
  const skdUri = shaka.util.StringUtils.fromBytesAutoDetect(initData);
  const contentId = getMyContentId(sdkUri);
  const cert = player.drmInfo().serverCertificate;
  return shaka.util.FairPlayUtils.initDataTransform(initData, contentId, cert);
});

License wrapping

Some FairPlay servers need to accept the license request in a different format or give the response in a different format. For more info, see the general {@tutorial license-wrapping} tutorial:

player.getNetworkingEngine().registerRequestFilter((type, request) => {
  if (type != shaka.net.NetworkingEngine.RequestType.LICENSE) {
    return;
  }

  const originalPayload = new Uint8Array(request.body);
  const base64Payload =
      shaka.util.Uint8ArrayUtils.toStandardBase64(originalPayload);
  const params = 'spc=' + base64Payload;
  request.headers['Content-Type'] = 'application/x-www-form-urlencoded';
  request.body = shaka.util.StringUtils.toUTF8(encodeURIComponent(params));
});

player.getNetworkingEngine().registerResponseFilter((type, response) => {
  if (type != shaka.net.NetworkingEngine.RequestType.LICENSE) {
    return;
  }

  let responseText = shaka.util.StringUtils.fromUTF8(response.data);
  // Trim whitespace.
  responseText = responseText.trim();

  // Look for <ckc> wrapper and remove it.
  if (responseText.substr(0, 5) === '<ckc>' &&
      responseText.substr(-6) === '</ckc>') {
    responseText = responseText.slice(5, -6);
  }

  // Decode the base64-encoded data into the format the browser expects.
  response.data = shaka.util.Uint8ArrayUtils.fromBase64(responseText).buffer;
});