streaming_protection_CommonEncryption.js
/**
* The copyright in this software is being made available under the BSD License,
* included below. This software may be subject to other third party and contributor
* rights, including patent rights, and no such rights are granted under this license.
*
* Copyright (c) 2013, Dash Industry Forum.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation and/or
* other materials provided with the distribution.
* * Neither the name of Dash Industry Forum nor the names of its
* contributors may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
const LICENSE_SERVER_MANIFEST_CONFIGURATIONS = {
attributes: ['Laurl', 'laurl'],
prefixes: ['clearkey', 'dashif']
};
/**
* @class
* @ignore
*/
class CommonEncryption {
/**
* Find and return the ContentProtection element in the given array
* that indicates support for MPEG Common Encryption
*
* @param {Array} cpArray array of content protection elements
* @returns {Object|null} the Common Encryption content protection element or
* null if one was not found
*/
static findCencContentProtection(cpArray) {
let retVal = null;
for (let i = 0; i < cpArray.length; ++i) {
let cp = cpArray[i];
if (cp.schemeIdUri.toLowerCase() === 'urn:mpeg:dash:mp4protection:2011' &&
(cp.value.toLowerCase() === 'cenc' || cp.value.toLowerCase() === 'cbcs'))
retVal = cp;
}
return retVal;
}
/**
* Returns just the data portion of a single PSSH
*
* @param {ArrayBuffer} pssh - the PSSH
* @return {ArrayBuffer} data portion of the PSSH
*/
static getPSSHData(pssh) {
let offset = 8; // Box size and type fields
let view = new DataView(pssh);
// Read version
let version = view.getUint8(offset);
offset += 20; // Version (1), flags (3), system ID (16)
if (version > 0) {
offset += 4 + (16 * view.getUint32(offset)); // Key ID count (4) and All key IDs (16*count)
}
offset += 4; // Data size
return pssh.slice(offset);
}
/**
* Returns the PSSH associated with the given key system from the concatenated
* list of PSSH boxes in the given initData
*
* @param {KeySystem} keySystem the desired
* key system
* @param {ArrayBuffer} initData 'cenc' initialization data. Concatenated list of PSSH.
* @returns {ArrayBuffer|null} The PSSH box data corresponding to the given key system, null if not found
* or null if a valid association could not be found.
*/
static getPSSHForKeySystem(keySystem, initData) {
let psshList = CommonEncryption.parsePSSHList(initData);
if (keySystem && psshList.hasOwnProperty(keySystem.uuid.toLowerCase())) {
return psshList[keySystem.uuid.toLowerCase()];
}
return null;
}
/**
* Parse a standard common encryption PSSH which contains a simple
* base64-encoding of the init data
*
* @param {Object} cpData the ContentProtection element
* @param {BASE64} BASE64 reference
* @returns {ArrayBuffer|null} the init data or null if not found
*/
static parseInitDataFromContentProtection(cpData, BASE64) {
if ('pssh' in cpData) {
// Remove whitespaces and newlines from pssh text
cpData.pssh.__text = cpData.pssh.__text.replace(/\r?\n|\r/g, '').replace(/\s+/g, '');
return BASE64.decodeArray(cpData.pssh.__text).buffer;
}
return null;
}
/**
* Parses list of PSSH boxes into keysystem-specific PSSH data
*
* @param {ArrayBuffer} data - the concatenated list of PSSH boxes as provided by
* CDM as initialization data when CommonEncryption content is detected
* @returns {Object|Array} an object that has a property named according to each of
* the detected key system UUIDs (e.g. 00000000-0000-0000-0000-0000000000)
* and a ArrayBuffer (the entire PSSH box) as the property value
*/
static parsePSSHList(data) {
if (data === null || data === undefined)
return [];
let dv = new DataView(data.buffer || data); // data.buffer first for Uint8Array support
let done = false;
let pssh = {};
// TODO: Need to check every data read for end of buffer
let byteCursor = 0;
while (!done) {
let size,
nextBox,
version,
systemID;
let boxStart = byteCursor;
if (byteCursor >= dv.buffer.byteLength)
break;
/* Box size */
size = dv.getUint32(byteCursor);
nextBox = byteCursor + size;
byteCursor += 4;
/* Verify PSSH */
if (dv.getUint32(byteCursor) !== 0x70737368) {
byteCursor = nextBox;
continue;
}
byteCursor += 4;
/* Version must be 0 or 1 */
version = dv.getUint8(byteCursor);
if (version !== 0 && version !== 1) {
byteCursor = nextBox;
continue;
}
byteCursor++;
byteCursor += 3; /* skip flags */
// 16-byte UUID/SystemID
systemID = '';
let i, val;
for (i = 0; i < 4; i++) {
val = dv.getUint8(byteCursor + i).toString(16);
systemID += (val.length === 1) ? '0' + val : val;
}
byteCursor += 4;
systemID += '-';
for (i = 0; i < 2; i++) {
val = dv.getUint8(byteCursor + i).toString(16);
systemID += (val.length === 1) ? '0' + val : val;
}
byteCursor += 2;
systemID += '-';
for (i = 0; i < 2; i++) {
val = dv.getUint8(byteCursor + i).toString(16);
systemID += (val.length === 1) ? '0' + val : val;
}
byteCursor += 2;
systemID += '-';
for (i = 0; i < 2; i++) {
val = dv.getUint8(byteCursor + i).toString(16);
systemID += (val.length === 1) ? '0' + val : val;
}
byteCursor += 2;
systemID += '-';
for (i = 0; i < 6; i++) {
val = dv.getUint8(byteCursor + i).toString(16);
systemID += (val.length === 1) ? '0' + val : val;
}
byteCursor += 6;
systemID = systemID.toLowerCase();
/* PSSH Data Size */
byteCursor += 4;
/* PSSH Data */
pssh[systemID] = dv.buffer.slice(boxStart, nextBox);
byteCursor = nextBox;
}
return pssh;
}
static getLicenseServerUrlFromMediaInfo(mediaInfo, schemeIdUri) {
try {
if (!mediaInfo || mediaInfo.length === 0) {
return null;
}
let i = 0;
let licenseServer = null;
while (i < mediaInfo.length && !licenseServer) {
const info = mediaInfo[i];
if (info && info.contentProtection && info.contentProtection.length > 0) {
const targetProtectionData = info.contentProtection.filter((cp) => {
return cp.schemeIdUri && cp.schemeIdUri === schemeIdUri;
});
if (targetProtectionData && targetProtectionData.length > 0) {
let j = 0;
while (j < targetProtectionData.length && !licenseServer) {
const ckData = targetProtectionData[j];
let k = 0;
while (k < LICENSE_SERVER_MANIFEST_CONFIGURATIONS.attributes.length && !licenseServer) {
let l = 0;
const attribute = LICENSE_SERVER_MANIFEST_CONFIGURATIONS.attributes[k];
while (l < LICENSE_SERVER_MANIFEST_CONFIGURATIONS.prefixes.length && !licenseServer) {
const prefix = LICENSE_SERVER_MANIFEST_CONFIGURATIONS.prefixes[l];
if (ckData[attribute] && ckData[attribute].__prefix && ckData[attribute].__prefix === prefix && ckData[attribute].__text) {
licenseServer = ckData[attribute].__text;
}
l += 1;
}
k += 1;
}
j += 1;
}
}
}
i += 1;
}
return licenseServer;
} catch (e) {
return null;
}
}
}
export default CommonEncryption;