Source: lib/dash/content_protection.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2016 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.provide('shaka.dash.ContentProtection');
  7. goog.require('goog.asserts');
  8. goog.require('shaka.log');
  9. goog.require('shaka.util.BufferUtils');
  10. goog.require('shaka.util.Error');
  11. goog.require('shaka.util.ManifestParserUtils');
  12. goog.require('shaka.util.Pssh');
  13. goog.require('shaka.util.StringUtils');
  14. goog.require('shaka.util.TXml');
  15. goog.require('shaka.util.Uint8ArrayUtils');
  16. /**
  17. * @summary A set of functions for parsing and interpreting ContentProtection
  18. * elements.
  19. */
  20. shaka.dash.ContentProtection = class {
  21. /**
  22. * Parses info from the ContentProtection elements at the AdaptationSet level.
  23. *
  24. * @param {!Array.<!shaka.extern.xml.Node>} elems
  25. * @param {boolean} ignoreDrmInfo
  26. * @param {!Object.<string, string>} keySystemsByURI
  27. * @return {shaka.dash.ContentProtection.Context}
  28. */
  29. static parseFromAdaptationSet(elems, ignoreDrmInfo, keySystemsByURI) {
  30. const ContentProtection = shaka.dash.ContentProtection;
  31. const ManifestParserUtils = shaka.util.ManifestParserUtils;
  32. const parsed = ContentProtection.parseElements_(elems);
  33. /** @type {Array.<shaka.extern.InitDataOverride>} */
  34. let defaultInit = null;
  35. /** @type {!Array.<shaka.extern.DrmInfo>} */
  36. let drmInfos = [];
  37. let parsedNonCenc = [];
  38. /** @type {?shaka.dash.ContentProtection.Aes128Info} */
  39. let aes128Info = null;
  40. // Get the default key ID; if there are multiple, they must all match.
  41. const keyIds = new Set(parsed.map((element) => element.keyId));
  42. // Remove any possible null value (elements may have no key ids).
  43. keyIds.delete(null);
  44. let encryptionScheme = 'cenc';
  45. if (keyIds.size > 1) {
  46. throw new shaka.util.Error(
  47. shaka.util.Error.Severity.CRITICAL,
  48. shaka.util.Error.Category.MANIFEST,
  49. shaka.util.Error.Code.DASH_CONFLICTING_KEY_IDS);
  50. }
  51. if (!ignoreDrmInfo) {
  52. const aes128Elements = parsed.filter((elem) => {
  53. return elem.schemeUri == ContentProtection.Aes128Protection_;
  54. });
  55. if (aes128Elements.length > 1) {
  56. throw new shaka.util.Error(
  57. shaka.util.Error.Severity.CRITICAL,
  58. shaka.util.Error.Category.MANIFEST,
  59. shaka.util.Error.Code.DASH_CONFLICTING_AES_128);
  60. }
  61. if (aes128Elements.length) {
  62. aes128Info = ContentProtection.parseAes128_(aes128Elements[0]);
  63. }
  64. const mp4ProtectionParsed = parsed.find((elem) => {
  65. return elem.schemeUri == ContentProtection.MP4Protection_;
  66. });
  67. if (mp4ProtectionParsed && mp4ProtectionParsed.encryptionScheme) {
  68. encryptionScheme = mp4ProtectionParsed.encryptionScheme;
  69. }
  70. // Find the default key ID and init data. Create a new array of all the
  71. // non-CENC elements.
  72. parsedNonCenc = parsed.filter((elem) => {
  73. if (elem.schemeUri == ContentProtection.MP4Protection_) {
  74. goog.asserts.assert(!elem.init || elem.init.length,
  75. 'Init data must be null or non-empty.');
  76. defaultInit = elem.init || defaultInit;
  77. return false;
  78. } else {
  79. return elem.schemeUri != ContentProtection.Aes128Protection_;
  80. }
  81. });
  82. if (parsedNonCenc.length) {
  83. drmInfos = ContentProtection.convertElements_(defaultInit,
  84. encryptionScheme, parsedNonCenc, keySystemsByURI, keyIds);
  85. // If there are no drmInfos after parsing, then add a dummy entry.
  86. // This may be removed in parseKeyIds.
  87. if (drmInfos.length == 0) {
  88. drmInfos = [ManifestParserUtils.createDrmInfo(
  89. '', encryptionScheme, defaultInit)];
  90. }
  91. }
  92. }
  93. // If there are only CENC element(s) or ignoreDrmInfo flag is set, assume
  94. // all key-systems are supported.
  95. if (parsed.length && !aes128Info &&
  96. (ignoreDrmInfo || !parsedNonCenc.length)) {
  97. drmInfos = [];
  98. for (const keySystem of Object.values(keySystemsByURI)) {
  99. // If the manifest doesn't specify any key systems, we shouldn't
  100. // put clearkey in this list. Otherwise, it may be triggered when
  101. // a real key system should be used instead.
  102. if (keySystem != 'org.w3.clearkey') {
  103. const info = ManifestParserUtils.createDrmInfo(
  104. keySystem, encryptionScheme, defaultInit);
  105. drmInfos.push(info);
  106. }
  107. }
  108. }
  109. // If we have a default key id, apply it to every initData.
  110. const defaultKeyId = Array.from(keyIds)[0] || null;
  111. if (defaultKeyId) {
  112. for (const info of drmInfos) {
  113. for (const initData of info.initData) {
  114. initData.keyId = defaultKeyId;
  115. }
  116. }
  117. }
  118. return {
  119. defaultKeyId: defaultKeyId,
  120. defaultInit: defaultInit,
  121. drmInfos: drmInfos,
  122. aes128Info: aes128Info,
  123. firstRepresentation: true,
  124. };
  125. }
  126. /**
  127. * Parses the given ContentProtection elements found at the Representation
  128. * level. This may update the |context|.
  129. *
  130. * @param {!Array.<!shaka.extern.xml.Node>} elems
  131. * @param {shaka.dash.ContentProtection.Context} context
  132. * @param {boolean} ignoreDrmInfo
  133. * @param {!Object.<string, string>} keySystemsByURI
  134. * @return {?string} The parsed key ID
  135. */
  136. static parseFromRepresentation(
  137. elems, context, ignoreDrmInfo, keySystemsByURI) {
  138. const ContentProtection = shaka.dash.ContentProtection;
  139. const repContext = ContentProtection.parseFromAdaptationSet(
  140. elems, ignoreDrmInfo, keySystemsByURI);
  141. if (context.firstRepresentation) {
  142. const asUnknown = context.drmInfos.length == 1 &&
  143. !context.drmInfos[0].keySystem;
  144. const asUnencrypted = context.drmInfos.length == 0;
  145. const repUnencrypted = repContext.drmInfos.length == 0;
  146. // There are two cases where we need to replace the |drmInfos| in the
  147. // context with those in the Representation:
  148. // 1. The AdaptationSet does not list any ContentProtection.
  149. // 2. The AdaptationSet only lists unknown key-systems.
  150. if (asUnencrypted || (asUnknown && !repUnencrypted)) {
  151. context.drmInfos = repContext.drmInfos;
  152. }
  153. context.firstRepresentation = false;
  154. } else if (repContext.drmInfos.length > 0) {
  155. // If this is not the first Representation, then we need to remove entries
  156. // from the context that do not appear in this Representation.
  157. context.drmInfos = context.drmInfos.filter((asInfo) => {
  158. return repContext.drmInfos.some((repInfo) => {
  159. return repInfo.keySystem == asInfo.keySystem;
  160. });
  161. });
  162. // If we have filtered out all key-systems, throw an error.
  163. if (context.drmInfos.length == 0) {
  164. throw new shaka.util.Error(
  165. shaka.util.Error.Severity.CRITICAL,
  166. shaka.util.Error.Category.MANIFEST,
  167. shaka.util.Error.Code.DASH_NO_COMMON_KEY_SYSTEM);
  168. }
  169. }
  170. return repContext.defaultKeyId || context.defaultKeyId;
  171. }
  172. /**
  173. * Gets a Widevine license URL from a content protection element
  174. * containing a custom `ms:laurl` or 'dashif:Laurl' elements
  175. *
  176. * @param {shaka.dash.ContentProtection.Element} element
  177. * @return {string}
  178. */
  179. static getWidevineLicenseUrl(element) {
  180. const StringUtils = shaka.util.StringUtils;
  181. const dashIfLaurlNode = shaka.util.TXml.findChildNS(
  182. element.node, shaka.dash.ContentProtection.DashIfNamespaceUri_,
  183. 'Laurl',
  184. );
  185. if (dashIfLaurlNode) {
  186. const textContents = shaka.util.TXml.getTextContents(dashIfLaurlNode);
  187. if (textContents) {
  188. return textContents;
  189. }
  190. }
  191. const mslaurlNode = shaka.util.TXml.findChildNS(
  192. element.node, 'urn:microsoft', 'laurl');
  193. if (mslaurlNode) {
  194. return StringUtils.htmlUnescape(
  195. mslaurlNode.attributes['licenseUrl']) || '';
  196. }
  197. return '';
  198. }
  199. /**
  200. * Gets a ClearKey license URL from a content protection element
  201. * containing a custom `clearkey::Laurl` or 'dashif:Laurl' elements
  202. *
  203. * @param {shaka.dash.ContentProtection.Element} element
  204. * @return {string}
  205. */
  206. static getClearKeyLicenseUrl(element) {
  207. const dashIfLaurlNode = shaka.util.TXml.findChildNS(
  208. element.node, shaka.dash.ContentProtection.DashIfNamespaceUri_,
  209. 'Laurl',
  210. );
  211. if (dashIfLaurlNode) {
  212. const textContents = shaka.util.TXml.getTextContents(dashIfLaurlNode);
  213. if (textContents) {
  214. return textContents;
  215. }
  216. }
  217. const clearKeyLaurlNode = shaka.util.TXml.findChildNS(
  218. element.node, shaka.dash.ContentProtection.ClearKeyNamespaceUri_,
  219. 'Laurl',
  220. );
  221. if (clearKeyLaurlNode &&
  222. clearKeyLaurlNode.attributes['Lic_type'] === 'EME-1.0') {
  223. if (clearKeyLaurlNode) {
  224. const textContents = shaka.util.TXml.getTextContents(clearKeyLaurlNode);
  225. if (textContents) {
  226. return textContents;
  227. }
  228. }
  229. }
  230. return '';
  231. }
  232. /**
  233. * Parses an Array buffer starting at byteOffset for PlayReady Object Records.
  234. * Each PRO Record is preceded by its PlayReady Record type and length in
  235. * bytes.
  236. *
  237. * PlayReady Object Record format: https://goo.gl/FTcu46
  238. *
  239. * @param {!DataView} view
  240. * @param {number} byteOffset
  241. * @return {!Array.<shaka.dash.ContentProtection.PlayReadyRecord>}
  242. * @private
  243. */
  244. static parseMsProRecords_(view, byteOffset) {
  245. const records = [];
  246. while (byteOffset < view.byteLength - 1) {
  247. const type = view.getUint16(byteOffset, true);
  248. byteOffset += 2;
  249. const byteLength = view.getUint16(byteOffset, true);
  250. byteOffset += 2;
  251. if ((byteLength & 1) != 0 || byteLength + byteOffset > view.byteLength) {
  252. shaka.log.warning('Malformed MS PRO object');
  253. return [];
  254. }
  255. const recordValue = shaka.util.BufferUtils.toUint8(
  256. view, byteOffset, byteLength);
  257. records.push({
  258. type: type,
  259. value: recordValue,
  260. });
  261. byteOffset += byteLength;
  262. }
  263. return records;
  264. }
  265. /**
  266. * Parses a buffer for PlayReady Objects. The data
  267. * should contain a 32-bit integer indicating the length of
  268. * the PRO in bytes. Following that, a 16-bit integer for
  269. * the number of PlayReady Object Records in the PRO. Lastly,
  270. * a byte array of the PRO Records themselves.
  271. *
  272. * PlayReady Object format: https://goo.gl/W8yAN4
  273. *
  274. * @param {BufferSource} data
  275. * @return {!Array.<shaka.dash.ContentProtection.PlayReadyRecord>}
  276. * @private
  277. */
  278. static parseMsPro_(data) {
  279. let byteOffset = 0;
  280. const view = shaka.util.BufferUtils.toDataView(data);
  281. // First 4 bytes is the PRO length (DWORD)
  282. const byteLength = view.getUint32(byteOffset, /* littleEndian= */ true);
  283. byteOffset += 4;
  284. if (byteLength != data.byteLength) {
  285. // Malformed PRO
  286. shaka.log.warning('PlayReady Object with invalid length encountered.');
  287. return [];
  288. }
  289. // Skip PRO Record count (WORD)
  290. byteOffset += 2;
  291. // Rest of the data contains the PRO Records
  292. const ContentProtection = shaka.dash.ContentProtection;
  293. return ContentProtection.parseMsProRecords_(view, byteOffset);
  294. }
  295. /**
  296. * PlayReady Header format: https://goo.gl/dBzxNA
  297. *
  298. * @param {!shaka.extern.xml.Node} xml
  299. * @return {string}
  300. * @private
  301. */
  302. static getLaurl_(xml) {
  303. const TXml = shaka.util.TXml;
  304. // LA_URL element is optional and no more than one is
  305. // allowed inside the DATA element. Only absolute URLs are allowed.
  306. // If the LA_URL element exists, it must not be empty.
  307. for (const elem of TXml.getElementsByTagName(xml, 'DATA')) {
  308. if (elem.children) {
  309. for (const child of elem.children) {
  310. if (child.tagName == 'LA_URL') {
  311. return /** @type{string} */(shaka.util.TXml.getTextContents(child));
  312. }
  313. }
  314. }
  315. }
  316. // Not found
  317. return '';
  318. }
  319. /**
  320. * Gets a PlayReady license URL from a content protection element
  321. * containing a PlayReady Header Object
  322. *
  323. * @param {shaka.dash.ContentProtection.Element} element
  324. * @return {string}
  325. */
  326. static getPlayReadyLicenseUrl(element) {
  327. const dashIfLaurlNode = shaka.util.TXml.findChildNS(
  328. element.node, shaka.dash.ContentProtection.DashIfNamespaceUri_,
  329. 'Laurl',
  330. );
  331. if (dashIfLaurlNode) {
  332. const textContents = shaka.util.TXml.getTextContents(dashIfLaurlNode);
  333. if (textContents) {
  334. return textContents;
  335. }
  336. }
  337. const proNode = shaka.util.TXml.findChildNS(
  338. element.node, 'urn:microsoft:playready', 'pro');
  339. if (!proNode || !shaka.util.TXml.getTextContents(proNode)) {
  340. return '';
  341. }
  342. const ContentProtection = shaka.dash.ContentProtection;
  343. const PLAYREADY_RECORD_TYPES = ContentProtection.PLAYREADY_RECORD_TYPES;
  344. const textContent =
  345. /** @type {string} */ (shaka.util.TXml.getTextContents(proNode));
  346. const bytes = shaka.util.Uint8ArrayUtils.fromBase64(textContent);
  347. const records = ContentProtection.parseMsPro_(bytes);
  348. const record = records.filter((record) => {
  349. return record.type === PLAYREADY_RECORD_TYPES.RIGHTS_MANAGEMENT;
  350. })[0];
  351. if (!record) {
  352. return '';
  353. }
  354. const xml = shaka.util.StringUtils.fromUTF16(record.value, true);
  355. const rootElement = shaka.util.TXml.parseXmlString(xml, 'WRMHEADER');
  356. if (!rootElement) {
  357. return '';
  358. }
  359. return ContentProtection.getLaurl_(rootElement);
  360. }
  361. /**
  362. * Gets a PlayReady initData from a content protection element
  363. * containing a PlayReady Pro Object
  364. *
  365. * @param {shaka.dash.ContentProtection.Element} element
  366. * @return {?Array.<shaka.extern.InitDataOverride>}
  367. * @private
  368. */
  369. static getInitDataFromPro_(element) {
  370. const proNode = shaka.util.TXml.findChildNS(
  371. element.node, 'urn:microsoft:playready', 'pro');
  372. if (!proNode || !shaka.util.TXml.getTextContents(proNode)) {
  373. return null;
  374. }
  375. const Uint8ArrayUtils = shaka.util.Uint8ArrayUtils;
  376. const textContent =
  377. /** @type{string} */ (shaka.util.TXml.getTextContents(proNode));
  378. const data = Uint8ArrayUtils.fromBase64(textContent);
  379. const systemId = new Uint8Array([
  380. 0x9a, 0x04, 0xf0, 0x79, 0x98, 0x40, 0x42, 0x86,
  381. 0xab, 0x92, 0xe6, 0x5b, 0xe0, 0x88, 0x5f, 0x95,
  382. ]);
  383. const keyIds = new Set();
  384. const psshVersion = 0;
  385. const pssh =
  386. shaka.util.Pssh.createPssh(data, systemId, keyIds, psshVersion);
  387. return [
  388. {
  389. initData: pssh,
  390. initDataType: 'cenc',
  391. keyId: element.keyId,
  392. },
  393. ];
  394. }
  395. /**
  396. * Creates ClearKey initData from Default_KID value retrieved from previously
  397. * parsed ContentProtection tag.
  398. * @param {shaka.dash.ContentProtection.Element} element
  399. * @param {!Set.<string>} keyIds
  400. * @return {?Array.<shaka.extern.InitDataOverride>}
  401. * @private
  402. */
  403. static getInitDataClearKey_(element, keyIds) {
  404. if (keyIds.size == 0) {
  405. return null;
  406. }
  407. const systemId = new Uint8Array([
  408. 0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02,
  409. 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b,
  410. ]);
  411. const data = new Uint8Array([]);
  412. const psshVersion = 1;
  413. const pssh =
  414. shaka.util.Pssh.createPssh(data, systemId, keyIds, psshVersion);
  415. return [
  416. {
  417. initData: pssh,
  418. initDataType: 'cenc',
  419. keyId: element.keyId,
  420. },
  421. ];
  422. }
  423. /**
  424. * Creates DrmInfo objects from the given element.
  425. *
  426. * @param {Array.<shaka.extern.InitDataOverride>} defaultInit
  427. * @param {string} encryptionScheme
  428. * @param {!Array.<shaka.dash.ContentProtection.Element>} elements
  429. * @param {!Object.<string, string>} keySystemsByURI
  430. * @param {!Set.<string>} keyIds
  431. * @return {!Array.<shaka.extern.DrmInfo>}
  432. * @private
  433. */
  434. static convertElements_(defaultInit, encryptionScheme, elements,
  435. keySystemsByURI, keyIds) {
  436. const ContentProtection = shaka.dash.ContentProtection;
  437. const ManifestParserUtils = shaka.util.ManifestParserUtils;
  438. const licenseUrlParsers = ContentProtection.licenseUrlParsers_;
  439. /** @type {!Array.<shaka.extern.DrmInfo>} */
  440. const out = [];
  441. for (const element of elements) {
  442. const keySystem = keySystemsByURI[element.schemeUri];
  443. if (keySystem) {
  444. goog.asserts.assert(
  445. !element.init || element.init.length,
  446. 'Init data must be null or non-empty.');
  447. const proInitData = ContentProtection.getInitDataFromPro_(element);
  448. let clearKeyInitData = null;
  449. if (element.schemeUri ===
  450. shaka.dash.ContentProtection.ClearKeySchemeUri_) {
  451. clearKeyInitData =
  452. ContentProtection.getInitDataClearKey_(element, keyIds);
  453. }
  454. const initData = element.init || defaultInit || proInitData ||
  455. clearKeyInitData;
  456. const info = ManifestParserUtils.createDrmInfo(
  457. keySystem, encryptionScheme, initData);
  458. const licenseParser = licenseUrlParsers.get(keySystem);
  459. if (licenseParser) {
  460. info.licenseServerUri = licenseParser(element);
  461. }
  462. out.push(info);
  463. }
  464. }
  465. return out;
  466. }
  467. /**
  468. * Parses the given ContentProtection elements. If there is an error, it
  469. * removes those elements.
  470. *
  471. * @param {!Array.<!shaka.extern.xml.Node>} elems
  472. * @return {!Array.<shaka.dash.ContentProtection.Element>}
  473. * @private
  474. */
  475. static parseElements_(elems) {
  476. /** @type {!Array.<shaka.dash.ContentProtection.Element>} */
  477. const out = [];
  478. for (const elem of elems) {
  479. const parsed = shaka.dash.ContentProtection.parseElement_(elem);
  480. if (parsed) {
  481. out.push(parsed);
  482. }
  483. }
  484. return out;
  485. }
  486. /**
  487. * Parses the given ContentProtection element.
  488. *
  489. * @param {!shaka.extern.xml.Node} elem
  490. * @return {?shaka.dash.ContentProtection.Element}
  491. * @private
  492. */
  493. static parseElement_(elem) {
  494. const NS = shaka.dash.ContentProtection.CencNamespaceUri_;
  495. const TXml = shaka.util.TXml;
  496. /** @type {?string} */
  497. let schemeUri = elem.attributes['schemeIdUri'];
  498. /** @type {?string} */
  499. let keyId = TXml.getAttributeNS(elem, NS, 'default_KID');
  500. /** @type {!Array.<string>} */
  501. const psshs = TXml.findChildrenNS(elem, NS, 'pssh')
  502. .map(TXml.getContents);
  503. const encryptionScheme = elem.attributes['value'];
  504. if (!schemeUri) {
  505. shaka.log.error('Missing required schemeIdUri attribute on',
  506. 'ContentProtection element', elem);
  507. return null;
  508. }
  509. schemeUri = schemeUri.toLowerCase();
  510. if (keyId) {
  511. keyId = keyId.replace(/-/g, '').toLowerCase();
  512. if (keyId.includes(' ')) {
  513. throw new shaka.util.Error(
  514. shaka.util.Error.Severity.CRITICAL,
  515. shaka.util.Error.Category.MANIFEST,
  516. shaka.util.Error.Code.DASH_MULTIPLE_KEY_IDS_NOT_SUPPORTED);
  517. }
  518. }
  519. /** @type {!Array.<shaka.extern.InitDataOverride>} */
  520. let init = [];
  521. try {
  522. // Try parsing PSSH data.
  523. init = psshs.map((pssh) => {
  524. return {
  525. initDataType: 'cenc',
  526. initData: shaka.util.Uint8ArrayUtils.fromBase64(pssh),
  527. keyId: null,
  528. };
  529. });
  530. } catch (e) {
  531. throw new shaka.util.Error(
  532. shaka.util.Error.Severity.CRITICAL,
  533. shaka.util.Error.Category.MANIFEST,
  534. shaka.util.Error.Code.DASH_PSSH_BAD_ENCODING);
  535. }
  536. return {
  537. node: elem,
  538. schemeUri,
  539. keyId,
  540. init: (init.length > 0 ? init : null),
  541. encryptionScheme,
  542. };
  543. }
  544. /**
  545. * Parses the given AES-128 ContentProtection element.
  546. *
  547. * @param {shaka.dash.ContentProtection.Element} element
  548. * @return {?shaka.dash.ContentProtection.Aes128Info}
  549. * @private
  550. */
  551. static parseAes128_(element) {
  552. // Check if the Web Crypto API is available.
  553. if (!window.crypto || !window.crypto.subtle) {
  554. shaka.log.alwaysWarn('Web Crypto API is not available to decrypt ' +
  555. 'AES-128. (Web Crypto only exists in secure origins like https)');
  556. throw new shaka.util.Error(
  557. shaka.util.Error.Severity.CRITICAL,
  558. shaka.util.Error.Category.MANIFEST,
  559. shaka.util.Error.Code.NO_WEB_CRYPTO_API);
  560. }
  561. const namespace = 'urn:mpeg:dash:schema:sea:2012';
  562. const segmentEncryption = shaka.util.TXml.findChildNS(
  563. element.node, namespace, 'SegmentEncryption');
  564. if (!segmentEncryption) {
  565. throw new shaka.util.Error(
  566. shaka.util.Error.Severity.CRITICAL,
  567. shaka.util.Error.Category.MANIFEST,
  568. shaka.util.Error.Code.DASH_UNSUPPORTED_AES_128);
  569. }
  570. const aesSchemeIdUri = 'urn:mpeg:dash:sea:aes128-cbc:2013';
  571. const segmentEncryptionSchemeIdUri =
  572. segmentEncryption.attributes['schemeIdUri'];
  573. if (segmentEncryptionSchemeIdUri != aesSchemeIdUri) {
  574. throw new shaka.util.Error(
  575. shaka.util.Error.Severity.CRITICAL,
  576. shaka.util.Error.Category.MANIFEST,
  577. shaka.util.Error.Code.DASH_UNSUPPORTED_AES_128);
  578. }
  579. const cryptoPeriod = shaka.util.TXml.findChildNS(
  580. element.node, namespace, 'CryptoPeriod');
  581. if (!cryptoPeriod) {
  582. throw new shaka.util.Error(
  583. shaka.util.Error.Severity.CRITICAL,
  584. shaka.util.Error.Category.MANIFEST,
  585. shaka.util.Error.Code.DASH_UNSUPPORTED_AES_128);
  586. }
  587. const ivHex = cryptoPeriod.attributes['IV'];
  588. const keyUri = shaka.util.StringUtils.htmlUnescape(
  589. cryptoPeriod.attributes['keyUriTemplate']);
  590. if (!ivHex || !keyUri) {
  591. throw new shaka.util.Error(
  592. shaka.util.Error.Severity.CRITICAL,
  593. shaka.util.Error.Category.MANIFEST,
  594. shaka.util.Error.Code.DASH_UNSUPPORTED_AES_128);
  595. }
  596. // Exclude 0x at the start of string.
  597. const iv = shaka.util.Uint8ArrayUtils.fromHex(ivHex.substr(2));
  598. if (iv.byteLength != 16) {
  599. throw new shaka.util.Error(
  600. shaka.util.Error.Severity.CRITICAL,
  601. shaka.util.Error.Category.MANIFEST,
  602. shaka.util.Error.Code.AES_128_INVALID_IV_LENGTH);
  603. }
  604. return {
  605. keyUri,
  606. iv,
  607. };
  608. }
  609. };
  610. /**
  611. * @typedef {{
  612. * type: number,
  613. * value: !Uint8Array
  614. * }}
  615. *
  616. * @description
  617. * The parsed result of a PlayReady object record.
  618. *
  619. * @property {number} type
  620. * Type of data stored in the record.
  621. * @property {!Uint8Array} value
  622. * Record content.
  623. */
  624. shaka.dash.ContentProtection.PlayReadyRecord;
  625. /**
  626. * Enum for PlayReady record types.
  627. * @enum {number}
  628. */
  629. shaka.dash.ContentProtection.PLAYREADY_RECORD_TYPES = {
  630. RIGHTS_MANAGEMENT: 0x001,
  631. RESERVED: 0x002,
  632. EMBEDDED_LICENSE: 0x003,
  633. };
  634. /**
  635. * @typedef {{
  636. * defaultKeyId: ?string,
  637. * defaultInit: Array.<shaka.extern.InitDataOverride>,
  638. * drmInfos: !Array.<shaka.extern.DrmInfo>,
  639. * aes128Info: ?shaka.dash.ContentProtection.Aes128Info,
  640. * firstRepresentation: boolean
  641. * }}
  642. *
  643. * @description
  644. * Contains information about the ContentProtection elements found at the
  645. * AdaptationSet level.
  646. *
  647. * @property {?string} defaultKeyId
  648. * The default key ID to use. This is used by parseKeyIds as a default. This
  649. * can be null to indicate that there is no default.
  650. * @property {Array.<shaka.extern.InitDataOverride>} defaultInit
  651. * The default init data override. This can be null to indicate that there
  652. * is no default.
  653. * @property {!Array.<shaka.extern.DrmInfo>} drmInfos
  654. * The DrmInfo objects.
  655. * @property {?shaka.dash.ContentProtection.Aes128Info} aes128Info
  656. * The AES-128 key info.
  657. * @property {boolean} firstRepresentation
  658. * True when first parsed; changed to false after the first call to
  659. * parseKeyIds. This is used to determine if a dummy key-system should be
  660. * overwritten; namely that the first representation can replace the dummy
  661. * from the AdaptationSet.
  662. */
  663. shaka.dash.ContentProtection.Context;
  664. /**
  665. * @typedef {{
  666. * keyUri: string,
  667. * iv: !Uint8Array
  668. * }}
  669. *
  670. * @description
  671. * Contains information about the AES-128 keyUri and IV found at the
  672. * AdaptationSet level.
  673. *
  674. * @property {string} method
  675. * The keyUri in the manifest.
  676. * @property {!Uint8Array} iv
  677. * The IV in the manifest.
  678. */
  679. shaka.dash.ContentProtection.Aes128Info;
  680. /**
  681. * @typedef {{
  682. * node: !shaka.extern.xml.Node,
  683. * schemeUri: string,
  684. * keyId: ?string,
  685. * init: Array.<shaka.extern.InitDataOverride>,
  686. * encryptionScheme: ?string
  687. * }}
  688. *
  689. * @description
  690. * The parsed result of a single ContentProtection element.
  691. *
  692. * @property {!shaka.extern.xml.Node} node
  693. * The ContentProtection XML element.
  694. * @property {string} schemeUri
  695. * The scheme URI.
  696. * @property {?string} keyId
  697. * The default key ID, if present.
  698. * @property {Array.<shaka.extern.InitDataOverride>} init
  699. * The init data, if present. If there is no init data, it will be null. If
  700. * this is non-null, there is at least one element.
  701. * @property {?string} encryptionScheme
  702. * The encryption scheme, if present.
  703. */
  704. shaka.dash.ContentProtection.Element;
  705. /**
  706. * A map of key system name to license server url parser.
  707. *
  708. * @const {!Map.<string, function(shaka.dash.ContentProtection.Element)>}
  709. * @private
  710. */
  711. shaka.dash.ContentProtection.licenseUrlParsers_ = new Map()
  712. .set('com.widevine.alpha',
  713. shaka.dash.ContentProtection.getWidevineLicenseUrl)
  714. .set('com.microsoft.playready',
  715. shaka.dash.ContentProtection.getPlayReadyLicenseUrl)
  716. .set('com.microsoft.playready.recommendation',
  717. shaka.dash.ContentProtection.getPlayReadyLicenseUrl)
  718. .set('com.microsoft.playready.software',
  719. shaka.dash.ContentProtection.getPlayReadyLicenseUrl)
  720. .set('com.microsoft.playready.hardware',
  721. shaka.dash.ContentProtection.getPlayReadyLicenseUrl)
  722. .set('org.w3.clearkey',
  723. shaka.dash.ContentProtection.getClearKeyLicenseUrl);
  724. /**
  725. * @const {string}
  726. * @private
  727. */
  728. shaka.dash.ContentProtection.MP4Protection_ =
  729. 'urn:mpeg:dash:mp4protection:2011';
  730. /**
  731. * @const {string}
  732. * @private
  733. */
  734. shaka.dash.ContentProtection.Aes128Protection_ =
  735. 'urn:mpeg:dash:sea:2012';
  736. /**
  737. * @const {string}
  738. * @private
  739. */
  740. shaka.dash.ContentProtection.CencNamespaceUri_ = 'urn:mpeg:cenc:2013';
  741. /**
  742. * @const {string}
  743. * @private
  744. */
  745. shaka.dash.ContentProtection.ClearKeyNamespaceUri_ =
  746. 'http://dashif.org/guidelines/clearKey';
  747. /**
  748. * @const {string}
  749. * @private
  750. */
  751. shaka.dash.ContentProtection.ClearKeySchemeUri_ =
  752. 'urn:uuid:e2719d58-a985-b3c9-781a-b030af78d30e';
  753. /**
  754. * @const {string}
  755. * @private
  756. */
  757. shaka.dash.ContentProtection.DashIfNamespaceUri_ =
  758. 'https://dashif.org/CPS';