Skip to content

Commit 2afa6d3

Browse files
committed
Support "clear-lead" key-session creation without new config
1 parent d00fd45 commit 2afa6d3

File tree

6 files changed

+272
-185
lines changed

6 files changed

+272
-185
lines changed

api-extractor/report/hls.js.api.md

Lines changed: 138 additions & 139 deletions
Large diffs are not rendered by default.

src/config.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,6 @@ export type EMEControllerConfig = {
9090
keyContext: MediaKeySessionContext
9191
) => ArrayBuffer;
9292
emeEnabled: boolean;
93-
useEmeEncryptedEvent: boolean;
9493
widevineLicenseUrl?: string;
9594
drmSystems: DRMSystemsConfiguration;
9695
drmSystemOptions: DRMSystemOptions;
@@ -311,7 +310,6 @@ export const hlsDefaultConfig: HlsConfig = {
311310
maxLoadingDelay: 4, // used by abr-controller
312311
minAutoBitrate: 0, // used by hls
313312
emeEnabled: false, // used by eme-controller
314-
useEmeEncryptedEvent: false, // used by eme-controller
315313
widevineLicenseUrl: undefined, // used by eme-controller
316314
drmSystems: {}, // used by eme-controller
317315
drmSystemOptions: {}, // used by eme-controller

src/controller/eme-controller.ts

Lines changed: 54 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import {
1212
keySystemDomainToKeySystemFormat as keySystemToKeySystemFormat,
1313
KeySystemFormats,
1414
keySystemFormatToKeySystemDomain,
15+
KeySystemIds,
16+
keySystemIdToKeySystemDomain,
1517
} from '../utils/mediakeys-helper';
1618
import {
1719
KeySystems,
@@ -31,6 +33,7 @@ import type {
3133
import type { EMEControllerConfig } from '../config';
3234
import type { Fragment } from '../loader/fragment';
3335
import Hex from '../utils/hex';
36+
import { parsePssh } from '../utils/mp4-tools';
3437

3538
const MAX_LICENSE_REQUEST_FAILURES = 3;
3639
const LOGGER_PREFIX = '[eme]';
@@ -410,10 +413,6 @@ class EMEController implements ComponentAPI {
410413

411414
this.log(`Starting session for key ${keyDetails}`);
412415

413-
if (this.media && !this.config.useEmeEncryptedEvent) {
414-
this.media.removeEventListener('encrypted', this.onMediaEncrypted);
415-
}
416-
417416
let keySessionContextPromise = this.keyIdToKeySessionPromise[keyId];
418417
if (!keySessionContextPromise) {
419418
keySessionContextPromise = this.keyIdToKeySessionPromise[keyId] =
@@ -431,10 +430,6 @@ class EMEController implements ComponentAPI {
431430
mediaKeys,
432431
decryptdata,
433432
});
434-
if (this.config.useEmeEncryptedEvent) {
435-
// Use 'encrypted' event initData and type rather than 'cenc' pssh from level-key
436-
return keySessionContext;
437-
}
438433
return this.generateRequestWithPreferredKeySession(
439434
keySessionContext,
440435
'cenc',
@@ -500,38 +495,63 @@ class EMEController implements ComponentAPI {
500495
}
501496

502497
private _onMediaEncrypted(event: MediaEncryptedEvent) {
503-
this.log(`"${event.type}" event: init data type: "${event.initDataType}"`);
498+
const { initDataType, initData } = event;
499+
this.log(`"${event.type}" event: init data type: "${initDataType}"`);
504500

505-
if (!this.config.useEmeEncryptedEvent) {
501+
// Ignore event when initData is null
502+
if (initData === null) {
506503
return;
507504
}
508505

509-
let keySessionContextPromise = this.keyIdToKeySessionPromise.encrypted;
506+
// Support clear-lead key-session creation (otherwise depend on playlist keys)
507+
const psshInfo = parsePssh(initData);
508+
if (psshInfo === null) {
509+
return;
510+
}
511+
let keyId: Uint8Array | null = null;
512+
if (
513+
psshInfo.version === 0 &&
514+
psshInfo.systemId === KeySystemIds.WIDEVINE &&
515+
psshInfo.data
516+
) {
517+
keyId = psshInfo.data.subarray(8, 24);
518+
}
519+
const keySystemDomain = keySystemIdToKeySystemDomain(
520+
psshInfo.systemId as KeySystemIds
521+
);
522+
if (!keySystemDomain || !keyId) {
523+
return;
524+
}
525+
526+
const keyIdHex = Hex.hexDump(keyId);
527+
let keySessionContextPromise = this.keyIdToKeySessionPromise[keyIdHex];
510528
if (!keySessionContextPromise) {
511-
keySessionContextPromise = this.keyIdToKeySessionPromise.encrypted =
512-
this.getKeySystemSelectionPromise().then(({ keySystem, mediaKeys }) => {
513-
this.throwIfDestroyed();
514-
const sessionParameters = {
515-
decryptdata: new LevelKey(
516-
'UNKNOWN',
517-
'encrypted',
518-
keySystemToKeySystemFormat(keySystem) ?? ''
519-
),
520-
keySystem,
521-
mediaKeys,
522-
};
523-
sessionParameters.decryptdata.keyId = new Uint8Array(16);
524-
return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
529+
keySessionContextPromise = this.keyIdToKeySessionPromise[keyIdHex] =
530+
this.getKeySystemSelectionPromise([keySystemDomain]).then(
531+
({ keySystem, mediaKeys }) => {
525532
this.throwIfDestroyed();
526-
const keySessionContext =
527-
this.createMediaKeySessionContext(sessionParameters);
528-
return this.generateRequestWithPreferredKeySession(
529-
keySessionContext,
530-
event.initDataType,
531-
event.initData
533+
const decryptdata = new LevelKey(
534+
'ISO-23001-7',
535+
keyIdHex,
536+
keySystemToKeySystemFormat(keySystem) ?? ''
532537
);
533-
});
534-
});
538+
decryptdata.pssh = new Uint8Array(initData);
539+
decryptdata.keyId = keyId;
540+
return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
541+
this.throwIfDestroyed();
542+
const keySessionContext = this.createMediaKeySessionContext({
543+
decryptdata,
544+
keySystem,
545+
mediaKeys,
546+
});
547+
return this.generateRequestWithPreferredKeySession(
548+
keySessionContext,
549+
initDataType,
550+
initData
551+
);
552+
});
553+
}
554+
);
535555
}
536556
keySessionContextPromise.catch((error) => this.handleError(error));
537557
}
@@ -1035,9 +1055,7 @@ class EMEController implements ComponentAPI {
10351055
// keep reference of media
10361056
this.media = media;
10371057

1038-
if (this.config.useEmeEncryptedEvent) {
1039-
media.addEventListener('encrypted', this.onMediaEncrypted);
1040-
}
1058+
media.addEventListener('encrypted', this.onMediaEncrypted);
10411059
media.addEventListener('waitingforkey', this.onWaitingForKey);
10421060
}
10431061

src/utils/mediakeys-helper.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export enum KeySystems {
1010
WIDEVINE = 'com.widevine.alpha',
1111
}
1212

13-
// Playlist parser
13+
// Playlist #EXT-X-KEY KEYFORMAT values
1414
export enum KeySystemFormats {
1515
CLEARKEY = 'org.w3.clearkey',
1616
FAIRPLAY = 'com.apple.streamingkeydelivery',
@@ -32,6 +32,27 @@ export function keySystemFormatToKeySystemDomain(
3232
}
3333
}
3434

35+
// System IDs for which we can extract a key ID from "encrypted" event PSSH
36+
export enum KeySystemIds {
37+
// CENC = '1077efecc0b24d02ace33c1e52e2fb4b'
38+
// CLEARKEY = 'e2719d58a985b3c9781ab030af78d30e',
39+
// FAIRPLAY = '94ce86fb07ff4f43adb893d2fa968ca2',
40+
// PLAYREADY = '9a04f07998404286ab92e65be0885f95',
41+
WIDEVINE = 'edef8ba979d64acea3c827dcd51d21ed',
42+
}
43+
44+
export function keySystemIdToKeySystemDomain(
45+
systemId: KeySystemIds
46+
): KeySystems | undefined {
47+
if (systemId === KeySystemIds.WIDEVINE) {
48+
return KeySystems.WIDEVINE;
49+
// } else if (systemId === KeySystemIds.PLAYREADY) {
50+
// return KeySystems.PLAYREADY;
51+
// } else if (systemId === KeySystemIds.CENC || systemId === KeySystemIds.CLEARKEY) {
52+
// return KeySystems.CLEARKEY;
53+
}
54+
}
55+
3556
export function keySystemDomainToKeySystemFormat(
3657
keySystem: KeySystems
3758
): KeySystemFormats | undefined {

src/utils/mp4-tools.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1099,3 +1099,42 @@ export function mp4pssh(
10991099
data || new Uint8Array()
11001100
);
11011101
}
1102+
1103+
export function parsePssh(initData: ArrayBuffer) {
1104+
if (!(initData instanceof ArrayBuffer) || initData.byteLength < 32) {
1105+
return null;
1106+
}
1107+
const result = {
1108+
version: 0,
1109+
systemId: '',
1110+
kids: null as null | Uint8Array[],
1111+
data: null as null | Uint8Array,
1112+
};
1113+
const view = new DataView(initData);
1114+
const boxSize = view.getUint32(0);
1115+
if (initData.byteLength !== boxSize && boxSize > 44) {
1116+
return null;
1117+
}
1118+
const type = view.getUint32(4);
1119+
if (type !== 0x70737368) {
1120+
return null;
1121+
}
1122+
result.version = view.getUint32(8) >>> 24;
1123+
if (result.version > 1) {
1124+
return null;
1125+
}
1126+
result.systemId = Hex.hexDump(new Uint8Array(initData, 12, 16));
1127+
const dataSizeOrKidCount = view.getUint32(28);
1128+
if (result.version === 0) {
1129+
if (boxSize - 32 < dataSizeOrKidCount) {
1130+
return null;
1131+
}
1132+
result.data = new Uint8Array(initData, 32, dataSizeOrKidCount);
1133+
} else if (result.version === 1) {
1134+
result.kids = [];
1135+
for (let i = 0; i < dataSizeOrKidCount; i++) {
1136+
result.kids.push(new Uint8Array(initData, 32 + i * 16, 16));
1137+
}
1138+
}
1139+
return result;
1140+
}

tests/unit/controller/eme-controller.ts

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,6 @@ describe('EMEController', function () {
111111
},
112112
},
113113
requestMediaKeySystemAccessFunc: reqMediaKsAccessSpy,
114-
// useEmeEncryptedEvent: true, // skip generate request, license exchange, and key status "usable"
115114
});
116115

117116
sinonFakeXMLHttpRequestStatic.onCreate = (
@@ -249,7 +248,7 @@ describe('EMEController', function () {
249248
});
250249
});
251250

252-
it('should trigger key system error(s) when bad encrypted data is received', function () {
251+
it('should ignore "encrypted" events with bad data', function () {
253252
const reqMediaKsAccessSpy = sinon.spy(function () {
254253
return Promise.resolve({
255254
// Media-keys mock
@@ -271,13 +270,26 @@ describe('EMEController', function () {
271270

272271
setupEach({
273272
emeEnabled: true,
274-
useEmeEncryptedEvent: true,
275273
requestMediaKeySystemAccessFunc: reqMediaKsAccessSpy,
276274
});
277275

278276
const badData = {
279277
initDataType: 'cenc',
280-
initData: 'bad data',
278+
initData: new Uint8Array([
279+
// box size
280+
0, 0, 0, 44,
281+
// "PSSH"
282+
112, 115, 115, 104,
283+
// version
284+
0, 0, 0, 0,
285+
// Widevine system id
286+
237, 239, 139, 169, 121, 214, 74, 206, 163, 200, 39, 220, 213, 29, 33,
287+
237,
288+
// data size
289+
0, 0, 0, 12,
290+
// data (incomplete key)
291+
0, 0, 0, 0, 0, 0, 0, 0, 240, 0, 186, 0,
292+
]).buffer,
281293
};
282294

283295
emeController.onMediaAttached(Events.MEDIA_ATTACHED, {
@@ -286,11 +298,11 @@ describe('EMEController', function () {
286298

287299
media.emit('encrypted', badData);
288300

289-
expect(emeController.keyIdToKeySessionPromise.encrypted).to.be.a('Promise');
290-
if (!emeController.keyIdToKeySessionPromise.encrypted) {
301+
expect(emeController.keyIdToKeySessionPromise.f000ba00).to.be.a('Promise');
302+
if (!emeController.keyIdToKeySessionPromise.f000ba00) {
291303
return;
292304
}
293-
return emeController.keyIdToKeySessionPromise.encrypted
305+
return emeController.keyIdToKeySessionPromise.f000ba00
294306
.catch(() => {})
295307
.finally(() => {
296308
expect(emeController.hls.trigger).callCount(1);

0 commit comments

Comments
 (0)