Skip to content

Commit d46bf0e

Browse files
committed
Fix FairPlay Streaming "encrypted" event handling regression
Fixes regression introduced in v1.6.7 with #7380
1 parent 8d1053c commit d46bf0e

File tree

1 file changed

+103
-0
lines changed

1 file changed

+103
-0
lines changed

src/controller/eme-controller.ts

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import {
2222
KeySystems,
2323
requestMediaKeySystemAccess,
2424
} from '../utils/mediakeys-helper';
25+
import { bin2str, parseSinf } from '../utils/mp4-tools';
26+
import { base64Decode } from '../utils/numeric-encoding-utils';
2527
import { stringify } from '../utils/safe-json-stringify';
2628
import { strToUtf8array } from '../utils/utf8-utils';
2729
import type { EMEControllerConfig, HlsConfig, LoadPolicy } from '../config';
@@ -552,6 +554,105 @@ class EMEController extends Logger implements ComponentAPI {
552554
return this.attemptKeySystemAccess(keySystemsToAttempt);
553555
}
554556

557+
private onMediaEncrypted = (event: MediaEncryptedEvent) => {
558+
const { initDataType, initData } = event;
559+
const logMessage = `"${event.type}" event: init data type: "${initDataType}"`;
560+
this.debug(logMessage);
561+
562+
// Ignore event when initData is null
563+
if (initData === null) {
564+
return;
565+
}
566+
567+
if (!this.keyFormatPromise) {
568+
let keySystems = Object.keys(
569+
this.keySystemAccessPromises,
570+
) as KeySystems[];
571+
if (!keySystems.length) {
572+
keySystems = getKeySystemsForConfig(this.config);
573+
}
574+
const keyFormats = keySystems
575+
.map(keySystemDomainToKeySystemFormat)
576+
.filter((k) => !!k) as KeySystemFormats[];
577+
this.keyFormatPromise = this.getKeyFormatPromise(keyFormats);
578+
}
579+
580+
this.keyFormatPromise.then((keySystemFormat) => {
581+
const keySystem = keySystemFormatToKeySystemDomain(keySystemFormat);
582+
if (initDataType !== 'sinf' || keySystem !== KeySystems.FAIRPLAY) {
583+
this.log(
584+
`Ignoring "${event.type}" event with init data type: "${initDataType}" for selected key-system ${keySystem}`,
585+
);
586+
return;
587+
}
588+
589+
// Match sinf keyId to playlist skd://keyId=
590+
let keyId: Uint8Array<ArrayBuffer> | undefined;
591+
try {
592+
const json = bin2str(new Uint8Array(initData));
593+
const sinf = base64Decode(JSON.parse(json).sinf);
594+
const tenc = parseSinf(sinf);
595+
if (!tenc) {
596+
throw new Error(
597+
`'schm' box missing or not cbcs/cenc with schi > tenc`,
598+
);
599+
}
600+
keyId = new Uint8Array(tenc.subarray(8, 24));
601+
} catch (error) {
602+
this.warn(`${logMessage} Failed to parse sinf: ${error}`);
603+
return;
604+
}
605+
606+
const keyIdHex = Hex.hexDump(keyId);
607+
const { keyIdToKeySessionPromise, mediaKeySessions } = this;
608+
let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex];
609+
610+
for (let i = 0; i < mediaKeySessions.length; i++) {
611+
// Match playlist key
612+
const keyContext = mediaKeySessions[i];
613+
const decryptdata = keyContext.decryptdata;
614+
if (!decryptdata.keyId) {
615+
continue;
616+
}
617+
const oldKeyIdHex = Hex.hexDump(decryptdata.keyId);
618+
if (
619+
keyIdHex === oldKeyIdHex ||
620+
decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1
621+
) {
622+
keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex];
623+
if (!keySessionContextPromise) {
624+
continue;
625+
}
626+
if (decryptdata.pssh) {
627+
break;
628+
}
629+
delete keyIdToKeySessionPromise[oldKeyIdHex];
630+
decryptdata.pssh = new Uint8Array(initData);
631+
decryptdata.keyId = keyId;
632+
keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] =
633+
keySessionContextPromise.then(() => {
634+
return this.generateRequestWithPreferredKeySession(
635+
keyContext,
636+
initDataType,
637+
initData,
638+
'encrypted-event-key-match',
639+
);
640+
});
641+
keySessionContextPromise.catch((error) => this.handleError(error));
642+
break;
643+
}
644+
}
645+
646+
if (!keySessionContextPromise) {
647+
this.handleError(
648+
new Error(
649+
`Key ID ${keyIdHex} not encountered in playlist. Key-system sessions ${mediaKeySessions.length}.`,
650+
),
651+
);
652+
}
653+
});
654+
};
655+
555656
private onWaitingForKey = (event: Event) => {
556657
this.log(`"${event.type}" event`);
557658
};
@@ -1130,13 +1231,15 @@ class EMEController extends Logger implements ComponentAPI {
11301231
// keep reference of media
11311232
this.media = media;
11321233

1234+
addEventListener(media, 'encrypted', this.onMediaEncrypted);
11331235
addEventListener(media, 'waitingforkey', this.onWaitingForKey);
11341236
}
11351237

11361238
private onMediaDetached() {
11371239
const media = this.media;
11381240

11391241
if (media) {
1242+
removeEventListener(media, 'encrypted', this.onMediaEncrypted);
11401243
removeEventListener(media, 'waitingforkey', this.onWaitingForKey);
11411244
this.media = null;
11421245
this.mediaKeys = null;

0 commit comments

Comments
 (0)