Buckets:
| 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.