Files
shaka-player/lib/dash/content_protection.js
T
Dave Nicholas 7116a34ec2 feat!: Remove DOM Parser (#6063)
## Background: 
The native DOM Parser can perform poorly on some older devices, this
approach is faster on newer devices but is considerably better on older
devices.
This PR replaces the usage of the DOM Parser for DASH, MSS, VTT and
TTML.

The draw back of this approach that it does not include any validation
at the cost of better performance.
2024-01-22 08:39:06 +01:00

829 lines
25 KiB
JavaScript

/*! @license
* Shaka Player
* Copyright 2016 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
goog.provide('shaka.dash.ContentProtection');
goog.require('goog.asserts');
goog.require('shaka.log');
goog.require('shaka.util.BufferUtils');
goog.require('shaka.util.Error');
goog.require('shaka.util.ManifestParserUtils');
goog.require('shaka.util.Pssh');
goog.require('shaka.util.StringUtils');
goog.require('shaka.util.TXml');
goog.require('shaka.util.Uint8ArrayUtils');
/**
* @summary A set of functions for parsing and interpreting ContentProtection
* elements.
*/
shaka.dash.ContentProtection = class {
/**
* Parses info from the ContentProtection elements at the AdaptationSet level.
*
* @param {!Array.<!shaka.extern.xml.Node>} elems
* @param {boolean} ignoreDrmInfo
* @param {!Object.<string, string>} keySystemsByURI
* @return {shaka.dash.ContentProtection.Context}
*/
static parseFromAdaptationSet(elems, ignoreDrmInfo, keySystemsByURI) {
const ContentProtection = shaka.dash.ContentProtection;
const ManifestParserUtils = shaka.util.ManifestParserUtils;
const parsed = ContentProtection.parseElements_(elems);
/** @type {Array.<shaka.extern.InitDataOverride>} */
let defaultInit = null;
/** @type {!Array.<shaka.extern.DrmInfo>} */
let drmInfos = [];
let parsedNonCenc = [];
/** @type {?shaka.dash.ContentProtection.Aes128Info} */
let aes128Info = null;
// Get the default key ID; if there are multiple, they must all match.
const keyIds = new Set(parsed.map((element) => element.keyId));
// Remove any possible null value (elements may have no key ids).
keyIds.delete(null);
if (keyIds.size > 1) {
throw new shaka.util.Error(
shaka.util.Error.Severity.CRITICAL,
shaka.util.Error.Category.MANIFEST,
shaka.util.Error.Code.DASH_CONFLICTING_KEY_IDS);
}
if (!ignoreDrmInfo) {
const aes128Elements = parsed.filter((elem) => {
return elem.schemeUri == ContentProtection.Aes128Protection_;
});
if (aes128Elements.length > 1) {
throw new shaka.util.Error(
shaka.util.Error.Severity.CRITICAL,
shaka.util.Error.Category.MANIFEST,
shaka.util.Error.Code.DASH_CONFLICTING_AES_128);
}
if (aes128Elements.length) {
aes128Info = ContentProtection.parseAes128_(aes128Elements[0]);
}
// Find the default key ID and init data. Create a new array of all the
// non-CENC elements.
parsedNonCenc = parsed.filter((elem) => {
if (elem.schemeUri == ContentProtection.MP4Protection_) {
goog.asserts.assert(!elem.init || elem.init.length,
'Init data must be null or non-empty.');
defaultInit = elem.init || defaultInit;
return false;
} else {
return elem.schemeUri != ContentProtection.Aes128Protection_;
}
});
if (parsedNonCenc.length) {
drmInfos = ContentProtection.convertElements_(
defaultInit, parsedNonCenc, keySystemsByURI, keyIds);
// If there are no drmInfos after parsing, then add a dummy entry.
// This may be removed in parseKeyIds.
if (drmInfos.length == 0) {
drmInfos = [ManifestParserUtils.createDrmInfo('', defaultInit)];
}
}
}
// If there are only CENC element(s) or ignoreDrmInfo flag is set, assume
// all key-systems are supported.
if (parsed.length && !aes128Info &&
(ignoreDrmInfo || !parsedNonCenc.length)) {
drmInfos = [];
for (const keySystem of Object.values(keySystemsByURI)) {
// If the manifest doesn't specify any key systems, we shouldn't
// put clearkey in this list. Otherwise, it may be triggered when
// a real key system should be used instead.
if (keySystem != 'org.w3.clearkey') {
const info =
ManifestParserUtils.createDrmInfo(keySystem, defaultInit);
drmInfos.push(info);
}
}
}
// If we have a default key id, apply it to every initData.
const defaultKeyId = Array.from(keyIds)[0] || null;
if (defaultKeyId) {
for (const info of drmInfos) {
for (const initData of info.initData) {
initData.keyId = defaultKeyId;
}
}
}
return {
defaultKeyId: defaultKeyId,
defaultInit: defaultInit,
drmInfos: drmInfos,
aes128Info: aes128Info,
firstRepresentation: true,
};
}
/**
* Parses the given ContentProtection elements found at the Representation
* level. This may update the |context|.
*
* @param {!Array.<!shaka.extern.xml.Node>} elems
* @param {shaka.dash.ContentProtection.Context} context
* @param {boolean} ignoreDrmInfo
* @param {!Object.<string, string>} keySystemsByURI
* @return {?string} The parsed key ID
*/
static parseFromRepresentation(
elems, context, ignoreDrmInfo, keySystemsByURI) {
const ContentProtection = shaka.dash.ContentProtection;
const repContext = ContentProtection.parseFromAdaptationSet(
elems, ignoreDrmInfo, keySystemsByURI);
if (context.firstRepresentation) {
const asUnknown = context.drmInfos.length == 1 &&
!context.drmInfos[0].keySystem;
const asUnencrypted = context.drmInfos.length == 0;
const repUnencrypted = repContext.drmInfos.length == 0;
// There are two cases where we need to replace the |drmInfos| in the
// context with those in the Representation:
// 1. The AdaptationSet does not list any ContentProtection.
// 2. The AdaptationSet only lists unknown key-systems.
if (asUnencrypted || (asUnknown && !repUnencrypted)) {
context.drmInfos = repContext.drmInfos;
}
context.firstRepresentation = false;
} else if (repContext.drmInfos.length > 0) {
// If this is not the first Representation, then we need to remove entries
// from the context that do not appear in this Representation.
context.drmInfos = context.drmInfos.filter((asInfo) => {
return repContext.drmInfos.some((repInfo) => {
return repInfo.keySystem == asInfo.keySystem;
});
});
// If we have filtered out all key-systems, throw an error.
if (context.drmInfos.length == 0) {
throw new shaka.util.Error(
shaka.util.Error.Severity.CRITICAL,
shaka.util.Error.Category.MANIFEST,
shaka.util.Error.Code.DASH_NO_COMMON_KEY_SYSTEM);
}
}
return repContext.defaultKeyId || context.defaultKeyId;
}
/**
* Gets a Widevine license URL from a content protection element
* containing a custom `ms:laurl` or 'dashif:Laurl' elements
*
* @param {shaka.dash.ContentProtection.Element} element
* @return {string}
*/
static getWidevineLicenseUrl(element) {
const dashIfLaurlNode = shaka.util.TXml.findChildNS(
element.node, shaka.dash.ContentProtection.DashIfNamespaceUri_,
'Laurl',
);
if (dashIfLaurlNode) {
const textContents = shaka.util.TXml.getTextContents(dashIfLaurlNode);
if (textContents) {
return textContents;
}
}
const mslaurlNode = shaka.util.TXml.findChildNS(
element.node, 'urn:microsoft', 'laurl');
if (mslaurlNode) {
return mslaurlNode.attributes['licenseUrl'] || '';
}
return '';
}
/**
* Gets a ClearKey license URL from a content protection element
* containing a custom `clearkey::Laurl` or 'dashif:Laurl' elements
*
* @param {shaka.dash.ContentProtection.Element} element
* @return {string}
*/
static getClearKeyLicenseUrl(element) {
const dashIfLaurlNode = shaka.util.TXml.findChildNS(
element.node, shaka.dash.ContentProtection.DashIfNamespaceUri_,
'Laurl',
);
if (dashIfLaurlNode) {
const textContents = shaka.util.TXml.getTextContents(dashIfLaurlNode);
if (textContents) {
return textContents;
}
}
const clearKeyLaurlNode = shaka.util.TXml.findChildNS(
element.node, shaka.dash.ContentProtection.ClearKeyNamespaceUri_,
'Laurl',
);
if (clearKeyLaurlNode &&
clearKeyLaurlNode.attributes['Lic_type'] === 'EME-1.0') {
if (clearKeyLaurlNode) {
const textContents = shaka.util.TXml.getTextContents(clearKeyLaurlNode);
if (textContents) {
return textContents;
}
}
}
return '';
}
/**
* Parses an Array buffer starting at byteOffset for PlayReady Object Records.
* Each PRO Record is preceded by its PlayReady Record type and length in
* bytes.
*
* PlayReady Object Record format: https://goo.gl/FTcu46
*
* @param {!DataView} view
* @param {number} byteOffset
* @return {!Array.<shaka.dash.ContentProtection.PlayReadyRecord>}
* @private
*/
static parseMsProRecords_(view, byteOffset) {
const records = [];
while (byteOffset < view.byteLength - 1) {
const type = view.getUint16(byteOffset, true);
byteOffset += 2;
const byteLength = view.getUint16(byteOffset, true);
byteOffset += 2;
if ((byteLength & 1) != 0 || byteLength + byteOffset > view.byteLength) {
shaka.log.warning('Malformed MS PRO object');
return [];
}
const recordValue = shaka.util.BufferUtils.toUint8(
view, byteOffset, byteLength);
records.push({
type: type,
value: recordValue,
});
byteOffset += byteLength;
}
return records;
}
/**
* Parses a buffer for PlayReady Objects. The data
* should contain a 32-bit integer indicating the length of
* the PRO in bytes. Following that, a 16-bit integer for
* the number of PlayReady Object Records in the PRO. Lastly,
* a byte array of the PRO Records themselves.
*
* PlayReady Object format: https://goo.gl/W8yAN4
*
* @param {BufferSource} data
* @return {!Array.<shaka.dash.ContentProtection.PlayReadyRecord>}
* @private
*/
static parseMsPro_(data) {
let byteOffset = 0;
const view = shaka.util.BufferUtils.toDataView(data);
// First 4 bytes is the PRO length (DWORD)
const byteLength = view.getUint32(byteOffset, /* littleEndian= */ true);
byteOffset += 4;
if (byteLength != data.byteLength) {
// Malformed PRO
shaka.log.warning('PlayReady Object with invalid length encountered.');
return [];
}
// Skip PRO Record count (WORD)
byteOffset += 2;
// Rest of the data contains the PRO Records
const ContentProtection = shaka.dash.ContentProtection;
return ContentProtection.parseMsProRecords_(view, byteOffset);
}
/**
* PlayReady Header format: https://goo.gl/dBzxNA
*
* @param {!shaka.extern.xml.Node} xml
* @return {string}
* @private
*/
static getLaurl_(xml) {
const TXml = shaka.util.TXml;
// LA_URL element is optional and no more than one is
// allowed inside the DATA element. Only absolute URLs are allowed.
// If the LA_URL element exists, it must not be empty.
for (const elem of TXml.getElementsByTagName(xml, 'DATA')) {
if (elem.children) {
for (const child of elem.children) {
if (child.tagName == 'LA_URL') {
return /** @type{string} */(shaka.util.TXml.getTextContents(child));
}
}
}
}
// Not found
return '';
}
/**
* Gets a PlayReady license URL from a content protection element
* containing a PlayReady Header Object
*
* @param {shaka.dash.ContentProtection.Element} element
* @return {string}
*/
static getPlayReadyLicenseUrl(element) {
const dashIfLaurlNode = shaka.util.TXml.findChildNS(
element.node, shaka.dash.ContentProtection.DashIfNamespaceUri_,
'Laurl',
);
if (dashIfLaurlNode) {
const textContents = shaka.util.TXml.getTextContents(dashIfLaurlNode);
if (textContents) {
return textContents;
}
}
const proNode = shaka.util.TXml.findChildNS(
element.node, 'urn:microsoft:playready', 'pro');
if (!proNode || !shaka.util.TXml.getTextContents(proNode)) {
return '';
}
const ContentProtection = shaka.dash.ContentProtection;
const PLAYREADY_RECORD_TYPES = ContentProtection.PLAYREADY_RECORD_TYPES;
const textContent =
/** @type {string} */ (shaka.util.TXml.getTextContents(proNode));
const bytes = shaka.util.Uint8ArrayUtils.fromBase64(textContent);
const records = ContentProtection.parseMsPro_(bytes);
const record = records.filter((record) => {
return record.type === PLAYREADY_RECORD_TYPES.RIGHTS_MANAGEMENT;
})[0];
if (!record) {
return '';
}
const xml = shaka.util.StringUtils.fromUTF16(record.value, true);
const rootElement = shaka.util.TXml.parseXmlString(xml, 'WRMHEADER');
if (!rootElement) {
return '';
}
return ContentProtection.getLaurl_(rootElement);
}
/**
* Gets a PlayReady initData from a content protection element
* containing a PlayReady Pro Object
*
* @param {shaka.dash.ContentProtection.Element} element
* @return {?Array.<shaka.extern.InitDataOverride>}
* @private
*/
static getInitDataFromPro_(element) {
const proNode = shaka.util.TXml.findChildNS(
element.node, 'urn:microsoft:playready', 'pro');
if (!proNode || !shaka.util.TXml.getTextContents(proNode)) {
return null;
}
const Uint8ArrayUtils = shaka.util.Uint8ArrayUtils;
const textContent =
/** @type{string} */ (shaka.util.TXml.getTextContents(proNode));
const data = Uint8ArrayUtils.fromBase64(textContent);
const systemId = new Uint8Array([
0x9a, 0x04, 0xf0, 0x79, 0x98, 0x40, 0x42, 0x86,
0xab, 0x92, 0xe6, 0x5b, 0xe0, 0x88, 0x5f, 0x95,
]);
const keyIds = new Set();
const psshVersion = 0;
const pssh =
shaka.util.Pssh.createPssh(data, systemId, keyIds, psshVersion);
return [
{
initData: pssh,
initDataType: 'cenc',
keyId: element.keyId,
},
];
}
/**
* Creates ClearKey initData from Default_KID value retrieved from previously
* parsed ContentProtection tag.
* @param {shaka.dash.ContentProtection.Element} element
* @param {!Set.<string>} keyIds
* @return {?Array.<shaka.extern.InitDataOverride>}
* @private
*/
static getInitDataClearKey_(element, keyIds) {
if (keyIds.size == 0) {
return null;
}
const systemId = new Uint8Array([
0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02,
0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b,
]);
const data = new Uint8Array([]);
const psshVersion = 1;
const pssh =
shaka.util.Pssh.createPssh(data, systemId, keyIds, psshVersion);
return [
{
initData: pssh,
initDataType: 'cenc',
keyId: element.keyId,
},
];
}
/**
* Creates DrmInfo objects from the given element.
*
* @param {Array.<shaka.extern.InitDataOverride>} defaultInit
* @param {!Array.<shaka.dash.ContentProtection.Element>} elements
* @param {!Object.<string, string>} keySystemsByURI
* @param {!Set.<string>} keyIds
* @return {!Array.<shaka.extern.DrmInfo>}
* @private
*/
static convertElements_(defaultInit, elements, keySystemsByURI, keyIds) {
const ContentProtection = shaka.dash.ContentProtection;
const ManifestParserUtils = shaka.util.ManifestParserUtils;
const licenseUrlParsers = ContentProtection.licenseUrlParsers_;
/** @type {!Array.<shaka.extern.DrmInfo>} */
const out = [];
for (const element of elements) {
const keySystem = keySystemsByURI[element.schemeUri];
if (keySystem) {
goog.asserts.assert(
!element.init || element.init.length,
'Init data must be null or non-empty.');
const proInitData = ContentProtection.getInitDataFromPro_(element);
let clearKeyInitData = null;
if (element.schemeUri ===
shaka.dash.ContentProtection.ClearKeySchemeUri_) {
clearKeyInitData =
ContentProtection.getInitDataClearKey_(element, keyIds);
}
const initData = element.init || defaultInit || proInitData ||
clearKeyInitData;
const info = ManifestParserUtils.createDrmInfo(keySystem, initData);
const licenseParser = licenseUrlParsers.get(keySystem);
if (licenseParser) {
info.licenseServerUri = licenseParser(element);
}
out.push(info);
}
}
return out;
}
/**
* Parses the given ContentProtection elements. If there is an error, it
* removes those elements.
*
* @param {!Array.<!shaka.extern.xml.Node>} elems
* @return {!Array.<shaka.dash.ContentProtection.Element>}
* @private
*/
static parseElements_(elems) {
/** @type {!Array.<shaka.dash.ContentProtection.Element>} */
const out = [];
for (const elem of elems) {
const parsed = shaka.dash.ContentProtection.parseElement_(elem);
if (parsed) {
out.push(parsed);
}
}
return out;
}
/**
* Parses the given ContentProtection element.
*
* @param {!shaka.extern.xml.Node} elem
* @return {?shaka.dash.ContentProtection.Element}
* @private
*/
static parseElement_(elem) {
const NS = shaka.dash.ContentProtection.CencNamespaceUri_;
const TXml = shaka.util.TXml;
/** @type {?string} */
let schemeUri = elem.attributes['schemeIdUri'];
/** @type {?string} */
let keyId = TXml.getAttributeNS(elem, NS, 'default_KID');
/** @type {!Array.<string>} */
const psshs = TXml.findChildrenNS(elem, NS, 'pssh')
.map(TXml.getContents);
if (!schemeUri) {
shaka.log.error('Missing required schemeIdUri attribute on',
'ContentProtection element', elem);
return null;
}
schemeUri = schemeUri.toLowerCase();
if (keyId) {
keyId = keyId.replace(/-/g, '').toLowerCase();
if (keyId.includes(' ')) {
throw new shaka.util.Error(
shaka.util.Error.Severity.CRITICAL,
shaka.util.Error.Category.MANIFEST,
shaka.util.Error.Code.DASH_MULTIPLE_KEY_IDS_NOT_SUPPORTED);
}
}
/** @type {!Array.<shaka.extern.InitDataOverride>} */
let init = [];
try {
// Try parsing PSSH data.
init = psshs.map((pssh) => {
return {
initDataType: 'cenc',
initData: shaka.util.Uint8ArrayUtils.fromBase64(pssh),
keyId: null,
};
});
} catch (e) {
throw new shaka.util.Error(
shaka.util.Error.Severity.CRITICAL,
shaka.util.Error.Category.MANIFEST,
shaka.util.Error.Code.DASH_PSSH_BAD_ENCODING);
}
return {
node: elem,
schemeUri: schemeUri,
keyId: keyId,
init: (init.length > 0 ? init : null),
};
}
/**
* Parses the given AES-128 ContentProtection element.
*
* @param {shaka.dash.ContentProtection.Element} element
* @return {?shaka.dash.ContentProtection.Aes128Info}
* @private
*/
static parseAes128_(element) {
// Check if the Web Crypto API is available.
if (!window.crypto || !window.crypto.subtle) {
shaka.log.alwaysWarn('Web Crypto API is not available to decrypt ' +
'AES-128. (Web Crypto only exists in secure origins like https)');
throw new shaka.util.Error(
shaka.util.Error.Severity.CRITICAL,
shaka.util.Error.Category.MANIFEST,
shaka.util.Error.Code.NO_WEB_CRYPTO_API);
}
const namespace = 'urn:mpeg:dash:schema:sea:2012';
const segmentEncryption = shaka.util.TXml.findChildNS(
element.node, namespace, 'SegmentEncryption');
if (!segmentEncryption) {
throw new shaka.util.Error(
shaka.util.Error.Severity.CRITICAL,
shaka.util.Error.Category.MANIFEST,
shaka.util.Error.Code.DASH_UNSUPPORTED_AES_128);
}
const aesSchemeIdUri = 'urn:mpeg:dash:sea:aes128-cbc:2013';
const segmentEncryptionSchemeIdUri =
segmentEncryption.attributes['schemeIdUri'];
if (segmentEncryptionSchemeIdUri != aesSchemeIdUri) {
throw new shaka.util.Error(
shaka.util.Error.Severity.CRITICAL,
shaka.util.Error.Category.MANIFEST,
shaka.util.Error.Code.DASH_UNSUPPORTED_AES_128);
}
const cryptoPeriod = shaka.util.TXml.findChildNS(
element.node, namespace, 'CryptoPeriod');
if (!cryptoPeriod) {
throw new shaka.util.Error(
shaka.util.Error.Severity.CRITICAL,
shaka.util.Error.Category.MANIFEST,
shaka.util.Error.Code.DASH_UNSUPPORTED_AES_128);
}
const ivHex = cryptoPeriod.attributes['IV'];
const keyUri = cryptoPeriod.attributes['keyUriTemplate'];
if (!ivHex || !keyUri) {
throw new shaka.util.Error(
shaka.util.Error.Severity.CRITICAL,
shaka.util.Error.Category.MANIFEST,
shaka.util.Error.Code.DASH_UNSUPPORTED_AES_128);
}
// Exclude 0x at the start of string.
const iv = shaka.util.Uint8ArrayUtils.fromHex(ivHex.substr(2));
if (iv.byteLength != 16) {
throw new shaka.util.Error(
shaka.util.Error.Severity.CRITICAL,
shaka.util.Error.Category.MANIFEST,
shaka.util.Error.Code.AES_128_INVALID_IV_LENGTH);
}
return {
keyUri,
iv,
};
}
};
/**
* @typedef {{
* type: number,
* value: !Uint8Array
* }}
*
* @description
* The parsed result of a PlayReady object record.
*
* @property {number} type
* Type of data stored in the record.
* @property {!Uint8Array} value
* Record content.
*/
shaka.dash.ContentProtection.PlayReadyRecord;
/**
* Enum for PlayReady record types.
* @enum {number}
*/
shaka.dash.ContentProtection.PLAYREADY_RECORD_TYPES = {
RIGHTS_MANAGEMENT: 0x001,
RESERVED: 0x002,
EMBEDDED_LICENSE: 0x003,
};
/**
* @typedef {{
* defaultKeyId: ?string,
* defaultInit: Array.<shaka.extern.InitDataOverride>,
* drmInfos: !Array.<shaka.extern.DrmInfo>,
* aes128Info: ?shaka.dash.ContentProtection.Aes128Info,
* firstRepresentation: boolean
* }}
*
* @description
* Contains information about the ContentProtection elements found at the
* AdaptationSet level.
*
* @property {?string} defaultKeyId
* The default key ID to use. This is used by parseKeyIds as a default. This
* can be null to indicate that there is no default.
* @property {Array.<shaka.extern.InitDataOverride>} defaultInit
* The default init data override. This can be null to indicate that there
* is no default.
* @property {!Array.<shaka.extern.DrmInfo>} drmInfos
* The DrmInfo objects.
* @property {?shaka.dash.ContentProtection.Aes128Info} aes128Info
* The AES-128 key info.
* @property {boolean} firstRepresentation
* True when first parsed; changed to false after the first call to
* parseKeyIds. This is used to determine if a dummy key-system should be
* overwritten; namely that the first representation can replace the dummy
* from the AdaptationSet.
*/
shaka.dash.ContentProtection.Context;
/**
* @typedef {{
* keyUri: string,
* iv: !Uint8Array
* }}
*
* @description
* Contains information about the AES-128 keyUri and IV found at the
* AdaptationSet level.
*
* @property {string} method
* The keyUri in the manifest.
* @property {!Uint8Array} iv
* The IV in the manifest.
*/
shaka.dash.ContentProtection.Aes128Info;
/**
* @typedef {{
* node: !shaka.extern.xml.Node,
* schemeUri: string,
* keyId: ?string,
* init: Array.<shaka.extern.InitDataOverride>
* }}
*
* @description
* The parsed result of a single ContentProtection element.
*
* @property {!shaka.extern.xml.Node} node
* The ContentProtection XML element.
* @property {string} schemeUri
* The scheme URI.
* @property {?string} keyId
* The default key ID, if present.
* @property {Array.<shaka.extern.InitDataOverride>} init
* The init data, if present. If there is no init data, it will be null. If
* this is non-null, there is at least one element.
*/
shaka.dash.ContentProtection.Element;
/**
* A map of key system name to license server url parser.
*
* @const {!Map.<string, function(shaka.dash.ContentProtection.Element)>}
* @private
*/
shaka.dash.ContentProtection.licenseUrlParsers_ = new Map()
.set('com.widevine.alpha',
shaka.dash.ContentProtection.getWidevineLicenseUrl)
.set('com.microsoft.playready',
shaka.dash.ContentProtection.getPlayReadyLicenseUrl)
.set('com.microsoft.playready.recommendation',
shaka.dash.ContentProtection.getPlayReadyLicenseUrl)
.set('com.microsoft.playready.software',
shaka.dash.ContentProtection.getPlayReadyLicenseUrl)
.set('com.microsoft.playready.hardware',
shaka.dash.ContentProtection.getPlayReadyLicenseUrl)
.set('org.w3.clearkey',
shaka.dash.ContentProtection.getClearKeyLicenseUrl);
/**
* @const {string}
* @private
*/
shaka.dash.ContentProtection.MP4Protection_ =
'urn:mpeg:dash:mp4protection:2011';
/**
* @const {string}
* @private
*/
shaka.dash.ContentProtection.Aes128Protection_ =
'urn:mpeg:dash:sea:2012';
/**
* @const {string}
* @private
*/
shaka.dash.ContentProtection.CencNamespaceUri_ = 'urn:mpeg:cenc:2013';
/**
* @const {string}
* @private
*/
shaka.dash.ContentProtection.ClearKeyNamespaceUri_ =
'http://dashif.org/guidelines/clearKey';
/**
* @const {string}
* @private
*/
shaka.dash.ContentProtection.ClearKeySchemeUri_ =
'urn:uuid:e2719d58-a985-b3c9-781a-b030af78d30e';
/**
* @const {string}
* @private
*/
shaka.dash.ContentProtection.DashIfNamespaceUri_ =
'https://dashif.org/CPS';