Source: lib/util/pssh.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2016 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.provide('shaka.util.Pssh');
  7. goog.require('goog.asserts');
  8. goog.require('shaka.log');
  9. goog.require('shaka.util.BufferUtils');
  10. goog.require('shaka.util.Mp4Parser');
  11. goog.require('shaka.util.Uint8ArrayUtils');
  12. /**
  13. * @summary
  14. * Parse a PSSH box and extract the system IDs.
  15. */
  16. shaka.util.Pssh = class {
  17. /**
  18. * @param {!Uint8Array} psshBox
  19. */
  20. constructor(psshBox) {
  21. /**
  22. * In hex.
  23. * @type {!Array.<string>}
  24. */
  25. this.systemIds = [];
  26. /**
  27. * In hex.
  28. * @type {!Array.<string>}
  29. */
  30. this.cencKeyIds = [];
  31. /**
  32. * Array with the pssh boxes found.
  33. * @type {!Array.<!Uint8Array>}
  34. */
  35. this.data = [];
  36. new shaka.util.Mp4Parser()
  37. .box('moov', shaka.util.Mp4Parser.children)
  38. .fullBox('pssh', (box) => this.parsePsshBox_(box))
  39. .parse(psshBox);
  40. if (this.data.length == 0) {
  41. shaka.log.warning('No pssh box found!');
  42. }
  43. }
  44. /**
  45. * @param {!shaka.extern.ParsedBox} box
  46. * @private
  47. */
  48. parsePsshBox_(box) {
  49. goog.asserts.assert(
  50. box.version != null,
  51. 'PSSH boxes are full boxes and must have a valid version');
  52. goog.asserts.assert(
  53. box.flags != null,
  54. 'PSSH boxes are full boxes and must have a valid flag');
  55. if (box.version > 1) {
  56. shaka.log.warning('Unrecognized PSSH version found!');
  57. return;
  58. }
  59. // The "reader" gives us a view on the payload of the box. Create a new
  60. // view that contains the whole box.
  61. const dataView = box.reader.getDataView();
  62. goog.asserts.assert(
  63. dataView.byteOffset >= 12, 'DataView at incorrect position');
  64. const pssh = shaka.util.BufferUtils.toUint8(dataView, -12, box.size);
  65. this.data.push(pssh);
  66. this.systemIds.push(
  67. shaka.util.Uint8ArrayUtils.toHex(box.reader.readBytes(16)));
  68. if (box.version > 0) {
  69. const numKeyIds = box.reader.readUint32();
  70. for (let i = 0; i < numKeyIds; i++) {
  71. const keyId =
  72. shaka.util.Uint8ArrayUtils.toHex(box.reader.readBytes(16));
  73. this.cencKeyIds.push(keyId);
  74. }
  75. }
  76. }
  77. /**
  78. * Creates a pssh blob from the given system ID, data, keyIds and version.
  79. *
  80. * @param {!Uint8Array} data
  81. * @param {!Uint8Array} systemId
  82. * @param {!Set.<string>} keyIds
  83. * @param {number} version
  84. * @return {!Uint8Array}
  85. */
  86. static createPssh(data, systemId, keyIds, version) {
  87. goog.asserts.assert(systemId.byteLength == 16, 'Invalid system ID length');
  88. const dataLength = data.length;
  89. let psshSize = 0x4 + 0x4 + 0x4 + systemId.length + 0x4 + dataLength;
  90. if (version > 0) {
  91. psshSize += 0x4 + (16 * keyIds.size);
  92. }
  93. /** @type {!Uint8Array} */
  94. const psshBox = new Uint8Array(psshSize);
  95. /** @type {!DataView} */
  96. const psshData = shaka.util.BufferUtils.toDataView(psshBox);
  97. let byteCursor = 0;
  98. psshData.setUint32(byteCursor, psshSize);
  99. byteCursor += 0x4;
  100. psshData.setUint32(byteCursor, 0x70737368); // 'pssh'
  101. byteCursor += 0x4;
  102. (version < 1) ? psshData.setUint32(byteCursor, 0) :
  103. psshData.setUint32(byteCursor, 0x01000000); // version + flags
  104. byteCursor += 0x4;
  105. psshBox.set(systemId, byteCursor);
  106. byteCursor += systemId.length;
  107. // if version > 0, add KID count and kid values.
  108. if (version > 0) {
  109. psshData.setUint32(byteCursor, keyIds.size); // KID_count
  110. byteCursor += 0x4;
  111. const Uint8ArrayUtils = shaka.util.Uint8ArrayUtils;
  112. for (const keyId of keyIds) {
  113. const KID = Uint8ArrayUtils.fromHex(keyId);
  114. psshBox.set(KID, byteCursor);
  115. byteCursor += KID.length;
  116. }
  117. }
  118. psshData.setUint32(byteCursor, dataLength);
  119. byteCursor += 0x4;
  120. psshBox.set(data, byteCursor);
  121. byteCursor += dataLength;
  122. goog.asserts.assert(byteCursor === psshSize, 'PSSH invalid length.');
  123. return psshBox;
  124. }
  125. /**
  126. * Normalise the initData array. This is to apply browser specific
  127. * work-arounds, e.g. removing duplicates which appears to occur
  128. * intermittently when the native msneedkey event fires (i.e. event.initData
  129. * contains dupes).
  130. *
  131. * @param {!Uint8Array} initData
  132. * @return {!Uint8Array}
  133. */
  134. static normaliseInitData(initData) {
  135. if (!initData) {
  136. return initData;
  137. }
  138. const pssh = new shaka.util.Pssh(initData);
  139. // If there is only a single pssh, return the original array.
  140. if (pssh.data.length <= 1) {
  141. return initData;
  142. }
  143. // Dedupe psshData.
  144. /** @type {!Array.<!Uint8Array>} */
  145. const dedupedInitDatas = [];
  146. for (const initData of pssh.data) {
  147. const found = dedupedInitDatas.some((x) => {
  148. return shaka.util.BufferUtils.equal(x, initData);
  149. });
  150. if (!found) {
  151. dedupedInitDatas.push(initData);
  152. }
  153. }
  154. return shaka.util.Uint8ArrayUtils.concat(...dedupedInitDatas);
  155. }
  156. };