hls.js 1.5.17 → 1.5.19
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 +342 -268
- package/dist/hls.js.d.ts +10 -6
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +112 -91
- 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 +111 -90
- 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 +292 -222
- 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/audio-stream-controller.ts +4 -2
- package/src/controller/base-stream-controller.ts +9 -0
- package/src/controller/buffer-controller.ts +1 -0
- package/src/controller/eme-controller.ts +184 -124
- package/src/controller/stream-controller.ts +4 -1
- package/src/hls.ts +24 -17
- package/src/loader/key-loader.ts +6 -1
- package/src/loader/level-key.ts +6 -30
- package/src/remux/mp4-remuxer.ts +11 -7
- package/src/types/component-api.ts +2 -0
- package/src/utils/level-helper.ts +37 -38
- package/src/utils/mediakeys-helper.ts +34 -1
- package/src/utils/xhr-loader.ts +52 -49
package/package.json
CHANGED
@@ -281,12 +281,14 @@ class AudioStreamController
|
|
281
281
|
const { hls, levels, media, trackId } = this;
|
282
282
|
const config = hls.config;
|
283
283
|
|
284
|
-
// 1. if
|
284
|
+
// 1. if buffering is suspended
|
285
|
+
// 2. if video not attached AND
|
285
286
|
// start fragment already requested OR start frag prefetch not enabled
|
286
|
-
//
|
287
|
+
// 3. if tracks or track not loaded and selected
|
287
288
|
// then exit loop
|
288
289
|
// => if media not attached but start frag prefetch is enabled and start frag not requested yet, we will not exit loop
|
289
290
|
if (
|
291
|
+
!this.buffering ||
|
290
292
|
(!media && (this.startFragRequested || !config.startFragPrefetch)) ||
|
291
293
|
!levels?.[trackId]
|
292
294
|
) {
|
@@ -97,6 +97,7 @@ export default class BaseStreamController
|
|
97
97
|
protected startFragRequested: boolean = false;
|
98
98
|
protected decrypter: Decrypter;
|
99
99
|
protected initPTS: RationalTimestamp[] = [];
|
100
|
+
protected buffering: boolean = true;
|
100
101
|
protected onvseeking: EventListener | null = null;
|
101
102
|
protected onvended: EventListener | null = null;
|
102
103
|
|
@@ -150,6 +151,14 @@ export default class BaseStreamController
|
|
150
151
|
this.state = State.STOPPED;
|
151
152
|
}
|
152
153
|
|
154
|
+
public pauseBuffering() {
|
155
|
+
this.buffering = false;
|
156
|
+
}
|
157
|
+
|
158
|
+
public resumeBuffering() {
|
159
|
+
this.buffering = true;
|
160
|
+
}
|
161
|
+
|
153
162
|
protected _streamEnded(
|
154
163
|
bufferInfo: BufferInfo,
|
155
164
|
levelDetails: LevelDetails,
|
@@ -9,12 +9,11 @@ import { logger } from '../utils/logger';
|
|
9
9
|
import {
|
10
10
|
getKeySystemsForConfig,
|
11
11
|
getSupportedMediaKeySystemConfigurations,
|
12
|
-
keySystemDomainToKeySystemFormat as keySystemToKeySystemFormat,
|
13
|
-
KeySystemFormats,
|
14
12
|
keySystemFormatToKeySystemDomain,
|
15
|
-
KeySystemIds,
|
16
13
|
keySystemIdToKeySystemDomain,
|
17
14
|
KeySystems,
|
15
|
+
keySystemDomainToKeySystemFormat as keySystemToKeySystemFormat,
|
16
|
+
parsePlayReadyWRM,
|
18
17
|
requestMediaKeySystemAccess,
|
19
18
|
} from '../utils/mediakeys-helper';
|
20
19
|
import { strToUtf8array } from '../utils/keysystem-util';
|
@@ -45,6 +44,7 @@ import type {
|
|
45
44
|
LoaderConfiguration,
|
46
45
|
LoaderContext,
|
47
46
|
} from '../types/loader';
|
47
|
+
import type { KeySystemFormats } from '../utils/mediakeys-helper';
|
48
48
|
|
49
49
|
const LOGGER_PREFIX = '[eme]';
|
50
50
|
|
@@ -94,8 +94,6 @@ class EMEController implements ComponentAPI {
|
|
94
94
|
private setMediaKeysQueue: Promise<void>[] = EMEController.CDMCleanupPromise
|
95
95
|
? [EMEController.CDMCleanupPromise]
|
96
96
|
: [];
|
97
|
-
private onMediaEncrypted = this._onMediaEncrypted.bind(this);
|
98
|
-
private onWaitingForKey = this._onWaitingForKey.bind(this);
|
99
97
|
|
100
98
|
private debug: (msg: any) => void = logger.debug.bind(logger, LOGGER_PREFIX);
|
101
99
|
private log: (msg: any) => void = logger.log.bind(logger, LOGGER_PREFIX);
|
@@ -117,13 +115,9 @@ class EMEController implements ComponentAPI {
|
|
117
115
|
config.licenseXhrSetup = config.licenseResponseCallback = undefined;
|
118
116
|
config.drmSystems = config.drmSystemOptions = {};
|
119
117
|
// @ts-ignore
|
120
|
-
this.hls =
|
121
|
-
this.onMediaEncrypted =
|
122
|
-
this.onWaitingForKey =
|
123
|
-
this.keyIdToKeySessionPromise =
|
124
|
-
null as any;
|
118
|
+
this.hls = this.config = this.keyIdToKeySessionPromise = null;
|
125
119
|
// @ts-ignore
|
126
|
-
this.
|
120
|
+
this.onMediaEncrypted = this.onWaitingForKey = null;
|
127
121
|
}
|
128
122
|
|
129
123
|
private registerListeners() {
|
@@ -140,7 +134,7 @@ class EMEController implements ComponentAPI {
|
|
140
134
|
this.hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
|
141
135
|
}
|
142
136
|
|
143
|
-
private getLicenseServerUrl(keySystem: KeySystems): string |
|
137
|
+
private getLicenseServerUrl(keySystem: KeySystems): string | undefined {
|
144
138
|
const { drmSystems, widevineLicenseUrl } = this.config;
|
145
139
|
const keySystemConfiguration = drmSystems[keySystem];
|
146
140
|
|
@@ -152,10 +146,16 @@ class EMEController implements ComponentAPI {
|
|
152
146
|
if (keySystem === KeySystems.WIDEVINE && widevineLicenseUrl) {
|
153
147
|
return widevineLicenseUrl;
|
154
148
|
}
|
149
|
+
}
|
155
150
|
|
156
|
-
|
157
|
-
|
158
|
-
)
|
151
|
+
private getLicenseServerUrlOrThrow(keySystem: KeySystems): string | never {
|
152
|
+
const url = this.getLicenseServerUrl(keySystem);
|
153
|
+
if (url === undefined) {
|
154
|
+
throw new Error(
|
155
|
+
`no license server URL configured for key-system "${keySystem}"`,
|
156
|
+
);
|
157
|
+
}
|
158
|
+
return url;
|
159
159
|
}
|
160
160
|
|
161
161
|
private getServerCertificateUrl(keySystem: KeySystems): string | void {
|
@@ -527,7 +527,7 @@ class EMEController implements ComponentAPI {
|
|
527
527
|
return this.attemptKeySystemAccess(keySystemsToAttempt);
|
528
528
|
}
|
529
529
|
|
530
|
-
private
|
530
|
+
private onMediaEncrypted = (event: MediaEncryptedEvent) => {
|
531
531
|
const { initDataType, initData } = event;
|
532
532
|
const logMessage = `"${event.type}" event: init data type: "${initDataType}"`;
|
533
533
|
this.debug(logMessage);
|
@@ -537,133 +537,191 @@ class EMEController implements ComponentAPI {
|
|
537
537
|
return;
|
538
538
|
}
|
539
539
|
|
540
|
-
|
541
|
-
|
540
|
+
if (!this.keyFormatPromise) {
|
541
|
+
let keySystems = Object.keys(
|
542
|
+
this.keySystemAccessPromises,
|
543
|
+
) as KeySystems[];
|
544
|
+
if (!keySystems.length) {
|
545
|
+
keySystems = getKeySystemsForConfig(this.config);
|
546
|
+
}
|
547
|
+
const keyFormats = keySystems
|
548
|
+
.map(keySystemToKeySystemFormat)
|
549
|
+
.filter((k) => !!k) as KeySystemFormats[];
|
550
|
+
this.keyFormatPromise = this.getKeyFormatPromise(keyFormats);
|
551
|
+
}
|
542
552
|
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
throw new Error(
|
554
|
-
`'schm' box missing or not cbcs/cenc with schi > tenc`,
|
553
|
+
this.keyFormatPromise.then((keySystemFormat) => {
|
554
|
+
const keySystem = keySystemFormatToKeySystemDomain(keySystemFormat);
|
555
|
+
|
556
|
+
let keyId: Uint8Array | null | undefined;
|
557
|
+
let keySystemDomain: KeySystems | undefined;
|
558
|
+
|
559
|
+
if (initDataType === 'sinf') {
|
560
|
+
if (keySystem !== KeySystems.FAIRPLAY) {
|
561
|
+
this.warn(
|
562
|
+
`Ignoring unexpected "${event.type}" event with init data type: "${initDataType}" for selected key-system ${keySystem}`,
|
555
563
|
);
|
564
|
+
return;
|
556
565
|
}
|
557
|
-
keyId
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
566
|
+
// Match sinf keyId to playlist skd://keyId=
|
567
|
+
const json = bin2str(new Uint8Array(initData));
|
568
|
+
try {
|
569
|
+
const sinf = base64Decode(JSON.parse(json).sinf);
|
570
|
+
const tenc = parseSinf(sinf);
|
571
|
+
if (!tenc) {
|
572
|
+
throw new Error(
|
573
|
+
`'schm' box missing or not cbcs/cenc with schi > tenc`,
|
574
|
+
);
|
575
|
+
}
|
576
|
+
keyId = tenc.subarray(8, 24);
|
577
|
+
keySystemDomain = KeySystems.FAIRPLAY;
|
578
|
+
} catch (error) {
|
579
|
+
this.warn(`${logMessage} Failed to parse sinf: ${error}`);
|
580
|
+
return;
|
581
|
+
}
|
582
|
+
} else {
|
570
583
|
if (
|
571
|
-
|
572
|
-
|
584
|
+
keySystem !== KeySystems.WIDEVINE &&
|
585
|
+
keySystem !== KeySystems.PLAYREADY
|
573
586
|
) {
|
574
|
-
this.warn(
|
575
|
-
|
576
|
-
this.log(
|
577
|
-
`ignoring ${logMessage} for ${(psshResults as PsshData[])
|
578
|
-
.map((pssh) => keySystemIdToKeySystemDomain(pssh.systemId))
|
579
|
-
.join(',')} pssh data in favor of playlist keys`,
|
587
|
+
this.warn(
|
588
|
+
`Ignoring unexpected "${event.type}" event with init data type: "${initDataType}" for selected key-system ${keySystem}`,
|
580
589
|
);
|
590
|
+
return;
|
581
591
|
}
|
582
|
-
|
583
|
-
|
584
|
-
keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId);
|
585
|
-
if (psshInfo.version === 0 && psshInfo.data) {
|
586
|
-
const offset = psshInfo.data.length - 22;
|
587
|
-
keyId = psshInfo.data.subarray(offset, offset + 16);
|
588
|
-
}
|
589
|
-
}
|
590
|
-
|
591
|
-
if (!keySystemDomain || !keyId) {
|
592
|
-
return;
|
593
|
-
}
|
592
|
+
// Support Widevine/PlayReady clear-lead key-session creation (otherwise depend on playlist keys)
|
593
|
+
const psshResults = parseMultiPssh(initData);
|
594
594
|
|
595
|
-
|
596
|
-
|
595
|
+
const psshInfos = psshResults.filter(
|
596
|
+
(pssh): pssh is PsshData =>
|
597
|
+
!!pssh.systemId &&
|
598
|
+
keySystemIdToKeySystemDomain(pssh.systemId) === keySystem,
|
599
|
+
);
|
597
600
|
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
const decryptdata = keyContext.decryptdata;
|
603
|
-
if (!decryptdata.keyId) {
|
604
|
-
continue;
|
605
|
-
}
|
606
|
-
const oldKeyIdHex = Hex.hexDump(decryptdata.keyId);
|
607
|
-
if (
|
608
|
-
keyIdHex === oldKeyIdHex ||
|
609
|
-
decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1
|
610
|
-
) {
|
611
|
-
keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex];
|
612
|
-
if (decryptdata.pssh) {
|
613
|
-
break;
|
601
|
+
if (psshInfos.length > 1) {
|
602
|
+
this.warn(
|
603
|
+
`${logMessage} Using first of ${psshInfos.length} pssh found for selected key-system ${keySystem}`,
|
604
|
+
);
|
614
605
|
}
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
606
|
+
|
607
|
+
const psshInfo = psshInfos[0];
|
608
|
+
|
609
|
+
if (!psshInfo) {
|
610
|
+
if (
|
611
|
+
psshResults.length === 0 ||
|
612
|
+
psshResults.some(
|
613
|
+
(pssh): pssh is PsshInvalidResult => !pssh.systemId,
|
614
|
+
)
|
615
|
+
) {
|
616
|
+
this.warn(`${logMessage} contains incomplete or invalid pssh data`);
|
617
|
+
} else {
|
618
|
+
this.log(
|
619
|
+
`ignoring ${logMessage} for ${(psshResults as PsshData[])
|
620
|
+
.map((pssh) => keySystemIdToKeySystemDomain(pssh.systemId))
|
621
|
+
.join(',')} pssh data in favor of playlist keys`,
|
625
622
|
);
|
626
|
-
}
|
627
|
-
|
623
|
+
}
|
624
|
+
return;
|
625
|
+
}
|
626
|
+
|
627
|
+
keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId);
|
628
|
+
if (psshInfo.version === 0 && psshInfo.data) {
|
629
|
+
if (keySystemDomain === KeySystems.WIDEVINE) {
|
630
|
+
const offset = psshInfo.data.length - 22;
|
631
|
+
keyId = psshInfo.data.subarray(offset, offset + 16);
|
632
|
+
} else if (keySystemDomain === KeySystems.PLAYREADY) {
|
633
|
+
keyId = parsePlayReadyWRM(psshInfo.data);
|
634
|
+
}
|
635
|
+
}
|
628
636
|
}
|
629
|
-
}
|
630
637
|
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
638
|
+
if (!keySystemDomain || !keyId) {
|
639
|
+
this.log(`Unable to handle ${logMessage} with key-system ${keySystem}`);
|
640
|
+
return;
|
641
|
+
}
|
642
|
+
|
643
|
+
const keyIdHex = Hex.hexDump(keyId);
|
644
|
+
const { keyIdToKeySessionPromise, mediaKeySessions } = this;
|
645
|
+
|
646
|
+
let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex];
|
647
|
+
for (let i = 0; i < mediaKeySessions.length; i++) {
|
648
|
+
// Match playlist key
|
649
|
+
const keyContext = mediaKeySessions[i];
|
650
|
+
const decryptdata = keyContext.decryptdata;
|
651
|
+
if (!decryptdata.keyId) {
|
652
|
+
continue;
|
653
|
+
}
|
654
|
+
const oldKeyIdHex = Hex.hexDump(decryptdata.keyId);
|
655
|
+
if (
|
656
|
+
keyIdHex === oldKeyIdHex ||
|
657
|
+
decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1
|
658
|
+
) {
|
659
|
+
keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex];
|
660
|
+
if (decryptdata.pssh) {
|
661
|
+
break;
|
662
|
+
}
|
663
|
+
delete keyIdToKeySessionPromise[oldKeyIdHex];
|
664
|
+
decryptdata.pssh = new Uint8Array(initData);
|
665
|
+
decryptdata.keyId = keyId;
|
666
|
+
keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] =
|
667
|
+
keySessionContextPromise.then(() => {
|
651
668
|
return this.generateRequestWithPreferredKeySession(
|
652
|
-
|
669
|
+
keyContext,
|
653
670
|
initDataType,
|
654
671
|
initData,
|
655
|
-
'encrypted-event-
|
672
|
+
'encrypted-event-key-match',
|
656
673
|
);
|
657
674
|
});
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
675
|
+
keySessionContextPromise.catch((error) => this.handleError(error));
|
676
|
+
break;
|
677
|
+
}
|
678
|
+
}
|
679
|
+
|
680
|
+
if (!keySessionContextPromise) {
|
681
|
+
if (keySystemDomain !== keySystem) {
|
682
|
+
this.log(
|
683
|
+
`Ignoring "${logMessage}" with ${keySystemDomain} init data for selected key-system ${keySystem}`,
|
684
|
+
);
|
685
|
+
return;
|
686
|
+
}
|
687
|
+
// "Clear-lead" (misc key not encountered in playlist)
|
688
|
+
keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] =
|
689
|
+
this.getKeySystemSelectionPromise([keySystemDomain]).then(
|
690
|
+
({ keySystem, mediaKeys }) => {
|
691
|
+
this.throwIfDestroyed();
|
692
|
+
|
693
|
+
const decryptdata = new LevelKey(
|
694
|
+
'ISO-23001-7',
|
695
|
+
keyIdHex,
|
696
|
+
keySystemToKeySystemFormat(keySystem) ?? '',
|
697
|
+
);
|
698
|
+
decryptdata.pssh = new Uint8Array(initData);
|
699
|
+
decryptdata.keyId = keyId as Uint8Array;
|
700
|
+
return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
|
701
|
+
this.throwIfDestroyed();
|
702
|
+
const keySessionContext = this.createMediaKeySessionContext({
|
703
|
+
decryptdata,
|
704
|
+
keySystem,
|
705
|
+
mediaKeys,
|
706
|
+
});
|
707
|
+
return this.generateRequestWithPreferredKeySession(
|
708
|
+
keySessionContext,
|
709
|
+
initDataType,
|
710
|
+
initData,
|
711
|
+
'encrypted-event-no-match',
|
712
|
+
);
|
713
|
+
});
|
714
|
+
},
|
715
|
+
);
|
663
716
|
|
664
|
-
|
717
|
+
keySessionContextPromise.catch((error) => this.handleError(error));
|
718
|
+
}
|
719
|
+
});
|
720
|
+
};
|
721
|
+
|
722
|
+
private onWaitingForKey = (event: Event) => {
|
665
723
|
this.log(`"${event.type}" event`);
|
666
|
-
}
|
724
|
+
};
|
667
725
|
|
668
726
|
private attemptSetMediaKeys(
|
669
727
|
keySystem: KeySystems,
|
@@ -1111,7 +1169,7 @@ class EMEController implements ComponentAPI {
|
|
1111
1169
|
): Promise<ArrayBuffer> {
|
1112
1170
|
const keyLoadPolicy = this.config.keyLoadPolicy.default;
|
1113
1171
|
return new Promise((resolve, reject) => {
|
1114
|
-
const url = this.
|
1172
|
+
const url = this.getLicenseServerUrlOrThrow(keySessionContext.keySystem);
|
1115
1173
|
this.log(`Sending license request to URL: ${url}`);
|
1116
1174
|
const xhr = new XMLHttpRequest();
|
1117
1175
|
xhr.responseType = 'arraybuffer';
|
@@ -1216,6 +1274,8 @@ class EMEController implements ComponentAPI {
|
|
1216
1274
|
// keep reference of media
|
1217
1275
|
this.media = media;
|
1218
1276
|
|
1277
|
+
media.removeEventListener('encrypted', this.onMediaEncrypted);
|
1278
|
+
media.removeEventListener('waitingforkey', this.onWaitingForKey);
|
1219
1279
|
media.addEventListener('encrypted', this.onMediaEncrypted);
|
1220
1280
|
media.addEventListener('waitingforkey', this.onWaitingForKey);
|
1221
1281
|
}
|
@@ -237,7 +237,7 @@ export default class StreamController
|
|
237
237
|
return;
|
238
238
|
}
|
239
239
|
|
240
|
-
const level = hls.nextLoadLevel;
|
240
|
+
const level = this.buffering ? hls.nextLoadLevel : hls.loadLevel;
|
241
241
|
if (!levels?.[level]) {
|
242
242
|
return;
|
243
243
|
}
|
@@ -262,6 +262,9 @@ export default class StreamController
|
|
262
262
|
this.state = State.ENDED;
|
263
263
|
return;
|
264
264
|
}
|
265
|
+
if (!this.buffering) {
|
266
|
+
return;
|
267
|
+
}
|
265
268
|
|
266
269
|
// set next load level : this will trigger a playlist load if needed
|
267
270
|
if (hls.loadLevel !== level && hls.manualLevel === -1) {
|
package/src/hls.ts
CHANGED
@@ -430,9 +430,13 @@ export default class Hls implements HlsEventEmitter {
|
|
430
430
|
startLoad(startPosition: number = -1) {
|
431
431
|
logger.log(`startLoad(${startPosition})`);
|
432
432
|
this.started = true;
|
433
|
-
this.
|
434
|
-
|
435
|
-
|
433
|
+
this.resumeBuffering();
|
434
|
+
for (let i = 0; i < this.networkControllers.length; i++) {
|
435
|
+
this.networkControllers[i].startLoad(startPosition);
|
436
|
+
if (!this.started || !this.networkControllers) {
|
437
|
+
break;
|
438
|
+
}
|
439
|
+
}
|
436
440
|
}
|
437
441
|
|
438
442
|
/**
|
@@ -441,32 +445,35 @@ export default class Hls implements HlsEventEmitter {
|
|
441
445
|
stopLoad() {
|
442
446
|
logger.log('stopLoad');
|
443
447
|
this.started = false;
|
444
|
-
this.networkControllers.
|
445
|
-
|
446
|
-
|
448
|
+
for (let i = 0; i < this.networkControllers.length; i++) {
|
449
|
+
this.networkControllers[i].stopLoad();
|
450
|
+
if (this.started || !this.networkControllers) {
|
451
|
+
break;
|
452
|
+
}
|
453
|
+
}
|
447
454
|
}
|
448
455
|
|
449
456
|
/**
|
450
|
-
* Resumes stream controller segment loading
|
457
|
+
* Resumes stream controller segment loading after `pauseBuffering` has been called.
|
451
458
|
*/
|
452
459
|
resumeBuffering() {
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
}
|
460
|
+
logger.log(`resume buffering`);
|
461
|
+
this.networkControllers.forEach((controller) => {
|
462
|
+
if (controller.resumeBuffering) {
|
463
|
+
controller.resumeBuffering();
|
464
|
+
}
|
465
|
+
});
|
460
466
|
}
|
461
467
|
|
462
468
|
/**
|
463
|
-
*
|
469
|
+
* Prevents stream controller from loading new segments until `resumeBuffering` is called.
|
464
470
|
* This allows for media buffering to be paused without interupting playlist loading.
|
465
471
|
*/
|
466
472
|
pauseBuffering() {
|
473
|
+
logger.log(`pause buffering`);
|
467
474
|
this.networkControllers.forEach((controller) => {
|
468
|
-
if (
|
469
|
-
controller.
|
475
|
+
if (controller.pauseBuffering) {
|
476
|
+
controller.pauseBuffering();
|
470
477
|
}
|
471
478
|
});
|
472
479
|
}
|
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)
|
package/src/loader/level-key.ts
CHANGED
@@ -2,7 +2,7 @@ import {
|
|
2
2
|
changeEndianness,
|
3
3
|
convertDataUriToArrayBytes,
|
4
4
|
} from '../utils/keysystem-util';
|
5
|
-
import { KeySystemFormats } from '../utils/mediakeys-helper';
|
5
|
+
import { KeySystemFormats, parsePlayReadyWRM } from '../utils/mediakeys-helper';
|
6
6
|
import { mp4pssh } from '../utils/mp4-tools';
|
7
7
|
import { logger } from '../utils/logger';
|
8
8
|
import { base64Decode } from '../utils/numeric-encoding-utils';
|
@@ -121,6 +121,8 @@ export class LevelKey implements DecryptData {
|
|
121
121
|
if (keyBytes) {
|
122
122
|
switch (this.keyFormat) {
|
123
123
|
case KeySystemFormats.WIDEVINE:
|
124
|
+
// Setting `pssh` on this LevelKey/DecryptData allows HLS.js to generate a session using
|
125
|
+
// the playlist-key before the "encrypted" event. (Comment out to only use "encrypted" path.)
|
124
126
|
this.pssh = keyBytes;
|
125
127
|
// In case of widevine keyID is embedded in PSSH box. Read Key ID.
|
126
128
|
if (keyBytes.length >= 22) {
|
@@ -136,38 +138,12 @@ export class LevelKey implements DecryptData {
|
|
136
138
|
0x5b, 0xe0, 0x88, 0x5f, 0x95,
|
137
139
|
]);
|
138
140
|
|
141
|
+
// Setting `pssh` on this LevelKey/DecryptData allows HLS.js to generate a session using
|
142
|
+
// the playlist-key before the "encrypted" event. (Comment out to only use "encrypted" path.)
|
139
143
|
this.pssh = mp4pssh(PlayReadyKeySystemUUID, null, keyBytes);
|
140
144
|
|
141
|
-
|
142
|
-
keyBytes.buffer,
|
143
|
-
keyBytes.byteOffset,
|
144
|
-
keyBytes.byteLength / 2,
|
145
|
-
);
|
146
|
-
const keyByteStr = String.fromCharCode.apply(
|
147
|
-
null,
|
148
|
-
Array.from(keyBytesUtf16),
|
149
|
-
);
|
145
|
+
this.keyId = parsePlayReadyWRM(keyBytes);
|
150
146
|
|
151
|
-
// Parse Playready WRMHeader XML
|
152
|
-
const xmlKeyBytes = keyByteStr.substring(
|
153
|
-
keyByteStr.indexOf('<'),
|
154
|
-
keyByteStr.length,
|
155
|
-
);
|
156
|
-
const parser = new DOMParser();
|
157
|
-
const xmlDoc = parser.parseFromString(xmlKeyBytes, 'text/xml');
|
158
|
-
const keyData = xmlDoc.getElementsByTagName('KID')[0];
|
159
|
-
if (keyData) {
|
160
|
-
const keyId = keyData.childNodes[0]
|
161
|
-
? keyData.childNodes[0].nodeValue
|
162
|
-
: keyData.getAttribute('VALUE');
|
163
|
-
if (keyId) {
|
164
|
-
const keyIdArray = base64Decode(keyId).subarray(0, 16);
|
165
|
-
// KID value in PRO is a base64-encoded little endian GUID interpretation of UUID
|
166
|
-
// KID value in ‘tenc’ is a big endian UUID GUID interpretation of UUID
|
167
|
-
changeEndianness(keyIdArray);
|
168
|
-
this.keyId = keyIdArray;
|
169
|
-
}
|
170
|
-
}
|
171
147
|
break;
|
172
148
|
}
|
173
149
|
default: {
|
package/src/remux/mp4-remuxer.ts
CHANGED
@@ -100,20 +100,24 @@ export default class MP4Remuxer implements Remuxer {
|
|
100
100
|
this.videoTrackConfig = undefined;
|
101
101
|
}
|
102
102
|
|
103
|
-
getVideoStartPts(videoSamples) {
|
103
|
+
getVideoStartPts(videoSamples: VideoSample[]) {
|
104
|
+
// Get the minimum PTS value relative to the first sample's PTS, normalized for 33-bit wrapping
|
104
105
|
let rolloverDetected = false;
|
106
|
+
const firstPts = videoSamples[0].pts;
|
105
107
|
const startPTS = videoSamples.reduce((minPTS, sample) => {
|
106
|
-
|
108
|
+
let pts = sample.pts;
|
109
|
+
let delta = pts - minPTS;
|
107
110
|
if (delta < -4294967296) {
|
108
111
|
// 2^32, see PTSNormalize for reasoning, but we're hitting a rollover here, and we don't want that to impact the timeOffset calculation
|
109
112
|
rolloverDetected = true;
|
110
|
-
|
111
|
-
|
113
|
+
pts = normalizePts(pts, firstPts);
|
114
|
+
delta = pts - minPTS;
|
115
|
+
}
|
116
|
+
if (delta > 0) {
|
112
117
|
return minPTS;
|
113
|
-
} else {
|
114
|
-
return sample.pts;
|
115
118
|
}
|
116
|
-
|
119
|
+
return pts;
|
120
|
+
}, firstPts);
|
117
121
|
if (rolloverDetected) {
|
118
122
|
logger.debug('PTS rollover detected');
|
119
123
|
}
|