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