download
raw
8.49 kB
import { arrayValuesMatch, optionalArrayValuesMatch } from '../utils/arrays';
import { isFullSegmentEncryption } from '../utils/encryption-methods-util';
import { hexToArrayBuffer } from '../utils/hex';
import { convertDataUriToArrayBytes } from '../utils/keysystem-util';
import { logger } from '../utils/logger';
import { KeySystemFormats, parsePlayReadyWRM } from '../utils/mediakeys-helper';
import { mp4pssh, parseMultiPssh } from '../utils/mp4-tools';
let keyUriToKeyIdMap: { [uri: string]: Uint8Array<ArrayBuffer> } = {};
export interface DecryptData {
uri: string;
method: string;
keyFormat: string;
keyFormatVersions: number[];
iv: Uint8Array<ArrayBuffer> | null;
key: Uint8Array<ArrayBuffer> | null;
keyId: Uint8Array<ArrayBuffer> | null;
pssh: Uint8Array<ArrayBuffer> | null;
encrypted: boolean;
isCommonEncryption: boolean;
}
export class LevelKey implements DecryptData {
public readonly uri: string;
public readonly method: string;
public readonly keyFormat: string;
public readonly keyFormatVersions: number[];
public readonly encrypted: boolean;
public readonly isCommonEncryption: boolean;
public iv: Uint8Array<ArrayBuffer> | null = null;
public key: Uint8Array<ArrayBuffer> | null = null;
public keyId: Uint8Array<ArrayBuffer> | null = null;
public pssh: Uint8Array<ArrayBuffer> | null = null;
static clearKeyUriToKeyIdMap() {
keyUriToKeyIdMap = {};
}
static setKeyIdForUri(uri: string, keyId: Uint8Array<ArrayBuffer>) {
keyUriToKeyIdMap[uri] = keyId;
}
static addKeyIdForUri(uri: string): Uint8Array<ArrayBuffer> {
const val = Object.keys(keyUriToKeyIdMap).length % Number.MAX_SAFE_INTEGER;
const keyId = new Uint8Array(16);
const dv = new DataView(keyId.buffer, 12, 4); // Just set the last 4 bytes
dv.setUint32(0, val);
keyUriToKeyIdMap[uri] = keyId;
return keyId;
}
constructor(
method: string,
uri: string,
format: string,
formatversions: number[] = [1],
iv: Uint8Array<ArrayBuffer> | null = null,
keyId?: string,
) {
this.method = method;
this.uri = uri;
this.keyFormat = format;
this.keyFormatVersions = formatversions;
this.iv = iv;
this.encrypted = method ? method !== 'NONE' : false;
this.isCommonEncryption =
this.encrypted && !isFullSegmentEncryption(method);
if (keyId?.startsWith('0x')) {
this.keyId = new Uint8Array(hexToArrayBuffer(keyId));
}
}
public matches(key: LevelKey): boolean {
return (
key.uri === this.uri &&
key.method === this.method &&
key.encrypted === this.encrypted &&
key.keyFormat === this.keyFormat &&
arrayValuesMatch(key.keyFormatVersions, this.keyFormatVersions) &&
optionalArrayValuesMatch(key.iv, this.iv) &&
optionalArrayValuesMatch(key.keyId, this.keyId)
);
}
public isSupported(): boolean {
// If it's Segment encryption or No encryption, just select that key system
if (this.method) {
if (isFullSegmentEncryption(this.method) || this.method === 'NONE') {
return true;
}
if (this.keyFormat === 'identity') {
// Maintain support for clear SAMPLE-AES with MPEG-3 TS
return this.method === 'SAMPLE-AES';
} else if (__USE_EME_DRM__) {
switch (this.keyFormat) {
case KeySystemFormats.FAIRPLAY:
case KeySystemFormats.WIDEVINE:
case KeySystemFormats.PLAYREADY:
case KeySystemFormats.CLEARKEY:
return (
['SAMPLE-AES', 'SAMPLE-AES-CENC', 'SAMPLE-AES-CTR'].indexOf(
this.method,
) !== -1
);
}
}
}
return false;
}
public getDecryptData(
sn: number | 'initSegment',
levelKeys?: { [key: string]: LevelKey | undefined },
): LevelKey | null {
if (!this.encrypted || !this.uri) {
return null;
}
if (isFullSegmentEncryption(this.method)) {
let iv = this.iv;
if (!iv) {
if (typeof sn !== 'number') {
// We are fetching decryption data for a initialization segment
// If the segment was encrypted with AES-128/256
// It must have an IV defined. We cannot substitute the Segment Number in.
logger.warn(
`missing IV for initialization segment with method="${this.method}" - compliance issue`,
);
// Explicitly set sn to resulting value from implicit conversions 'initSegment' values for IV generation.
sn = 0;
}
iv = createInitializationVector(sn);
}
const decryptdata = new LevelKey(
this.method,
this.uri,
'identity',
this.keyFormatVersions,
iv,
);
return decryptdata;
}
if (!__USE_EME_DRM__) {
return this;
}
if (this.keyId) {
// Handle case where key id is changed in KEY_LOADING event handler #7542#issuecomment-3305203929
const assignedKeyId = keyUriToKeyIdMap[this.uri];
if (assignedKeyId && !arrayValuesMatch(this.keyId, assignedKeyId)) {
LevelKey.setKeyIdForUri(this.uri, this.keyId);
}
if (this.pssh) {
return this;
}
}
// Key bytes are signalled the KEYID attribute, typically only found on WideVine KEY tags
// Initialize keyId if possible
const keyBytes = convertDataUriToArrayBytes(this.uri);
if (keyBytes) {
switch (this.keyFormat) {
case KeySystemFormats.WIDEVINE:
// Setting `pssh` on this LevelKey/DecryptData allows HLS.js to generate a session using
// the playlist-key before the "encrypted" event. (Comment out to only use "encrypted" path.)
this.pssh = keyBytes;
// In case of Widevine, if KEYID is not in the playlist, assume only two fields in the pssh KEY tag URI.
if (!this.keyId) {
const results = parseMultiPssh(keyBytes.buffer);
if (results.length) {
const psshData = results[0];
this.keyId = psshData.kids?.length ? psshData.kids[0] : null;
}
}
if (!this.keyId) {
this.keyId = getKeyIdFromPlayReadyKey(levelKeys);
}
break;
case KeySystemFormats.PLAYREADY: {
const PlayReadyKeySystemUUID = new Uint8Array([
0x9a, 0x04, 0xf0, 0x79, 0x98, 0x40, 0x42, 0x86, 0xab, 0x92, 0xe6,
0x5b, 0xe0, 0x88, 0x5f, 0x95,
]);
// Setting `pssh` on this LevelKey/DecryptData allows HLS.js to generate a session using
// the playlist-key before the "encrypted" event. (Comment out to only use "encrypted" path.)
this.pssh = mp4pssh(PlayReadyKeySystemUUID, null, keyBytes);
this.keyId = parsePlayReadyWRM(keyBytes);
break;
}
default: {
let keydata = keyBytes.subarray(0, 16);
if (keydata.length !== 16) {
const padded = new Uint8Array(16);
padded.set(keydata, 16 - keydata.length);
keydata = padded;
}
this.keyId = keydata;
break;
}
}
}
// Default behavior: get keyId from other KEY tag or URI lookup
if (!this.keyId || this.keyId.byteLength !== 16) {
let keyId: Uint8Array<ArrayBuffer> | null | undefined;
keyId = getKeyIdFromWidevineKey(levelKeys);
if (!keyId) {
keyId = getKeyIdFromPlayReadyKey(levelKeys);
if (!keyId) {
keyId = keyUriToKeyIdMap[this.uri];
}
}
if (keyId) {
this.keyId = keyId;
LevelKey.setKeyIdForUri(this.uri, keyId);
}
}
return this;
}
}
function getKeyIdFromWidevineKey(
levelKeys: { [key: string]: LevelKey | undefined } | undefined,
) {
const widevineKey = levelKeys?.[KeySystemFormats.WIDEVINE];
if (widevineKey) {
return widevineKey.keyId;
}
return null;
}
function getKeyIdFromPlayReadyKey(
levelKeys: { [key: string]: LevelKey | undefined } | undefined,
) {
const playReadyKey = levelKeys?.[KeySystemFormats.PLAYREADY];
if (playReadyKey) {
const playReadyKeyBytes = convertDataUriToArrayBytes(playReadyKey.uri);
if (playReadyKeyBytes) {
return parsePlayReadyWRM(playReadyKeyBytes);
}
}
return null;
}
function createInitializationVector(segmentNumber: number) {
const uint8View = new Uint8Array(16);
for (let i = 12; i < 16; i++) {
uint8View[i] = (segmentNumber >> (8 * (15 - i))) & 0xff;
}
return uint8View;
}

Xet Storage Details

Size:
8.49 kB
·
Xet hash:
900959d6e2f73cd82ea5dc4ed6696500bdcf0ecf17dd5ef60949111dd550bc3f

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.