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 './tsdemuxer';
  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(observer, config, {
  24. removePKCS7Padding: false,
  25. });
  26. }
  27.  
  28. decryptBuffer(
  29. encryptedData: Uint8Array | ArrayBuffer,
  30. callback: (decryptedData: ArrayBuffer) => void
  31. ) {
  32. this.decrypter.decrypt(
  33. encryptedData,
  34. this.keyData.key.buffer,
  35. this.keyData.iv.buffer,
  36. callback
  37. );
  38. }
  39.  
  40. // AAC - encrypt all full 16 bytes blocks starting from offset 16
  41. private decryptAacSample(
  42. samples: AudioSample[],
  43. sampleIndex: number,
  44. callback: () => void,
  45. sync: boolean
  46. ) {
  47. const curUnit = samples[sampleIndex].unit;
  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. const localthis = this;
  58. this.decryptBuffer(encryptedBuffer, (decryptedBuffer: ArrayBuffer) => {
  59. const decryptedData = new Uint8Array(decryptedBuffer);
  60. curUnit.set(decryptedData, 16);
  61.  
  62. if (!sync) {
  63. localthis.decryptAacSamples(samples, sampleIndex + 1, callback);
  64. }
  65. });
  66. }
  67.  
  68. decryptAacSamples(
  69. samples: AudioSample[],
  70. sampleIndex: number,
  71. callback: () => void
  72. ) {
  73. for (; ; sampleIndex++) {
  74. if (sampleIndex >= samples.length) {
  75. callback();
  76. return;
  77. }
  78.  
  79. if (samples[sampleIndex].unit.length < 32) {
  80. continue;
  81. }
  82.  
  83. const sync = this.decrypter.isSync();
  84.  
  85. this.decryptAacSample(samples, sampleIndex, callback, sync);
  86.  
  87. if (!sync) {
  88. return;
  89. }
  90. }
  91. }
  92.  
  93. // AVC - encrypt one 16 bytes block out of ten, starting from offset 32
  94. getAvcEncryptedData(decodedData: Uint8Array) {
  95. const encryptedDataLen =
  96. Math.floor((decodedData.length - 48) / 160) * 16 + 16;
  97. const encryptedData = new Int8Array(encryptedDataLen);
  98. let outputPos = 0;
  99. for (
  100. let inputPos = 32;
  101. inputPos <= decodedData.length - 16;
  102. inputPos += 160, outputPos += 16
  103. ) {
  104. encryptedData.set(
  105. decodedData.subarray(inputPos, inputPos + 16),
  106. outputPos
  107. );
  108. }
  109.  
  110. return encryptedData;
  111. }
  112.  
  113. getAvcDecryptedUnit(
  114. decodedData: Uint8Array,
  115. decryptedData: ArrayLike<number> | ArrayBuffer | SharedArrayBuffer
  116. ) {
  117. const uint8DecryptedData = new Uint8Array(decryptedData);
  118. let inputPos = 0;
  119. for (
  120. let outputPos = 32;
  121. outputPos <= decodedData.length - 16;
  122. outputPos += 160, inputPos += 16
  123. ) {
  124. decodedData.set(
  125. uint8DecryptedData.subarray(inputPos, inputPos + 16),
  126. outputPos
  127. );
  128. }
  129.  
  130. return decodedData;
  131. }
  132.  
  133. decryptAvcSample(
  134. samples: AvcSample[],
  135. sampleIndex: number,
  136. unitIndex: number,
  137. callback: () => void,
  138. curUnit: AvcSampleUnit,
  139. sync: boolean
  140. ) {
  141. const decodedData = discardEPB(curUnit.data);
  142. const encryptedData = this.getAvcEncryptedData(decodedData);
  143. const localthis = this;
  144.  
  145. this.decryptBuffer(
  146. encryptedData.buffer,
  147. function (decryptedBuffer: ArrayBuffer) {
  148. curUnit.data = localthis.getAvcDecryptedUnit(
  149. decodedData,
  150. decryptedBuffer
  151. );
  152.  
  153. if (!sync) {
  154. localthis.decryptAvcSamples(
  155. samples,
  156. sampleIndex,
  157. unitIndex + 1,
  158. callback
  159. );
  160. }
  161. }
  162. );
  163. }
  164.  
  165. decryptAvcSamples(
  166. samples: DemuxedVideoTrack['samples'],
  167. sampleIndex: number,
  168. unitIndex: number,
  169. callback: () => void
  170. ) {
  171. if (samples instanceof Uint8Array) {
  172. throw new Error('Cannot decrypt samples of type Uint8Array');
  173. }
  174.  
  175. for (; ; sampleIndex++, unitIndex = 0) {
  176. if (sampleIndex >= samples.length) {
  177. callback();
  178. return;
  179. }
  180.  
  181. const curUnits = samples[sampleIndex].units;
  182. for (; ; unitIndex++) {
  183. if (unitIndex >= curUnits.length) {
  184. break;
  185. }
  186.  
  187. const curUnit = curUnits[unitIndex];
  188. if (
  189. curUnit.data.length <= 48 ||
  190. (curUnit.type !== 1 && curUnit.type !== 5)
  191. ) {
  192. continue;
  193. }
  194.  
  195. const sync = this.decrypter.isSync();
  196.  
  197. this.decryptAvcSample(
  198. samples,
  199. sampleIndex,
  200. unitIndex,
  201. callback,
  202. curUnit,
  203. sync
  204. );
  205.  
  206. if (!sync) {
  207. return;
  208. }
  209. }
  210. }
  211. }
  212. }
  213.  
  214. export default SampleAesDecrypter;