src/demux/sample-aes.ts
/**
* SAMPLE-AES decrypter
*/
import { HlsConfig } from '../config';
import Decrypter from '../crypt/decrypter';
import { HlsEventEmitter } from '../events';
import type {
AudioSample,
AvcSample,
AvcSampleUnit,
DemuxedVideoTrack,
KeyData,
} from '../types/demuxer';
class SampleAesDecrypter {
private keyData: KeyData;
private discardEPB: (data: Uint8Array) => Uint8Array;
private decrypter: Decrypter;
constructor(
observer: HlsEventEmitter,
config: HlsConfig,
keyData: KeyData,
discardEPB: (data: Uint8Array) => Uint8Array
) {
this.keyData = keyData;
this.discardEPB = discardEPB;
this.decrypter = new Decrypter(observer, config, {
removePKCS7Padding: false,
});
}
// TODO: Fix callback return type.
decryptBuffer(
encryptedData: Uint8Array | ArrayBufferLike,
callback: (decryptedData: any) => void
) {
// TODO: `this.decrypter` is an instance of `Decrypter`, which has no function named decrypt!?
(this.decrypter as any).decrypt(
encryptedData,
this.keyData.key.buffer,
this.keyData.iv.buffer,
callback
);
}
// AAC - encrypt all full 16 bytes blocks starting from offset 16
decryptAacSample(
samples: AudioSample[],
sampleIndex: number,
callback: () => void,
sync: boolean
) {
const curUnit = samples[sampleIndex].unit;
const encryptedData = curUnit.subarray(
16,
curUnit.length - (curUnit.length % 16)
);
const encryptedBuffer = encryptedData.buffer.slice(
encryptedData.byteOffset,
encryptedData.byteOffset + encryptedData.length
);
const localthis = this;
this.decryptBuffer(encryptedBuffer, function (decryptedData) {
decryptedData = new Uint8Array(decryptedData);
curUnit.set(decryptedData, 16);
if (!sync) {
localthis.decryptAacSamples(samples, sampleIndex + 1, callback);
}
});
}
decryptAacSamples(
samples: AudioSample[],
sampleIndex: number,
callback: () => void
) {
for (; ; sampleIndex++) {
if (sampleIndex >= samples.length) {
callback();
return;
}
if (samples[sampleIndex].unit.length < 32) {
continue;
}
const sync = this.decrypter.isSync();
this.decryptAacSample(samples, sampleIndex, callback, sync);
if (!sync) {
return;
}
}
}
// AVC - encrypt one 16 bytes block out of ten, starting from offset 32
getAvcEncryptedData(decodedData: Uint8Array) {
const encryptedDataLen =
Math.floor((decodedData.length - 48) / 160) * 16 + 16;
const encryptedData = new Int8Array(encryptedDataLen);
let outputPos = 0;
for (
let inputPos = 32;
inputPos <= decodedData.length - 16;
inputPos += 160, outputPos += 16
) {
encryptedData.set(
decodedData.subarray(inputPos, inputPos + 16),
outputPos
);
}
return encryptedData;
}
getAvcDecryptedUnit(
decodedData: Uint8Array,
decryptedData: ArrayLike<number> | ArrayBuffer | SharedArrayBuffer
) {
const uint8DecryptedData = new Uint8Array(decryptedData);
let inputPos = 0;
for (
let outputPos = 32;
outputPos <= decodedData.length - 16;
outputPos += 160, inputPos += 16
) {
decodedData.set(
uint8DecryptedData.subarray(inputPos, inputPos + 16),
outputPos
);
}
return decodedData;
}
decryptAvcSample(
samples: AvcSample[],
sampleIndex: number,
unitIndex: number,
callback: () => void,
curUnit: AvcSampleUnit,
sync: boolean
) {
const decodedData = this.discardEPB(curUnit.data);
const encryptedData = this.getAvcEncryptedData(decodedData);
const localthis = this;
this.decryptBuffer(encryptedData.buffer, function (decryptedData) {
curUnit.data = localthis.getAvcDecryptedUnit(decodedData, decryptedData);
if (!sync) {
localthis.decryptAvcSamples(
samples,
sampleIndex,
unitIndex + 1,
callback
);
}
});
}
decryptAvcSamples(
samples: DemuxedVideoTrack['samples'],
sampleIndex: number,
unitIndex: number,
callback: () => void
) {
if (samples instanceof Uint8Array) {
throw new Error('Cannot decrypt samples of type Uint8Array');
}
for (; ; sampleIndex++, unitIndex = 0) {
if (sampleIndex >= samples.length) {
callback();
return;
}
const curUnits = samples[sampleIndex].units;
for (; ; unitIndex++) {
if (unitIndex >= curUnits.length) {
break;
}
const curUnit = curUnits[unitIndex];
if (
curUnit.data.length <= 48 ||
(curUnit.type !== 1 && curUnit.type !== 5)
) {
continue;
}
const sync = this.decrypter.isSync();
this.decryptAvcSample(
samples,
sampleIndex,
unitIndex,
callback,
curUnit,
sync
);
if (!sync) {
return;
}
}
}
}
}
export default SampleAesDecrypter;