Skip to content

Commit d00fd45

Browse files
committed
Map key-sessions by key ID and log key ID more often than URI
1 parent 94bf06d commit d00fd45

File tree

3 files changed

+111
-85
lines changed

3 files changed

+111
-85
lines changed

src/controller/eme-controller.ts

Lines changed: 66 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@ import {
2020
import { strToUtf8array } from '../utils/keysystem-util';
2121
import { utf8ArrayToStr } from '../demux/id3';
2222
import { base64Decode, base64Encode } from '../utils/numeric-encoding-utils';
23-
import { LevelKey } from '../loader/level-key';
24-
23+
import { DecryptData, LevelKey } from '../loader/level-key';
2524
import type Hls from '../hls';
2625
import type { ComponentAPI } from '../types/component-api';
2726
import type {
@@ -70,8 +69,8 @@ class EMEController implements ComponentAPI {
7069
} = {};
7170
private _requestLicenseFailureCount: number = 0;
7271
private mediaKeySessions: MediaKeySessionContext[] = [];
73-
private keyUriToKeySessionPromise: {
74-
[keyuri: string]: Promise<MediaKeySessionContext>;
72+
private keyIdToKeySessionPromise: {
73+
[keyId: string]: Promise<MediaKeySessionContext>;
7574
} = {};
7675
private setMediaKeysQueue: Promise<void>[] = EMEController.CDMCleanupPromise
7776
? [EMEController.CDMCleanupPromise]
@@ -83,10 +82,6 @@ class EMEController implements ComponentAPI {
8382
private warn: (msg: any) => void = logger.warn.bind(logger, LOGGER_PREFIX);
8483
private error: (msg: any) => void = logger.error.bind(logger, LOGGER_PREFIX);
8584

86-
/**
87-
* @constructs
88-
* @param {Hls} hls Our Hls.js instance
89-
*/
9085
constructor(hls: Hls) {
9186
this.hls = hls;
9287
this.config = hls.config;
@@ -100,7 +95,7 @@ class EMEController implements ComponentAPI {
10095
this.hls =
10196
this.onMediaEncrypted =
10297
this.onWaitingForKey =
103-
this.keyUriToKeySessionPromise =
98+
this.keyIdToKeySessionPromise =
10499
null as any;
105100
}
106101

@@ -298,7 +293,9 @@ class EMEController implements ComponentAPI {
298293
console.assert(!!mediaKeys, 'mediaKeys is defined');
299294

300295
this.log(
301-
`Creating key-system session "${keySystem}" uri: ${decryptdata.uri}`
296+
`Creating key-system session "${keySystem}" keyId: ${Hex.hexDump(
297+
decryptdata.keyId! || []
298+
)}`
302299
);
303300

304301
const mediaKeysSession = mediaKeys.createSession();
@@ -321,7 +318,8 @@ class EMEController implements ComponentAPI {
321318
const keySessionContext = this.createMediaKeySessionContext(
322319
mediaKeySessionContext
323320
);
324-
this.keyUriToKeySessionPromise[decryptdata.uri] =
321+
const keyId = this.getKeyIdString(decryptdata);
322+
this.keyIdToKeySessionPromise[keyId] =
325323
this.generateRequestWithPreferredKeySession(
326324
keySessionContext,
327325
'cenc',
@@ -330,6 +328,16 @@ class EMEController implements ComponentAPI {
330328
this.removeSession(mediaKeySessionContext);
331329
}
332330

331+
private getKeyIdString(decryptdata: DecryptData | undefined): string | never {
332+
if (!decryptdata) {
333+
throw new Error('Could not read keyId of undefined decryptdata');
334+
}
335+
if (decryptdata.keyId === null) {
336+
throw new Error('keyId is null');
337+
}
338+
return Hex.hexDump(decryptdata.keyId);
339+
}
340+
333341
private handleParsedKeyResponse(
334342
mediaKeySessionContext: MediaKeySessionContext,
335343
licenseResponse: ArrayBuffer
@@ -357,8 +365,9 @@ class EMEController implements ComponentAPI {
357365
): Promise<void> {
358366
const keySession = mediaKeySessionContext.mediaKeysSession;
359367
this.log(
360-
`Updating key-session "${keySession.sessionId}" for ${
361-
mediaKeySessionContext.decryptdata?.uri
368+
`Updating key-session "${keySession.sessionId}" for keyID ${Hex.hexDump(
369+
mediaKeySessionContext.decryptdata?.keyId! || []
370+
)}
362371
} (data length: ${data ? data.byteLength : data})`
363372
);
364373
return keySession.update(data);
@@ -396,45 +405,44 @@ class EMEController implements ComponentAPI {
396405
public loadKey(data: KeyLoadedData): Promise<MediaKeySessionContext> {
397406
const decryptdata = data.keyInfo.decryptdata;
398407

399-
this.log(
400-
`Starting session for key ${decryptdata.keyFormat} ${decryptdata.uri}`
401-
);
408+
const keyId = this.getKeyIdString(decryptdata);
409+
const keyDetails = `(keyId: ${keyId} format: "${decryptdata.keyFormat}" method: ${decryptdata.method} uri: ${decryptdata.uri})`;
410+
411+
this.log(`Starting session for key ${keyDetails}`);
402412

403413
if (this.media && !this.config.useEmeEncryptedEvent) {
404414
this.media.removeEventListener('encrypted', this.onMediaEncrypted);
405415
}
406416

407-
let keySessionContextPromise =
408-
this.keyUriToKeySessionPromise[decryptdata.uri];
417+
let keySessionContextPromise = this.keyIdToKeySessionPromise[keyId];
409418
if (!keySessionContextPromise) {
410-
keySessionContextPromise = this.keyUriToKeySessionPromise[
411-
decryptdata.uri
412-
] = this.getKeySystemForKeyPromise(decryptdata).then(
413-
({ keySystem, mediaKeys }) => {
414-
this.throwIfDestroyed();
415-
this.log(
416-
`Handle encrypted media sn: ${data.frag.sn} ${data.frag.type}: ${data.frag.level} using key (method: ${decryptdata.method} format: "${decryptdata.keyFormat}" uri: ${decryptdata.uri})`
417-
);
418-
419-
return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
419+
keySessionContextPromise = this.keyIdToKeySessionPromise[keyId] =
420+
this.getKeySystemForKeyPromise(decryptdata).then(
421+
({ keySystem, mediaKeys }) => {
420422
this.throwIfDestroyed();
421-
const keySessionContext = this.createMediaKeySessionContext({
422-
keySystem,
423-
mediaKeys,
424-
decryptdata,
425-
});
426-
if (this.config.useEmeEncryptedEvent) {
427-
// Use 'encrypted' event initData and type rather than 'cenc' pssh from level-key
428-
return keySessionContext;
429-
}
430-
return this.generateRequestWithPreferredKeySession(
431-
keySessionContext,
432-
'cenc',
433-
decryptdata.pssh
423+
this.log(
424+
`Handle encrypted media sn: ${data.frag.sn} ${data.frag.type}: ${data.frag.level} using key ${keyDetails}`
434425
);
435-
});
436-
}
437-
);
426+
427+
return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
428+
this.throwIfDestroyed();
429+
const keySessionContext = this.createMediaKeySessionContext({
430+
keySystem,
431+
mediaKeys,
432+
decryptdata,
433+
});
434+
if (this.config.useEmeEncryptedEvent) {
435+
// Use 'encrypted' event initData and type rather than 'cenc' pssh from level-key
436+
return keySessionContext;
437+
}
438+
return this.generateRequestWithPreferredKeySession(
439+
keySessionContext,
440+
'cenc',
441+
decryptdata.pssh
442+
);
443+
});
444+
}
445+
);
438446

439447
keySessionContextPromise.catch((error) => this.handleError(error));
440448
}
@@ -468,8 +476,8 @@ class EMEController implements ComponentAPI {
468476
private getKeySystemForKeyPromise(
469477
decryptdata: LevelKey
470478
): Promise<{ keySystem: KeySystems; mediaKeys: MediaKeys }> {
471-
const mediaKeySessionContext =
472-
this.keyUriToKeySessionPromise[decryptdata.uri];
479+
const keyId = this.getKeyIdString(decryptdata);
480+
const mediaKeySessionContext = this.keyIdToKeySessionPromise[keyId];
473481
if (!mediaKeySessionContext) {
474482
const keySystem = keySystemFormatToKeySystemDomain(
475483
decryptdata.keyFormat as KeySystemFormats
@@ -498,9 +506,9 @@ class EMEController implements ComponentAPI {
498506
return;
499507
}
500508

501-
let keySessionContextPromise = this.keyUriToKeySessionPromise.encrypted;
509+
let keySessionContextPromise = this.keyIdToKeySessionPromise.encrypted;
502510
if (!keySessionContextPromise) {
503-
keySessionContextPromise = this.keyUriToKeySessionPromise.encrypted =
511+
keySessionContextPromise = this.keyIdToKeySessionPromise.encrypted =
504512
this.getKeySystemSelectionPromise().then(({ keySystem, mediaKeys }) => {
505513
this.throwIfDestroyed();
506514
const sessionParameters = {
@@ -512,6 +520,7 @@ class EMEController implements ComponentAPI {
512520
keySystem,
513521
mediaKeys,
514522
};
523+
sessionParameters.decryptdata.keyId = new Uint8Array(16);
515524
return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
516525
this.throwIfDestroyed();
517526
const keySessionContext =
@@ -574,10 +583,9 @@ class EMEController implements ComponentAPI {
574583
);
575584
}
576585

586+
const keyId = this.getKeyIdString(context.decryptdata);
577587
this.log(
578-
`Generating key-session request for ${
579-
context.decryptdata?.uri
580-
} (init data type: ${initDataType} length: ${
588+
`Generating key-session request for ${keyId} (init data type: ${initDataType} length: ${
581589
initData ? initData.byteLength : null
582590
})`
583591
);
@@ -649,7 +657,7 @@ class EMEController implements ComponentAPI {
649657
.generateRequest(initDataType, initData)
650658
.then(() => {
651659
this.log(
652-
`Key-session generation succeeded for "${context.mediaKeysSession?.sessionId}" ${context.decryptdata?.uri}`
660+
`Request generated for key-session "${context.mediaKeysSession?.sessionId}" keyId: ${keyId}`
653661
);
654662
return context;
655663
})
@@ -711,7 +719,7 @@ class EMEController implements ComponentAPI {
711719
const keyStatus = mediaKeySessionContext.keyStatus;
712720
if (keyStatus === 'expired') {
713721
this.warn(
714-
`${mediaKeySessionContext.keySystem} expired for key ${mediaKeySessionContext.decryptdata.uri}`
722+
`${mediaKeySessionContext.keySystem} expired for key ${keyId}`
715723
);
716724
this.renewKeySession(mediaKeySessionContext);
717725
}
@@ -726,9 +734,11 @@ class EMEController implements ComponentAPI {
726734
(status: MediaKeyStatus, keyId: BufferSource) => {
727735
this.log(
728736
`key status change "${status}" for keyStatuses keyId: ${Hex.hexDump(
729-
keyId
737+
'buffer' in keyId
738+
? new Uint8Array(keyId.buffer, keyId.byteOffset, keyId.byteLength)
739+
: new Uint8Array(keyId)
730740
)} session keyId: ${Hex.hexDump(
731-
mediaKeySessionContext.decryptdata.keyId || []
741+
new Uint8Array(mediaKeySessionContext.decryptdata.keyId || [])
732742
)} uri: ${mediaKeySessionContext.decryptdata.uri}`
733743
);
734744
mediaKeySessionContext.keyStatus = status;
@@ -1043,7 +1053,7 @@ class EMEController implements ComponentAPI {
10431053
this._requestLicenseFailureCount = 0;
10441054
this.setMediaKeysQueue = [];
10451055
this.mediaKeySessions = [];
1046-
this.keyUriToKeySessionPromise = {};
1056+
this.keyIdToKeySessionPromise = {};
10471057
LevelKey.clearKeyUriToKeyIdMap();
10481058

10491059
// Close all sessions and remove media keys from the video element.

src/utils/hex.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*/
44

55
const Hex = {
6-
hexDump: function (array) {
6+
hexDump: function (array: Uint8Array) {
77
let str = '';
88
for (let i = 0; i < array.length; i++) {
99
let h = array[i].toString(16);

tests/unit/controller/eme-controller.ts

Lines changed: 44 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ const expect = chai.expect;
1616

1717
type EMEControllerTestable = Omit<
1818
EMEController,
19-
'hls' | 'keyUriToKeySessionPromise' | 'mediaKeySessions'
19+
'hls' | 'keyIdToKeySessionPromise' | 'mediaKeySessions'
2020
> & {
2121
hls: HlsMock;
22-
keyUriToKeySessionPromise: {
23-
[keyuri: string]: Promise<MediaKeySessionContext>;
22+
keyIdToKeySessionPromise: {
23+
[keyId: string]: Promise<MediaKeySessionContext>;
2424
};
2525
mediaKeySessions: MediaKeySessionContext[];
2626
onMediaAttached: (
@@ -286,13 +286,11 @@ describe('EMEController', function () {
286286

287287
media.emit('encrypted', badData);
288288

289-
expect(emeController.keyUriToKeySessionPromise.encrypted).to.be.a(
290-
'Promise'
291-
);
292-
if (!emeController.keyUriToKeySessionPromise.encrypted) {
289+
expect(emeController.keyIdToKeySessionPromise.encrypted).to.be.a('Promise');
290+
if (!emeController.keyIdToKeySessionPromise.encrypted) {
293291
return;
294292
}
295-
return emeController.keyUriToKeySessionPromise.encrypted
293+
return emeController.keyIdToKeySessionPromise.encrypted
296294
.catch(() => {})
297295
.finally(() => {
298296
expect(emeController.hls.trigger).callCount(1);
@@ -373,20 +371,24 @@ describe('EMEController', function () {
373371
},
374372
} as any);
375373

376-
expect(emeController.keyUriToKeySessionPromise['data://key-uri']).to.be.a(
377-
'Promise'
378-
);
379-
if (!emeController.keyUriToKeySessionPromise['data://key-uri']) {
374+
expect(
375+
emeController.keyIdToKeySessionPromise['00000000000000000000000000000000']
376+
).to.be.a('Promise');
377+
if (
378+
!emeController.keyIdToKeySessionPromise[
379+
'00000000000000000000000000000000'
380+
]
381+
) {
380382
return;
381383
}
382-
return emeController.keyUriToKeySessionPromise['data://key-uri'].finally(
383-
() => {
384-
expect(mediaKeysSetServerCertificateSpy).to.have.been.calledOnce;
385-
expect(mediaKeysSetServerCertificateSpy).to.have.been.calledWith(
386-
xhrInstance.response
387-
);
388-
}
389-
);
384+
return emeController.keyIdToKeySessionPromise[
385+
'00000000000000000000000000000000'
386+
].finally(() => {
387+
expect(mediaKeysSetServerCertificateSpy).to.have.been.calledOnce;
388+
expect(mediaKeysSetServerCertificateSpy).to.have.been.calledWith(
389+
xhrInstance.response
390+
);
391+
});
390392
});
391393

392394
it('should fetch the server certificate and trigger update failed error', function () {
@@ -444,17 +446,24 @@ describe('EMEController', function () {
444446
encrypted: true,
445447
method: 'SAMPLE-AES',
446448
uri: 'data://key-uri',
449+
keyId: new Uint8Array(16),
447450
},
448451
},
449452
} as any);
450453

451454
expect(
452-
emeController.keyUriToKeySessionPromise['data://key-uri']
453-
).to.not.equal(null);
454-
if (!emeController.keyUriToKeySessionPromise['data://key-uri']) {
455+
emeController.keyIdToKeySessionPromise['00000000000000000000000000000000']
456+
).to.be.a('Promise');
457+
if (
458+
!emeController.keyIdToKeySessionPromise[
459+
'00000000000000000000000000000000'
460+
]
461+
) {
455462
return;
456463
}
457-
return emeController.keyUriToKeySessionPromise['data://key-uri']
464+
return emeController.keyIdToKeySessionPromise[
465+
'00000000000000000000000000000000'
466+
]
458467
.catch(() => {})
459468
.finally(() => {
460469
expect(mediaKeysSetServerCertificateSpy).to.have.been.calledOnce;
@@ -517,17 +526,24 @@ describe('EMEController', function () {
517526
encrypted: true,
518527
method: 'SAMPLE-AES',
519528
uri: 'data://key-uri',
529+
keyId: new Uint8Array(16),
520530
},
521531
},
522532
} as any);
523533

524534
expect(
525-
emeController.keyUriToKeySessionPromise['data://key-uri']
526-
).to.not.equal(null);
527-
if (!emeController.keyUriToKeySessionPromise['data://key-uri']) {
535+
emeController.keyIdToKeySessionPromise['00000000000000000000000000000000']
536+
).to.be.a('Promise');
537+
if (
538+
!emeController.keyIdToKeySessionPromise[
539+
'00000000000000000000000000000000'
540+
]
541+
) {
528542
return;
529543
}
530-
return emeController.keyUriToKeySessionPromise['data://key-uri']
544+
return emeController.keyIdToKeySessionPromise[
545+
'00000000000000000000000000000000'
546+
]
531547
.catch(() => {})
532548
.finally(() => {
533549
expect(emeController.hls.trigger).to.have.been.calledOnce;

0 commit comments

Comments
 (0)