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