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. if (curUnit.length <= 16) {
  49. // No encrypted portion in this sample (first 16 bytes is not
  50. // encrypted, see https://developer.apple.com/library/archive/documentation/AudioVideo/Conceptual/HLS_Sample_Encryption/Encryption/Encryption.html),
  51. return;
  52. }
  53. const encryptedData = curUnit.subarray(
  54. 16,
  55. curUnit.length - (curUnit.length % 16)
  56. );
  57. const encryptedBuffer = encryptedData.buffer.slice(
  58. encryptedData.byteOffset,
  59. encryptedData.byteOffset + encryptedData.length
  60. );
  61.  
  62. const localthis = this;
  63. this.decryptBuffer(encryptedBuffer, (decryptedBuffer: ArrayBuffer) => {
  64. const decryptedData = new Uint8Array(decryptedBuffer);
  65. curUnit.set(decryptedData, 16);
  66.  
  67. if (!sync) {
  68. localthis.decryptAacSamples(samples, sampleIndex + 1, callback);
  69. }
  70. });
  71. }
  72.  
  73. decryptAacSamples(
  74. samples: AudioSample[],
  75. sampleIndex: number,
  76. callback: () => void
  77. ) {
  78. for (; ; sampleIndex++) {
  79. if (sampleIndex >= samples.length) {
  80. callback();
  81. return;
  82. }
  83.  
  84. if (samples[sampleIndex].unit.length < 32) {
  85. continue;
  86. }
  87.  
  88. const sync = this.decrypter.isSync();
  89.  
  90. this.decryptAacSample(samples, sampleIndex, callback, sync);
  91.  
  92. if (!sync) {
  93. return;
  94. }
  95. }
  96. }
  97.  
  98. // AVC - encrypt one 16 bytes block out of ten, starting from offset 32
  99. getAvcEncryptedData(decodedData: Uint8Array) {
  100. const encryptedDataLen =
  101. Math.floor((decodedData.length - 48) / 160) * 16 + 16;
  102. const encryptedData = new Int8Array(encryptedDataLen);
  103. let outputPos = 0;
  104. for (
  105. let inputPos = 32;
  106. inputPos < decodedData.length - 16;
  107. inputPos += 160, outputPos += 16
  108. ) {
  109. encryptedData.set(
  110. decodedData.subarray(inputPos, inputPos + 16),
  111. outputPos
  112. );
  113. }
  114.  
  115. return encryptedData;
  116. }
  117.  
  118. getAvcDecryptedUnit(
  119. decodedData: Uint8Array,
  120. decryptedData: ArrayLike<number> | ArrayBuffer | SharedArrayBuffer
  121. ) {
  122. const uint8DecryptedData = new Uint8Array(decryptedData);
  123. let inputPos = 0;
  124. for (
  125. let outputPos = 32;
  126. outputPos < decodedData.length - 16;
  127. outputPos += 160, inputPos += 16
  128. ) {
  129. decodedData.set(
  130. uint8DecryptedData.subarray(inputPos, inputPos + 16),
  131. outputPos
  132. );
  133. }
  134.  
  135. return decodedData;
  136. }
  137.  
  138. decryptAvcSample(
  139. samples: AvcSample[],
  140. sampleIndex: number,
  141. unitIndex: number,
  142. callback: () => void,
  143. curUnit: AvcSampleUnit,
  144. sync: boolean
  145. ) {
  146. const decodedData = discardEPB(curUnit.data);
  147. const encryptedData = this.getAvcEncryptedData(decodedData);
  148. const localthis = this;
  149.  
  150. this.decryptBuffer(
  151. encryptedData.buffer,
  152. function (decryptedBuffer: ArrayBuffer) {
  153. curUnit.data = localthis.getAvcDecryptedUnit(
  154. decodedData,
  155. decryptedBuffer
  156. );
  157.  
  158. if (!sync) {
  159. localthis.decryptAvcSamples(
  160. samples,
  161. sampleIndex,
  162. unitIndex + 1,
  163. callback
  164. );
  165. }
  166. }
  167. );
  168. }
  169.  
  170. decryptAvcSamples(
  171. samples: DemuxedVideoTrack['samples'],
  172. sampleIndex: number,
  173. unitIndex: number,
  174. callback: () => void
  175. ) {
  176. if (samples instanceof Uint8Array) {
  177. throw new Error('Cannot decrypt samples of type Uint8Array');
  178. }
  179.  
  180. for (; ; sampleIndex++, unitIndex = 0) {
  181. if (sampleIndex >= samples.length) {
  182. callback();
  183. return;
  184. }
  185.  
  186. const curUnits = samples[sampleIndex].units;
  187. for (; ; unitIndex++) {
  188. if (unitIndex >= curUnits.length) {
  189. break;
  190. }
  191.  
  192. const curUnit = curUnits[unitIndex];
  193. if (
  194. curUnit.data.length <= 48 ||
  195. (curUnit.type !== 1 && curUnit.type !== 5)
  196. ) {
  197. continue;
  198. }
  199.  
  200. const sync = this.decrypter.isSync();
  201.  
  202. this.decryptAvcSample(
  203. samples,
  204. sampleIndex,
  205. unitIndex,
  206. callback,
  207. curUnit,
  208. sync
  209. );
  210.  
  211. if (!sync) {
  212. return;
  213. }
  214. }
  215. }
  216. }
  217. }
  218.  
  219. export default SampleAesDecrypter;