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/dist/hls.js +177 -146
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +44 -115
- package/dist/hls.light.js.map +1 -1
- package/dist/hls.light.min.js +1 -1
- package/dist/hls.light.min.js.map +1 -1
- package/dist/hls.light.mjs +46 -117
- package/dist/hls.light.mjs.map +1 -1
- package/dist/hls.min.js +1 -1
- package/dist/hls.min.js.map +1 -1
- package/dist/hls.mjs +162 -135
- package/dist/hls.mjs.map +1 -1
- package/dist/hls.worker.js +1 -1
- package/dist/hls.worker.js.map +1 -1
- package/package.json +1 -1
- package/src/controller/eme-controller.ts +154 -116
- package/src/demux/audio/base-audio-demuxer.ts +6 -6
- package/src/demux/tsdemuxer.ts +2 -3
- package/src/demux/video/hevc-video-parser.ts +12 -12
- package/src/loader/key-loader.ts +6 -1
- package/src/remux/mp4-generator.ts +99 -45
- package/src/remux/mp4-remuxer.ts +24 -38
- package/src/types/demuxer.ts +41 -1
- package/src/types/remuxer.ts +25 -0
package/package.json
CHANGED
@@ -535,147 +535,185 @@ class EMEController extends Logger implements ComponentAPI {
|
|
535
535
|
return;
|
536
536
|
}
|
537
537
|
|
538
|
-
|
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
|
-
|
574
|
-
|
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
|
-
|
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
|
-
|
583
|
-
|
582
|
+
keySystem !== KeySystems.WIDEVINE &&
|
583
|
+
keySystem !== KeySystems.PLAYREADY
|
584
584
|
) {
|
585
|
-
this.warn(
|
586
|
-
|
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
|
-
|
594
|
-
|
590
|
+
// Support Widevine/PlayReady clear-lead key-session creation (otherwise depend on playlist keys)
|
591
|
+
const psshResults = parseMultiPssh(initData);
|
595
592
|
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
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
|
-
|
608
|
-
return;
|
609
|
-
}
|
605
|
+
const psshInfo = psshInfos[0];
|
610
606
|
|
611
|
-
|
612
|
-
|
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
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
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
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
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
|
-
|
666
|
+
keyContext,
|
670
667
|
initDataType,
|
671
668
|
initData,
|
672
|
-
'encrypted-event-
|
669
|
+
'encrypted-event-key-match',
|
673
670
|
);
|
674
671
|
});
|
675
|
-
|
676
|
-
|
677
|
-
|
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
|
22
|
-
protected _id3Track
|
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
|
}
|
package/src/demux/tsdemuxer.ts
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
}
|
package/src/loader/key-loader.ts
CHANGED
@@ -112,7 +112,12 @@ export default class KeyLoader implements ComponentAPI {
|
|
112
112
|
}
|
113
113
|
|
114
114
|
load(frag: Fragment): Promise<KeyLoadedData> {
|
115
|
-
if (
|
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)
|