Home Reference Source

src/demux/sample-aes.ts

  1. /**
  2. * SAMPLE-AES decrypter
  3. */
  4.  
  5. import { HlsConfig } from '../config';
  6. import Decrypter from '../crypt/decrypter';
  7. import { HlsEventEmitter } from '../events';
  8. import type {
  9. AudioSample,
  10. AvcSample,
  11. AvcSampleUnit,
  12. DemuxedVideoTrack,
  13. KeyData,
  14. } from '../types/demuxer';
  15. import { discardEPB } from '../utils/mp4-tools';
  16.  
  17. class SampleAesDecrypter {
  18. private keyData: KeyData;
  19. private decrypter: Decrypter;
  20.  
  21. constructor(observer: HlsEventEmitter, config: HlsConfig, keyData: KeyData) {
  22. this.keyData = keyData;
  23. this.decrypter = new Decrypter(config, {
  24. removePKCS7Padding: false,
  25. });
  26. }
  27.  
  28. decryptBuffer(encryptedData: Uint8Array | ArrayBuffer): Promise<ArrayBuffer> {
  29. return this.decrypter.decrypt(
  30. encryptedData,
  31. this.keyData.key.buffer,
  32. this.keyData.iv.buffer
  33. );
  34. }
  35.  
  36. // AAC - encrypt all full 16 bytes blocks starting from offset 16
  37. private decryptAacSample(
  38. samples: AudioSample[],
  39. sampleIndex: number,
  40. callback: () => void
  41. ) {
  42. const curUnit = samples[sampleIndex].unit;
  43. if (curUnit.length <= 16) {
  44. // No encrypted portion in this sample (first 16 bytes is not
  45. // encrypted, see https://developer.apple.com/library/archive/documentation/AudioVideo/Conceptual/HLS_Sample_Encryption/Encryption/Encryption.html),
  46. return;
  47. }
  48. const encryptedData = curUnit.subarray(
  49. 16,
  50. curUnit.length - (curUnit.length % 16)
  51. );
  52. const encryptedBuffer = encryptedData.buffer.slice(
  53. encryptedData.byteOffset,
  54. encryptedData.byteOffset + encryptedData.length
  55. );
  56.  
  57. this.decryptBuffer(encryptedBuffer).then((decryptedBuffer: ArrayBuffer) => {
  58. const decryptedData = new Uint8Array(decryptedBuffer);
  59. curUnit.set(decryptedData, 16);
  60.  
  61. if (!this.decrypter.isSync()) {
  62. this.decryptAacSamples(samples, sampleIndex + 1, callback);
  63. }
  64. });
  65. }
  66.  
  67. decryptAacSamples(
  68. samples: AudioSample[],
  69. sampleIndex: number,
  70. callback: () => void
  71. ) {
  72. for (; ; sampleIndex++) {
  73. if (sampleIndex >= samples.length) {
  74. callback();
  75. return;
  76. }
  77.  
  78. if (samples[sampleIndex].unit.length < 32) {
  79. continue;
  80. }
  81.  
  82. this.decryptAacSample(samples, sampleIndex, callback);
  83.  
  84. if (!this.decrypter.isSync()) {
  85. return;
  86. }
  87. }
  88. }
  89.  
  90. // AVC - encrypt one 16 bytes block out of ten, starting from offset 32
  91. getAvcEncryptedData(decodedData: Uint8Array) {
  92. const encryptedDataLen =
  93. Math.floor((decodedData.length - 48) / 160) * 16 + 16;
  94. const encryptedData = new Int8Array(encryptedDataLen);
  95. let outputPos = 0;
  96. for (
  97. let inputPos = 32;
  98. inputPos < decodedData.length - 16;
  99. inputPos += 160, outputPos += 16
  100. ) {
  101. encryptedData.set(
  102. decodedData.subarray(inputPos, inputPos + 16),
  103. outputPos
  104. );
  105. }
  106.  
  107. return encryptedData;
  108. }
  109.  
  110. getAvcDecryptedUnit(
  111. decodedData: Uint8Array,
  112. decryptedData: ArrayLike<number> | ArrayBuffer | SharedArrayBuffer
  113. ) {
  114. const uint8DecryptedData = new Uint8Array(decryptedData);
  115. let inputPos = 0;
  116. for (
  117. let outputPos = 32;
  118. outputPos < decodedData.length - 16;
  119. outputPos += 160, inputPos += 16
  120. ) {
  121. decodedData.set(
  122. uint8DecryptedData.subarray(inputPos, inputPos + 16),
  123. outputPos
  124. );
  125. }
  126.  
  127. return decodedData;
  128. }
  129.  
  130. decryptAvcSample(
  131. samples: AvcSample[],
  132. sampleIndex: number,
  133. unitIndex: number,
  134. callback: () => void,
  135. curUnit: AvcSampleUnit
  136. ) {
  137. const decodedData = discardEPB(curUnit.data);
  138. const encryptedData = this.getAvcEncryptedData(decodedData);
  139.  
  140. this.decryptBuffer(encryptedData.buffer).then(
  141. (decryptedBuffer: ArrayBuffer) => {
  142. curUnit.data = this.getAvcDecryptedUnit(decodedData, decryptedBuffer);
  143.  
  144. if (!this.decrypter.isSync()) {
  145. this.decryptAvcSamples(samples, sampleIndex, unitIndex + 1, callback);
  146. }
  147. }
  148. );
  149. }
  150.  
  151. decryptAvcSamples(
  152. samples: DemuxedVideoTrack['samples'],
  153. sampleIndex: number,
  154. unitIndex: number,
  155. callback: () => void
  156. ) {
  157. if (samples instanceof Uint8Array) {
  158. throw new Error('Cannot decrypt samples of type Uint8Array');
  159. }
  160.  
  161. for (; ; sampleIndex++, unitIndex = 0) {
  162. if (sampleIndex >= samples.length) {
  163. callback();
  164. return;
  165. }
  166.  
  167. const curUnits = samples[sampleIndex].units;
  168. for (; ; unitIndex++) {
  169. if (unitIndex >= curUnits.length) {
  170. break;
  171. }
  172.  
  173. const curUnit = curUnits[unitIndex];
  174. if (
  175. curUnit.data.length <= 48 ||
  176. (curUnit.type !== 1 && curUnit.type !== 5)
  177. ) {
  178. continue;
  179. }
  180.  
  181. this.decryptAvcSample(
  182. samples,
  183. sampleIndex,
  184. unitIndex,
  185. callback,
  186. curUnit
  187. );
  188.  
  189. if (!this.decrypter.isSync()) {
  190. return;
  191. }
  192. }
  193. }
  194. }
  195. }
  196.  
  197. export default SampleAesDecrypter;