Skip to content

Commit 7a089f0

Browse files
committed
Fix level removal on key status error and optimize key-bytes comparison
Add "FIXME" comments for future MediaKeySessionContext multi-key handling improvements
1 parent bd8cea9 commit 7a089f0

File tree

7 files changed

+43
-33
lines changed

7 files changed

+43
-33
lines changed

src/controller/base-stream-controller.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2038,8 +2038,8 @@ export default class BaseStreamController
20382038
}
20392039

20402040
protected resetWhenMissingContext(chunkMeta: ChunkMetadata | Fragment) {
2041-
this.warn(
2042-
`The loading context changed while buffering fragment ${chunkMeta.sn} of ${this.playlistLabel()} ${chunkMeta.level}. This chunk will not be buffered.`,
2041+
this.log(
2042+
`Loading context changed while buffering sn ${chunkMeta.sn} of ${this.playlistLabel()} ${chunkMeta.level === -1 ? '<removed>' : chunkMeta.level}. This chunk will not be buffered.`,
20432043
);
20442044
this.removeUnbufferedFrags();
20452045
this.resetStartWhenNotLoaded(this.levelLastLoaded);

src/controller/eme-controller.ts

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,9 @@ interface KeySystemAccessPromises {
5555
export interface MediaKeySessionContext {
5656
keySystem: KeySystems;
5757
mediaKeys: MediaKeys;
58-
decryptdata: LevelKey;
58+
decryptdata: LevelKey; // FIXME: LevelKey has a URI which should be bound to the session, but is dependent one KeyId specifically. Session context should be allowed to adopt multiple level keys.
5959
mediaKeysSession: MediaKeySession;
60-
keyStatus: MediaKeyStatus;
60+
keyStatus: MediaKeyStatus; // FIXME: MediaKeySession can manage multiple keys with each with its own status
6161
licenseXhr?: XMLHttpRequest;
6262
_onmessage?: (this: MediaKeySession, ev: MediaKeyMessageEvent) => any;
6363
_onkeystatuseschange?: (this: MediaKeySession, ev: Event) => any;
@@ -376,7 +376,7 @@ 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 ${arrayToHex(
379+
`Updating key-session "${keySession.sessionId}" for keyId ${arrayToHex(
380380
mediaKeySessionContext.decryptdata.keyId || [],
381381
)}
382382
} (data length: ${data.byteLength})`,
@@ -791,7 +791,7 @@ class EMEController extends Logger implements ComponentAPI {
791791
return;
792792
}
793793
const initialStatus = context.keyStatus;
794-
this.onKeyStatusChange(context, licenseStatus);
794+
this.onKeyStatusChange(context);
795795
const status = context.keyStatus;
796796
if (status !== initialStatus) {
797797
licenseStatus.emit('keyStatus', status, context);
@@ -873,10 +873,7 @@ class EMEController extends Logger implements ComponentAPI {
873873
});
874874
}
875875

876-
private onKeyStatusChange(
877-
mediaKeySessionContext: MediaKeySessionContext,
878-
licenseStatus: EventEmitter<string | symbol, any>,
879-
) {
876+
private onKeyStatusChange(mediaKeySessionContext: MediaKeySessionContext) {
880877
const sessionLevelKeyId = arrayToHex(
881878
new Uint8Array(mediaKeySessionContext.decryptdata.keyId || []),
882879
);
@@ -1371,10 +1368,11 @@ class EMEController extends Logger implements ComponentAPI {
13711368
private removeSession(
13721369
mediaKeySessionContext: MediaKeySessionContext,
13731370
): Promise<void> | void {
1374-
const { mediaKeysSession, licenseXhr } = mediaKeySessionContext;
1375-
if (mediaKeysSession as any) {
1371+
const { mediaKeysSession, licenseXhr, decryptdata } =
1372+
mediaKeySessionContext;
1373+
if (mediaKeysSession as MediaKeySession | undefined) {
13761374
this.log(
1377-
`Remove licenses and keys and close session ${mediaKeysSession.sessionId}`,
1375+
`Remove licenses and keys and close session "${mediaKeysSession.sessionId}" keyId: ${arrayToHex((decryptdata as LevelKey | undefined)?.keyId || [])}`,
13781376
);
13791377
if (mediaKeySessionContext._onmessage) {
13801378
mediaKeysSession.removeEventListener(

src/controller/error-controller.ts

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -496,7 +496,7 @@ export default class ErrorController
496496
return;
497497
}
498498
const { flags } = errorAction;
499-
let nextAutoLevel = errorAction.nextAutoLevel;
499+
const nextAutoLevel = errorAction.nextAutoLevel;
500500

501501
switch (flags) {
502502
case ErrorActionFlags.None:
@@ -526,27 +526,20 @@ export default class ErrorController
526526
if (levelKey) {
527527
// Penalize all levels with key
528528
const levels = this.hls.levels;
529-
let levelIndex = hls.loadLevel;
530-
const removeLevels: number[] = [];
531529
for (let i = levels.length; i--; ) {
532530
if (this.variantHasKey(levels[i], levelKey)) {
533531
this.log(
534-
`Banned key found in level ${i} or audio group "${levels[i].audioGroups?.join(',')}" (${data.frag?.type} fragment) ${arrayToHex(levelKey.keyId || [])}`,
532+
`Banned key found in level ${i} (${levels[i].bitrate}bps) or audio group "${levels[i].audioGroups?.join(',')}" (${data.frag?.type} fragment) ${arrayToHex(levelKey.keyId || [])}`,
535533
);
536534
levels[i].fragmentError++;
537535
levels[i].loadError++;
538-
levelIndex = i;
539-
removeLevels.unshift(i);
540-
}
541-
}
542-
const switchAction = this.getLevelSwitchAction(data, levelIndex);
543-
nextAutoLevel = switchAction.nextAutoLevel;
544-
for (let i = removeLevels.length; i--; ) {
545-
if (nextAutoLevel !== i) {
546536
this.log(`Removing level ${i} with key error (${data.error})`);
547537
this.hls.removeLevel(i);
548538
}
549539
}
540+
if (levels.length) {
541+
errorAction.resolved = true;
542+
}
550543
}
551544
break;
552545
}

src/loader/key-loader.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -206,10 +206,6 @@ export default class KeyLoader extends Logger implements ComponentAPI {
206206
// Return the correct fragment with updated decryptdata key and loaded keyInfo
207207
const { keyInfo } = keyLoadedData;
208208
decryptdata.key = keyInfo.decryptdata.key;
209-
// log key-status unblocking change for debugging only
210-
// this.log(
211-
// `${frag.type} ${frag.level} key ${arrayToHex(decryptdata.keyId || [])} "${keyStatus}" session key ${keyInfo.decryptdata.keyId ? arrayToHex(keyInfo.decryptdata.keyId) : null}`,
212-
// );
213209
return { frag, keyInfo };
214210
});
215211
}

src/loader/level-details.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ export class LevelDetails {
9797
frag.setKeyFormat(levelKey.keyFormat as KeySystemFormats);
9898
decryptdata = frag.decryptdata;
9999
}
100-
return decryptdata && levelKey.matches(decryptdata);
100+
return !!decryptdata && levelKey.matches(decryptdata);
101101
});
102102
}
103103

src/loader/level-key.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { arrayValuesMatch, optionalArrayValuesMatch } from '../utils/arrays';
12
import { isFullSegmentEncryption } from '../utils/encryption-methods-util';
23
import { hexToArrayBuffer } from '../utils/hex';
34
import { convertDataUriToArrayBytes } from '../utils/keysystem-util';
@@ -63,9 +64,9 @@ export class LevelKey implements DecryptData {
6364
key.method === this.method &&
6465
key.encrypted === this.encrypted &&
6566
key.keyFormat === this.keyFormat &&
66-
key.keyFormatVersions.join(',') === this.keyFormatVersions.join(',') &&
67-
key.iv?.join(',') === this.iv?.join(',') &&
68-
key.keyId?.join(',') === this.keyId?.join(',')
67+
arrayValuesMatch(key.keyFormatVersions, this.keyFormatVersions) &&
68+
optionalArrayValuesMatch(key.iv, this.iv) &&
69+
optionalArrayValuesMatch(key.keyId, this.keyId)
6970
);
7071
}
7172

src/utils/arrays.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
export function arrayValuesMatch(
2+
a: (string | number)[] | Uint8Array,
3+
b: (string | number)[] | Uint8Array,
4+
): boolean {
5+
if (a.length === b.length) {
6+
return !a.some((value: string | number, i: number) => value !== b[i]);
7+
}
8+
return false;
9+
}
10+
11+
export function optionalArrayValuesMatch(
12+
a: (string | number)[] | Uint8Array | null | undefined,
13+
b: (string | number)[] | Uint8Array | null | undefined,
14+
): boolean {
15+
if (!a && !b) {
16+
return true;
17+
}
18+
if (!a || !b) {
19+
return false;
20+
}
21+
return arrayValuesMatch(a, b);
22+
}

0 commit comments

Comments
 (0)