diff --git a/src/controller/eme-controller.ts b/src/controller/eme-controller.ts index ee3a358cc8b..42e38500e21 100644 --- a/src/controller/eme-controller.ts +++ b/src/controller/eme-controller.ts @@ -14,8 +14,6 @@ import { keySystemFormatToKeySystemDomain, KeySystemIds, keySystemIdToKeySystemDomain, -} from '../utils/mediakeys-helper'; -import { KeySystems, requestMediaKeySystemAccess, } from '../utils/mediakeys-helper'; @@ -23,7 +21,7 @@ import { strToUtf8array } from '../utils/utf8-utils'; import { base64Decode } from '../utils/numeric-encoding-utils'; import { DecryptData, LevelKey } from '../loader/level-key'; import Hex from '../utils/hex'; -import { bin2str, parsePssh, parseSinf } from '../utils/mp4-tools'; +import { bin2str, parseMultiPssh, parseSinf } from '../utils/mp4-tools'; import { EventEmitter } from 'eventemitter3'; import type Hls from '../hls'; import type { ComponentAPI } from '../types/component-api'; @@ -542,20 +540,24 @@ class EMEController extends Logger implements ComponentAPI { } } else { // Support clear-lead key-session creation (otherwise depend on playlist keys) - const psshInfo = parsePssh(initData); - if (psshInfo === null) { + const psshInfos = parseMultiPssh(initData); + const psshInfo = psshInfos.filter( + (pssh) => pssh.systemId === KeySystemIds.WIDEVINE, + )[0]; + if (!psshInfo) { return; } + keySystemDomain = keySystemIdToKeySystemDomain( + psshInfo.systemId as KeySystemIds, + ); if ( psshInfo.version === 0 && - psshInfo.systemId === KeySystemIds.WIDEVINE && - psshInfo.data + psshInfo.data && + psshInfo.data.length >= 30 ) { - keyId = psshInfo.data.subarray(8, 24); + const offset = psshInfo.data.length - 22; + keyId = psshInfo.data.subarray(offset, offset + 16); } - keySystemDomain = keySystemIdToKeySystemDomain( - psshInfo.systemId as KeySystemIds, - ); } if (!keySystemDomain || !keyId) { @@ -570,7 +572,7 @@ class EMEController extends Logger implements ComponentAPI { // Match playlist key const keyContext = mediaKeySessions[i]; const decryptdata = keyContext.decryptdata; - if (decryptdata.pssh || !decryptdata.keyId) { + if (!decryptdata.keyId) { continue; } const oldKeyIdHex = Hex.hexDump(decryptdata.keyId); @@ -579,6 +581,9 @@ class EMEController extends Logger implements ComponentAPI { decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1 ) { keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex]; + if (decryptdata.pssh) { + break; + } delete keyIdToKeySessionPromise[oldKeyIdHex]; decryptdata.pssh = new Uint8Array(initData); decryptdata.keyId = keyId; diff --git a/src/utils/mediakeys-helper.ts b/src/utils/mediakeys-helper.ts index 029813f3a4a..426fb43a026 100755 --- a/src/utils/mediakeys-helper.ts +++ b/src/utils/mediakeys-helper.ts @@ -36,10 +36,10 @@ export function keySystemFormatToKeySystemDomain( // System IDs for which we can extract a key ID from "encrypted" event PSSH export const enum KeySystemIds { - // CENC = '1077efecc0b24d02ace33c1e52e2fb4b' - // CLEARKEY = 'e2719d58a985b3c9781ab030af78d30e', - // FAIRPLAY = '94ce86fb07ff4f43adb893d2fa968ca2', - // PLAYREADY = '9a04f07998404286ab92e65be0885f95', + CENC = '1077efecc0b24d02ace33c1e52e2fb4b', + CLEARKEY = 'e2719d58a985b3c9781ab030af78d30e', + FAIRPLAY = '94ce86fb07ff4f43adb893d2fa968ca2', + PLAYREADY = '9a04f07998404286ab92e65be0885f95', WIDEVINE = 'edef8ba979d64acea3c827dcd51d21ed', } @@ -48,10 +48,13 @@ export function keySystemIdToKeySystemDomain( ): KeySystems | undefined { if (systemId === KeySystemIds.WIDEVINE) { return KeySystems.WIDEVINE; - // } else if (systemId === KeySystemIds.PLAYREADY) { - // return KeySystems.PLAYREADY; - // } else if (systemId === KeySystemIds.CENC || systemId === KeySystemIds.CLEARKEY) { - // return KeySystems.CLEARKEY; + } else if (systemId === KeySystemIds.PLAYREADY) { + return KeySystems.PLAYREADY; + } else if ( + systemId === KeySystemIds.CENC || + systemId === KeySystemIds.CLEARKEY + ) { + return KeySystems.CLEARKEY; } } diff --git a/src/utils/mp4-tools.ts b/src/utils/mp4-tools.ts index 60085068b29..d30745f3e4e 100644 --- a/src/utils/mp4-tools.ts +++ b/src/utils/mp4-tools.ts @@ -3,6 +3,7 @@ import { sliceUint8 } from './typed-array'; import { utf8ArrayToStr } from '@svta/common-media-library/utils/utf8ArrayToStr'; import { logger } from '../utils/logger'; import Hex from './hex'; +import type { KeySystemIds } from './mediakeys-helper'; import type { PassthroughTrack, UserdataSample } from '../types/demuxer'; import type { DecryptData } from '../loader/level-key'; @@ -1351,41 +1352,68 @@ export function mp4pssh( ); } -export function parsePssh(initData: ArrayBuffer) { - if (!(initData instanceof ArrayBuffer) || initData.byteLength < 32) { - return null; +type PsshData = { + version: 0 | 1; + systemId: KeySystemIds; + kids: null | Uint8Array[]; + data: null | Uint8Array; + size: number; +}; + +export function parseMultiPssh(initData: ArrayBuffer): PsshData[] { + const results: PsshData[] = []; + if (initData instanceof ArrayBuffer) { + const length = initData.byteLength; + let offset = 0; + while (offset + 32 < length) { + const view = new DataView(initData, offset); + const pssh = parsePssh(view); + if ('systemId' in pssh) { + results.push(pssh); + } + offset += pssh.size; + } } - const result = { - version: 0, - systemId: '', - kids: null as null | Uint8Array[], - data: null as null | Uint8Array, - }; - const view = new DataView(initData); - const boxSize = view.getUint32(0); - if (initData.byteLength !== boxSize && boxSize > 44) { - return null; + return results; +} + +function parsePssh(view: DataView): PsshData | { size: number } { + const size = view.getUint32(0); + const length = view.byteLength; + if (view.byteLength < size) { + return { size: length }; } const type = view.getUint32(4); if (type !== 0x70737368) { - return null; + return { size }; } - result.version = view.getUint32(8) >>> 24; - if (result.version > 1) { - return null; + const version = view.getUint32(8) >>> 24; + if (version !== 0 && version !== 1) { + return { size }; } - result.systemId = Hex.hexDump(new Uint8Array(initData, 12, 16)); + const buffer = view.buffer; + const systemId = Hex.hexDump( + new Uint8Array(buffer, view.byteOffset + 12, 16), + ) as KeySystemIds; const dataSizeOrKidCount = view.getUint32(28); - if (result.version === 0) { - if (boxSize - 32 < dataSizeOrKidCount) { - return null; + let kids: null | Uint8Array[] = null; + let data: null | Uint8Array = null; + if (version === 0) { + if (size - 32 < dataSizeOrKidCount) { + return { size }; } - result.data = new Uint8Array(initData, 32, dataSizeOrKidCount); - } else if (result.version === 1) { - result.kids = []; + data = new Uint8Array(buffer, view.byteOffset + 32, dataSizeOrKidCount); + } else if (version === 1) { + kids = []; for (let i = 0; i < dataSizeOrKidCount; i++) { - result.kids.push(new Uint8Array(initData, 32 + i * 16, 16)); + kids.push(new Uint8Array(buffer, view.byteOffset + 32 + i * 16, 16)); } } - return result; + return { + version, + systemId, + kids, + data, + size, + }; } diff --git a/tests/unit/controller/eme-controller.ts b/tests/unit/controller/eme-controller.ts index b67b734cf8f..bfb21b91aa2 100644 --- a/tests/unit/controller/eme-controller.ts +++ b/tests/unit/controller/eme-controller.ts @@ -295,18 +295,7 @@ describe('EMEController', function () { media.emit('encrypted', badData); - expect(emeController.keyIdToKeySessionPromise.f000ba00).to.be.a('Promise'); - if (!emeController.keyIdToKeySessionPromise.f000ba00) { - return; - } - return emeController.keyIdToKeySessionPromise.f000ba00 - .catch(() => {}) - .finally(() => { - expect(emeController.hls.trigger).callCount(1); - expect(emeController.hls.trigger.args[0][1].details).to.equal( - ErrorDetails.KEY_SYSTEM_NO_SESSION, - ); - }); + expect(emeController.keyIdToKeySessionPromise).to.deep.equal({}); }); it('should fetch the server certificate and set it into the session', function () {