hls.js 1.6.0-beta.2.0.canary.10876 → 1.6.0-beta.2.0.canary.10878

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -535,147 +535,185 @@ class EMEController extends Logger implements ComponentAPI {
535
535
  return;
536
536
  }
537
537
 
538
- let keyId: Uint8Array | null | undefined;
539
- let keySystemDomain: KeySystems | undefined;
540
-
541
- if (
542
- initDataType === 'sinf' &&
543
- this.getLicenseServerUrl(KeySystems.FAIRPLAY)
544
- ) {
545
- // Match sinf keyId to playlist skd://keyId=
546
- const json = bin2str(new Uint8Array(initData));
547
- try {
548
- const sinf = base64Decode(JSON.parse(json).sinf);
549
- const tenc = parseSinf(sinf);
550
- if (!tenc) {
551
- throw new Error(
552
- `'schm' box missing or not cbcs/cenc with schi > tenc`,
553
- );
554
- }
555
- keyId = tenc.subarray(8, 24);
556
- keySystemDomain = KeySystems.FAIRPLAY;
557
- } catch (error) {
558
- this.warn(`${logMessage} Failed to parse sinf: ${error}`);
559
- return;
560
- }
561
- } else if (this.getLicenseServerUrl(KeySystems.WIDEVINE)) {
562
- // Support Widevine clear-lead key-session creation (otherwise depend on playlist keys)
563
- const psshResults = parseMultiPssh(initData);
564
-
565
- // TODO: If using keySystemAccessPromises we might want to wait until one is resolved
538
+ if (!this.keyFormatPromise) {
566
539
  let keySystems = Object.keys(
567
540
  this.keySystemAccessPromises,
568
541
  ) as KeySystems[];
569
542
  if (!keySystems.length) {
570
543
  keySystems = getKeySystemsForConfig(this.config);
571
544
  }
545
+ const keyFormats = keySystems
546
+ .map(keySystemToKeySystemFormat)
547
+ .filter((k) => !!k) as KeySystemFormats[];
548
+ this.keyFormatPromise = this.getKeyFormatPromise(keyFormats);
549
+ }
572
550
 
573
- const psshInfo = psshResults.filter((pssh): pssh is PsshData => {
574
- const keySystem = pssh.systemId
575
- ? keySystemIdToKeySystemDomain(pssh.systemId)
576
- : null;
577
- return keySystem ? keySystems.indexOf(keySystem) > -1 : false;
578
- })[0];
551
+ this.keyFormatPromise.then((keySystemFormat) => {
552
+ const keySystem = keySystemFormatToKeySystemDomain(keySystemFormat);
579
553
 
580
- if (!psshInfo) {
554
+ let keyId: Uint8Array | null | undefined;
555
+ let keySystemDomain: KeySystems | undefined;
556
+
557
+ if (initDataType === 'sinf') {
558
+ if (keySystem !== KeySystems.FAIRPLAY) {
559
+ this.warn(
560
+ `Ignoring unexpected "${event.type}" event with init data type: "${initDataType}" for selected key-system ${keySystem}`,
561
+ );
562
+ return;
563
+ }
564
+ // Match sinf keyId to playlist skd://keyId=
565
+ const json = bin2str(new Uint8Array(initData));
566
+ try {
567
+ const sinf = base64Decode(JSON.parse(json).sinf);
568
+ const tenc = parseSinf(sinf);
569
+ if (!tenc) {
570
+ throw new Error(
571
+ `'schm' box missing or not cbcs/cenc with schi > tenc`,
572
+ );
573
+ }
574
+ keyId = tenc.subarray(8, 24);
575
+ keySystemDomain = KeySystems.FAIRPLAY;
576
+ } catch (error) {
577
+ this.warn(`${logMessage} Failed to parse sinf: ${error}`);
578
+ return;
579
+ }
580
+ } else {
581
581
  if (
582
- psshResults.length === 0 ||
583
- psshResults.some((pssh): pssh is PsshInvalidResult => !pssh.systemId)
582
+ keySystem !== KeySystems.WIDEVINE &&
583
+ keySystem !== KeySystems.PLAYREADY
584
584
  ) {
585
- this.warn(`${logMessage} contains incomplete or invalid pssh data`);
586
- } else {
587
- this.log(
588
- `ignoring ${logMessage} for ${(psshResults as PsshData[])
589
- .map((pssh) => keySystemIdToKeySystemDomain(pssh.systemId))
590
- .join(',')} pssh data in favor of playlist keys`,
585
+ this.warn(
586
+ `Ignoring unexpected "${event.type}" event with init data type: "${initDataType}" for selected key-system ${keySystem}`,
591
587
  );
588
+ return;
592
589
  }
593
- return;
594
- }
590
+ // Support Widevine/PlayReady clear-lead key-session creation (otherwise depend on playlist keys)
591
+ const psshResults = parseMultiPssh(initData);
595
592
 
596
- keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId);
597
- if (psshInfo.version === 0 && psshInfo.data) {
598
- if (keySystemDomain === KeySystems.WIDEVINE) {
599
- const offset = psshInfo.data.length - 22;
600
- keyId = psshInfo.data.subarray(offset, offset + 16);
601
- } else if (keySystemDomain === KeySystems.PLAYREADY) {
602
- keyId = parsePlayReadyWRM(psshInfo.data);
593
+ const psshInfos = psshResults.filter(
594
+ (pssh): pssh is PsshData =>
595
+ !!pssh.systemId &&
596
+ keySystemIdToKeySystemDomain(pssh.systemId) === keySystem,
597
+ );
598
+
599
+ if (psshInfos.length > 1) {
600
+ this.warn(
601
+ `${logMessage} Using first of ${psshInfos.length} pssh found for selected key-system ${keySystem}`,
602
+ );
603
603
  }
604
- }
605
- }
606
604
 
607
- if (!keySystemDomain || !keyId) {
608
- return;
609
- }
605
+ const psshInfo = psshInfos[0];
610
606
 
611
- const keyIdHex = Hex.hexDump(keyId);
612
- const { keyIdToKeySessionPromise, mediaKeySessions } = this;
607
+ if (!psshInfo) {
608
+ if (
609
+ psshResults.length === 0 ||
610
+ psshResults.some(
611
+ (pssh): pssh is PsshInvalidResult => !pssh.systemId,
612
+ )
613
+ ) {
614
+ this.warn(`${logMessage} contains incomplete or invalid pssh data`);
615
+ } else {
616
+ this.log(
617
+ `ignoring ${logMessage} for ${(psshResults as PsshData[])
618
+ .map((pssh) => keySystemIdToKeySystemDomain(pssh.systemId))
619
+ .join(',')} pssh data in favor of playlist keys`,
620
+ );
621
+ }
622
+ return;
623
+ }
613
624
 
614
- let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex];
615
- for (let i = 0; i < mediaKeySessions.length; i++) {
616
- // Match playlist key
617
- const keyContext = mediaKeySessions[i];
618
- const decryptdata = keyContext.decryptdata;
619
- if (!decryptdata.keyId) {
620
- continue;
621
- }
622
- const oldKeyIdHex = Hex.hexDump(decryptdata.keyId);
623
- if (
624
- keyIdHex === oldKeyIdHex ||
625
- decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1
626
- ) {
627
- keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex];
628
- if (decryptdata.pssh) {
629
- break;
625
+ keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId);
626
+ if (psshInfo.version === 0 && psshInfo.data) {
627
+ if (keySystemDomain === KeySystems.WIDEVINE) {
628
+ const offset = psshInfo.data.length - 22;
629
+ keyId = psshInfo.data.subarray(offset, offset + 16);
630
+ } else if (keySystemDomain === KeySystems.PLAYREADY) {
631
+ keyId = parsePlayReadyWRM(psshInfo.data);
632
+ }
630
633
  }
631
- delete keyIdToKeySessionPromise[oldKeyIdHex];
632
- decryptdata.pssh = new Uint8Array(initData);
633
- decryptdata.keyId = keyId;
634
- keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] =
635
- keySessionContextPromise.then(() => {
636
- return this.generateRequestWithPreferredKeySession(
637
- keyContext,
638
- initDataType,
639
- initData,
640
- 'encrypted-event-key-match',
641
- );
642
- });
643
- keySessionContextPromise.catch((error) => this.handleError(error));
644
- break;
645
634
  }
646
- }
647
635
 
648
- if (!keySessionContextPromise) {
649
- // Clear-lead key (not encountered in playlist)
650
- keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] =
651
- this.getKeySystemSelectionPromise([keySystemDomain]).then(
652
- ({ keySystem, mediaKeys }) => {
653
- this.throwIfDestroyed();
654
- const decryptdata = new LevelKey(
655
- 'ISO-23001-7',
656
- keyIdHex,
657
- keySystemToKeySystemFormat(keySystem) ?? '',
658
- );
659
- decryptdata.pssh = new Uint8Array(initData);
660
- decryptdata.keyId = keyId as Uint8Array;
661
- return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
662
- this.throwIfDestroyed();
663
- const keySessionContext = this.createMediaKeySessionContext({
664
- decryptdata,
665
- keySystem,
666
- mediaKeys,
667
- });
636
+ if (!keySystemDomain || !keyId) {
637
+ return;
638
+ }
639
+
640
+ const keyIdHex = Hex.hexDump(keyId);
641
+ const { keyIdToKeySessionPromise, mediaKeySessions } = this;
642
+
643
+ let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex];
644
+ for (let i = 0; i < mediaKeySessions.length; i++) {
645
+ // Match playlist key
646
+ const keyContext = mediaKeySessions[i];
647
+ const decryptdata = keyContext.decryptdata;
648
+ if (!decryptdata.keyId) {
649
+ continue;
650
+ }
651
+ const oldKeyIdHex = Hex.hexDump(decryptdata.keyId);
652
+ if (
653
+ keyIdHex === oldKeyIdHex ||
654
+ decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1
655
+ ) {
656
+ keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex];
657
+ if (decryptdata.pssh) {
658
+ break;
659
+ }
660
+ delete keyIdToKeySessionPromise[oldKeyIdHex];
661
+ decryptdata.pssh = new Uint8Array(initData);
662
+ decryptdata.keyId = keyId;
663
+ keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] =
664
+ keySessionContextPromise.then(() => {
668
665
  return this.generateRequestWithPreferredKeySession(
669
- keySessionContext,
666
+ keyContext,
670
667
  initDataType,
671
668
  initData,
672
- 'encrypted-event-no-match',
669
+ 'encrypted-event-key-match',
673
670
  );
674
671
  });
675
- },
676
- );
677
- keySessionContextPromise.catch((error) => this.handleError(error));
678
- }
672
+ keySessionContextPromise.catch((error) => this.handleError(error));
673
+ break;
674
+ }
675
+ }
676
+
677
+ if (!keySessionContextPromise) {
678
+ if (keySystemDomain !== keySystem) {
679
+ this.log(
680
+ `Ignoring "${event.type}" event with ${keySystemDomain} init data for selected key-system ${keySystem}`,
681
+ );
682
+ return;
683
+ }
684
+ // "Clear-lead" (misc key not encountered in playlist)
685
+ keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] =
686
+ this.getKeySystemSelectionPromise([keySystemDomain]).then(
687
+ ({ keySystem, mediaKeys }) => {
688
+ this.throwIfDestroyed();
689
+
690
+ const decryptdata = new LevelKey(
691
+ 'ISO-23001-7',
692
+ keyIdHex,
693
+ keySystemToKeySystemFormat(keySystem) ?? '',
694
+ );
695
+ decryptdata.pssh = new Uint8Array(initData);
696
+ decryptdata.keyId = keyId as Uint8Array;
697
+ return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
698
+ this.throwIfDestroyed();
699
+ const keySessionContext = this.createMediaKeySessionContext({
700
+ decryptdata,
701
+ keySystem,
702
+ mediaKeys,
703
+ });
704
+ return this.generateRequestWithPreferredKeySession(
705
+ keySessionContext,
706
+ initDataType,
707
+ initData,
708
+ 'encrypted-event-no-match',
709
+ );
710
+ });
711
+ },
712
+ );
713
+
714
+ keySessionContextPromise.catch((error) => this.handleError(error));
715
+ }
716
+ });
679
717
  };
680
718
 
681
719
  private onWaitingForKey = (event: Event) => {
@@ -112,7 +112,12 @@ export default class KeyLoader implements ComponentAPI {
112
112
  }
113
113
 
114
114
  load(frag: Fragment): Promise<KeyLoadedData> {
115
- if (!frag.decryptdata && frag.encrypted && this.emeController) {
115
+ if (
116
+ !frag.decryptdata &&
117
+ frag.encrypted &&
118
+ this.emeController &&
119
+ this.config.emeEnabled
120
+ ) {
116
121
  // Multiple keys, but none selected, resolve in eme-controller
117
122
  return this.emeController
118
123
  .selectKeySystemFormat(frag)