Skip to content

Commit

Permalink
feat: add new methods to FairPlayUtils (#4029)
Browse files Browse the repository at this point in the history
This adds 3 new methods to FairPlayUtils:

- isFairPlaySupported --> Closes PR #3562
- spcFairPlayRequest --> This method adds basic spc FairPlay request filter
- commonFairPlayResponse --> This method adds a basic FairPlay response filter that supports a lot of DRM providers.
  • Loading branch information
Álvaro Velad Galván committed Mar 16, 2022
1 parent f5f5177 commit f1eeac1
Showing 1 changed file with 109 additions and 0 deletions.
109 changes: 109 additions & 0 deletions lib/util/fairplay_utils.js
Expand Up @@ -8,16 +8,41 @@ goog.provide('shaka.util.FairPlayUtils');

goog.require('goog.Uri');
goog.require('goog.asserts');
goog.require('shaka.net.NetworkingEngine');
goog.require('shaka.util.BufferUtils');
goog.require('shaka.util.Error');
goog.require('shaka.util.StringUtils');
goog.require('shaka.util.Uint8ArrayUtils');


/**
* @summary A set of FairPlay utility functions.
* @export
*/
shaka.util.FairPlayUtils = class {
/**
* Check if FairPlay is supported.
*
* @return {!Promise.<boolean>}
* @export
*/
static async isFairPlaySupported() {
const config = {
initDataTypes: ['cenc', 'sinf', 'skd'],
videoCapabilities: [
{
contentType: 'video/mp4; codecs="avc1.42E01E"',
},
],
};
try {
await navigator.requestMediaKeySystemAccess('com.apple.fps', [config]);
return true;
} catch (err) {
return false;
}
}

/**
* Using the default method, extract a content ID from the init data. This is
* based on the FairPlay example documentation.
Expand Down Expand Up @@ -106,4 +131,88 @@ shaka.util.FairPlayUtils = class {
offset == rebuiltInitData.length, 'Inconsistent init data length');
return rebuiltInitData;
}

/**
* SPC FairPlay request.
*
* @param {shaka.net.NetworkingEngine.RequestType} type
* @param {shaka.extern.Request} request
* @export
*/
static spcFairPlayRequest(type, request) {
if (type !== shaka.net.NetworkingEngine.RequestType.LICENSE) {
return;
}
const body = /** @type {!(ArrayBuffer|ArrayBufferView)} */(request.body);
const originalPayload = shaka.util.BufferUtils.toUint8(body);
const base64Payload = shaka.util.Uint8ArrayUtils.toBase64(originalPayload);
request.headers['Content-Type'] = 'application/x-www-form-urlencoded';
request.body = shaka.util.StringUtils.toUTF8('spc=' + base64Payload);
}

/**
* Common FairPlay response transform for some DRMs providers.
*
* @param {shaka.net.NetworkingEngine.RequestType} type
* @param {shaka.extern.Response} response
* @export
*/
static commonFairPlayResponse(type, response) {
if (type !== shaka.net.NetworkingEngine.RequestType.LICENSE) {
return;
}

// In Apple's docs, responses can be of the form:
// '\n<ckc>base64encoded</ckc>\n' or 'base64encoded'
// We have also seen responses in JSON format from some of our partners.
// In all of these text-based formats, the CKC data is base64-encoded.

let responseText;
try {
// Convert it to text for further processing.
responseText = shaka.util.StringUtils.fromUTF8(response.data);
} catch (error) {
// Assume it's not a text format of any kind and leave it alone.
return;
}

let licenseProcessing = false;

// 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);
licenseProcessing = true;
}

// Look for a JSON wrapper and remove it.
try {
const responseObject = /** @type {!Object} */(JSON.parse(responseText));
if (responseObject['ckc']) {
responseText = responseObject['ckc'];
licenseProcessing = true;
}
if (responseObject['CkcMessage']) {
responseText = responseObject['CkcMessage'];
licenseProcessing = true;
}
if (responseObject['License']) {
responseText = responseObject['License'];
licenseProcessing = true;
}
} catch (err) {
// It wasn't JSON. Fall through with other transformations.
}

if (licenseProcessing) {
// Decode the base64-encoded data into the format the browser expects.
// It's not clear why FairPlay license servers don't just serve this
// directly.
response.data = shaka.util.BufferUtils.toArrayBuffer(
shaka.util.Uint8ArrayUtils.fromBase64(responseText));
}
}
};

0 comments on commit f1eeac1

Please sign in to comment.