mirror of
https://github.com/shaka-project/shaka-player.git
synced 2026-06-24 17:35:10 +03:00
5ae80cc67d
This makes a large number of small typo fixes. It also rewords a number of comments and JSDoc descriptions, and does some formatting standardization. This doesn't fix every single issue, but it fixes a lot. Notably, there were some formatting issues I declined to standardize due to ambivalence on what the proper standardization would be; for example, when and where empty lines should show up in JSDoc. Change-Id: I5904ec91b96417a9ac5e19cb4f7b07a084f26ac8
360 lines
12 KiB
JavaScript
360 lines
12 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.dash.ContentProtection');
|
|
|
|
goog.require('goog.asserts');
|
|
goog.require('shaka.log');
|
|
goog.require('shaka.util.Error');
|
|
goog.require('shaka.util.Functional');
|
|
goog.require('shaka.util.ManifestParserUtils');
|
|
goog.require('shaka.util.MapUtils');
|
|
goog.require('shaka.util.Uint8ArrayUtils');
|
|
goog.require('shaka.util.XmlUtils');
|
|
|
|
|
|
/**
|
|
* @namespace shaka.dash.ContentProtection
|
|
* @summary A set of functions for parsing and interpreting ContentProtection
|
|
* elements.
|
|
*/
|
|
|
|
|
|
/**
|
|
* @typedef {{
|
|
* defaultKeyId: ?string,
|
|
* defaultInit: Array.<shakaExtern.InitDataOverride>,
|
|
* drmInfos: !Array.<shakaExtern.DrmInfo>,
|
|
* 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.<shakaExtern.InitDataOverride>} defaultInit
|
|
* The default init data override. This can be null to indicate that there
|
|
* is no default.
|
|
* @property {!Array.<shakaExtern.DrmInfo>} drmInfos
|
|
* The DrmInfo objects.
|
|
* @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 {{
|
|
* node: !Element,
|
|
* schemeUri: string,
|
|
* keyId: ?string,
|
|
* init: Array.<shakaExtern.InitDataOverride>
|
|
* }}
|
|
*
|
|
* @description
|
|
* The parsed result of a single ContentProtection element.
|
|
*
|
|
* @property {!Element} node
|
|
* The ContentProtection XML element.
|
|
* @property {string} schemeUri
|
|
* The scheme URI.
|
|
* @property {?string} keyId
|
|
* The default key ID, if present.
|
|
* @property {Array.<shakaExtern.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 scheme URI to key system name.
|
|
*
|
|
* @const {!Object.<string, string>}
|
|
* @private
|
|
*/
|
|
shaka.dash.ContentProtection.defaultKeySystems_ = {
|
|
'urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b': 'org.w3.clearkey',
|
|
'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed': 'com.widevine.alpha',
|
|
'urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95': 'com.microsoft.playready',
|
|
'urn:uuid:f239e769-efa3-4850-9c16-a903c6932efb': 'com.adobe.primetime'
|
|
};
|
|
|
|
|
|
/**
|
|
* @const {string}
|
|
* @private
|
|
*/
|
|
shaka.dash.ContentProtection.MP4Protection_ =
|
|
'urn:mpeg:dash:mp4protection:2011';
|
|
|
|
|
|
/**
|
|
* Parses info from the ContentProtection elements at the AdaptationSet level.
|
|
*
|
|
* @param {!Array.<!Element>} elems
|
|
* @param {shakaExtern.DashContentProtectionCallback} callback
|
|
* @param {boolean} ignoreDrmInfo
|
|
* @return {shaka.dash.ContentProtection.Context}
|
|
*/
|
|
shaka.dash.ContentProtection.parseFromAdaptationSet = function(
|
|
elems, callback, ignoreDrmInfo) {
|
|
const ContentProtection = shaka.dash.ContentProtection;
|
|
const Functional = shaka.util.Functional;
|
|
const MapUtils = shaka.util.MapUtils;
|
|
const ManifestParserUtils = shaka.util.ManifestParserUtils;
|
|
let parsed = ContentProtection.parseElements_(elems);
|
|
/** @type {Array.<shakaExtern.InitDataOverride>} */
|
|
let defaultInit = null;
|
|
/** @type {!Array.<shakaExtern.DrmInfo>} */
|
|
let drmInfos = [];
|
|
let parsedNonCenc = [];
|
|
|
|
// Get the default key ID; if there are multiple, they must all match.
|
|
let keyIds = parsed.map(function(elem) { return elem.keyId; })
|
|
.filter(Functional.isNotNull);
|
|
if (keyIds.length) {
|
|
if (keyIds.filter(Functional.isNotDuplicate).length > 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) {
|
|
// Find the default key ID and init data. Create a new array of all the
|
|
// non-CENC elements.
|
|
parsedNonCenc = parsed.filter(function(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 true;
|
|
}
|
|
});
|
|
|
|
if (parsedNonCenc.length) {
|
|
drmInfos = ContentProtection.convertElements_(
|
|
defaultInit, callback, parsedNonCenc);
|
|
|
|
// 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 && (ignoreDrmInfo || !parsedNonCenc.length)) {
|
|
let keySystems = ContentProtection.defaultKeySystems_;
|
|
drmInfos =
|
|
MapUtils.values(keySystems)
|
|
.map(function(keySystem) {
|
|
return ManifestParserUtils.createDrmInfo(keySystem, defaultInit);
|
|
});
|
|
}
|
|
|
|
/** @type {?string} */
|
|
let defaultKeyId = keyIds[0] || null;
|
|
|
|
// Attach the default keyId, if it exists, to every initData.
|
|
if (defaultKeyId) {
|
|
drmInfos.forEach(function(drmInfo) {
|
|
drmInfo.initData.forEach(function(initData) {
|
|
initData.keyId = defaultKeyId;
|
|
});
|
|
});
|
|
}
|
|
|
|
return {
|
|
defaultKeyId: defaultKeyId,
|
|
defaultInit: defaultInit,
|
|
drmInfos: drmInfos,
|
|
firstRepresentation: true
|
|
};
|
|
};
|
|
|
|
|
|
/**
|
|
* Parses the given ContentProtection elements found at the Representation
|
|
* level. This may update the |context|.
|
|
*
|
|
* @param {!Array.<!Element>} elems
|
|
* @param {shakaExtern.DashContentProtectionCallback} callback
|
|
* @param {shaka.dash.ContentProtection.Context} context
|
|
* @param {boolean} ignoreDrmInfo
|
|
* @return {?string} The parsed key ID
|
|
*/
|
|
shaka.dash.ContentProtection.parseFromRepresentation = function(
|
|
elems, callback, context, ignoreDrmInfo) {
|
|
const ContentProtection = shaka.dash.ContentProtection;
|
|
let repContext = ContentProtection.parseFromAdaptationSet(
|
|
elems, callback, ignoreDrmInfo);
|
|
|
|
if (context.firstRepresentation) {
|
|
let asUnknown = context.drmInfos.length == 1 &&
|
|
!context.drmInfos[0].keySystem;
|
|
let asUnencrypted = context.drmInfos.length == 0;
|
|
let 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(function(asInfo) {
|
|
return repContext.drmInfos.some(function(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;
|
|
};
|
|
|
|
|
|
/**
|
|
* Creates DrmInfo objects from the given element.
|
|
*
|
|
* @param {Array.<shakaExtern.InitDataOverride>} defaultInit
|
|
* @param {shakaExtern.DashContentProtectionCallback} callback
|
|
* @param {!Array.<shaka.dash.ContentProtection.Element>} elements
|
|
* @return {!Array.<shakaExtern.DrmInfo>}
|
|
* @private
|
|
*/
|
|
shaka.dash.ContentProtection.convertElements_ = function(
|
|
defaultInit, callback, elements) {
|
|
const Functional = shaka.util.Functional;
|
|
return elements.map(
|
|
/**
|
|
* @param {shaka.dash.ContentProtection.Element} element
|
|
* @return {!Array.<shakaExtern.DrmInfo>}
|
|
*/
|
|
function(element) {
|
|
const ManifestParserUtils = shaka.util.ManifestParserUtils;
|
|
const ContentProtection = shaka.dash.ContentProtection;
|
|
let keySystem = ContentProtection.defaultKeySystems_[element.schemeUri];
|
|
if (keySystem) {
|
|
goog.asserts.assert(!element.init || element.init.length,
|
|
'Init data must be null or non-empty.');
|
|
let initData = element.init || defaultInit;
|
|
return [ManifestParserUtils.createDrmInfo(keySystem, initData)];
|
|
} else {
|
|
goog.asserts.assert(
|
|
callback, 'ContentProtection callback is required');
|
|
return callback(element.node) || [];
|
|
}
|
|
}).reduce(Functional.collapseArrays, []);
|
|
};
|
|
|
|
|
|
/**
|
|
* Parses the given ContentProtection elements. If there is an error, it
|
|
* removes those elements.
|
|
*
|
|
* @param {!Array.<!Element>} elems
|
|
* @return {!Array.<shaka.dash.ContentProtection.Element>}
|
|
* @private
|
|
*/
|
|
shaka.dash.ContentProtection.parseElements_ = function(elems) {
|
|
const Functional = shaka.util.Functional;
|
|
return elems.map(
|
|
/**
|
|
* @param {!Element} elem
|
|
* @return {?shaka.dash.ContentProtection.Element}
|
|
*/
|
|
function(elem) {
|
|
/** @type {?string} */
|
|
let schemeUri = elem.getAttribute('schemeIdUri');
|
|
/** @type {?string} */
|
|
let keyId = elem.getAttribute('cenc:default_KID');
|
|
/** @type {!Array.<string>} */
|
|
let psshs = shaka.util.XmlUtils.findChildren(elem, 'cenc:pssh')
|
|
.map(shaka.util.XmlUtils.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.indexOf(' ') >= 0) {
|
|
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.<shakaExtern.InitDataOverride>} */
|
|
let init = [];
|
|
try {
|
|
init = psshs.map(function(pssh) {
|
|
/** @type {shakaExtern.InitDataOverride} */
|
|
let ret = {
|
|
initDataType: 'cenc',
|
|
initData: shaka.util.Uint8ArrayUtils.fromBase64(pssh),
|
|
keyId: null
|
|
};
|
|
return ret;
|
|
});
|
|
} 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);
|
|
}
|
|
|
|
/** @type {shaka.dash.ContentProtection.Element} */
|
|
let element = {
|
|
node: elem,
|
|
schemeUri: schemeUri,
|
|
keyId: keyId,
|
|
init: (init.length > 0 ? init : null)
|
|
};
|
|
return element;
|
|
}).filter(Functional.isNotNull);
|
|
};
|
|
|