Skip to content

Commit bd8cea9

Browse files
committed
Fix handling of one-to-many KEY URI to Key IDs
#7413
1 parent 5d86f38 commit bd8cea9

File tree

11 files changed

+142
-110
lines changed

11 files changed

+142
-110
lines changed

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

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2985,8 +2985,8 @@ export interface KeyLoadedData {
29852985
// Warning: (ae-missing-release-tag) "KeyLoader" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal)
29862986
//
29872987
// @public (undocumented)
2988-
export class KeyLoader implements ComponentAPI {
2989-
constructor(config: HlsConfig);
2988+
export class KeyLoader extends Logger implements ComponentAPI {
2989+
constructor(config: HlsConfig, logger: ILogger);
29902990
// (undocumented)
29912991
abort(type?: PlaylistLevelType): void;
29922992
// (undocumented)
@@ -3003,10 +3003,6 @@ export class KeyLoader implements ComponentAPI {
30033003
// (undocumented)
30043004
emeController: EMEController | null;
30053005
// (undocumented)
3006-
keyUriToKeyInfo: {
3007-
[keyuri: string]: KeyLoaderInfo | undefined;
3008-
};
3009-
// (undocumented)
30103006
load(frag: Fragment): Promise<KeyLoadedData>;
30113007
// (undocumented)
30123008
loadClear(loadingFrag: Fragment, encryptedFragments: Fragment[], startFragRequested: boolean): null | Promise<void>;

src/controller/eme-controller.ts

Lines changed: 70 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
addEventListener,
1212
removeEventListener,
1313
} from '../utils/event-listener-helper';
14-
import Hex from '../utils/hex';
14+
import { arrayToHex } from '../utils/hex';
1515
import { Logger } from '../utils/logger';
1616
import {
1717
getKeySystemsForConfig,
@@ -44,6 +44,7 @@ import type {
4444
LoaderContext,
4545
} from '../types/loader';
4646
import type { KeySystemFormats } from '../utils/mediakeys-helper';
47+
4748
interface KeySystemAccessPromises {
4849
keySystemAccess: Promise<MediaKeySystemAccess>;
4950
mediaKeys?: Promise<MediaKeys>;
@@ -81,12 +82,12 @@ class EMEController extends Logger implements ComponentAPI {
8182
private media: HTMLMediaElement | null = null;
8283
private keyFormatPromise: Promise<KeySystemFormats> | null = null;
8384
private keySystemAccessPromises: {
84-
[keysystem: string]: KeySystemAccessPromises;
85+
[keysystem: string]: KeySystemAccessPromises | undefined;
8586
} = {};
8687
private _requestLicenseFailureCount: number = 0;
8788
private mediaKeySessions: MediaKeySessionContext[] = [];
8889
private keyIdToKeySessionPromise: {
89-
[keyId: string]: Promise<MediaKeySessionContext>;
90+
[keyId: string]: Promise<MediaKeySessionContext> | undefined;
9091
} = {};
9192
private mediaKeys: MediaKeys | null = null;
9293
private setMediaKeysQueue: Promise<void>[] = EMEController.CDMCleanupPromise
@@ -112,7 +113,7 @@ class EMEController extends Logger implements ComponentAPI {
112113
// @ts-ignore
113114
this.hls = this.config = this.keyIdToKeySessionPromise = null;
114115
// @ts-ignore
115-
this.onWaitingForKey = null;
116+
this.onMediaEncrypted = this.onWaitingForKey = null;
116117
}
117118

118119
private registerListeners() {
@@ -250,8 +251,7 @@ class EMEController extends Logger implements ComponentAPI {
250251
videoCodecs,
251252
this.config.drmSystemOptions || {},
252253
);
253-
const keySystemAccessPromises: KeySystemAccessPromises =
254-
this.keySystemAccessPromises[keySystem];
254+
let keySystemAccessPromises = this.keySystemAccessPromises[keySystem];
255255
let keySystemAccess = keySystemAccessPromises?.keySystemAccess;
256256
if (!keySystemAccess) {
257257
this.log(
@@ -263,10 +263,11 @@ class EMEController extends Logger implements ComponentAPI {
263263
keySystem,
264264
mediaKeySystemConfigs,
265265
);
266-
const keySystemAccessPromises: KeySystemAccessPromises =
267-
(this.keySystemAccessPromises[keySystem] = {
268-
keySystemAccess,
269-
});
266+
const keySystemAccessPromisesNew = (keySystemAccessPromises =
267+
this.keySystemAccessPromises[keySystem] =
268+
{
269+
keySystemAccess,
270+
}) as KeySystemAccessPromises;
270271
keySystemAccess.catch((error) => {
271272
this.log(
272273
`Failed to obtain access to key-system "${keySystem}": ${error}`,
@@ -280,11 +281,10 @@ class EMEController extends Logger implements ComponentAPI {
280281
const certificateRequest = this.fetchServerCertificate(keySystem);
281282

282283
this.log(`Create media-keys for "${keySystem}"`);
283-
keySystemAccessPromises.mediaKeys = mediaKeySystemAccess
284-
.createMediaKeys()
285-
.then((mediaKeys) => {
284+
const mediaKeys = (keySystemAccessPromisesNew.mediaKeys =
285+
mediaKeySystemAccess.createMediaKeys().then((mediaKeys) => {
286286
this.log(`Media-keys created for "${keySystem}"`);
287-
keySystemAccessPromises.hasMediaKeys = true;
287+
keySystemAccessPromisesNew.hasMediaKeys = true;
288288
return certificateRequest.then((certificate) => {
289289
if (certificate) {
290290
return this.setMediaKeysServerCertificate(
@@ -295,18 +295,18 @@ class EMEController extends Logger implements ComponentAPI {
295295
}
296296
return mediaKeys;
297297
});
298-
});
298+
}));
299299

300-
keySystemAccessPromises.mediaKeys.catch((error) => {
300+
mediaKeys.catch((error) => {
301301
this.error(
302302
`Failed to create media-keys for "${keySystem}"}: ${error}`,
303303
);
304304
});
305305

306-
return keySystemAccessPromises.mediaKeys;
306+
return mediaKeys;
307307
});
308308
}
309-
return keySystemAccess.then(() => keySystemAccessPromises.mediaKeys!);
309+
return keySystemAccess.then(() => keySystemAccessPromises!.mediaKeys!);
310310
}
311311

312312
private createMediaKeySessionContext({
@@ -319,8 +319,8 @@ class EMEController extends Logger implements ComponentAPI {
319319
mediaKeys: MediaKeys;
320320
}): MediaKeySessionContext {
321321
this.log(
322-
`Creating key-system session "${keySystem}" keyId: ${Hex.hexDump(
323-
decryptdata.keyId! || [],
322+
`Creating key-system session "${keySystem}" keyId: ${arrayToHex(
323+
decryptdata.keyId || ([] as number[]),
324324
)}`,
325325
);
326326

@@ -367,7 +367,7 @@ class EMEController extends Logger implements ComponentAPI {
367367
if (decryptdata.keyId === null) {
368368
throw new Error('keyId is null');
369369
}
370-
return Hex.hexDump(decryptdata.keyId);
370+
return arrayToHex(decryptdata.keyId);
371371
}
372372

373373
private updateKeySession(
@@ -376,10 +376,10 @@ class EMEController extends Logger implements ComponentAPI {
376376
): Promise<void> {
377377
const keySession = mediaKeySessionContext.mediaKeysSession;
378378
this.log(
379-
`Updating key-session "${keySession.sessionId}" for keyID ${Hex.hexDump(
380-
mediaKeySessionContext.decryptdata?.keyId! || [],
379+
`Updating key-session "${keySession.sessionId}" for keyID ${arrayToHex(
380+
mediaKeySessionContext.decryptdata.keyId || [],
381381
)}
382-
} (data length: ${data ? data.byteLength : data})`,
382+
} (data length: ${data.byteLength})`,
383383
);
384384
return keySession.update(data);
385385
}
@@ -388,7 +388,7 @@ class EMEController extends Logger implements ComponentAPI {
388388
return (Object.keys(this.keySystemAccessPromises) as KeySystems[])
389389
.map((keySystem) => ({
390390
keySystem,
391-
hasMediaKeys: this.keySystemAccessPromises[keySystem].hasMediaKeys,
391+
hasMediaKeys: this.keySystemAccessPromises[keySystem]!.hasMediaKeys,
392392
}))
393393
.filter(({ hasMediaKeys }) => !!hasMediaKeys)
394394
.map(({ keySystem }) => keySystemDomainToKeySystemFormat(keySystem))
@@ -455,7 +455,7 @@ class EMEController extends Logger implements ComponentAPI {
455455
const badStatus = this.bannedKeyIds[keyId];
456456
if (badStatus) {
457457
const error = getKeyStatusError(badStatus, decryptdata);
458-
this.handleError(error);
458+
this.handleError(error, data.frag);
459459
return Promise.reject(error);
460460
}
461461
const keyDetails = `(keyId: ${keyId} format: "${decryptdata.keyFormat}" method: ${decryptdata.method} uri: ${decryptdata.uri})`;
@@ -493,7 +493,9 @@ class EMEController extends Logger implements ComponentAPI {
493493
);
494494
});
495495

496-
keySessionContextPromise.catch((error) => this.handleError(error));
496+
keySessionContextPromise.catch((error) =>
497+
this.handleError(error, data.frag),
498+
);
497499
this.keyIdToKeySessionPromise[keyId] = keySessionContextPromise;
498500

499501
return keySessionContextPromise;
@@ -503,17 +505,20 @@ class EMEController extends Logger implements ComponentAPI {
503505
}
504506

505507
private throwIfDestroyed(message = 'Invalid state'): void | never {
506-
if (!this.hls) {
508+
if (!this.hls as any) {
507509
throw new Error('invalid state');
508510
}
509511
}
510512

511-
private handleError(error: EMEKeyError | Error) {
512-
if (!this.hls) {
513+
private handleError(error: EMEKeyError | Error, frag?: Fragment) {
514+
if (!this.hls as any) {
513515
return;
514516
}
515517
this.error(error.message);
516518
if (error instanceof EMEKeyError) {
519+
if (frag) {
520+
error.data.frag = frag;
521+
}
517522
this.hls.trigger(Events.ERROR, error.data);
518523
} else {
519524
this.hls.trigger(Events.ERROR, {
@@ -613,7 +618,7 @@ class EMEController extends Logger implements ComponentAPI {
613618
return;
614619
}
615620

616-
const keyIdHex = Hex.hexDump(keyId);
621+
const keyIdHex = arrayToHex(keyId);
617622
const { keyIdToKeySessionPromise, mediaKeySessions } = this;
618623
let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex];
619624

@@ -624,7 +629,7 @@ class EMEController extends Logger implements ComponentAPI {
624629
if (!decryptdata.keyId) {
625630
continue;
626631
}
627-
const oldKeyIdHex = Hex.hexDump(decryptdata.keyId);
632+
const oldKeyIdHex = arrayToHex(decryptdata.keyId);
628633
if (
629634
keyIdHex === oldKeyIdHex ||
630635
decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1
@@ -726,7 +731,7 @@ class EMEController extends Logger implements ComponentAPI {
726731
context.decryptdata.pssh = initData ? new Uint8Array(initData) : null;
727732
} catch (error) {
728733
this.warn(error.message);
729-
if (this.hls?.config.debug) {
734+
if ((this.hls as any) && this.hls.config.debug) {
730735
throw error;
731736
}
732737
}
@@ -740,15 +745,15 @@ class EMEController extends Logger implements ComponentAPI {
740745
const keyId = this.getKeyIdString(context.decryptdata);
741746
this.log(
742747
`Generating key-session request for "${reason}": ${keyId} (init data type: ${initDataType} length: ${
743-
initData ? initData.byteLength : null
748+
initData.byteLength
744749
})`,
745750
);
746751

747752
const licenseStatus = new EventEmitter();
748753

749754
const onmessage = (context._onmessage = (event: MediaKeyMessageEvent) => {
750755
const keySession = context.mediaKeysSession;
751-
if (!keySession) {
756+
if (!keySession as any) {
752757
licenseStatus.emit('error', new Error('invalid state'));
753758
return;
754759
}
@@ -781,7 +786,7 @@ class EMEController extends Logger implements ComponentAPI {
781786
event: Event,
782787
) => {
783788
const keySession = context.mediaKeysSession;
784-
if (!keySession) {
789+
if (!keySession as any) {
785790
licenseStatus.emit('error', new Error('invalid state'));
786791
return;
787792
}
@@ -841,7 +846,7 @@ class EMEController extends Logger implements ComponentAPI {
841846
.generateRequest(initDataType, initData)
842847
.then(() => {
843848
this.log(
844-
`Request generated for key-session "${context.mediaKeysSession?.sessionId}" keyId: ${keyId}`,
849+
`Request generated for key-session "${context.mediaKeysSession.sessionId}" keyId: ${keyId}`,
845850
);
846851
})
847852
.catch((error) => {
@@ -872,7 +877,7 @@ class EMEController extends Logger implements ComponentAPI {
872877
mediaKeySessionContext: MediaKeySessionContext,
873878
licenseStatus: EventEmitter<string | symbol, any>,
874879
) {
875-
const sessionLevelKeyId = Hex.hexDump(
880+
const sessionLevelKeyId = arrayToHex(
876881
new Uint8Array(mediaKeySessionContext.decryptdata.keyId || []),
877882
);
878883
mediaKeySessionContext.mediaKeysSession.keyStatuses.forEach(
@@ -884,7 +889,7 @@ class EMEController extends Logger implements ComponentAPI {
884889
keyId = status;
885890
status = temp;
886891
}
887-
const keyIdWithStatusChange = Hex.hexDump(
892+
const keyIdWithStatusChange = arrayToHex(
888893
'buffer' in keyId
889894
? new Uint8Array(keyId.buffer, keyId.byteOffset, keyId.byteLength)
890895
: new Uint8Array(keyId),
@@ -992,7 +997,7 @@ class EMEController extends Logger implements ComponentAPI {
992997
this.log(
993998
`setServerCertificate ${
994999
success ? 'success' : 'not supported by CDM'
995-
} (${cert?.byteLength}) on "${keySystem}"`,
1000+
} (${cert.byteLength}) on "${keySystem}"`,
9961001
);
9971002
resolve(mediaKeys);
9981003
})
@@ -1102,7 +1107,7 @@ class EMEController extends Logger implements ComponentAPI {
11021107

11031108
return Promise.resolve()
11041109
.then(() => {
1105-
if (!keysListItem.decryptdata) {
1110+
if (!keysListItem.decryptdata as any) {
11061111
throw new Error('Key removed');
11071112
}
11081113
return licenseXhrSetup.call(
@@ -1114,7 +1119,7 @@ class EMEController extends Logger implements ComponentAPI {
11141119
);
11151120
})
11161121
.catch((error: Error) => {
1117-
if (!keysListItem.decryptdata) {
1122+
if (!keysListItem.decryptdata as any) {
11181123
// Key session removed. Cancel license request.
11191124
throw error;
11201125
}
@@ -1152,7 +1157,10 @@ class EMEController extends Logger implements ComponentAPI {
11521157
const xhr = new XMLHttpRequest();
11531158
xhr.responseType = 'arraybuffer';
11541159
xhr.onreadystatechange = () => {
1155-
if (!this.hls || !keySessionContext.mediaKeysSession) {
1160+
if (
1161+
(!this.hls as any) ||
1162+
(!keySessionContext.mediaKeysSession as any)
1163+
) {
11561164
return reject(new Error('invalid state'));
11571165
}
11581166
if (xhr.readyState === 4) {
@@ -1295,20 +1303,24 @@ class EMEController extends Logger implements ComponentAPI {
12951303
this.removeSession(mediaKeySessionContext),
12961304
)
12971305
.concat(
1298-
media?.setMediaKeys(null)?.catch((error) => {
1299-
this.log(`Could not clear media keys: ${error}`);
1300-
this.hls?.trigger(Events.ERROR, {
1301-
type: ErrorTypes.OTHER_ERROR,
1302-
details: ErrorDetails.KEY_SYSTEM_DESTROY_MEDIA_KEYS_ERROR,
1303-
fatal: false,
1304-
error: new Error(`Could not clear media keys: ${error}`),
1305-
});
1306-
}),
1306+
(media?.setMediaKeys(null) as Promise<void> | null)?.catch(
1307+
(error) => {
1308+
this.log(`Could not clear media keys: ${error}`);
1309+
if (!this.hls as any) return;
1310+
this.hls.trigger(Events.ERROR, {
1311+
type: ErrorTypes.OTHER_ERROR,
1312+
details: ErrorDetails.KEY_SYSTEM_DESTROY_MEDIA_KEYS_ERROR,
1313+
fatal: false,
1314+
error: new Error(`Could not clear media keys: ${error}`),
1315+
});
1316+
},
1317+
),
13071318
),
13081319
)
13091320
.catch((error) => {
13101321
this.log(`Could not close sessions and clear media keys: ${error}`);
1311-
this.hls?.trigger(Events.ERROR, {
1322+
if (!this.hls as any) return;
1323+
this.hls.trigger(Events.ERROR, {
13121324
type: ErrorTypes.OTHER_ERROR,
13131325
details: ErrorDetails.KEY_SYSTEM_DESTROY_CLOSE_SESSION_ERROR,
13141326
fatal: false,
@@ -1360,7 +1372,7 @@ class EMEController extends Logger implements ComponentAPI {
13601372
mediaKeySessionContext: MediaKeySessionContext,
13611373
): Promise<void> | void {
13621374
const { mediaKeysSession, licenseXhr } = mediaKeySessionContext;
1363-
if (mediaKeysSession) {
1375+
if (mediaKeysSession as any) {
13641376
this.log(
13651377
`Remove licenses and keys and close session ${mediaKeysSession.sessionId}`,
13661378
);
@@ -1403,7 +1415,8 @@ class EMEController extends Logger implements ComponentAPI {
14031415
return removePromise
14041416
.catch((error) => {
14051417
this.log(`Could not remove session: ${error}`);
1406-
this.hls?.trigger(Events.ERROR, {
1418+
if (!this.hls as any) return;
1419+
this.hls.trigger(Events.ERROR, {
14071420
type: ErrorTypes.OTHER_ERROR,
14081421
details: ErrorDetails.KEY_SYSTEM_DESTROY_REMOVE_SESSION_ERROR,
14091422
fatal: false,
@@ -1415,7 +1428,8 @@ class EMEController extends Logger implements ComponentAPI {
14151428
})
14161429
.catch((error) => {
14171430
this.log(`Could not close session: ${error}`);
1418-
this.hls?.trigger(Events.ERROR, {
1431+
if (!this.hls as any) return;
1432+
this.hls.trigger(Events.ERROR, {
14191433
type: ErrorTypes.OTHER_ERROR,
14201434
details: ErrorDetails.KEY_SYSTEM_DESTROY_CLOSE_SESSION_ERROR,
14211435
fatal: false,

0 commit comments

Comments
 (0)