Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add new methods to FairPlayUtils #4029

Merged
merged 1 commit into from Mar 16, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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));
}
}
};