hls.js 1.6.0-beta.2.0.canary.10873 → 1.6.0-beta.2.0.canary.10877

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.
package/package.json CHANGED
@@ -134,5 +134,5 @@
134
134
  "url-toolkit": "2.2.5",
135
135
  "wrangler": "3.99.0"
136
136
  },
137
- "version": "1.6.0-beta.2.0.canary.10873"
137
+ "version": "1.6.0-beta.2.0.canary.10877"
138
138
  }
@@ -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) {
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) => {
@@ -18,8 +18,8 @@ import { dummyTrack } from '../dummy-demuxed-track';
18
18
  import type { RationalTimestamp } from '../../utils/timescale-conversion';
19
19
 
20
20
  class BaseAudioDemuxer implements Demuxer {
21
- protected _audioTrack!: DemuxedAudioTrack;
22
- protected _id3Track!: DemuxedMetadataTrack;
21
+ protected _audioTrack?: DemuxedAudioTrack;
22
+ protected _id3Track?: DemuxedMetadataTrack;
23
23
  protected frameIndex: number = 0;
24
24
  protected cachedData: Uint8Array | null = null;
25
25
  protected basePTS: number | null = null;
@@ -74,8 +74,8 @@ class BaseAudioDemuxer implements Demuxer {
74
74
  let id3Data: Uint8Array | undefined = getId3Data(data, 0);
75
75
  let offset = id3Data ? id3Data.length : 0;
76
76
  let lastDataIndex;
77
- const track = this._audioTrack;
78
- const id3Track = this._id3Track;
77
+ const track = this._audioTrack as DemuxedAudioTrack;
78
+ const id3Track = this._id3Track as DemuxedMetadataTrack;
79
79
  const timestamp = id3Data ? getId3Timestamp(id3Data) : undefined;
80
80
  const length = data.length;
81
81
 
@@ -167,9 +167,9 @@ class BaseAudioDemuxer implements Demuxer {
167
167
  }
168
168
 
169
169
  return {
170
- audioTrack: this._audioTrack,
170
+ audioTrack: this._audioTrack as DemuxedAudioTrack,
171
171
  videoTrack: dummyTrack() as DemuxedVideoTrackBase,
172
- id3Track: this._id3Track,
172
+ id3Track: this._id3Track as DemuxedMetadataTrack,
173
173
  textTrack: dummyTrack() as DemuxedUserdataTrack,
174
174
  };
175
175
  }
@@ -774,8 +774,8 @@ function parsePMT(
774
774
  audioPid: -1,
775
775
  videoPid: -1,
776
776
  id3Pid: -1,
777
- segmentVideoCodec: 'avc',
778
- segmentAudioCodec: 'aac',
777
+ segmentVideoCodec: 'avc' as 'avc' | 'hevc',
778
+ segmentAudioCodec: 'aac' as 'aac' | 'ac3' | 'mp3',
779
779
  };
780
780
  const sectionLength = ((data[offset + 1] & 0x0f) << 8) | data[offset + 2];
781
781
  const tableEnd = offset + 3 + sectionLength - 4;
@@ -822,7 +822,6 @@ function parsePMT(
822
822
  // logger.log('AVC PID:' + pid);
823
823
  if (result.videoPid === -1) {
824
824
  result.videoPid = pid;
825
- result.segmentVideoCodec = 'avc';
826
825
  }
827
826
 
828
827
  break;
@@ -158,12 +158,7 @@ class HevcVideoParser extends BaseVideoParser {
158
158
  track.params[prop] = config.params[prop];
159
159
  }
160
160
  }
161
- if (
162
- (!track.vps && !track.sps.length) ||
163
- (track.vps && track.vps[0] === this.initVPS)
164
- ) {
165
- track.sps.push(unit.data);
166
- }
161
+ this.pushParameterSet(track.sps, unit.data, track.vps);
167
162
  if (!VideoSample) {
168
163
  VideoSample = this.VideoSample = this.createVideoSample(
169
164
  true,
@@ -185,12 +180,7 @@ class HevcVideoParser extends BaseVideoParser {
185
180
  track.params[prop] = config[prop];
186
181
  }
187
182
  }
188
- if (
189
- (!track.vps && !track.pps.length) ||
190
- (track.vps && track.vps[0] === this.initVPS)
191
- ) {
192
- track.pps.push(unit.data);
193
- }
183
+ this.pushParameterSet(track.pps, unit.data, track.vps);
194
184
  }
195
185
  break;
196
186
 
@@ -227,6 +217,16 @@ class HevcVideoParser extends BaseVideoParser {
227
217
  }
228
218
  }
229
219
 
220
+ private pushParameterSet(
221
+ parameterSets: Uint8Array[],
222
+ data: Uint8Array,
223
+ vps: Uint8Array[] | undefined,
224
+ ) {
225
+ if ((vps && vps[0] === this.initVPS) || (!vps && !parameterSets.length)) {
226
+ parameterSets.push(data);
227
+ }
228
+ }
229
+
230
230
  protected getNALuType(data: Uint8Array, offset: number): number {
231
231
  return (data[offset] & 0x7e) >>> 1;
232
232
  }
@@ -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)