Files
shaka-player/lib/util/fairplay_utils.js
T
Jacob Trimble c5b9d6804f Allow custom content ID in FairPlay.
Now there is a generic callback to transform the init data before
passing it to the browser.  This can be used by apps to use a custom
content ID in FairPlay content.  This also adds some utilities to help
in writing these functions and moves the default behavior to DrmEngine.

Closes #1951

Change-Id: I78ce660c126b53a69d5f55b16775ffcdbbe4d748
2019-08-07 18:38:40 +00:00

123 lines
3.9 KiB
JavaScript

/**
* @license
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
goog.provide('shaka.util.FairPlayUtils');
goog.require('goog.Uri');
goog.require('shaka.util.Uint8ArrayUtils');
/**
* @summary A set of FairPlay utility functions.
* @exportInterface
*/
shaka.util.FairPlayUtils = class {
/**
* Using the default method, extract a content ID from the init data. This is
* based on the FairPlay example documentation.
*
* @param {!BufferSource} initData
* @return {string}
* @export
*/
static defaultGetContentId(initData) {
const uint8 = new Uint8Array(initData);
const dataview = shaka.util.Uint8ArrayUtils.toDataView(uint8);
// The first part is a 4 byte little-endian int, which is the length of
// the second part.
const length = dataview.getUint32(
/* position= */ 0, /* littleEndian= */ true);
if (length + 4 != uint8.byteLength) {
throw new RangeError('Malformed FairPlay init data');
}
// The second part is a UTF-16 LE URI from the manifest.
const uriString = shaka.util.StringUtils.fromUTF16(
uint8.subarray(4), /* littleEndian= */ true);
// The domain of that URI is the content ID according to Apple's FPS
// sample.
const uri = new goog.Uri(uriString);
return uri.getDomain();
}
/**
* Transforms the init data buffer using the given data. The format is:
*
* <pre>
* [4 bytes] initDataSize
* [initDataSize bytes] initData
* [4 bytes] contentIdSize
* [contentIdSize bytes] contentId
* [4 bytes] certSize
* [certSize bytes] cert
* </pre>
*
* @param {!BufferSource} initData
* @param {!BufferSource|string} contentId
* @param {?BufferSource} cert The server certificate; this will throw if not
* provided.
* @return {!Uint8Array}
* @export
*/
static initDataTransform(initData, contentId, cert) {
if (!cert || !cert.byteLength) {
throw new shaka.util.Error(
shaka.util.Error.Severity.CRITICAL,
shaka.util.Error.Category.DRM,
shaka.util.Error.Code.SERVER_CERTIFICATE_REQUIRED);
}
// From that, we build a new init data to use in the session. This is
// composed of several parts. First, the raw init data we already got.
// Second, a 4-byte LE length followed by the content ID in UTF-16-LE.
// Third, a 4-byte LE length followed by the certificate.
/** @type {!Uint8Array} */
let contentIdArray;
if (typeof contentId == 'string') {
contentIdArray = new Uint8Array(
shaka.util.StringUtils.toUTF16(contentId, /* littleEndian= */ true));
} else {
contentIdArray = new Uint8Array(contentId);
}
const rebuiltInitData = new Uint8Array(
8 + initData.byteLength + contentIdArray.byteLength + cert.byteLength);
let offset = 0;
/** @param {!Uint8Array} array */
const append = (array) => {
rebuiltInitData.set(array, offset);
offset += array.byteLength;
};
/** @param {!Uint8Array} array */
const appendWithLength = (array) => {
const view = new DataView(rebuiltInitData.buffer);
const value = array.byteLength;
view.setUint32(offset, value, /* littleEndian= */ true);
offset += 4;
append(array);
};
append(new Uint8Array(initData));
appendWithLength(contentIdArray);
appendWithLength(new Uint8Array(cert));
return rebuiltInitData;
}
};