hls.js 1.5.2-0.canary.9934 → 1.5.3
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-demo.js +0 -5
- package/dist/hls-demo.js.map +1 -1
- package/dist/hls.js +757 -883
- package/dist/hls.js.d.ts +47 -56
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +477 -600
- 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 +335 -446
- 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 +572 -681
- 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 +11 -11
- package/src/config.ts +2 -3
- package/src/controller/abr-controller.ts +22 -22
- package/src/controller/audio-stream-controller.ts +14 -11
- package/src/controller/audio-track-controller.ts +1 -1
- package/src/controller/base-playlist-controller.ts +7 -7
- package/src/controller/base-stream-controller.ts +29 -47
- package/src/controller/buffer-controller.ts +11 -10
- package/src/controller/cap-level-controller.ts +2 -1
- package/src/controller/cmcd-controller.ts +3 -25
- package/src/controller/content-steering-controller.ts +6 -8
- package/src/controller/eme-controller.ts +22 -9
- package/src/controller/error-controller.ts +8 -6
- package/src/controller/fps-controller.ts +3 -2
- package/src/controller/gap-controller.ts +16 -43
- package/src/controller/latency-controller.ts +11 -9
- package/src/controller/level-controller.ts +17 -5
- package/src/controller/stream-controller.ts +31 -24
- package/src/controller/subtitle-stream-controller.ts +14 -13
- package/src/controller/subtitle-track-controller.ts +3 -5
- package/src/controller/timeline-controller.ts +30 -23
- package/src/crypt/aes-crypto.ts +2 -21
- package/src/crypt/decrypter.ts +18 -32
- package/src/crypt/fast-aes-key.ts +5 -24
- package/src/demux/audio/adts.ts +4 -9
- package/src/demux/sample-aes.ts +0 -2
- package/src/demux/transmuxer-interface.ts +12 -4
- package/src/demux/transmuxer-worker.ts +4 -4
- package/src/demux/transmuxer.ts +3 -16
- package/src/demux/tsdemuxer.ts +17 -12
- package/src/events.ts +0 -7
- package/src/hls.ts +20 -33
- package/src/loader/fragment-loader.ts +2 -9
- package/src/loader/key-loader.ts +0 -2
- package/src/loader/level-key.ts +9 -10
- package/src/remux/mp4-remuxer.ts +4 -20
- package/src/task-loop.ts +2 -5
- package/src/types/demuxer.ts +0 -1
- package/src/types/events.ts +0 -4
- package/src/utils/codecs.ts +4 -33
- package/src/utils/logger.ts +24 -53
- package/src/crypt/decrypter-aes-mode.ts +0 -4
- package/src/utils/encryption-methods-util.ts +0 -21
package/dist/hls.mjs
CHANGED
@@ -256,7 +256,6 @@ let Events = /*#__PURE__*/function (Events) {
|
|
256
256
|
Events["MEDIA_ATTACHED"] = "hlsMediaAttached";
|
257
257
|
Events["MEDIA_DETACHING"] = "hlsMediaDetaching";
|
258
258
|
Events["MEDIA_DETACHED"] = "hlsMediaDetached";
|
259
|
-
Events["MEDIA_ENDED"] = "hlsMediaEnded";
|
260
259
|
Events["BUFFER_RESET"] = "hlsBufferReset";
|
261
260
|
Events["BUFFER_CODECS"] = "hlsBufferCodecs";
|
262
261
|
Events["BUFFER_CREATED"] = "hlsBufferCreated";
|
@@ -370,23 +369,6 @@ let ErrorDetails = /*#__PURE__*/function (ErrorDetails) {
|
|
370
369
|
return ErrorDetails;
|
371
370
|
}({});
|
372
371
|
|
373
|
-
class Logger {
|
374
|
-
constructor(label, logger) {
|
375
|
-
this.trace = void 0;
|
376
|
-
this.debug = void 0;
|
377
|
-
this.log = void 0;
|
378
|
-
this.warn = void 0;
|
379
|
-
this.info = void 0;
|
380
|
-
this.error = void 0;
|
381
|
-
const lb = `[${label}]:`;
|
382
|
-
this.trace = noop;
|
383
|
-
this.debug = logger.debug.bind(null, lb);
|
384
|
-
this.log = logger.log.bind(null, lb);
|
385
|
-
this.warn = logger.warn.bind(null, lb);
|
386
|
-
this.info = logger.info.bind(null, lb);
|
387
|
-
this.error = logger.error.bind(null, lb);
|
388
|
-
}
|
389
|
-
}
|
390
372
|
const noop = function noop() {};
|
391
373
|
const fakeLogger = {
|
392
374
|
trace: noop,
|
@@ -396,9 +378,7 @@ const fakeLogger = {
|
|
396
378
|
info: noop,
|
397
379
|
error: noop
|
398
380
|
};
|
399
|
-
|
400
|
-
return _extends({}, fakeLogger);
|
401
|
-
}
|
381
|
+
let exportedLogger = fakeLogger;
|
402
382
|
|
403
383
|
// let lastCallTime;
|
404
384
|
// function formatMsgWithTimeInfo(type, msg) {
|
@@ -409,36 +389,35 @@ function createLogger() {
|
|
409
389
|
// return msg;
|
410
390
|
// }
|
411
391
|
|
412
|
-
function consolePrintFn(type
|
392
|
+
function consolePrintFn(type) {
|
413
393
|
const func = self.console[type];
|
414
|
-
|
394
|
+
if (func) {
|
395
|
+
return func.bind(self.console, `[${type}] >`);
|
396
|
+
}
|
397
|
+
return noop;
|
415
398
|
}
|
416
|
-
function
|
417
|
-
|
399
|
+
function exportLoggerFunctions(debugConfig, ...functions) {
|
400
|
+
functions.forEach(function (type) {
|
401
|
+
exportedLogger[type] = debugConfig[type] ? debugConfig[type].bind(debugConfig) : consolePrintFn(type);
|
402
|
+
});
|
418
403
|
}
|
419
|
-
|
420
|
-
function enableLogs(debugConfig, context, id) {
|
404
|
+
function enableLogs(debugConfig, id) {
|
421
405
|
// check that console is available
|
422
|
-
const newLogger = createLogger();
|
423
406
|
if (typeof console === 'object' && debugConfig === true || typeof debugConfig === 'object') {
|
424
|
-
|
407
|
+
exportLoggerFunctions(debugConfig,
|
425
408
|
// Remove out from list here to hard-disable a log-level
|
426
409
|
// 'trace',
|
427
|
-
'debug', 'log', 'info', 'warn', 'error'
|
428
|
-
keys.forEach(key => {
|
429
|
-
newLogger[key] = getLoggerFn(key, debugConfig, id);
|
430
|
-
});
|
410
|
+
'debug', 'log', 'info', 'warn', 'error');
|
431
411
|
// Some browsers don't allow to use bind on console object anyway
|
432
412
|
// fallback to default if needed
|
433
413
|
try {
|
434
|
-
|
414
|
+
exportedLogger.log(`Debug logs enabled for "${id}" in hls.js version ${"1.5.3"}`);
|
435
415
|
} catch (e) {
|
436
|
-
|
437
|
-
return createLogger();
|
416
|
+
exportedLogger = fakeLogger;
|
438
417
|
}
|
418
|
+
} else {
|
419
|
+
exportedLogger = fakeLogger;
|
439
420
|
}
|
440
|
-
exportedLogger = newLogger;
|
441
|
-
return newLogger;
|
442
421
|
}
|
443
422
|
const logger = exportedLogger;
|
444
423
|
|
@@ -1057,26 +1036,6 @@ function strToUtf8array(str) {
|
|
1057
1036
|
return Uint8Array.from(unescape(encodeURIComponent(str)), c => c.charCodeAt(0));
|
1058
1037
|
}
|
1059
1038
|
|
1060
|
-
var DecrypterAesMode = {
|
1061
|
-
cbc: 0,
|
1062
|
-
ctr: 1
|
1063
|
-
};
|
1064
|
-
|
1065
|
-
function isFullSegmentEncryption(method) {
|
1066
|
-
return method === 'AES-128' || method === 'AES-256' || method === 'AES-256-CTR';
|
1067
|
-
}
|
1068
|
-
function getAesModeFromFullSegmentMethod(method) {
|
1069
|
-
switch (method) {
|
1070
|
-
case 'AES-128':
|
1071
|
-
case 'AES-256':
|
1072
|
-
return DecrypterAesMode.cbc;
|
1073
|
-
case 'AES-256-CTR':
|
1074
|
-
return DecrypterAesMode.ctr;
|
1075
|
-
default:
|
1076
|
-
throw new Error(`invalid full segment method ${method}`);
|
1077
|
-
}
|
1078
|
-
}
|
1079
|
-
|
1080
1039
|
/** returns `undefined` is `self` is missing, e.g. in node */
|
1081
1040
|
const optionalSelf = typeof self !== 'undefined' ? self : undefined;
|
1082
1041
|
|
@@ -2715,12 +2674,12 @@ class LevelKey {
|
|
2715
2674
|
this.keyFormatVersions = formatversions;
|
2716
2675
|
this.iv = iv;
|
2717
2676
|
this.encrypted = method ? method !== 'NONE' : false;
|
2718
|
-
this.isCommonEncryption = this.encrypted &&
|
2677
|
+
this.isCommonEncryption = this.encrypted && method !== 'AES-128';
|
2719
2678
|
}
|
2720
2679
|
isSupported() {
|
2721
2680
|
// If it's Segment encryption or No encryption, just select that key system
|
2722
2681
|
if (this.method) {
|
2723
|
-
if (
|
2682
|
+
if (this.method === 'AES-128' || this.method === 'NONE') {
|
2724
2683
|
return true;
|
2725
2684
|
}
|
2726
2685
|
if (this.keyFormat === 'identity') {
|
@@ -2742,13 +2701,14 @@ class LevelKey {
|
|
2742
2701
|
if (!this.encrypted || !this.uri) {
|
2743
2702
|
return null;
|
2744
2703
|
}
|
2745
|
-
if (
|
2704
|
+
if (this.method === 'AES-128' && this.uri && !this.iv) {
|
2746
2705
|
if (typeof sn !== 'number') {
|
2747
2706
|
// We are fetching decryption data for a initialization segment
|
2748
|
-
// If the segment was encrypted with AES-128
|
2707
|
+
// If the segment was encrypted with AES-128
|
2749
2708
|
// It must have an IV defined. We cannot substitute the Segment Number in.
|
2750
|
-
|
2751
|
-
|
2709
|
+
if (this.method === 'AES-128' && !this.iv) {
|
2710
|
+
logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`);
|
2711
|
+
}
|
2752
2712
|
// Explicitly set sn to resulting value from implicit conversions 'initSegment' values for IV generation.
|
2753
2713
|
sn = 0;
|
2754
2714
|
}
|
@@ -3027,28 +2987,23 @@ function getCodecCompatibleNameLower(lowerCaseCodec, preferManagedMediaSource =
|
|
3027
2987
|
if (CODEC_COMPATIBLE_NAMES[lowerCaseCodec]) {
|
3028
2988
|
return CODEC_COMPATIBLE_NAMES[lowerCaseCodec];
|
3029
2989
|
}
|
2990
|
+
|
2991
|
+
// Idealy fLaC and Opus would be first (spec-compliant) but
|
2992
|
+
// some browsers will report that fLaC is supported then fail.
|
2993
|
+
// see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
|
3030
2994
|
const codecsToCheck = {
|
3031
|
-
// Idealy fLaC and Opus would be first (spec-compliant) but
|
3032
|
-
// some browsers will report that fLaC is supported then fail.
|
3033
|
-
// see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
|
3034
2995
|
flac: ['flac', 'fLaC', 'FLAC'],
|
3035
|
-
opus: ['opus', 'Opus']
|
3036
|
-
// Replace audio codec info if browser does not support mp4a.40.34,
|
3037
|
-
// and demuxer can fallback to 'audio/mpeg' or 'audio/mp4;codecs="mp3"'
|
3038
|
-
'mp4a.40.34': ['mp3']
|
2996
|
+
opus: ['opus', 'Opus']
|
3039
2997
|
}[lowerCaseCodec];
|
3040
2998
|
for (let i = 0; i < codecsToCheck.length; i++) {
|
3041
|
-
var _getMediaSource;
|
3042
2999
|
if (isCodecMediaSourceSupported(codecsToCheck[i], 'audio', preferManagedMediaSource)) {
|
3043
3000
|
CODEC_COMPATIBLE_NAMES[lowerCaseCodec] = codecsToCheck[i];
|
3044
3001
|
return codecsToCheck[i];
|
3045
|
-
} else if (codecsToCheck[i] === 'mp3' && (_getMediaSource = getMediaSource(preferManagedMediaSource)) != null && _getMediaSource.isTypeSupported('audio/mpeg')) {
|
3046
|
-
return '';
|
3047
3002
|
}
|
3048
3003
|
}
|
3049
3004
|
return lowerCaseCodec;
|
3050
3005
|
}
|
3051
|
-
const AUDIO_CODEC_REGEXP = /flac|opus
|
3006
|
+
const AUDIO_CODEC_REGEXP = /flac|opus/i;
|
3052
3007
|
function getCodecCompatibleName(codec, preferManagedMediaSource = true) {
|
3053
3008
|
return codec.replace(AUDIO_CODEC_REGEXP, m => getCodecCompatibleNameLower(m.toLowerCase(), preferManagedMediaSource));
|
3054
3009
|
}
|
@@ -3071,16 +3026,6 @@ function convertAVC1ToAVCOTI(codec) {
|
|
3071
3026
|
}
|
3072
3027
|
return codec;
|
3073
3028
|
}
|
3074
|
-
function getM2TSSupportedAudioTypes(preferManagedMediaSource) {
|
3075
|
-
const MediaSource = getMediaSource(preferManagedMediaSource) || {
|
3076
|
-
isTypeSupported: () => false
|
3077
|
-
};
|
3078
|
-
return {
|
3079
|
-
mpeg: MediaSource.isTypeSupported('audio/mpeg'),
|
3080
|
-
mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
|
3081
|
-
ac3: MediaSource.isTypeSupported('audio/mp4; codecs="ac-3"')
|
3082
|
-
};
|
3083
|
-
}
|
3084
3029
|
|
3085
3030
|
const MASTER_PLAYLIST_REGEX = /#EXT-X-STREAM-INF:([^\r\n]*)(?:[\r\n](?:#[^\r\n]*)?)*([^\r\n]+)|#EXT-X-(SESSION-DATA|SESSION-KEY|DEFINE|CONTENT-STEERING|START):([^\r\n]*)[\r\n]+/g;
|
3086
3031
|
const MASTER_PLAYLIST_MEDIA_REGEX = /#EXT-X-MEDIA:(.*)/g;
|
@@ -4747,47 +4692,7 @@ class LatencyController {
|
|
4747
4692
|
this.currentTime = 0;
|
4748
4693
|
this.stallCount = 0;
|
4749
4694
|
this._latency = null;
|
4750
|
-
this.
|
4751
|
-
const {
|
4752
|
-
media,
|
4753
|
-
levelDetails
|
4754
|
-
} = this;
|
4755
|
-
if (!media || !levelDetails) {
|
4756
|
-
return;
|
4757
|
-
}
|
4758
|
-
this.currentTime = media.currentTime;
|
4759
|
-
const latency = this.computeLatency();
|
4760
|
-
if (latency === null) {
|
4761
|
-
return;
|
4762
|
-
}
|
4763
|
-
this._latency = latency;
|
4764
|
-
|
4765
|
-
// Adapt playbackRate to meet target latency in low-latency mode
|
4766
|
-
const {
|
4767
|
-
lowLatencyMode,
|
4768
|
-
maxLiveSyncPlaybackRate
|
4769
|
-
} = this.config;
|
4770
|
-
if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
|
4771
|
-
return;
|
4772
|
-
}
|
4773
|
-
const targetLatency = this.targetLatency;
|
4774
|
-
if (targetLatency === null) {
|
4775
|
-
return;
|
4776
|
-
}
|
4777
|
-
const distanceFromTarget = latency - targetLatency;
|
4778
|
-
// Only adjust playbackRate when within one target duration of targetLatency
|
4779
|
-
// and more than one second from under-buffering.
|
4780
|
-
// Playback further than one target duration from target can be considered DVR playback.
|
4781
|
-
const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
|
4782
|
-
const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
|
4783
|
-
if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
|
4784
|
-
const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
|
4785
|
-
const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
|
4786
|
-
media.playbackRate = Math.min(max, Math.max(1, rate));
|
4787
|
-
} else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
|
4788
|
-
media.playbackRate = 1;
|
4789
|
-
}
|
4790
|
-
};
|
4695
|
+
this.timeupdateHandler = () => this.timeupdate();
|
4791
4696
|
this.hls = hls;
|
4792
4697
|
this.config = hls.config;
|
4793
4698
|
this.registerListeners();
|
@@ -4879,7 +4784,7 @@ class LatencyController {
|
|
4879
4784
|
this.onMediaDetaching();
|
4880
4785
|
this.levelDetails = null;
|
4881
4786
|
// @ts-ignore
|
4882
|
-
this.hls = null;
|
4787
|
+
this.hls = this.timeupdateHandler = null;
|
4883
4788
|
}
|
4884
4789
|
registerListeners() {
|
4885
4790
|
this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
@@ -4897,11 +4802,11 @@ class LatencyController {
|
|
4897
4802
|
}
|
4898
4803
|
onMediaAttached(event, data) {
|
4899
4804
|
this.media = data.media;
|
4900
|
-
this.media.addEventListener('timeupdate', this.
|
4805
|
+
this.media.addEventListener('timeupdate', this.timeupdateHandler);
|
4901
4806
|
}
|
4902
4807
|
onMediaDetaching() {
|
4903
4808
|
if (this.media) {
|
4904
|
-
this.media.removeEventListener('timeupdate', this.
|
4809
|
+
this.media.removeEventListener('timeupdate', this.timeupdateHandler);
|
4905
4810
|
this.media = null;
|
4906
4811
|
}
|
4907
4812
|
}
|
@@ -4915,10 +4820,10 @@ class LatencyController {
|
|
4915
4820
|
}) {
|
4916
4821
|
this.levelDetails = details;
|
4917
4822
|
if (details.advanced) {
|
4918
|
-
this.
|
4823
|
+
this.timeupdate();
|
4919
4824
|
}
|
4920
4825
|
if (!details.live && this.media) {
|
4921
|
-
this.media.removeEventListener('timeupdate', this.
|
4826
|
+
this.media.removeEventListener('timeupdate', this.timeupdateHandler);
|
4922
4827
|
}
|
4923
4828
|
}
|
4924
4829
|
onError(event, data) {
|
@@ -4928,7 +4833,48 @@ class LatencyController {
|
|
4928
4833
|
}
|
4929
4834
|
this.stallCount++;
|
4930
4835
|
if ((_this$levelDetails = this.levelDetails) != null && _this$levelDetails.live) {
|
4931
|
-
|
4836
|
+
logger.warn('[playback-rate-controller]: Stall detected, adjusting target latency');
|
4837
|
+
}
|
4838
|
+
}
|
4839
|
+
timeupdate() {
|
4840
|
+
const {
|
4841
|
+
media,
|
4842
|
+
levelDetails
|
4843
|
+
} = this;
|
4844
|
+
if (!media || !levelDetails) {
|
4845
|
+
return;
|
4846
|
+
}
|
4847
|
+
this.currentTime = media.currentTime;
|
4848
|
+
const latency = this.computeLatency();
|
4849
|
+
if (latency === null) {
|
4850
|
+
return;
|
4851
|
+
}
|
4852
|
+
this._latency = latency;
|
4853
|
+
|
4854
|
+
// Adapt playbackRate to meet target latency in low-latency mode
|
4855
|
+
const {
|
4856
|
+
lowLatencyMode,
|
4857
|
+
maxLiveSyncPlaybackRate
|
4858
|
+
} = this.config;
|
4859
|
+
if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
|
4860
|
+
return;
|
4861
|
+
}
|
4862
|
+
const targetLatency = this.targetLatency;
|
4863
|
+
if (targetLatency === null) {
|
4864
|
+
return;
|
4865
|
+
}
|
4866
|
+
const distanceFromTarget = latency - targetLatency;
|
4867
|
+
// Only adjust playbackRate when within one target duration of targetLatency
|
4868
|
+
// and more than one second from under-buffering.
|
4869
|
+
// Playback further than one target duration from target can be considered DVR playback.
|
4870
|
+
const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
|
4871
|
+
const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
|
4872
|
+
if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
|
4873
|
+
const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
|
4874
|
+
const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
|
4875
|
+
media.playbackRate = Math.min(max, Math.max(1, rate));
|
4876
|
+
} else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
|
4877
|
+
media.playbackRate = 1;
|
4932
4878
|
}
|
4933
4879
|
}
|
4934
4880
|
estimateLiveEdge() {
|
@@ -5700,13 +5646,18 @@ var ErrorActionFlags = {
|
|
5700
5646
|
MoveAllAlternatesMatchingHDCP: 2,
|
5701
5647
|
SwitchToSDR: 4
|
5702
5648
|
}; // Reserved for future use
|
5703
|
-
class ErrorController
|
5649
|
+
class ErrorController {
|
5704
5650
|
constructor(hls) {
|
5705
|
-
super('error-controller', hls.logger);
|
5706
5651
|
this.hls = void 0;
|
5707
5652
|
this.playlistError = 0;
|
5708
5653
|
this.penalizedRenditions = {};
|
5654
|
+
this.log = void 0;
|
5655
|
+
this.warn = void 0;
|
5656
|
+
this.error = void 0;
|
5709
5657
|
this.hls = hls;
|
5658
|
+
this.log = logger.log.bind(logger, `[info]:`);
|
5659
|
+
this.warn = logger.warn.bind(logger, `[warning]:`);
|
5660
|
+
this.error = logger.error.bind(logger, `[error]:`);
|
5710
5661
|
this.registerListeners();
|
5711
5662
|
}
|
5712
5663
|
registerListeners() {
|
@@ -6058,13 +6009,16 @@ class ErrorController extends Logger {
|
|
6058
6009
|
}
|
6059
6010
|
}
|
6060
6011
|
|
6061
|
-
class BasePlaylistController
|
6012
|
+
class BasePlaylistController {
|
6062
6013
|
constructor(hls, logPrefix) {
|
6063
|
-
super(logPrefix, hls.logger);
|
6064
6014
|
this.hls = void 0;
|
6065
6015
|
this.timer = -1;
|
6066
6016
|
this.requestScheduled = -1;
|
6067
6017
|
this.canLoad = false;
|
6018
|
+
this.log = void 0;
|
6019
|
+
this.warn = void 0;
|
6020
|
+
this.log = logger.log.bind(logger, `${logPrefix}:`);
|
6021
|
+
this.warn = logger.warn.bind(logger, `${logPrefix}:`);
|
6068
6022
|
this.hls = hls;
|
6069
6023
|
}
|
6070
6024
|
destroy() {
|
@@ -6097,7 +6051,7 @@ class BasePlaylistController extends Logger {
|
|
6097
6051
|
try {
|
6098
6052
|
uri = new self.URL(attr.URI, previous.url).href;
|
6099
6053
|
} catch (error) {
|
6100
|
-
|
6054
|
+
logger.warn(`Could not construct new URL for Rendition Report: ${error}`);
|
6101
6055
|
uri = attr.URI || '';
|
6102
6056
|
}
|
6103
6057
|
// Use exact match. Otherwise, the last partial match, if any, will be used
|
@@ -6856,9 +6810,8 @@ function searchDownAndUpList(arr, searchIndex, predicate) {
|
|
6856
6810
|
return -1;
|
6857
6811
|
}
|
6858
6812
|
|
6859
|
-
class AbrController
|
6813
|
+
class AbrController {
|
6860
6814
|
constructor(_hls) {
|
6861
|
-
super('abr', _hls.logger);
|
6862
6815
|
this.hls = void 0;
|
6863
6816
|
this.lastLevelLoadSec = 0;
|
6864
6817
|
this.lastLoadedFragLevel = -1;
|
@@ -6972,7 +6925,7 @@ class AbrController extends Logger {
|
|
6972
6925
|
this.resetEstimator(nextLoadLevelBitrate);
|
6973
6926
|
}
|
6974
6927
|
this.clearTimer();
|
6975
|
-
|
6928
|
+
logger.warn(`[abr] Fragment ${frag.sn}${part ? ' part ' + part.index : ''} of level ${frag.level} is loading too slowly;
|
6976
6929
|
Time to underbuffer: ${bufferStarvationDelay.toFixed(3)} s
|
6977
6930
|
Estimated load time for current fragment: ${fragLoadedDelay.toFixed(3)} s
|
6978
6931
|
Estimated load time for down switch fragment: ${fragLevelNextLoadedDelay.toFixed(3)} s
|
@@ -6992,7 +6945,7 @@ class AbrController extends Logger {
|
|
6992
6945
|
}
|
6993
6946
|
resetEstimator(abrEwmaDefaultEstimate) {
|
6994
6947
|
if (abrEwmaDefaultEstimate) {
|
6995
|
-
|
6948
|
+
logger.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
|
6996
6949
|
this.hls.config.abrEwmaDefaultEstimate = abrEwmaDefaultEstimate;
|
6997
6950
|
}
|
6998
6951
|
this.firstSelection = -1;
|
@@ -7224,7 +7177,7 @@ class AbrController extends Logger {
|
|
7224
7177
|
}
|
7225
7178
|
const firstLevel = this.hls.firstLevel;
|
7226
7179
|
const clamped = Math.min(Math.max(firstLevel, minAutoLevel), maxAutoLevel);
|
7227
|
-
|
7180
|
+
logger.warn(`[abr] Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`);
|
7228
7181
|
return clamped;
|
7229
7182
|
}
|
7230
7183
|
get forcedAutoLevel() {
|
@@ -7309,13 +7262,13 @@ class AbrController extends Logger {
|
|
7309
7262
|
// cap maxLoadingDelay and ensure it is not bigger 'than bitrate test' frag duration
|
7310
7263
|
const maxLoadingDelay = currentFragDuration ? Math.min(currentFragDuration, config.maxLoadingDelay) : config.maxLoadingDelay;
|
7311
7264
|
maxStarvationDelay = maxLoadingDelay - bitrateTestDelay;
|
7312
|
-
|
7265
|
+
logger.info(`[abr] bitrate test took ${Math.round(1000 * bitrateTestDelay)}ms, set first fragment max fetchDuration to ${Math.round(1000 * maxStarvationDelay)} ms`);
|
7313
7266
|
// don't use conservative factor on bitrate test
|
7314
7267
|
bwFactor = bwUpFactor = 1;
|
7315
7268
|
}
|
7316
7269
|
}
|
7317
7270
|
const bestLevel = this.findBestLevel(avgbw, minAutoLevel, maxAutoLevel, bufferStarvationDelay, maxStarvationDelay, bwFactor, bwUpFactor);
|
7318
|
-
|
7271
|
+
logger.info(`[abr] ${bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'}, optimal quality level ${bestLevel}`);
|
7319
7272
|
if (bestLevel > -1) {
|
7320
7273
|
return bestLevel;
|
7321
7274
|
}
|
@@ -7377,7 +7330,7 @@ class AbrController extends Logger {
|
|
7377
7330
|
currentVideoRange = preferHDR ? videoRanges[videoRanges.length - 1] : videoRanges[0];
|
7378
7331
|
currentFrameRate = minFramerate;
|
7379
7332
|
currentBw = Math.max(currentBw, minBitrate);
|
7380
|
-
|
7333
|
+
logger.log(`[abr] picked start tier ${JSON.stringify(startTier)}`);
|
7381
7334
|
} else {
|
7382
7335
|
currentCodecSet = level == null ? void 0 : level.codecSet;
|
7383
7336
|
currentVideoRange = level == null ? void 0 : level.videoRange;
|
@@ -7386,7 +7339,7 @@ class AbrController extends Logger {
|
|
7386
7339
|
const ttfbEstimateSec = this.bwEstimator.getEstimateTTFB() / 1000;
|
7387
7340
|
const levelsSkipped = [];
|
7388
7341
|
for (let i = maxAutoLevel; i >= minAutoLevel; i--) {
|
7389
|
-
var _levelInfo$supportedR
|
7342
|
+
var _levelInfo$supportedR;
|
7390
7343
|
const levelInfo = levels[i];
|
7391
7344
|
const upSwitch = i > selectionBaseLevel;
|
7392
7345
|
if (!levelInfo) {
|
@@ -7401,11 +7354,11 @@ class AbrController extends Logger {
|
|
7401
7354
|
const levels = this.hls.levels;
|
7402
7355
|
const index = levels.indexOf(levelInfo);
|
7403
7356
|
if (decodingInfo.error) {
|
7404
|
-
|
7357
|
+
logger.warn(`[abr] MediaCapabilities decodingInfo error: "${decodingInfo.error}" for level ${index} ${JSON.stringify(decodingInfo)}`);
|
7405
7358
|
} else if (!decodingInfo.supported) {
|
7406
|
-
|
7359
|
+
logger.warn(`[abr] Unsupported MediaCapabilities decodingInfo result for level ${index} ${JSON.stringify(decodingInfo)}`);
|
7407
7360
|
if (index > -1 && levels.length > 1) {
|
7408
|
-
|
7361
|
+
logger.log(`[abr] Removing unsupported level ${index}`);
|
7409
7362
|
this.hls.removeLevel(index);
|
7410
7363
|
}
|
7411
7364
|
}
|
@@ -7417,7 +7370,7 @@ class AbrController extends Logger {
|
|
7417
7370
|
|
7418
7371
|
// skip candidates which change codec-family or video-range,
|
7419
7372
|
// and which decrease or increase frame-rate for up and down-switch respectfully
|
7420
|
-
if (currentCodecSet && levelInfo.codecSet !== currentCodecSet || currentVideoRange && levelInfo.videoRange !== currentVideoRange || upSwitch && currentFrameRate > levelInfo.frameRate || !upSwitch && currentFrameRate > 0 && currentFrameRate < levelInfo.frameRate ||
|
7373
|
+
if (currentCodecSet && levelInfo.codecSet !== currentCodecSet || currentVideoRange && levelInfo.videoRange !== currentVideoRange || upSwitch && currentFrameRate > levelInfo.frameRate || !upSwitch && currentFrameRate > 0 && currentFrameRate < levelInfo.frameRate || levelInfo.supportedResult && !((_levelInfo$supportedR = levelInfo.supportedResult.decodingInfoResults) != null && _levelInfo$supportedR[0].smooth)) {
|
7421
7374
|
levelsSkipped.push(i);
|
7422
7375
|
continue;
|
7423
7376
|
}
|
@@ -7452,9 +7405,9 @@ class AbrController extends Logger {
|
|
7452
7405
|
const forcedAutoLevel = this.forcedAutoLevel;
|
7453
7406
|
if (i !== loadLevel && (forcedAutoLevel === -1 || forcedAutoLevel !== loadLevel)) {
|
7454
7407
|
if (levelsSkipped.length) {
|
7455
|
-
|
7408
|
+
logger.trace(`[abr] Skipped level(s) ${levelsSkipped.join(',')} of ${maxAutoLevel} max with CODECS and VIDEO-RANGE:"${levels[levelsSkipped[0]].codecs}" ${levels[levelsSkipped[0]].videoRange}; not compatible with "${level.codecs}" ${currentVideoRange}`);
|
7456
7409
|
}
|
7457
|
-
|
7410
|
+
logger.info(`[abr] switch candidate:${selectionBaseLevel}->${i} adjustedbw(${Math.round(adjustedbw)})-bitrate=${Math.round(adjustedbw - bitrate)} ttfb:${ttfbEstimateSec.toFixed(1)} avgDuration:${avgDuration.toFixed(1)} maxFetchDuration:${maxFetchDuration.toFixed(1)} fetchDuration:${fetchDuration.toFixed(1)} firstSelection:${firstSelection} codecSet:${currentCodecSet} videoRange:${currentVideoRange} hls.loadLevel:${loadLevel}`);
|
7458
7411
|
}
|
7459
7412
|
if (firstSelection) {
|
7460
7413
|
this.firstSelection = i;
|
@@ -7504,9 +7457,8 @@ class AbrController extends Logger {
|
|
7504
7457
|
* we are limiting the task execution per call stack to exactly one, but scheduling/post-poning further
|
7505
7458
|
* task processing on the next main loop iteration (also known as "next tick" in the Node/JS runtime lingo).
|
7506
7459
|
*/
|
7507
|
-
class TaskLoop
|
7508
|
-
constructor(
|
7509
|
-
super(label, logger);
|
7460
|
+
class TaskLoop {
|
7461
|
+
constructor() {
|
7510
7462
|
this._boundTick = void 0;
|
7511
7463
|
this._tickTimer = null;
|
7512
7464
|
this._tickInterval = null;
|
@@ -8597,8 +8549,8 @@ function createLoaderContext(frag, part = null) {
|
|
8597
8549
|
var _frag$decryptdata;
|
8598
8550
|
let byteRangeStart = start;
|
8599
8551
|
let byteRangeEnd = end;
|
8600
|
-
if (frag.sn === 'initSegment' &&
|
8601
|
-
// MAP segment encrypted with method 'AES-128'
|
8552
|
+
if (frag.sn === 'initSegment' && ((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method) === 'AES-128') {
|
8553
|
+
// MAP segment encrypted with method 'AES-128', when served with HTTP Range,
|
8602
8554
|
// has the unencrypted size specified in the range.
|
8603
8555
|
// Ref: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-6.3.6
|
8604
8556
|
const fragmentLen = end - start;
|
@@ -8631,9 +8583,6 @@ function createGapLoadError(frag, part) {
|
|
8631
8583
|
(part ? part : frag).stats.aborted = true;
|
8632
8584
|
return new LoadError(errorData);
|
8633
8585
|
}
|
8634
|
-
function isMethodFullSegmentAesCbc(method) {
|
8635
|
-
return method === 'AES-128' || method === 'AES-256';
|
8636
|
-
}
|
8637
8586
|
class LoadError extends Error {
|
8638
8587
|
constructor(data) {
|
8639
8588
|
super(data.error.message);
|
@@ -8643,61 +8592,33 @@ class LoadError extends Error {
|
|
8643
8592
|
}
|
8644
8593
|
|
8645
8594
|
class AESCrypto {
|
8646
|
-
constructor(subtle, iv
|
8595
|
+
constructor(subtle, iv) {
|
8647
8596
|
this.subtle = void 0;
|
8648
8597
|
this.aesIV = void 0;
|
8649
|
-
this.aesMode = void 0;
|
8650
8598
|
this.subtle = subtle;
|
8651
8599
|
this.aesIV = iv;
|
8652
|
-
this.aesMode = aesMode;
|
8653
8600
|
}
|
8654
8601
|
decrypt(data, key) {
|
8655
|
-
|
8656
|
-
|
8657
|
-
|
8658
|
-
|
8659
|
-
iv: this.aesIV
|
8660
|
-
}, key, data);
|
8661
|
-
case DecrypterAesMode.ctr:
|
8662
|
-
return this.subtle.decrypt({
|
8663
|
-
name: 'AES-CTR',
|
8664
|
-
counter: this.aesIV,
|
8665
|
-
length: 64
|
8666
|
-
},
|
8667
|
-
//64 : NIST SP800-38A standard suggests that the counter should occupy half of the counter block
|
8668
|
-
key, data);
|
8669
|
-
default:
|
8670
|
-
throw new Error(`[AESCrypto] invalid aes mode ${this.aesMode}`);
|
8671
|
-
}
|
8602
|
+
return this.subtle.decrypt({
|
8603
|
+
name: 'AES-CBC',
|
8604
|
+
iv: this.aesIV
|
8605
|
+
}, key, data);
|
8672
8606
|
}
|
8673
8607
|
}
|
8674
8608
|
|
8675
8609
|
class FastAESKey {
|
8676
|
-
constructor(subtle, key
|
8610
|
+
constructor(subtle, key) {
|
8677
8611
|
this.subtle = void 0;
|
8678
8612
|
this.key = void 0;
|
8679
|
-
this.aesMode = void 0;
|
8680
8613
|
this.subtle = subtle;
|
8681
8614
|
this.key = key;
|
8682
|
-
this.aesMode = aesMode;
|
8683
8615
|
}
|
8684
8616
|
expandKey() {
|
8685
|
-
const subtleAlgoName = getSubtleAlgoName(this.aesMode);
|
8686
8617
|
return this.subtle.importKey('raw', this.key, {
|
8687
|
-
name:
|
8618
|
+
name: 'AES-CBC'
|
8688
8619
|
}, false, ['encrypt', 'decrypt']);
|
8689
8620
|
}
|
8690
8621
|
}
|
8691
|
-
function getSubtleAlgoName(aesMode) {
|
8692
|
-
switch (aesMode) {
|
8693
|
-
case DecrypterAesMode.cbc:
|
8694
|
-
return 'AES-CBC';
|
8695
|
-
case DecrypterAesMode.ctr:
|
8696
|
-
return 'AES-CTR';
|
8697
|
-
default:
|
8698
|
-
throw new Error(`[FastAESKey] invalid aes mode ${aesMode}`);
|
8699
|
-
}
|
8700
|
-
}
|
8701
8622
|
|
8702
8623
|
// PKCS7
|
8703
8624
|
function removePadding(array) {
|
@@ -8947,8 +8868,7 @@ class Decrypter {
|
|
8947
8868
|
this.currentIV = null;
|
8948
8869
|
this.currentResult = null;
|
8949
8870
|
this.useSoftware = void 0;
|
8950
|
-
this.
|
8951
|
-
this.enableSoftwareAES = config.enableSoftwareAES;
|
8871
|
+
this.useSoftware = config.enableSoftwareAES;
|
8952
8872
|
this.removePKCS7Padding = removePKCS7Padding;
|
8953
8873
|
// built in decryptor expects PKCS7 padding
|
8954
8874
|
if (removePKCS7Padding) {
|
@@ -8961,7 +8881,9 @@ class Decrypter {
|
|
8961
8881
|
/* no-op */
|
8962
8882
|
}
|
8963
8883
|
}
|
8964
|
-
|
8884
|
+
if (this.subtle === null) {
|
8885
|
+
this.useSoftware = true;
|
8886
|
+
}
|
8965
8887
|
}
|
8966
8888
|
destroy() {
|
8967
8889
|
this.subtle = null;
|
@@ -8999,10 +8921,10 @@ class Decrypter {
|
|
8999
8921
|
this.softwareDecrypter = null;
|
9000
8922
|
}
|
9001
8923
|
}
|
9002
|
-
decrypt(data, key, iv
|
8924
|
+
decrypt(data, key, iv) {
|
9003
8925
|
if (this.useSoftware) {
|
9004
8926
|
return new Promise((resolve, reject) => {
|
9005
|
-
this.softwareDecrypt(new Uint8Array(data), key, iv
|
8927
|
+
this.softwareDecrypt(new Uint8Array(data), key, iv);
|
9006
8928
|
const decryptResult = this.flush();
|
9007
8929
|
if (decryptResult) {
|
9008
8930
|
resolve(decryptResult.buffer);
|
@@ -9011,21 +8933,17 @@ class Decrypter {
|
|
9011
8933
|
}
|
9012
8934
|
});
|
9013
8935
|
}
|
9014
|
-
return this.webCryptoDecrypt(new Uint8Array(data), key, iv
|
8936
|
+
return this.webCryptoDecrypt(new Uint8Array(data), key, iv);
|
9015
8937
|
}
|
9016
8938
|
|
9017
8939
|
// Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
|
9018
8940
|
// data is handled in the flush() call
|
9019
|
-
softwareDecrypt(data, key, iv
|
8941
|
+
softwareDecrypt(data, key, iv) {
|
9020
8942
|
const {
|
9021
8943
|
currentIV,
|
9022
8944
|
currentResult,
|
9023
8945
|
remainderData
|
9024
8946
|
} = this;
|
9025
|
-
if (aesMode !== DecrypterAesMode.cbc || key.byteLength !== 16) {
|
9026
|
-
logger.warn('SoftwareDecrypt: can only handle AES-128-CBC');
|
9027
|
-
return null;
|
9028
|
-
}
|
9029
8947
|
this.logOnce('JS AES decrypt');
|
9030
8948
|
// The output is staggered during progressive parsing - the current result is cached, and emitted on the next call
|
9031
8949
|
// This is done in order to strip PKCS7 padding, which is found at the end of each segment. We only know we've reached
|
@@ -9058,11 +8976,11 @@ class Decrypter {
|
|
9058
8976
|
}
|
9059
8977
|
return result;
|
9060
8978
|
}
|
9061
|
-
webCryptoDecrypt(data, key, iv
|
8979
|
+
webCryptoDecrypt(data, key, iv) {
|
9062
8980
|
const subtle = this.subtle;
|
9063
8981
|
if (this.key !== key || !this.fastAesKey) {
|
9064
8982
|
this.key = key;
|
9065
|
-
this.fastAesKey = new FastAESKey(subtle, key
|
8983
|
+
this.fastAesKey = new FastAESKey(subtle, key);
|
9066
8984
|
}
|
9067
8985
|
return this.fastAesKey.expandKey().then(aesKey => {
|
9068
8986
|
// decrypt using web crypto
|
@@ -9070,25 +8988,22 @@ class Decrypter {
|
|
9070
8988
|
return Promise.reject(new Error('web crypto not initialized'));
|
9071
8989
|
}
|
9072
8990
|
this.logOnce('WebCrypto AES decrypt');
|
9073
|
-
const crypto = new AESCrypto(subtle, new Uint8Array(iv)
|
8991
|
+
const crypto = new AESCrypto(subtle, new Uint8Array(iv));
|
9074
8992
|
return crypto.decrypt(data.buffer, aesKey);
|
9075
8993
|
}).catch(err => {
|
9076
8994
|
logger.warn(`[decrypter]: WebCrypto Error, disable WebCrypto API, ${err.name}: ${err.message}`);
|
9077
|
-
return this.onWebCryptoError(data, key, iv
|
8995
|
+
return this.onWebCryptoError(data, key, iv);
|
9078
8996
|
});
|
9079
8997
|
}
|
9080
|
-
onWebCryptoError(data, key, iv
|
9081
|
-
|
9082
|
-
|
9083
|
-
|
9084
|
-
|
9085
|
-
|
9086
|
-
|
9087
|
-
if (decryptResult) {
|
9088
|
-
return decryptResult.buffer;
|
9089
|
-
}
|
8998
|
+
onWebCryptoError(data, key, iv) {
|
8999
|
+
this.useSoftware = true;
|
9000
|
+
this.logEnabled = true;
|
9001
|
+
this.softwareDecrypt(data, key, iv);
|
9002
|
+
const decryptResult = this.flush();
|
9003
|
+
if (decryptResult) {
|
9004
|
+
return decryptResult.buffer;
|
9090
9005
|
}
|
9091
|
-
throw new Error('WebCrypto
|
9006
|
+
throw new Error('WebCrypto and softwareDecrypt: failed to decrypt data');
|
9092
9007
|
}
|
9093
9008
|
getValidChunk(data) {
|
9094
9009
|
let currentChunk = data;
|
@@ -9139,7 +9054,7 @@ const State = {
|
|
9139
9054
|
};
|
9140
9055
|
class BaseStreamController extends TaskLoop {
|
9141
9056
|
constructor(hls, fragmentTracker, keyLoader, logPrefix, playlistType) {
|
9142
|
-
super(
|
9057
|
+
super();
|
9143
9058
|
this.hls = void 0;
|
9144
9059
|
this.fragPrevious = null;
|
9145
9060
|
this.fragCurrent = null;
|
@@ -9164,88 +9079,22 @@ class BaseStreamController extends TaskLoop {
|
|
9164
9079
|
this.startFragRequested = false;
|
9165
9080
|
this.decrypter = void 0;
|
9166
9081
|
this.initPTS = [];
|
9167
|
-
this.
|
9168
|
-
|
9169
|
-
|
9170
|
-
|
9171
|
-
|
9172
|
-
mediaBuffer,
|
9173
|
-
state
|
9174
|
-
} = this;
|
9175
|
-
const currentTime = media ? media.currentTime : 0;
|
9176
|
-
const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
|
9177
|
-
this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
|
9178
|
-
if (this.state === State.ENDED) {
|
9179
|
-
this.resetLoadingState();
|
9180
|
-
} else if (fragCurrent) {
|
9181
|
-
// Seeking while frag load is in progress
|
9182
|
-
const tolerance = config.maxFragLookUpTolerance;
|
9183
|
-
const fragStartOffset = fragCurrent.start - tolerance;
|
9184
|
-
const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
|
9185
|
-
// if seeking out of buffered range or into new one
|
9186
|
-
if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
|
9187
|
-
const pastFragment = currentTime > fragEndOffset;
|
9188
|
-
// if the seek position is outside the current fragment range
|
9189
|
-
if (currentTime < fragStartOffset || pastFragment) {
|
9190
|
-
if (pastFragment && fragCurrent.loader) {
|
9191
|
-
this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
|
9192
|
-
fragCurrent.abortRequests();
|
9193
|
-
this.resetLoadingState();
|
9194
|
-
}
|
9195
|
-
this.fragPrevious = null;
|
9196
|
-
}
|
9197
|
-
}
|
9198
|
-
}
|
9199
|
-
if (media) {
|
9200
|
-
// Remove gap fragments
|
9201
|
-
this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
|
9202
|
-
this.lastCurrentTime = currentTime;
|
9203
|
-
}
|
9204
|
-
|
9205
|
-
// in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
|
9206
|
-
if (!this.loadedmetadata && !bufferInfo.len) {
|
9207
|
-
this.nextLoadPosition = this.startPosition = currentTime;
|
9208
|
-
}
|
9209
|
-
|
9210
|
-
// Async tick to speed up processing
|
9211
|
-
this.tickImmediate();
|
9212
|
-
};
|
9213
|
-
this.onMediaEnded = () => {
|
9214
|
-
// reset startPosition and lastCurrentTime to restart playback @ stream beginning
|
9215
|
-
this.startPosition = this.lastCurrentTime = 0;
|
9216
|
-
if (this.playlistType === PlaylistLevelType.MAIN) {
|
9217
|
-
this.hls.trigger(Events.MEDIA_ENDED, {
|
9218
|
-
stalled: false
|
9219
|
-
});
|
9220
|
-
}
|
9221
|
-
};
|
9082
|
+
this.onvseeking = null;
|
9083
|
+
this.onvended = null;
|
9084
|
+
this.logPrefix = '';
|
9085
|
+
this.log = void 0;
|
9086
|
+
this.warn = void 0;
|
9222
9087
|
this.playlistType = playlistType;
|
9088
|
+
this.logPrefix = logPrefix;
|
9089
|
+
this.log = logger.log.bind(logger, `${logPrefix}:`);
|
9090
|
+
this.warn = logger.warn.bind(logger, `${logPrefix}:`);
|
9223
9091
|
this.hls = hls;
|
9224
9092
|
this.fragmentLoader = new FragmentLoader(hls.config);
|
9225
9093
|
this.keyLoader = keyLoader;
|
9226
9094
|
this.fragmentTracker = fragmentTracker;
|
9227
9095
|
this.config = hls.config;
|
9228
9096
|
this.decrypter = new Decrypter(hls.config);
|
9229
|
-
}
|
9230
|
-
registerListeners() {
|
9231
|
-
const {
|
9232
|
-
hls
|
9233
|
-
} = this;
|
9234
|
-
hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
9235
|
-
hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
9236
|
-
hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
9237
9097
|
hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
|
9238
|
-
hls.on(Events.ERROR, this.onError, this);
|
9239
|
-
}
|
9240
|
-
unregisterListeners() {
|
9241
|
-
const {
|
9242
|
-
hls
|
9243
|
-
} = this;
|
9244
|
-
hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
9245
|
-
hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
9246
|
-
hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
9247
|
-
hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
|
9248
|
-
hls.off(Events.ERROR, this.onError, this);
|
9249
9098
|
}
|
9250
9099
|
doTick() {
|
9251
9100
|
this.onTickEnd();
|
@@ -9299,8 +9148,10 @@ class BaseStreamController extends TaskLoop {
|
|
9299
9148
|
}
|
9300
9149
|
onMediaAttached(event, data) {
|
9301
9150
|
const media = this.media = this.mediaBuffer = data.media;
|
9302
|
-
|
9303
|
-
|
9151
|
+
this.onvseeking = this.onMediaSeeking.bind(this);
|
9152
|
+
this.onvended = this.onMediaEnded.bind(this);
|
9153
|
+
media.addEventListener('seeking', this.onvseeking);
|
9154
|
+
media.addEventListener('ended', this.onvended);
|
9304
9155
|
const config = this.config;
|
9305
9156
|
if (this.levels && config.autoStartLoad && this.state === State.STOPPED) {
|
9306
9157
|
this.startLoad(config.startPosition);
|
@@ -9314,9 +9165,10 @@ class BaseStreamController extends TaskLoop {
|
|
9314
9165
|
}
|
9315
9166
|
|
9316
9167
|
// remove video listeners
|
9317
|
-
if (media) {
|
9318
|
-
media.removeEventListener('seeking', this.
|
9319
|
-
media.removeEventListener('ended', this.
|
9168
|
+
if (media && this.onvseeking && this.onvended) {
|
9169
|
+
media.removeEventListener('seeking', this.onvseeking);
|
9170
|
+
media.removeEventListener('ended', this.onvended);
|
9171
|
+
this.onvseeking = this.onvended = null;
|
9320
9172
|
}
|
9321
9173
|
if (this.keyLoader) {
|
9322
9174
|
this.keyLoader.detach();
|
@@ -9326,8 +9178,56 @@ class BaseStreamController extends TaskLoop {
|
|
9326
9178
|
this.fragmentTracker.removeAllFragments();
|
9327
9179
|
this.stopLoad();
|
9328
9180
|
}
|
9329
|
-
|
9330
|
-
|
9181
|
+
onMediaSeeking() {
|
9182
|
+
const {
|
9183
|
+
config,
|
9184
|
+
fragCurrent,
|
9185
|
+
media,
|
9186
|
+
mediaBuffer,
|
9187
|
+
state
|
9188
|
+
} = this;
|
9189
|
+
const currentTime = media ? media.currentTime : 0;
|
9190
|
+
const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
|
9191
|
+
this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
|
9192
|
+
if (this.state === State.ENDED) {
|
9193
|
+
this.resetLoadingState();
|
9194
|
+
} else if (fragCurrent) {
|
9195
|
+
// Seeking while frag load is in progress
|
9196
|
+
const tolerance = config.maxFragLookUpTolerance;
|
9197
|
+
const fragStartOffset = fragCurrent.start - tolerance;
|
9198
|
+
const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
|
9199
|
+
// if seeking out of buffered range or into new one
|
9200
|
+
if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
|
9201
|
+
const pastFragment = currentTime > fragEndOffset;
|
9202
|
+
// if the seek position is outside the current fragment range
|
9203
|
+
if (currentTime < fragStartOffset || pastFragment) {
|
9204
|
+
if (pastFragment && fragCurrent.loader) {
|
9205
|
+
this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
|
9206
|
+
fragCurrent.abortRequests();
|
9207
|
+
this.resetLoadingState();
|
9208
|
+
}
|
9209
|
+
this.fragPrevious = null;
|
9210
|
+
}
|
9211
|
+
}
|
9212
|
+
}
|
9213
|
+
if (media) {
|
9214
|
+
// Remove gap fragments
|
9215
|
+
this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
|
9216
|
+
this.lastCurrentTime = currentTime;
|
9217
|
+
}
|
9218
|
+
|
9219
|
+
// in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
|
9220
|
+
if (!this.loadedmetadata && !bufferInfo.len) {
|
9221
|
+
this.nextLoadPosition = this.startPosition = currentTime;
|
9222
|
+
}
|
9223
|
+
|
9224
|
+
// Async tick to speed up processing
|
9225
|
+
this.tickImmediate();
|
9226
|
+
}
|
9227
|
+
onMediaEnded() {
|
9228
|
+
// reset startPosition and lastCurrentTime to restart playback @ stream beginning
|
9229
|
+
this.startPosition = this.lastCurrentTime = 0;
|
9230
|
+
}
|
9331
9231
|
onManifestLoaded(event, data) {
|
9332
9232
|
this.startTimeOffset = data.startTimeOffset;
|
9333
9233
|
this.initPTS = [];
|
@@ -9337,7 +9237,7 @@ class BaseStreamController extends TaskLoop {
|
|
9337
9237
|
this.stopLoad();
|
9338
9238
|
super.onHandlerDestroying();
|
9339
9239
|
// @ts-ignore
|
9340
|
-
this.hls =
|
9240
|
+
this.hls = null;
|
9341
9241
|
}
|
9342
9242
|
onHandlerDestroyed() {
|
9343
9243
|
this.state = State.STOPPED;
|
@@ -9468,10 +9368,10 @@ class BaseStreamController extends TaskLoop {
|
|
9468
9368
|
const decryptData = frag.decryptdata;
|
9469
9369
|
|
9470
9370
|
// check to see if the payload needs to be decrypted
|
9471
|
-
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv &&
|
9371
|
+
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method === 'AES-128') {
|
9472
9372
|
const startTime = self.performance.now();
|
9473
9373
|
// decrypt init segment data
|
9474
|
-
return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer
|
9374
|
+
return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
|
9475
9375
|
hls.trigger(Events.ERROR, {
|
9476
9376
|
type: ErrorTypes.MEDIA_ERROR,
|
9477
9377
|
details: ErrorDetails.FRAG_DECRYPT_ERROR,
|
@@ -9583,7 +9483,7 @@ class BaseStreamController extends TaskLoop {
|
|
9583
9483
|
}
|
9584
9484
|
let keyLoadingPromise = null;
|
9585
9485
|
if (frag.encrypted && !((_frag$decryptdata = frag.decryptdata) != null && _frag$decryptdata.key)) {
|
9586
|
-
this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.
|
9486
|
+
this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.logPrefix === '[stream-controller]' ? 'level' : 'track'} ${frag.level}`);
|
9587
9487
|
this.state = State.KEY_LOADING;
|
9588
9488
|
this.fragCurrent = frag;
|
9589
9489
|
keyLoadingPromise = this.keyLoader.load(frag).then(keyLoadedData => {
|
@@ -9614,7 +9514,7 @@ class BaseStreamController extends TaskLoop {
|
|
9614
9514
|
const partIndex = this.getNextPart(partList, frag, targetBufferTime);
|
9615
9515
|
if (partIndex > -1) {
|
9616
9516
|
const part = partList[partIndex];
|
9617
|
-
this.log(`Loading part sn: ${frag.sn} p: ${part.index} cc: ${frag.cc} of playlist [${details.startSN}-${details.endSN}] parts [0-${partIndex}-${partList.length - 1}] ${this.
|
9517
|
+
this.log(`Loading part sn: ${frag.sn} p: ${part.index} cc: ${frag.cc} of playlist [${details.startSN}-${details.endSN}] parts [0-${partIndex}-${partList.length - 1}] ${this.logPrefix === '[stream-controller]' ? 'level' : 'track'}: ${frag.level}, target: ${parseFloat(targetBufferTime.toFixed(3))}`);
|
9618
9518
|
this.nextLoadPosition = part.start + part.duration;
|
9619
9519
|
this.state = State.FRAG_LOADING;
|
9620
9520
|
let _result;
|
@@ -9643,7 +9543,7 @@ class BaseStreamController extends TaskLoop {
|
|
9643
9543
|
}
|
9644
9544
|
}
|
9645
9545
|
}
|
9646
|
-
this.log(`Loading fragment ${frag.sn} cc: ${frag.cc} ${details ? 'of [' + details.startSN + '-' + details.endSN + '] ' : ''}${this.
|
9546
|
+
this.log(`Loading fragment ${frag.sn} cc: ${frag.cc} ${details ? 'of [' + details.startSN + '-' + details.endSN + '] ' : ''}${this.logPrefix === '[stream-controller]' ? 'level' : 'track'}: ${frag.level}, target: ${parseFloat(targetBufferTime.toFixed(3))}`);
|
9647
9547
|
// Don't update nextLoadPosition for fragments which are not buffered
|
9648
9548
|
if (isFiniteNumber(frag.sn) && !this.bitrateTest) {
|
9649
9549
|
this.nextLoadPosition = frag.start + frag.duration;
|
@@ -10228,7 +10128,7 @@ class BaseStreamController extends TaskLoop {
|
|
10228
10128
|
errorAction.resolved = true;
|
10229
10129
|
}
|
10230
10130
|
} else {
|
10231
|
-
|
10131
|
+
logger.warn(`${data.details} reached or exceeded max retry (${retryCount})`);
|
10232
10132
|
return;
|
10233
10133
|
}
|
10234
10134
|
} else if ((errorAction == null ? void 0 : errorAction.action) === NetworkErrorAction.SendAlternateToPenaltyBox) {
|
@@ -10637,7 +10537,6 @@ const initPTSFn = (timestamp, timeOffset, initPTS) => {
|
|
10637
10537
|
*/
|
10638
10538
|
function getAudioConfig(observer, data, offset, audioCodec) {
|
10639
10539
|
let adtsObjectType;
|
10640
|
-
let originalAdtsObjectType;
|
10641
10540
|
let adtsExtensionSamplingIndex;
|
10642
10541
|
let adtsChannelConfig;
|
10643
10542
|
let config;
|
@@ -10645,7 +10544,7 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
10645
10544
|
const manifestCodec = audioCodec;
|
10646
10545
|
const adtsSamplingRates = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
|
10647
10546
|
// byte 2
|
10648
|
-
adtsObjectType =
|
10547
|
+
adtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
|
10649
10548
|
const adtsSamplingIndex = (data[offset + 2] & 0x3c) >>> 2;
|
10650
10549
|
if (adtsSamplingIndex > adtsSamplingRates.length - 1) {
|
10651
10550
|
const error = new Error(`invalid ADTS sampling index:${adtsSamplingIndex}`);
|
@@ -10662,8 +10561,8 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
10662
10561
|
// byte 3
|
10663
10562
|
adtsChannelConfig |= (data[offset + 3] & 0xc0) >>> 6;
|
10664
10563
|
logger.log(`manifest codec:${audioCodec}, ADTS type:${adtsObjectType}, samplingIndex:${adtsSamplingIndex}`);
|
10665
|
-
//
|
10666
|
-
if (/firefox
|
10564
|
+
// firefox: freq less than 24kHz = AAC SBR (HE-AAC)
|
10565
|
+
if (/firefox/i.test(userAgent)) {
|
10667
10566
|
if (adtsSamplingIndex >= 6) {
|
10668
10567
|
adtsObjectType = 5;
|
10669
10568
|
config = new Array(4);
|
@@ -10757,7 +10656,6 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
10757
10656
|
samplerate: adtsSamplingRates[adtsSamplingIndex],
|
10758
10657
|
channelCount: adtsChannelConfig,
|
10759
10658
|
codec: 'mp4a.40.' + adtsObjectType,
|
10760
|
-
parsedCodec: 'mp4a.40.' + originalAdtsObjectType,
|
10761
10659
|
manifestCodec
|
10762
10660
|
};
|
10763
10661
|
}
|
@@ -10812,8 +10710,7 @@ function initTrackConfig(track, observer, data, offset, audioCodec) {
|
|
10812
10710
|
track.channelCount = config.channelCount;
|
10813
10711
|
track.codec = config.codec;
|
10814
10712
|
track.manifestCodec = config.manifestCodec;
|
10815
|
-
track.
|
10816
|
-
logger.log(`parsed codec:${track.parsedCodec}, codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
|
10713
|
+
logger.log(`parsed codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
|
10817
10714
|
}
|
10818
10715
|
}
|
10819
10716
|
function getFrameDuration(samplerate) {
|
@@ -11989,7 +11886,7 @@ class SampleAesDecrypter {
|
|
11989
11886
|
});
|
11990
11887
|
}
|
11991
11888
|
decryptBuffer(encryptedData) {
|
11992
|
-
return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer
|
11889
|
+
return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer);
|
11993
11890
|
}
|
11994
11891
|
|
11995
11892
|
// AAC - encrypt all full 16 bytes blocks starting from offset 16
|
@@ -13947,24 +13844,12 @@ class MP4Remuxer {
|
|
13947
13844
|
inputSamples[0].dts = firstDTS;
|
13948
13845
|
inputSamples[0].pts = firstPTS;
|
13949
13846
|
} else {
|
13950
|
-
let isPTSOrderRetained = true;
|
13951
13847
|
for (let i = 0; i < inputSamples.length; i++) {
|
13952
|
-
if (inputSamples[i].dts > firstPTS
|
13848
|
+
if (inputSamples[i].dts > firstPTS) {
|
13953
13849
|
break;
|
13954
13850
|
}
|
13955
|
-
const prevPTS = inputSamples[i].pts;
|
13956
13851
|
inputSamples[i].dts -= delta;
|
13957
13852
|
inputSamples[i].pts -= delta;
|
13958
|
-
|
13959
|
-
// check to see if this sample's PTS order has changed
|
13960
|
-
// relative to the next one
|
13961
|
-
if (i < inputSamples.length - 1) {
|
13962
|
-
const nextSamplePTS = inputSamples[i + 1].pts;
|
13963
|
-
const currentSamplePTS = inputSamples[i].pts;
|
13964
|
-
const currentOrder = nextSamplePTS <= currentSamplePTS;
|
13965
|
-
const prevOrder = nextSamplePTS <= prevPTS;
|
13966
|
-
isPTSOrderRetained = currentOrder == prevOrder;
|
13967
|
-
}
|
13968
13853
|
}
|
13969
13854
|
}
|
13970
13855
|
logger.log(`Video: Initial PTS/DTS adjusted: ${toMsFromMpegTsClock(firstPTS, true)}/${toMsFromMpegTsClock(firstDTS, true)}, delta: ${toMsFromMpegTsClock(delta, true)} ms`);
|
@@ -14245,7 +14130,7 @@ class MP4Remuxer {
|
|
14245
14130
|
logger.warn(`[mp4-remuxer]: Injecting ${missing} audio frame @ ${(nextPts / inputTimeScale).toFixed(3)}s due to ${Math.round(1000 * delta / inputTimeScale)} ms gap.`);
|
14246
14131
|
for (let j = 0; j < missing; j++) {
|
14247
14132
|
const newStamp = Math.max(nextPts, 0);
|
14248
|
-
let fillFrame = AAC.getSilentFrame(track.
|
14133
|
+
let fillFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
|
14249
14134
|
if (!fillFrame) {
|
14250
14135
|
logger.log('[mp4-remuxer]: Unable to get silent frame for given audio codec; duplicating last frame instead.');
|
14251
14136
|
fillFrame = sample.unit.subarray();
|
@@ -14373,7 +14258,7 @@ class MP4Remuxer {
|
|
14373
14258
|
// samples count of this segment's duration
|
14374
14259
|
const nbSamples = Math.ceil((endDTS - startDTS) / frameDuration);
|
14375
14260
|
// silent frame
|
14376
|
-
const silentFrame = AAC.getSilentFrame(track.
|
14261
|
+
const silentFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
|
14377
14262
|
logger.warn('[mp4-remuxer]: remux empty Audio');
|
14378
14263
|
// Can't remux if we can't generate a silent frame...
|
14379
14264
|
if (!silentFrame) {
|
@@ -14767,15 +14652,13 @@ class Transmuxer {
|
|
14767
14652
|
initSegmentData
|
14768
14653
|
} = transmuxConfig;
|
14769
14654
|
const keyData = getEncryptionType(uintData, decryptdata);
|
14770
|
-
if (keyData &&
|
14655
|
+
if (keyData && keyData.method === 'AES-128') {
|
14771
14656
|
const decrypter = this.getDecrypter();
|
14772
|
-
const aesMode = getAesModeFromFullSegmentMethod(keyData.method);
|
14773
|
-
|
14774
14657
|
// Software decryption is synchronous; webCrypto is not
|
14775
14658
|
if (decrypter.isSync()) {
|
14776
14659
|
// Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
|
14777
14660
|
// data is handled in the flush() call
|
14778
|
-
let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer
|
14661
|
+
let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer);
|
14779
14662
|
// For Low-Latency HLS Parts, decrypt in place, since part parsing is expected on push progress
|
14780
14663
|
const loadingParts = chunkMeta.part > -1;
|
14781
14664
|
if (loadingParts) {
|
@@ -14787,7 +14670,7 @@ class Transmuxer {
|
|
14787
14670
|
}
|
14788
14671
|
uintData = new Uint8Array(decryptedData);
|
14789
14672
|
} else {
|
14790
|
-
this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer
|
14673
|
+
this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer).then(decryptedData => {
|
14791
14674
|
// Calling push here is important; if flush() is called while this is still resolving, this ensures that
|
14792
14675
|
// the decrypted data has been transmuxed
|
14793
14676
|
const result = this.push(decryptedData, null, chunkMeta);
|
@@ -15441,7 +15324,14 @@ class TransmuxerInterface {
|
|
15441
15324
|
this.observer = new EventEmitter();
|
15442
15325
|
this.observer.on(Events.FRAG_DECRYPTED, forwardMessage);
|
15443
15326
|
this.observer.on(Events.ERROR, forwardMessage);
|
15444
|
-
const
|
15327
|
+
const MediaSource = getMediaSource(config.preferManagedMediaSource) || {
|
15328
|
+
isTypeSupported: () => false
|
15329
|
+
};
|
15330
|
+
const m2tsTypeSupported = {
|
15331
|
+
mpeg: MediaSource.isTypeSupported('audio/mpeg'),
|
15332
|
+
mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
|
15333
|
+
ac3: MediaSource.isTypeSupported('audio/mp4; codecs="ac-3"')
|
15334
|
+
};
|
15445
15335
|
|
15446
15336
|
// navigator.vendor is not always available in Web Worker
|
15447
15337
|
// refer to https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/navigator
|
@@ -15729,7 +15619,7 @@ const TICK_INTERVAL$2 = 100; // how often to tick in ms
|
|
15729
15619
|
|
15730
15620
|
class AudioStreamController extends BaseStreamController {
|
15731
15621
|
constructor(hls, fragmentTracker, keyLoader) {
|
15732
|
-
super(hls, fragmentTracker, keyLoader, 'audio-stream-controller', PlaylistLevelType.AUDIO);
|
15622
|
+
super(hls, fragmentTracker, keyLoader, '[audio-stream-controller]', PlaylistLevelType.AUDIO);
|
15733
15623
|
this.videoBuffer = null;
|
15734
15624
|
this.videoTrackCC = -1;
|
15735
15625
|
this.waitingVideoCC = -1;
|
@@ -15741,24 +15631,27 @@ class AudioStreamController extends BaseStreamController {
|
|
15741
15631
|
this.flushing = false;
|
15742
15632
|
this.bufferFlushed = false;
|
15743
15633
|
this.cachedTrackLoadedData = null;
|
15744
|
-
this.
|
15634
|
+
this._registerListeners();
|
15745
15635
|
}
|
15746
15636
|
onHandlerDestroying() {
|
15747
|
-
this.
|
15637
|
+
this._unregisterListeners();
|
15748
15638
|
super.onHandlerDestroying();
|
15749
15639
|
this.mainDetails = null;
|
15750
15640
|
this.bufferedTrack = null;
|
15751
15641
|
this.switchingTrack = null;
|
15752
15642
|
}
|
15753
|
-
|
15754
|
-
super.registerListeners();
|
15643
|
+
_registerListeners() {
|
15755
15644
|
const {
|
15756
15645
|
hls
|
15757
15646
|
} = this;
|
15647
|
+
hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
15648
|
+
hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
15649
|
+
hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
15758
15650
|
hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
15759
15651
|
hls.on(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
|
15760
15652
|
hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
15761
15653
|
hls.on(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
|
15654
|
+
hls.on(Events.ERROR, this.onError, this);
|
15762
15655
|
hls.on(Events.BUFFER_RESET, this.onBufferReset, this);
|
15763
15656
|
hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
15764
15657
|
hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
|
@@ -15766,18 +15659,18 @@ class AudioStreamController extends BaseStreamController {
|
|
15766
15659
|
hls.on(Events.INIT_PTS_FOUND, this.onInitPtsFound, this);
|
15767
15660
|
hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
15768
15661
|
}
|
15769
|
-
|
15662
|
+
_unregisterListeners() {
|
15770
15663
|
const {
|
15771
15664
|
hls
|
15772
15665
|
} = this;
|
15773
|
-
|
15774
|
-
|
15775
|
-
|
15776
|
-
super.unregisterListeners();
|
15666
|
+
hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
15667
|
+
hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
15668
|
+
hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
15777
15669
|
hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
15778
15670
|
hls.off(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
|
15779
15671
|
hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
15780
15672
|
hls.off(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
|
15673
|
+
hls.off(Events.ERROR, this.onError, this);
|
15781
15674
|
hls.off(Events.BUFFER_RESET, this.onBufferReset, this);
|
15782
15675
|
hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
15783
15676
|
hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
|
@@ -16509,7 +16402,7 @@ class AudioStreamController extends BaseStreamController {
|
|
16509
16402
|
|
16510
16403
|
class AudioTrackController extends BasePlaylistController {
|
16511
16404
|
constructor(hls) {
|
16512
|
-
super(hls, 'audio-track-controller');
|
16405
|
+
super(hls, '[audio-track-controller]');
|
16513
16406
|
this.tracks = [];
|
16514
16407
|
this.groupIds = null;
|
16515
16408
|
this.tracksInGroup = [];
|
@@ -16828,23 +16721,26 @@ const TICK_INTERVAL$1 = 500; // how often to tick in ms
|
|
16828
16721
|
|
16829
16722
|
class SubtitleStreamController extends BaseStreamController {
|
16830
16723
|
constructor(hls, fragmentTracker, keyLoader) {
|
16831
|
-
super(hls, fragmentTracker, keyLoader, 'subtitle-stream-controller', PlaylistLevelType.SUBTITLE);
|
16724
|
+
super(hls, fragmentTracker, keyLoader, '[subtitle-stream-controller]', PlaylistLevelType.SUBTITLE);
|
16832
16725
|
this.currentTrackId = -1;
|
16833
16726
|
this.tracksBuffered = [];
|
16834
16727
|
this.mainDetails = null;
|
16835
|
-
this.
|
16728
|
+
this._registerListeners();
|
16836
16729
|
}
|
16837
16730
|
onHandlerDestroying() {
|
16838
|
-
this.
|
16731
|
+
this._unregisterListeners();
|
16839
16732
|
super.onHandlerDestroying();
|
16840
16733
|
this.mainDetails = null;
|
16841
16734
|
}
|
16842
|
-
|
16843
|
-
super.registerListeners();
|
16735
|
+
_registerListeners() {
|
16844
16736
|
const {
|
16845
16737
|
hls
|
16846
16738
|
} = this;
|
16739
|
+
hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
16740
|
+
hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
16741
|
+
hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
16847
16742
|
hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
16743
|
+
hls.on(Events.ERROR, this.onError, this);
|
16848
16744
|
hls.on(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
|
16849
16745
|
hls.on(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
|
16850
16746
|
hls.on(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
|
@@ -16852,12 +16748,15 @@ class SubtitleStreamController extends BaseStreamController {
|
|
16852
16748
|
hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
|
16853
16749
|
hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
16854
16750
|
}
|
16855
|
-
|
16856
|
-
super.unregisterListeners();
|
16751
|
+
_unregisterListeners() {
|
16857
16752
|
const {
|
16858
16753
|
hls
|
16859
16754
|
} = this;
|
16755
|
+
hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
16756
|
+
hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
16757
|
+
hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
16860
16758
|
hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
16759
|
+
hls.off(Events.ERROR, this.onError, this);
|
16861
16760
|
hls.off(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
|
16862
16761
|
hls.off(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
|
16863
16762
|
hls.off(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
|
@@ -17084,10 +16983,10 @@ class SubtitleStreamController extends BaseStreamController {
|
|
17084
16983
|
return;
|
17085
16984
|
}
|
17086
16985
|
// check to see if the payload needs to be decrypted
|
17087
|
-
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv &&
|
16986
|
+
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method === 'AES-128') {
|
17088
16987
|
const startTime = performance.now();
|
17089
16988
|
// decrypt the subtitles
|
17090
|
-
this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer
|
16989
|
+
this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
|
17091
16990
|
hls.trigger(Events.ERROR, {
|
17092
16991
|
type: ErrorTypes.MEDIA_ERROR,
|
17093
16992
|
details: ErrorDetails.FRAG_DECRYPT_ERROR,
|
@@ -17221,7 +17120,7 @@ class BufferableInstance {
|
|
17221
17120
|
|
17222
17121
|
class SubtitleTrackController extends BasePlaylistController {
|
17223
17122
|
constructor(hls) {
|
17224
|
-
super(hls, 'subtitle-track-controller');
|
17123
|
+
super(hls, '[subtitle-track-controller]');
|
17225
17124
|
this.media = null;
|
17226
17125
|
this.tracks = [];
|
17227
17126
|
this.groupIds = null;
|
@@ -17230,10 +17129,10 @@ class SubtitleTrackController extends BasePlaylistController {
|
|
17230
17129
|
this.currentTrack = null;
|
17231
17130
|
this.selectDefaultTrack = true;
|
17232
17131
|
this.queuedDefaultTrack = -1;
|
17132
|
+
this.asyncPollTrackChange = () => this.pollTrackChange(0);
|
17233
17133
|
this.useTextTrackPolling = false;
|
17234
17134
|
this.subtitlePollingInterval = -1;
|
17235
17135
|
this._subtitleDisplay = true;
|
17236
|
-
this.asyncPollTrackChange = () => this.pollTrackChange(0);
|
17237
17136
|
this.onTextTracksChanged = () => {
|
17238
17137
|
if (!this.useTextTrackPolling) {
|
17239
17138
|
self.clearInterval(this.subtitlePollingInterval);
|
@@ -17267,7 +17166,6 @@ class SubtitleTrackController extends BasePlaylistController {
|
|
17267
17166
|
this.tracks.length = 0;
|
17268
17167
|
this.tracksInGroup.length = 0;
|
17269
17168
|
this.currentTrack = null;
|
17270
|
-
// @ts-ignore
|
17271
17169
|
this.onTextTracksChanged = this.asyncPollTrackChange = null;
|
17272
17170
|
super.destroy();
|
17273
17171
|
}
|
@@ -17728,9 +17626,8 @@ class BufferOperationQueue {
|
|
17728
17626
|
}
|
17729
17627
|
|
17730
17628
|
const VIDEO_CODEC_PROFILE_REPLACE = /(avc[1234]|hvc1|hev1|dvh[1e]|vp09|av01)(?:\.[^.,]+)+/;
|
17731
|
-
class BufferController
|
17629
|
+
class BufferController {
|
17732
17630
|
constructor(hls) {
|
17733
|
-
super('buffer-controller', hls.logger);
|
17734
17631
|
// The level details used to determine duration, target-duration and live
|
17735
17632
|
this.details = null;
|
17736
17633
|
// cache the self generated object url to detect hijack of video tag
|
@@ -17760,6 +17657,9 @@ class BufferController extends Logger {
|
|
17760
17657
|
this.tracks = {};
|
17761
17658
|
this.pendingTracks = {};
|
17762
17659
|
this.sourceBuffer = void 0;
|
17660
|
+
this.log = void 0;
|
17661
|
+
this.warn = void 0;
|
17662
|
+
this.error = void 0;
|
17763
17663
|
this._onEndStreaming = event => {
|
17764
17664
|
if (!this.hls) {
|
17765
17665
|
return;
|
@@ -17805,11 +17705,15 @@ class BufferController extends Logger {
|
|
17805
17705
|
_objectUrl
|
17806
17706
|
} = this;
|
17807
17707
|
if (mediaSrc !== _objectUrl) {
|
17808
|
-
|
17708
|
+
logger.error(`Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`);
|
17809
17709
|
}
|
17810
17710
|
};
|
17811
17711
|
this.hls = hls;
|
17712
|
+
const logPrefix = '[buffer-controller]';
|
17812
17713
|
this.appendSource = hls.config.preferManagedMediaSource;
|
17714
|
+
this.log = logger.log.bind(logger, logPrefix);
|
17715
|
+
this.warn = logger.warn.bind(logger, logPrefix);
|
17716
|
+
this.error = logger.error.bind(logger, logPrefix);
|
17813
17717
|
this._initSourceBuffer();
|
17814
17718
|
this.registerListeners();
|
17815
17719
|
}
|
@@ -17822,12 +17726,6 @@ class BufferController extends Logger {
|
|
17822
17726
|
this.lastMpegAudioChunk = null;
|
17823
17727
|
// @ts-ignore
|
17824
17728
|
this.hls = null;
|
17825
|
-
// @ts-ignore
|
17826
|
-
this._onMediaSourceOpen = this._onMediaSourceClose = null;
|
17827
|
-
// @ts-ignore
|
17828
|
-
this._onMediaSourceEnded = null;
|
17829
|
-
// @ts-ignore
|
17830
|
-
this._onStartStreaming = this._onEndStreaming = null;
|
17831
17729
|
}
|
17832
17730
|
registerListeners() {
|
17833
17731
|
const {
|
@@ -21092,12 +20990,14 @@ class TimelineController {
|
|
21092
20990
|
this.cea608Parser1 = this.cea608Parser2 = undefined;
|
21093
20991
|
}
|
21094
20992
|
initCea608Parsers() {
|
21095
|
-
|
21096
|
-
|
21097
|
-
|
21098
|
-
|
21099
|
-
|
21100
|
-
|
20993
|
+
if (this.config.enableCEA708Captions && (!this.cea608Parser1 || !this.cea608Parser2)) {
|
20994
|
+
const channel1 = new OutputFilter(this, 'textTrack1');
|
20995
|
+
const channel2 = new OutputFilter(this, 'textTrack2');
|
20996
|
+
const channel3 = new OutputFilter(this, 'textTrack3');
|
20997
|
+
const channel4 = new OutputFilter(this, 'textTrack4');
|
20998
|
+
this.cea608Parser1 = new Cea608Parser(1, channel1, channel2);
|
20999
|
+
this.cea608Parser2 = new Cea608Parser(3, channel3, channel4);
|
21000
|
+
}
|
21101
21001
|
}
|
21102
21002
|
addCues(trackName, startTime, endTime, screen, cueRanges) {
|
21103
21003
|
// skip cues which overlap more than 50% with previously parsed time ranges
|
@@ -21335,7 +21235,7 @@ class TimelineController {
|
|
21335
21235
|
if (inUseTracks != null && inUseTracks.length) {
|
21336
21236
|
const unusedTextTracks = inUseTracks.filter(t => t !== null).map(t => t.label);
|
21337
21237
|
if (unusedTextTracks.length) {
|
21338
|
-
|
21238
|
+
logger.warn(`Media element contains unused subtitle tracks: ${unusedTextTracks.join(', ')}. Replace media element for each source to clear TextTracks and captions menu.`);
|
21339
21239
|
}
|
21340
21240
|
}
|
21341
21241
|
} else if (this.tracks.length) {
|
@@ -21380,23 +21280,26 @@ class TimelineController {
|
|
21380
21280
|
return level == null ? void 0 : level.attrs['CLOSED-CAPTIONS'];
|
21381
21281
|
}
|
21382
21282
|
onFragLoading(event, data) {
|
21283
|
+
this.initCea608Parsers();
|
21284
|
+
const {
|
21285
|
+
cea608Parser1,
|
21286
|
+
cea608Parser2,
|
21287
|
+
lastCc,
|
21288
|
+
lastSn,
|
21289
|
+
lastPartIndex
|
21290
|
+
} = this;
|
21291
|
+
if (!this.enabled || !cea608Parser1 || !cea608Parser2) {
|
21292
|
+
return;
|
21293
|
+
}
|
21383
21294
|
// if this frag isn't contiguous, clear the parser so cues with bad start/end times aren't added to the textTrack
|
21384
|
-
if (
|
21295
|
+
if (data.frag.type === PlaylistLevelType.MAIN) {
|
21385
21296
|
var _data$part$index, _data$part;
|
21386
|
-
const {
|
21387
|
-
cea608Parser1,
|
21388
|
-
cea608Parser2,
|
21389
|
-
lastSn
|
21390
|
-
} = this;
|
21391
|
-
if (!cea608Parser1 || !cea608Parser2) {
|
21392
|
-
return;
|
21393
|
-
}
|
21394
21297
|
const {
|
21395
21298
|
cc,
|
21396
21299
|
sn
|
21397
21300
|
} = data.frag;
|
21398
|
-
const partIndex = (_data$part$index = (_data$part = data.part) == null ? void 0 : _data$part.index) != null ? _data$part$index : -1;
|
21399
|
-
if (!(sn === lastSn + 1 || sn === lastSn && partIndex ===
|
21301
|
+
const partIndex = (_data$part$index = data == null ? void 0 : (_data$part = data.part) == null ? void 0 : _data$part.index) != null ? _data$part$index : -1;
|
21302
|
+
if (!(sn === lastSn + 1 || sn === lastSn && partIndex === lastPartIndex + 1 || cc === lastCc)) {
|
21400
21303
|
cea608Parser1.reset();
|
21401
21304
|
cea608Parser2.reset();
|
21402
21305
|
}
|
@@ -21453,7 +21356,7 @@ class TimelineController {
|
|
21453
21356
|
frag: frag
|
21454
21357
|
});
|
21455
21358
|
}, error => {
|
21456
|
-
|
21359
|
+
logger.log(`Failed to parse IMSC1: ${error}`);
|
21457
21360
|
hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {
|
21458
21361
|
success: false,
|
21459
21362
|
frag: frag,
|
@@ -21494,7 +21397,7 @@ class TimelineController {
|
|
21494
21397
|
this._fallbackToIMSC1(frag, payload);
|
21495
21398
|
}
|
21496
21399
|
// Something went wrong while parsing. Trigger event with success false.
|
21497
|
-
|
21400
|
+
logger.log(`Failed to parse VTT cue: ${error}`);
|
21498
21401
|
if (missingInitPTS && maxAvCC > frag.cc) {
|
21499
21402
|
return;
|
21500
21403
|
}
|
@@ -21555,7 +21458,12 @@ class TimelineController {
|
|
21555
21458
|
this.captionsTracks = {};
|
21556
21459
|
}
|
21557
21460
|
onFragParsingUserdata(event, data) {
|
21558
|
-
|
21461
|
+
this.initCea608Parsers();
|
21462
|
+
const {
|
21463
|
+
cea608Parser1,
|
21464
|
+
cea608Parser2
|
21465
|
+
} = this;
|
21466
|
+
if (!this.enabled || !cea608Parser1 || !cea608Parser2) {
|
21559
21467
|
return;
|
21560
21468
|
}
|
21561
21469
|
const {
|
@@ -21570,12 +21478,9 @@ class TimelineController {
|
|
21570
21478
|
for (let i = 0; i < samples.length; i++) {
|
21571
21479
|
const ccBytes = samples[i].bytes;
|
21572
21480
|
if (ccBytes) {
|
21573
|
-
if (!this.cea608Parser1) {
|
21574
|
-
this.initCea608Parsers();
|
21575
|
-
}
|
21576
21481
|
const ccdatas = this.extractCea608Data(ccBytes);
|
21577
|
-
|
21578
|
-
|
21482
|
+
cea608Parser1.addData(samples[i].pts, ccdatas[0]);
|
21483
|
+
cea608Parser2.addData(samples[i].pts, ccdatas[1]);
|
21579
21484
|
}
|
21580
21485
|
}
|
21581
21486
|
}
|
@@ -21771,7 +21676,7 @@ class CapLevelController {
|
|
21771
21676
|
const hls = this.hls;
|
21772
21677
|
const maxLevel = this.getMaxLevel(levels.length - 1);
|
21773
21678
|
if (maxLevel !== this.autoLevelCapping) {
|
21774
|
-
|
21679
|
+
logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
|
21775
21680
|
}
|
21776
21681
|
hls.autoLevelCapping = maxLevel;
|
21777
21682
|
if (hls.autoLevelCapping > this.autoLevelCapping && this.streamController) {
|
@@ -21949,10 +21854,10 @@ class FPSController {
|
|
21949
21854
|
totalDroppedFrames: droppedFrames
|
21950
21855
|
});
|
21951
21856
|
if (droppedFPS > 0) {
|
21952
|
-
//
|
21857
|
+
// logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
|
21953
21858
|
if (currentDropped > hls.config.fpsDroppedMonitoringThreshold * currentDecoded) {
|
21954
21859
|
let currentLevel = hls.currentLevel;
|
21955
|
-
|
21860
|
+
logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
|
21956
21861
|
if (currentLevel > 0 && (hls.autoLevelCapping === -1 || hls.autoLevelCapping >= currentLevel)) {
|
21957
21862
|
currentLevel = currentLevel - 1;
|
21958
21863
|
hls.trigger(Events.FPS_DROP_LEVEL_CAPPING, {
|
@@ -21984,6 +21889,7 @@ class FPSController {
|
|
21984
21889
|
}
|
21985
21890
|
}
|
21986
21891
|
|
21892
|
+
const LOGGER_PREFIX = '[eme]';
|
21987
21893
|
/**
|
21988
21894
|
* Controller to deal with encrypted media extensions (EME)
|
21989
21895
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/Encrypted_Media_Extensions_API
|
@@ -21991,9 +21897,8 @@ class FPSController {
|
|
21991
21897
|
* @class
|
21992
21898
|
* @constructor
|
21993
21899
|
*/
|
21994
|
-
class EMEController
|
21900
|
+
class EMEController {
|
21995
21901
|
constructor(hls) {
|
21996
|
-
super('eme', hls.logger);
|
21997
21902
|
this.hls = void 0;
|
21998
21903
|
this.config = void 0;
|
21999
21904
|
this.media = null;
|
@@ -22003,100 +21908,12 @@ class EMEController extends Logger {
|
|
22003
21908
|
this.mediaKeySessions = [];
|
22004
21909
|
this.keyIdToKeySessionPromise = {};
|
22005
21910
|
this.setMediaKeysQueue = EMEController.CDMCleanupPromise ? [EMEController.CDMCleanupPromise] : [];
|
22006
|
-
this.onMediaEncrypted =
|
22007
|
-
|
22008
|
-
|
22009
|
-
|
22010
|
-
|
22011
|
-
|
22012
|
-
|
22013
|
-
// Ignore event when initData is null
|
22014
|
-
if (initData === null) {
|
22015
|
-
return;
|
22016
|
-
}
|
22017
|
-
let keyId;
|
22018
|
-
let keySystemDomain;
|
22019
|
-
if (initDataType === 'sinf' && this.config.drmSystems[KeySystems.FAIRPLAY]) {
|
22020
|
-
// Match sinf keyId to playlist skd://keyId=
|
22021
|
-
const json = bin2str(new Uint8Array(initData));
|
22022
|
-
try {
|
22023
|
-
const sinf = base64Decode(JSON.parse(json).sinf);
|
22024
|
-
const tenc = parseSinf(new Uint8Array(sinf));
|
22025
|
-
if (!tenc) {
|
22026
|
-
return;
|
22027
|
-
}
|
22028
|
-
keyId = tenc.subarray(8, 24);
|
22029
|
-
keySystemDomain = KeySystems.FAIRPLAY;
|
22030
|
-
} catch (error) {
|
22031
|
-
this.warn('Failed to parse sinf "encrypted" event message initData');
|
22032
|
-
return;
|
22033
|
-
}
|
22034
|
-
} else {
|
22035
|
-
// Support clear-lead key-session creation (otherwise depend on playlist keys)
|
22036
|
-
const psshInfo = parsePssh(initData);
|
22037
|
-
if (psshInfo === null) {
|
22038
|
-
return;
|
22039
|
-
}
|
22040
|
-
if (psshInfo.version === 0 && psshInfo.systemId === KeySystemIds.WIDEVINE && psshInfo.data) {
|
22041
|
-
keyId = psshInfo.data.subarray(8, 24);
|
22042
|
-
}
|
22043
|
-
keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId);
|
22044
|
-
}
|
22045
|
-
if (!keySystemDomain || !keyId) {
|
22046
|
-
return;
|
22047
|
-
}
|
22048
|
-
const keyIdHex = Hex.hexDump(keyId);
|
22049
|
-
const {
|
22050
|
-
keyIdToKeySessionPromise,
|
22051
|
-
mediaKeySessions
|
22052
|
-
} = this;
|
22053
|
-
let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex];
|
22054
|
-
for (let i = 0; i < mediaKeySessions.length; i++) {
|
22055
|
-
// Match playlist key
|
22056
|
-
const keyContext = mediaKeySessions[i];
|
22057
|
-
const decryptdata = keyContext.decryptdata;
|
22058
|
-
if (decryptdata.pssh || !decryptdata.keyId) {
|
22059
|
-
continue;
|
22060
|
-
}
|
22061
|
-
const oldKeyIdHex = Hex.hexDump(decryptdata.keyId);
|
22062
|
-
if (keyIdHex === oldKeyIdHex || decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1) {
|
22063
|
-
keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex];
|
22064
|
-
delete keyIdToKeySessionPromise[oldKeyIdHex];
|
22065
|
-
decryptdata.pssh = new Uint8Array(initData);
|
22066
|
-
decryptdata.keyId = keyId;
|
22067
|
-
keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = keySessionContextPromise.then(() => {
|
22068
|
-
return this.generateRequestWithPreferredKeySession(keyContext, initDataType, initData, 'encrypted-event-key-match');
|
22069
|
-
});
|
22070
|
-
break;
|
22071
|
-
}
|
22072
|
-
}
|
22073
|
-
if (!keySessionContextPromise) {
|
22074
|
-
// Clear-lead key (not encountered in playlist)
|
22075
|
-
keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = this.getKeySystemSelectionPromise([keySystemDomain]).then(({
|
22076
|
-
keySystem,
|
22077
|
-
mediaKeys
|
22078
|
-
}) => {
|
22079
|
-
var _keySystemToKeySystem;
|
22080
|
-
this.throwIfDestroyed();
|
22081
|
-
const decryptdata = new LevelKey('ISO-23001-7', keyIdHex, (_keySystemToKeySystem = keySystemDomainToKeySystemFormat(keySystem)) != null ? _keySystemToKeySystem : '');
|
22082
|
-
decryptdata.pssh = new Uint8Array(initData);
|
22083
|
-
decryptdata.keyId = keyId;
|
22084
|
-
return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
|
22085
|
-
this.throwIfDestroyed();
|
22086
|
-
const keySessionContext = this.createMediaKeySessionContext({
|
22087
|
-
decryptdata,
|
22088
|
-
keySystem,
|
22089
|
-
mediaKeys
|
22090
|
-
});
|
22091
|
-
return this.generateRequestWithPreferredKeySession(keySessionContext, initDataType, initData, 'encrypted-event-no-match');
|
22092
|
-
});
|
22093
|
-
});
|
22094
|
-
}
|
22095
|
-
keySessionContextPromise.catch(error => this.handleError(error));
|
22096
|
-
};
|
22097
|
-
this.onWaitingForKey = event => {
|
22098
|
-
this.log(`"${event.type}" event`);
|
22099
|
-
};
|
21911
|
+
this.onMediaEncrypted = this._onMediaEncrypted.bind(this);
|
21912
|
+
this.onWaitingForKey = this._onWaitingForKey.bind(this);
|
21913
|
+
this.debug = logger.debug.bind(logger, LOGGER_PREFIX);
|
21914
|
+
this.log = logger.log.bind(logger, LOGGER_PREFIX);
|
21915
|
+
this.warn = logger.warn.bind(logger, LOGGER_PREFIX);
|
21916
|
+
this.error = logger.error.bind(logger, LOGGER_PREFIX);
|
22100
21917
|
this.hls = hls;
|
22101
21918
|
this.config = hls.config;
|
22102
21919
|
this.registerListeners();
|
@@ -22110,9 +21927,9 @@ class EMEController extends Logger {
|
|
22110
21927
|
config.licenseXhrSetup = config.licenseResponseCallback = undefined;
|
22111
21928
|
config.drmSystems = config.drmSystemOptions = {};
|
22112
21929
|
// @ts-ignore
|
22113
|
-
this.hls = this.
|
21930
|
+
this.hls = this.onMediaEncrypted = this.onWaitingForKey = this.keyIdToKeySessionPromise = null;
|
22114
21931
|
// @ts-ignore
|
22115
|
-
this.
|
21932
|
+
this.config = null;
|
22116
21933
|
}
|
22117
21934
|
registerListeners() {
|
22118
21935
|
this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
@@ -22376,6 +22193,100 @@ class EMEController extends Logger {
|
|
22376
22193
|
}
|
22377
22194
|
return this.attemptKeySystemAccess(keySystemsToAttempt);
|
22378
22195
|
}
|
22196
|
+
_onMediaEncrypted(event) {
|
22197
|
+
const {
|
22198
|
+
initDataType,
|
22199
|
+
initData
|
22200
|
+
} = event;
|
22201
|
+
this.debug(`"${event.type}" event: init data type: "${initDataType}"`);
|
22202
|
+
|
22203
|
+
// Ignore event when initData is null
|
22204
|
+
if (initData === null) {
|
22205
|
+
return;
|
22206
|
+
}
|
22207
|
+
let keyId;
|
22208
|
+
let keySystemDomain;
|
22209
|
+
if (initDataType === 'sinf' && this.config.drmSystems[KeySystems.FAIRPLAY]) {
|
22210
|
+
// Match sinf keyId to playlist skd://keyId=
|
22211
|
+
const json = bin2str(new Uint8Array(initData));
|
22212
|
+
try {
|
22213
|
+
const sinf = base64Decode(JSON.parse(json).sinf);
|
22214
|
+
const tenc = parseSinf(new Uint8Array(sinf));
|
22215
|
+
if (!tenc) {
|
22216
|
+
return;
|
22217
|
+
}
|
22218
|
+
keyId = tenc.subarray(8, 24);
|
22219
|
+
keySystemDomain = KeySystems.FAIRPLAY;
|
22220
|
+
} catch (error) {
|
22221
|
+
this.warn('Failed to parse sinf "encrypted" event message initData');
|
22222
|
+
return;
|
22223
|
+
}
|
22224
|
+
} else {
|
22225
|
+
// Support clear-lead key-session creation (otherwise depend on playlist keys)
|
22226
|
+
const psshInfo = parsePssh(initData);
|
22227
|
+
if (psshInfo === null) {
|
22228
|
+
return;
|
22229
|
+
}
|
22230
|
+
if (psshInfo.version === 0 && psshInfo.systemId === KeySystemIds.WIDEVINE && psshInfo.data) {
|
22231
|
+
keyId = psshInfo.data.subarray(8, 24);
|
22232
|
+
}
|
22233
|
+
keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId);
|
22234
|
+
}
|
22235
|
+
if (!keySystemDomain || !keyId) {
|
22236
|
+
return;
|
22237
|
+
}
|
22238
|
+
const keyIdHex = Hex.hexDump(keyId);
|
22239
|
+
const {
|
22240
|
+
keyIdToKeySessionPromise,
|
22241
|
+
mediaKeySessions
|
22242
|
+
} = this;
|
22243
|
+
let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex];
|
22244
|
+
for (let i = 0; i < mediaKeySessions.length; i++) {
|
22245
|
+
// Match playlist key
|
22246
|
+
const keyContext = mediaKeySessions[i];
|
22247
|
+
const decryptdata = keyContext.decryptdata;
|
22248
|
+
if (decryptdata.pssh || !decryptdata.keyId) {
|
22249
|
+
continue;
|
22250
|
+
}
|
22251
|
+
const oldKeyIdHex = Hex.hexDump(decryptdata.keyId);
|
22252
|
+
if (keyIdHex === oldKeyIdHex || decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1) {
|
22253
|
+
keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex];
|
22254
|
+
delete keyIdToKeySessionPromise[oldKeyIdHex];
|
22255
|
+
decryptdata.pssh = new Uint8Array(initData);
|
22256
|
+
decryptdata.keyId = keyId;
|
22257
|
+
keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = keySessionContextPromise.then(() => {
|
22258
|
+
return this.generateRequestWithPreferredKeySession(keyContext, initDataType, initData, 'encrypted-event-key-match');
|
22259
|
+
});
|
22260
|
+
break;
|
22261
|
+
}
|
22262
|
+
}
|
22263
|
+
if (!keySessionContextPromise) {
|
22264
|
+
// Clear-lead key (not encountered in playlist)
|
22265
|
+
keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = this.getKeySystemSelectionPromise([keySystemDomain]).then(({
|
22266
|
+
keySystem,
|
22267
|
+
mediaKeys
|
22268
|
+
}) => {
|
22269
|
+
var _keySystemToKeySystem;
|
22270
|
+
this.throwIfDestroyed();
|
22271
|
+
const decryptdata = new LevelKey('ISO-23001-7', keyIdHex, (_keySystemToKeySystem = keySystemDomainToKeySystemFormat(keySystem)) != null ? _keySystemToKeySystem : '');
|
22272
|
+
decryptdata.pssh = new Uint8Array(initData);
|
22273
|
+
decryptdata.keyId = keyId;
|
22274
|
+
return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
|
22275
|
+
this.throwIfDestroyed();
|
22276
|
+
const keySessionContext = this.createMediaKeySessionContext({
|
22277
|
+
decryptdata,
|
22278
|
+
keySystem,
|
22279
|
+
mediaKeys
|
22280
|
+
});
|
22281
|
+
return this.generateRequestWithPreferredKeySession(keySessionContext, initDataType, initData, 'encrypted-event-no-match');
|
22282
|
+
});
|
22283
|
+
});
|
22284
|
+
}
|
22285
|
+
keySessionContextPromise.catch(error => this.handleError(error));
|
22286
|
+
}
|
22287
|
+
_onWaitingForKey(event) {
|
22288
|
+
this.log(`"${event.type}" event`);
|
22289
|
+
}
|
22379
22290
|
attemptSetMediaKeys(keySystem, mediaKeys) {
|
22380
22291
|
const queue = this.setMediaKeysQueue.slice();
|
22381
22292
|
this.log(`Setting media-keys for "${keySystem}"`);
|
@@ -22968,6 +22879,20 @@ class SfItem {
|
|
22968
22879
|
}
|
22969
22880
|
}
|
22970
22881
|
|
22882
|
+
/**
|
22883
|
+
* A class to represent structured field tokens when `Symbol` is not available.
|
22884
|
+
*
|
22885
|
+
* @group Structured Field
|
22886
|
+
*
|
22887
|
+
* @beta
|
22888
|
+
*/
|
22889
|
+
class SfToken {
|
22890
|
+
constructor(description) {
|
22891
|
+
this.description = void 0;
|
22892
|
+
this.description = description;
|
22893
|
+
}
|
22894
|
+
}
|
22895
|
+
|
22971
22896
|
const DICT = 'Dict';
|
22972
22897
|
|
22973
22898
|
function format(value) {
|
@@ -22991,27 +22916,29 @@ function throwError(action, src, type, cause) {
|
|
22991
22916
|
});
|
22992
22917
|
}
|
22993
22918
|
|
22994
|
-
|
22995
|
-
return throwError('serialize', src, type, cause);
|
22996
|
-
}
|
22919
|
+
const BARE_ITEM = 'Bare Item';
|
22997
22920
|
|
22998
|
-
|
22999
|
-
|
23000
|
-
|
23001
|
-
|
23002
|
-
|
23003
|
-
|
23004
|
-
|
23005
|
-
|
23006
|
-
|
23007
|
-
|
23008
|
-
this.description = description;
|
23009
|
-
}
|
22921
|
+
const BOOLEAN = 'Boolean';
|
22922
|
+
|
22923
|
+
const BYTES = 'Byte Sequence';
|
22924
|
+
|
22925
|
+
const DECIMAL = 'Decimal';
|
22926
|
+
|
22927
|
+
const INTEGER = 'Integer';
|
22928
|
+
|
22929
|
+
function isInvalidInt(value) {
|
22930
|
+
return value < -999999999999999 || 999999999999999 < value;
|
23010
22931
|
}
|
23011
22932
|
|
23012
|
-
const
|
22933
|
+
const STRING_REGEX = /[\x00-\x1f\x7f]+/; // eslint-disable-line no-control-regex
|
23013
22934
|
|
23014
|
-
const
|
22935
|
+
const TOKEN = 'Token';
|
22936
|
+
|
22937
|
+
const KEY = 'Key';
|
22938
|
+
|
22939
|
+
function serializeError(src, type, cause) {
|
22940
|
+
return throwError('serialize', src, type, cause);
|
22941
|
+
}
|
23015
22942
|
|
23016
22943
|
// 4.1.9. Serializing a Boolean
|
23017
22944
|
//
|
@@ -23050,8 +22977,6 @@ function base64encode(binary) {
|
|
23050
22977
|
return btoa(String.fromCharCode(...binary));
|
23051
22978
|
}
|
23052
22979
|
|
23053
|
-
const BYTES = 'Byte Sequence';
|
23054
|
-
|
23055
22980
|
// 4.1.8. Serializing a Byte Sequence
|
23056
22981
|
//
|
23057
22982
|
// Given a Byte Sequence as input_bytes, return an ASCII string suitable
|
@@ -23083,12 +23008,6 @@ function serializeByteSequence(value) {
|
|
23083
23008
|
return `:${base64encode(value)}:`;
|
23084
23009
|
}
|
23085
23010
|
|
23086
|
-
const INTEGER = 'Integer';
|
23087
|
-
|
23088
|
-
function isInvalidInt(value) {
|
23089
|
-
return value < -999999999999999 || 999999999999999 < value;
|
23090
|
-
}
|
23091
|
-
|
23092
23011
|
// 4.1.4. Serializing an Integer
|
23093
23012
|
//
|
23094
23013
|
// Given an Integer as input_integer, return an ASCII string suitable
|
@@ -23154,8 +23073,6 @@ function roundToEven(value, precision) {
|
|
23154
23073
|
}
|
23155
23074
|
}
|
23156
23075
|
|
23157
|
-
const DECIMAL = 'Decimal';
|
23158
|
-
|
23159
23076
|
// 4.1.5. Serializing a Decimal
|
23160
23077
|
//
|
23161
23078
|
// Given a decimal number as input_decimal, return an ASCII string
|
@@ -23201,8 +23118,6 @@ function serializeDecimal(value) {
|
|
23201
23118
|
|
23202
23119
|
const STRING = 'String';
|
23203
23120
|
|
23204
|
-
const STRING_REGEX = /[\x00-\x1f\x7f]+/; // eslint-disable-line no-control-regex
|
23205
|
-
|
23206
23121
|
// 4.1.6. Serializing a String
|
23207
23122
|
//
|
23208
23123
|
// Given a String as input_string, return an ASCII string suitable for
|
@@ -23238,8 +23153,6 @@ function symbolToStr(symbol) {
|
|
23238
23153
|
return symbol.description || symbol.toString().slice(7, -1);
|
23239
23154
|
}
|
23240
23155
|
|
23241
|
-
const TOKEN = 'Token';
|
23242
|
-
|
23243
23156
|
function serializeToken(token) {
|
23244
23157
|
const value = symbolToStr(token);
|
23245
23158
|
if (/^([a-zA-Z*])([!#$%&'*+\-.^_`|~\w:/]*)$/.test(value) === false) {
|
@@ -23307,8 +23220,6 @@ function serializeBareItem(value) {
|
|
23307
23220
|
}
|
23308
23221
|
}
|
23309
23222
|
|
23310
|
-
const KEY = 'Key';
|
23311
|
-
|
23312
23223
|
// 4.1.1.3. Serializing a Key
|
23313
23224
|
//
|
23314
23225
|
// Given a key as input_key, return an ASCII string suitable for use in
|
@@ -23550,6 +23461,36 @@ function urlToRelativePath(url, base) {
|
|
23550
23461
|
return toPath.join('/');
|
23551
23462
|
}
|
23552
23463
|
|
23464
|
+
/**
|
23465
|
+
* Generate a random v4 UUID
|
23466
|
+
*
|
23467
|
+
* @returns A random v4 UUID
|
23468
|
+
*
|
23469
|
+
* @group Utils
|
23470
|
+
*
|
23471
|
+
* @beta
|
23472
|
+
*/
|
23473
|
+
function uuid() {
|
23474
|
+
try {
|
23475
|
+
return crypto.randomUUID();
|
23476
|
+
} catch (error) {
|
23477
|
+
try {
|
23478
|
+
const url = URL.createObjectURL(new Blob());
|
23479
|
+
const uuid = url.toString();
|
23480
|
+
URL.revokeObjectURL(url);
|
23481
|
+
return uuid.slice(uuid.lastIndexOf('/') + 1);
|
23482
|
+
} catch (error) {
|
23483
|
+
let dt = new Date().getTime();
|
23484
|
+
const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
|
23485
|
+
const r = (dt + Math.random() * 16) % 16 | 0;
|
23486
|
+
dt = Math.floor(dt / 16);
|
23487
|
+
return (c == 'x' ? r : r & 0x3 | 0x8).toString(16);
|
23488
|
+
});
|
23489
|
+
return uuid;
|
23490
|
+
}
|
23491
|
+
}
|
23492
|
+
}
|
23493
|
+
|
23553
23494
|
const toRounded = value => Math.round(value);
|
23554
23495
|
const toUrlSafe = (value, options) => {
|
23555
23496
|
if (options != null && options.baseUrl) {
|
@@ -23775,36 +23716,6 @@ function appendCmcdQuery(url, cmcd, options) {
|
|
23775
23716
|
return `${url}${separator}${query}`;
|
23776
23717
|
}
|
23777
23718
|
|
23778
|
-
/**
|
23779
|
-
* Generate a random v4 UUID
|
23780
|
-
*
|
23781
|
-
* @returns A random v4 UUID
|
23782
|
-
*
|
23783
|
-
* @group Utils
|
23784
|
-
*
|
23785
|
-
* @beta
|
23786
|
-
*/
|
23787
|
-
function uuid() {
|
23788
|
-
try {
|
23789
|
-
return crypto.randomUUID();
|
23790
|
-
} catch (error) {
|
23791
|
-
try {
|
23792
|
-
const url = URL.createObjectURL(new Blob());
|
23793
|
-
const uuid = url.toString();
|
23794
|
-
URL.revokeObjectURL(url);
|
23795
|
-
return uuid.slice(uuid.lastIndexOf('/') + 1);
|
23796
|
-
} catch (error) {
|
23797
|
-
let dt = new Date().getTime();
|
23798
|
-
const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
|
23799
|
-
const r = (dt + Math.random() * 16) % 16 | 0;
|
23800
|
-
dt = Math.floor(dt / 16);
|
23801
|
-
return (c == 'x' ? r : r & 0x3 | 0x8).toString(16);
|
23802
|
-
});
|
23803
|
-
return uuid;
|
23804
|
-
}
|
23805
|
-
}
|
23806
|
-
}
|
23807
|
-
|
23808
23719
|
/**
|
23809
23720
|
* Controller to deal with Common Media Client Data (CMCD)
|
23810
23721
|
* @see https://cdn.cta.tech/cta/media/media/resources/standards/pdfs/cta-5004-final.pdf
|
@@ -23868,12 +23779,6 @@ class CMCDController {
|
|
23868
23779
|
data.tb = this.getTopBandwidth(ot) / 1000;
|
23869
23780
|
data.bl = this.getBufferLength(ot);
|
23870
23781
|
}
|
23871
|
-
const next = this.getNextFrag(fragment);
|
23872
|
-
if (next) {
|
23873
|
-
if (next.url && next.url !== fragment.url) {
|
23874
|
-
data.nor = next.url;
|
23875
|
-
}
|
23876
|
-
}
|
23877
23782
|
this.apply(context, data);
|
23878
23783
|
} catch (error) {
|
23879
23784
|
logger.warn('Could not generate segment CMCD data.', error);
|
@@ -23966,7 +23871,7 @@ class CMCDController {
|
|
23966
23871
|
data.su = this.buffering;
|
23967
23872
|
}
|
23968
23873
|
|
23969
|
-
// TODO: Implement rtp, nrr, dl
|
23874
|
+
// TODO: Implement rtp, nrr, nor, dl
|
23970
23875
|
|
23971
23876
|
const {
|
23972
23877
|
includeKeys
|
@@ -23977,28 +23882,15 @@ class CMCDController {
|
|
23977
23882
|
return acc;
|
23978
23883
|
}, {});
|
23979
23884
|
}
|
23980
|
-
const options = {
|
23981
|
-
baseUrl: context.url
|
23982
|
-
};
|
23983
23885
|
if (this.useHeaders) {
|
23984
23886
|
if (!context.headers) {
|
23985
23887
|
context.headers = {};
|
23986
23888
|
}
|
23987
|
-
appendCmcdHeaders(context.headers, data
|
23889
|
+
appendCmcdHeaders(context.headers, data);
|
23988
23890
|
} else {
|
23989
|
-
context.url = appendCmcdQuery(context.url, data
|
23891
|
+
context.url = appendCmcdQuery(context.url, data);
|
23990
23892
|
}
|
23991
23893
|
}
|
23992
|
-
getNextFrag(fragment) {
|
23993
|
-
var _this$hls$levels$frag;
|
23994
|
-
const levelDetails = (_this$hls$levels$frag = this.hls.levels[fragment.level]) == null ? void 0 : _this$hls$levels$frag.details;
|
23995
|
-
if (levelDetails) {
|
23996
|
-
const index = fragment.sn - levelDetails.startSN;
|
23997
|
-
return levelDetails.fragments[index + 1];
|
23998
|
-
}
|
23999
|
-
return undefined;
|
24000
|
-
}
|
24001
|
-
|
24002
23894
|
/**
|
24003
23895
|
* The CMCD object type.
|
24004
23896
|
*/
|
@@ -24127,10 +24019,10 @@ class CMCDController {
|
|
24127
24019
|
}
|
24128
24020
|
|
24129
24021
|
const PATHWAY_PENALTY_DURATION_MS = 300000;
|
24130
|
-
class ContentSteeringController
|
24022
|
+
class ContentSteeringController {
|
24131
24023
|
constructor(hls) {
|
24132
|
-
super('content-steering', hls.logger);
|
24133
24024
|
this.hls = void 0;
|
24025
|
+
this.log = void 0;
|
24134
24026
|
this.loader = null;
|
24135
24027
|
this.uri = null;
|
24136
24028
|
this.pathwayId = '.';
|
@@ -24145,6 +24037,7 @@ class ContentSteeringController extends Logger {
|
|
24145
24037
|
this.subtitleTracks = null;
|
24146
24038
|
this.penalizedPathways = {};
|
24147
24039
|
this.hls = hls;
|
24040
|
+
this.log = logger.log.bind(logger, `[content-steering]:`);
|
24148
24041
|
this.registerListeners();
|
24149
24042
|
}
|
24150
24043
|
registerListeners() {
|
@@ -24268,7 +24161,7 @@ class ContentSteeringController extends Logger {
|
|
24268
24161
|
errorAction.resolved = this.pathwayId !== errorPathway;
|
24269
24162
|
}
|
24270
24163
|
if (!errorAction.resolved) {
|
24271
|
-
|
24164
|
+
logger.warn(`Could not resolve ${data.details} ("${data.error.message}") with content-steering for Pathway: ${errorPathway} levels: ${levels ? levels.length : levels} priorities: ${JSON.stringify(pathwayPriority)} penalized: ${JSON.stringify(this.penalizedPathways)}`);
|
24272
24165
|
}
|
24273
24166
|
}
|
24274
24167
|
}
|
@@ -24439,7 +24332,7 @@ class ContentSteeringController extends Logger {
|
|
24439
24332
|
onSuccess: (response, stats, context, networkDetails) => {
|
24440
24333
|
this.log(`Loaded steering manifest: "${url}"`);
|
24441
24334
|
const steeringData = response.data;
|
24442
|
-
if (
|
24335
|
+
if (steeringData.VERSION !== 1) {
|
24443
24336
|
this.log(`Steering VERSION ${steeringData.VERSION} not supported!`);
|
24444
24337
|
return;
|
24445
24338
|
}
|
@@ -25409,7 +25302,7 @@ function timelineConfig() {
|
|
25409
25302
|
/**
|
25410
25303
|
* @ignore
|
25411
25304
|
*/
|
25412
|
-
function mergeConfig(defaultConfig, userConfig
|
25305
|
+
function mergeConfig(defaultConfig, userConfig) {
|
25413
25306
|
if ((userConfig.liveSyncDurationCount || userConfig.liveMaxLatencyDurationCount) && (userConfig.liveSyncDuration || userConfig.liveMaxLatencyDuration)) {
|
25414
25307
|
throw new Error("Illegal hls.js config: don't mix up liveSyncDurationCount/liveMaxLatencyDurationCount and liveSyncDuration/liveMaxLatencyDuration");
|
25415
25308
|
}
|
@@ -25479,7 +25372,7 @@ function deepCpy(obj) {
|
|
25479
25372
|
/**
|
25480
25373
|
* @ignore
|
25481
25374
|
*/
|
25482
|
-
function enableStreamingMode(config
|
25375
|
+
function enableStreamingMode(config) {
|
25483
25376
|
const currentLoader = config.loader;
|
25484
25377
|
if (currentLoader !== FetchLoader && currentLoader !== XhrLoader) {
|
25485
25378
|
// If a developer has configured their own loader, respect that choice
|
@@ -25496,9 +25389,10 @@ function enableStreamingMode(config, logger) {
|
|
25496
25389
|
}
|
25497
25390
|
}
|
25498
25391
|
|
25392
|
+
let chromeOrFirefox;
|
25499
25393
|
class LevelController extends BasePlaylistController {
|
25500
25394
|
constructor(hls, contentSteeringController) {
|
25501
|
-
super(hls, 'level-controller');
|
25395
|
+
super(hls, '[level-controller]');
|
25502
25396
|
this._levels = [];
|
25503
25397
|
this._firstLevel = -1;
|
25504
25398
|
this._maxAutoLevel = -1;
|
@@ -25569,15 +25463,23 @@ class LevelController extends BasePlaylistController {
|
|
25569
25463
|
let videoCodecFound = false;
|
25570
25464
|
let audioCodecFound = false;
|
25571
25465
|
data.levels.forEach(levelParsed => {
|
25572
|
-
var _videoCodec;
|
25466
|
+
var _audioCodec, _videoCodec;
|
25573
25467
|
const attributes = levelParsed.attrs;
|
25468
|
+
|
25469
|
+
// erase audio codec info if browser does not support mp4a.40.34.
|
25470
|
+
// demuxer will autodetect codec and fallback to mpeg/audio
|
25574
25471
|
let {
|
25575
25472
|
audioCodec,
|
25576
25473
|
videoCodec
|
25577
25474
|
} = levelParsed;
|
25475
|
+
if (((_audioCodec = audioCodec) == null ? void 0 : _audioCodec.indexOf('mp4a.40.34')) !== -1) {
|
25476
|
+
chromeOrFirefox || (chromeOrFirefox = /chrome|firefox/i.test(navigator.userAgent));
|
25477
|
+
if (chromeOrFirefox) {
|
25478
|
+
levelParsed.audioCodec = audioCodec = undefined;
|
25479
|
+
}
|
25480
|
+
}
|
25578
25481
|
if (audioCodec) {
|
25579
|
-
|
25580
|
-
levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource) || undefined;
|
25482
|
+
levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource);
|
25581
25483
|
}
|
25582
25484
|
if (((_videoCodec = videoCodec) == null ? void 0 : _videoCodec.indexOf('avc1')) === 0) {
|
25583
25485
|
videoCodec = levelParsed.videoCodec = convertAVC1ToAVCOTI(videoCodec);
|
@@ -26163,8 +26065,6 @@ class KeyLoader {
|
|
26163
26065
|
}
|
26164
26066
|
return this.loadKeyEME(keyInfo, frag);
|
26165
26067
|
case 'AES-128':
|
26166
|
-
case 'AES-256':
|
26167
|
-
case 'AES-256-CTR':
|
26168
26068
|
return this.loadKeyHTTP(keyInfo, frag);
|
26169
26069
|
default:
|
26170
26070
|
return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`Key supplied with unsupported METHOD: "${decryptdata.method}"`)));
|
@@ -26302,9 +26202,8 @@ const STALL_MINIMUM_DURATION_MS = 250;
|
|
26302
26202
|
const MAX_START_GAP_JUMP = 2.0;
|
26303
26203
|
const SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1;
|
26304
26204
|
const SKIP_BUFFER_RANGE_START = 0.05;
|
26305
|
-
class GapController
|
26205
|
+
class GapController {
|
26306
26206
|
constructor(config, media, fragmentTracker, hls) {
|
26307
|
-
super('gap-controller', hls.logger);
|
26308
26207
|
this.config = void 0;
|
26309
26208
|
this.media = null;
|
26310
26209
|
this.fragmentTracker = void 0;
|
@@ -26314,7 +26213,6 @@ class GapController extends Logger {
|
|
26314
26213
|
this.stalled = null;
|
26315
26214
|
this.moved = false;
|
26316
26215
|
this.seeking = false;
|
26317
|
-
this.ended = 0;
|
26318
26216
|
this.config = config;
|
26319
26217
|
this.media = media;
|
26320
26218
|
this.fragmentTracker = fragmentTracker;
|
@@ -26332,7 +26230,7 @@ class GapController extends Logger {
|
|
26332
26230
|
*
|
26333
26231
|
* @param lastCurrentTime - Previously read playhead position
|
26334
26232
|
*/
|
26335
|
-
poll(lastCurrentTime, activeFrag
|
26233
|
+
poll(lastCurrentTime, activeFrag) {
|
26336
26234
|
const {
|
26337
26235
|
config,
|
26338
26236
|
media,
|
@@ -26351,7 +26249,6 @@ class GapController extends Logger {
|
|
26351
26249
|
|
26352
26250
|
// The playhead is moving, no-op
|
26353
26251
|
if (currentTime !== lastCurrentTime) {
|
26354
|
-
this.ended = 0;
|
26355
26252
|
this.moved = true;
|
26356
26253
|
if (!seeking) {
|
26357
26254
|
this.nudgeRetry = 0;
|
@@ -26360,7 +26257,7 @@ class GapController extends Logger {
|
|
26360
26257
|
// The playhead is now moving, but was previously stalled
|
26361
26258
|
if (this.stallReported) {
|
26362
26259
|
const _stalledDuration = self.performance.now() - stalled;
|
26363
|
-
|
26260
|
+
logger.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(_stalledDuration)}ms`);
|
26364
26261
|
this.stallReported = false;
|
26365
26262
|
}
|
26366
26263
|
this.stalled = null;
|
@@ -26396,6 +26293,7 @@ class GapController extends Logger {
|
|
26396
26293
|
// Skip start gaps if we haven't played, but the last poll detected the start of a stall
|
26397
26294
|
// The addition poll gives the browser a chance to jump the gap for us
|
26398
26295
|
if (!this.moved && this.stalled !== null) {
|
26296
|
+
var _level$details;
|
26399
26297
|
// There is no playable buffer (seeked, waiting for buffer)
|
26400
26298
|
const isBuffered = bufferInfo.len > 0;
|
26401
26299
|
if (!isBuffered && !nextStart) {
|
@@ -26407,8 +26305,9 @@ class GapController extends Logger {
|
|
26407
26305
|
// When joining a live stream with audio tracks, account for live playlist window sliding by allowing
|
26408
26306
|
// a larger jump over start gaps caused by the audio-stream-controller buffering a start fragment
|
26409
26307
|
// that begins over 1 target duration after the video start position.
|
26410
|
-
const
|
26411
|
-
const
|
26308
|
+
const level = this.hls.levels ? this.hls.levels[this.hls.currentLevel] : null;
|
26309
|
+
const isLive = level == null ? void 0 : (_level$details = level.details) == null ? void 0 : _level$details.live;
|
26310
|
+
const maxStartGapJump = isLive ? level.details.targetduration * 2 : MAX_START_GAP_JUMP;
|
26412
26311
|
const partialOrGap = this.fragmentTracker.getPartialFragment(currentTime);
|
26413
26312
|
if (startJump > 0 && (startJump <= maxStartGapJump || partialOrGap)) {
|
26414
26313
|
if (!media.paused) {
|
@@ -26426,17 +26325,6 @@ class GapController extends Logger {
|
|
26426
26325
|
}
|
26427
26326
|
const stalledDuration = tnow - stalled;
|
26428
26327
|
if (!seeking && stalledDuration >= STALL_MINIMUM_DURATION_MS) {
|
26429
|
-
// Dispatch MEDIA_ENDED when media.ended/ended event is not signalled at end of stream
|
26430
|
-
if (state === State.ENDED && !(levelDetails && levelDetails.live) && Math.abs(currentTime - ((levelDetails == null ? void 0 : levelDetails.edge) || 0)) < 1) {
|
26431
|
-
if (stalledDuration < 1000 || this.ended) {
|
26432
|
-
return;
|
26433
|
-
}
|
26434
|
-
this.ended = currentTime;
|
26435
|
-
this.hls.trigger(Events.MEDIA_ENDED, {
|
26436
|
-
stalled: true
|
26437
|
-
});
|
26438
|
-
return;
|
26439
|
-
}
|
26440
26328
|
// Report stalling after trying to fix
|
26441
26329
|
this._reportStall(bufferInfo);
|
26442
26330
|
if (!this.media) {
|
@@ -26480,7 +26368,7 @@ class GapController extends Logger {
|
|
26480
26368
|
// needs to cross some sort of threshold covering all source-buffers content
|
26481
26369
|
// to start playing properly.
|
26482
26370
|
if ((bufferInfo.len > config.maxBufferHole || bufferInfo.nextStart && bufferInfo.nextStart - currentTime < config.maxBufferHole) && stalledDurationMs > config.highBufferWatchdogPeriod * 1000) {
|
26483
|
-
|
26371
|
+
logger.warn('Trying to nudge playhead over buffer-hole');
|
26484
26372
|
// Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds
|
26485
26373
|
// We only try to jump the hole if it's under the configured size
|
26486
26374
|
// Reset stalled so to rearm watchdog timer
|
@@ -26504,7 +26392,7 @@ class GapController extends Logger {
|
|
26504
26392
|
// Report stalled error once
|
26505
26393
|
this.stallReported = true;
|
26506
26394
|
const error = new Error(`Playback stalling at @${media.currentTime} due to low buffer (${JSON.stringify(bufferInfo)})`);
|
26507
|
-
|
26395
|
+
logger.warn(error.message);
|
26508
26396
|
hls.trigger(Events.ERROR, {
|
26509
26397
|
type: ErrorTypes.MEDIA_ERROR,
|
26510
26398
|
details: ErrorDetails.BUFFER_STALLED_ERROR,
|
@@ -26572,7 +26460,7 @@ class GapController extends Logger {
|
|
26572
26460
|
}
|
26573
26461
|
}
|
26574
26462
|
const targetTime = Math.max(startTime + SKIP_BUFFER_RANGE_START, currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS);
|
26575
|
-
|
26463
|
+
logger.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`);
|
26576
26464
|
this.moved = true;
|
26577
26465
|
this.stalled = null;
|
26578
26466
|
media.currentTime = targetTime;
|
@@ -26613,7 +26501,7 @@ class GapController extends Logger {
|
|
26613
26501
|
const targetTime = currentTime + (nudgeRetry + 1) * config.nudgeOffset;
|
26614
26502
|
// playback stalled in buffered area ... let's nudge currentTime to try to overcome this
|
26615
26503
|
const error = new Error(`Nudging 'currentTime' from ${currentTime} to ${targetTime}`);
|
26616
|
-
|
26504
|
+
logger.warn(error.message);
|
26617
26505
|
media.currentTime = targetTime;
|
26618
26506
|
hls.trigger(Events.ERROR, {
|
26619
26507
|
type: ErrorTypes.MEDIA_ERROR,
|
@@ -26623,7 +26511,7 @@ class GapController extends Logger {
|
|
26623
26511
|
});
|
26624
26512
|
} else {
|
26625
26513
|
const error = new Error(`Playhead still not moving while enough data buffered @${currentTime} after ${config.nudgeMaxRetry} nudges`);
|
26626
|
-
|
26514
|
+
logger.error(error.message);
|
26627
26515
|
hls.trigger(Events.ERROR, {
|
26628
26516
|
type: ErrorTypes.MEDIA_ERROR,
|
26629
26517
|
details: ErrorDetails.BUFFER_STALLED_ERROR,
|
@@ -26638,7 +26526,7 @@ const TICK_INTERVAL = 100; // how often to tick in ms
|
|
26638
26526
|
|
26639
26527
|
class StreamController extends BaseStreamController {
|
26640
26528
|
constructor(hls, fragmentTracker, keyLoader) {
|
26641
|
-
super(hls, fragmentTracker, keyLoader, 'stream-controller', PlaylistLevelType.MAIN);
|
26529
|
+
super(hls, fragmentTracker, keyLoader, '[stream-controller]', PlaylistLevelType.MAIN);
|
26642
26530
|
this.audioCodecSwap = false;
|
26643
26531
|
this.gapController = null;
|
26644
26532
|
this.level = -1;
|
@@ -26646,43 +26534,27 @@ class StreamController extends BaseStreamController {
|
|
26646
26534
|
this.altAudio = false;
|
26647
26535
|
this.audioOnly = false;
|
26648
26536
|
this.fragPlaying = null;
|
26537
|
+
this.onvplaying = null;
|
26538
|
+
this.onvseeked = null;
|
26649
26539
|
this.fragLastKbps = 0;
|
26650
26540
|
this.couldBacktrack = false;
|
26651
26541
|
this.backtrackFragment = null;
|
26652
26542
|
this.audioCodecSwitch = false;
|
26653
26543
|
this.videoBuffer = null;
|
26654
|
-
this.
|
26655
|
-
// tick to speed up FRAG_CHANGED triggering
|
26656
|
-
this.tick();
|
26657
|
-
};
|
26658
|
-
this.onMediaSeeked = () => {
|
26659
|
-
const media = this.media;
|
26660
|
-
const currentTime = media ? media.currentTime : null;
|
26661
|
-
if (isFiniteNumber(currentTime)) {
|
26662
|
-
this.log(`Media seeked to ${currentTime.toFixed(3)}`);
|
26663
|
-
}
|
26664
|
-
|
26665
|
-
// If seeked was issued before buffer was appended do not tick immediately
|
26666
|
-
const bufferInfo = this.getMainFwdBufferInfo();
|
26667
|
-
if (bufferInfo === null || bufferInfo.len === 0) {
|
26668
|
-
this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
|
26669
|
-
return;
|
26670
|
-
}
|
26671
|
-
|
26672
|
-
// tick to speed up FRAG_CHANGED triggering
|
26673
|
-
this.tick();
|
26674
|
-
};
|
26675
|
-
this.registerListeners();
|
26544
|
+
this._registerListeners();
|
26676
26545
|
}
|
26677
|
-
|
26678
|
-
super.registerListeners();
|
26546
|
+
_registerListeners() {
|
26679
26547
|
const {
|
26680
26548
|
hls
|
26681
26549
|
} = this;
|
26550
|
+
hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
26551
|
+
hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
26552
|
+
hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
26682
26553
|
hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
|
26683
26554
|
hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
|
26684
26555
|
hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
26685
26556
|
hls.on(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
|
26557
|
+
hls.on(Events.ERROR, this.onError, this);
|
26686
26558
|
hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
26687
26559
|
hls.on(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
|
26688
26560
|
hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
@@ -26690,14 +26562,17 @@ class StreamController extends BaseStreamController {
|
|
26690
26562
|
hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
|
26691
26563
|
hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
26692
26564
|
}
|
26693
|
-
|
26694
|
-
super.unregisterListeners();
|
26565
|
+
_unregisterListeners() {
|
26695
26566
|
const {
|
26696
26567
|
hls
|
26697
26568
|
} = this;
|
26569
|
+
hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
26570
|
+
hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
26571
|
+
hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
26698
26572
|
hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
|
26699
26573
|
hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
26700
26574
|
hls.off(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
|
26575
|
+
hls.off(Events.ERROR, this.onError, this);
|
26701
26576
|
hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
26702
26577
|
hls.off(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
|
26703
26578
|
hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
@@ -26706,9 +26581,7 @@ class StreamController extends BaseStreamController {
|
|
26706
26581
|
hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
26707
26582
|
}
|
26708
26583
|
onHandlerDestroying() {
|
26709
|
-
|
26710
|
-
this.onMediaPlaying = this.onMediaSeeked = null;
|
26711
|
-
this.unregisterListeners();
|
26584
|
+
this._unregisterListeners();
|
26712
26585
|
super.onHandlerDestroying();
|
26713
26586
|
}
|
26714
26587
|
startLoad(startPosition) {
|
@@ -27035,17 +26908,20 @@ class StreamController extends BaseStreamController {
|
|
27035
26908
|
onMediaAttached(event, data) {
|
27036
26909
|
super.onMediaAttached(event, data);
|
27037
26910
|
const media = data.media;
|
27038
|
-
|
27039
|
-
|
26911
|
+
this.onvplaying = this.onMediaPlaying.bind(this);
|
26912
|
+
this.onvseeked = this.onMediaSeeked.bind(this);
|
26913
|
+
media.addEventListener('playing', this.onvplaying);
|
26914
|
+
media.addEventListener('seeked', this.onvseeked);
|
27040
26915
|
this.gapController = new GapController(this.config, media, this.fragmentTracker, this.hls);
|
27041
26916
|
}
|
27042
26917
|
onMediaDetaching() {
|
27043
26918
|
const {
|
27044
26919
|
media
|
27045
26920
|
} = this;
|
27046
|
-
if (media) {
|
27047
|
-
media.removeEventListener('playing', this.
|
27048
|
-
media.removeEventListener('seeked', this.
|
26921
|
+
if (media && this.onvplaying && this.onvseeked) {
|
26922
|
+
media.removeEventListener('playing', this.onvplaying);
|
26923
|
+
media.removeEventListener('seeked', this.onvseeked);
|
26924
|
+
this.onvplaying = this.onvseeked = null;
|
27049
26925
|
this.videoBuffer = null;
|
27050
26926
|
}
|
27051
26927
|
this.fragPlaying = null;
|
@@ -27055,6 +26931,27 @@ class StreamController extends BaseStreamController {
|
|
27055
26931
|
}
|
27056
26932
|
super.onMediaDetaching();
|
27057
26933
|
}
|
26934
|
+
onMediaPlaying() {
|
26935
|
+
// tick to speed up FRAG_CHANGED triggering
|
26936
|
+
this.tick();
|
26937
|
+
}
|
26938
|
+
onMediaSeeked() {
|
26939
|
+
const media = this.media;
|
26940
|
+
const currentTime = media ? media.currentTime : null;
|
26941
|
+
if (isFiniteNumber(currentTime)) {
|
26942
|
+
this.log(`Media seeked to ${currentTime.toFixed(3)}`);
|
26943
|
+
}
|
26944
|
+
|
26945
|
+
// If seeked was issued before buffer was appended do not tick immediately
|
26946
|
+
const bufferInfo = this.getMainFwdBufferInfo();
|
26947
|
+
if (bufferInfo === null || bufferInfo.len === 0) {
|
26948
|
+
this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
|
26949
|
+
return;
|
26950
|
+
}
|
26951
|
+
|
26952
|
+
// tick to speed up FRAG_CHANGED triggering
|
26953
|
+
this.tick();
|
26954
|
+
}
|
27058
26955
|
onManifestLoading() {
|
27059
26956
|
// reset buffer on manifest loading
|
27060
26957
|
this.log('Trigger BUFFER_RESET');
|
@@ -27346,10 +27243,8 @@ class StreamController extends BaseStreamController {
|
|
27346
27243
|
}
|
27347
27244
|
if (this.loadedmetadata || !BufferHelper.getBuffered(media).length) {
|
27348
27245
|
// Resolve gaps using the main buffer, whose ranges are the intersections of the A/V sourcebuffers
|
27349
|
-
const
|
27350
|
-
|
27351
|
-
const levelDetails = this.getLevelDetails();
|
27352
|
-
gapController.poll(this.lastCurrentTime, activeFrag, levelDetails, state);
|
27246
|
+
const activeFrag = this.state !== State.IDLE ? this.fragCurrent : null;
|
27247
|
+
gapController.poll(this.lastCurrentTime, activeFrag);
|
27353
27248
|
}
|
27354
27249
|
this.lastCurrentTime = media.currentTime;
|
27355
27250
|
}
|
@@ -27787,7 +27682,7 @@ class Hls {
|
|
27787
27682
|
* Get the video-dev/hls.js package version.
|
27788
27683
|
*/
|
27789
27684
|
static get version() {
|
27790
|
-
return "1.5.
|
27685
|
+
return "1.5.3";
|
27791
27686
|
}
|
27792
27687
|
|
27793
27688
|
/**
|
@@ -27850,10 +27745,6 @@ class Hls {
|
|
27850
27745
|
* The configuration object provided on player instantiation.
|
27851
27746
|
*/
|
27852
27747
|
this.userConfig = void 0;
|
27853
|
-
/**
|
27854
|
-
* The logger functions used by this player instance, configured on player instantiation.
|
27855
|
-
*/
|
27856
|
-
this.logger = void 0;
|
27857
27748
|
this.coreComponents = void 0;
|
27858
27749
|
this.networkControllers = void 0;
|
27859
27750
|
this.started = false;
|
@@ -27873,11 +27764,11 @@ class Hls {
|
|
27873
27764
|
this._media = null;
|
27874
27765
|
this.url = null;
|
27875
27766
|
this.triggeringException = void 0;
|
27876
|
-
|
27877
|
-
const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig
|
27767
|
+
enableLogs(userConfig.debug || false, 'Hls instance');
|
27768
|
+
const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig);
|
27878
27769
|
this.userConfig = userConfig;
|
27879
27770
|
if (config.progressive) {
|
27880
|
-
enableStreamingMode(config
|
27771
|
+
enableStreamingMode(config);
|
27881
27772
|
}
|
27882
27773
|
|
27883
27774
|
// core controllers and network loaders
|
@@ -27976,7 +27867,7 @@ class Hls {
|
|
27976
27867
|
try {
|
27977
27868
|
return this.emit(event, event, eventObject);
|
27978
27869
|
} catch (error) {
|
27979
|
-
|
27870
|
+
logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
|
27980
27871
|
// Prevent recursion in error event handlers that throw #5497
|
27981
27872
|
if (!this.triggeringException) {
|
27982
27873
|
this.triggeringException = true;
|
@@ -28002,7 +27893,7 @@ class Hls {
|
|
28002
27893
|
* Dispose of the instance
|
28003
27894
|
*/
|
28004
27895
|
destroy() {
|
28005
|
-
|
27896
|
+
logger.log('destroy');
|
28006
27897
|
this.trigger(Events.DESTROYING, undefined);
|
28007
27898
|
this.detachMedia();
|
28008
27899
|
this.removeAllListeners();
|
@@ -28023,7 +27914,7 @@ class Hls {
|
|
28023
27914
|
* Attaches Hls.js to a media element
|
28024
27915
|
*/
|
28025
27916
|
attachMedia(media) {
|
28026
|
-
|
27917
|
+
logger.log('attachMedia');
|
28027
27918
|
this._media = media;
|
28028
27919
|
this.trigger(Events.MEDIA_ATTACHING, {
|
28029
27920
|
media: media
|
@@ -28034,7 +27925,7 @@ class Hls {
|
|
28034
27925
|
* Detach Hls.js from the media
|
28035
27926
|
*/
|
28036
27927
|
detachMedia() {
|
28037
|
-
|
27928
|
+
logger.log('detachMedia');
|
28038
27929
|
this.trigger(Events.MEDIA_DETACHING, undefined);
|
28039
27930
|
this._media = null;
|
28040
27931
|
}
|
@@ -28051,7 +27942,7 @@ class Hls {
|
|
28051
27942
|
});
|
28052
27943
|
this._autoLevelCapping = -1;
|
28053
27944
|
this._maxHdcpLevel = null;
|
28054
|
-
|
27945
|
+
logger.log(`loadSource:${loadingSource}`);
|
28055
27946
|
if (media && loadedSource && (loadedSource !== loadingSource || this.bufferController.hasSourceTypes())) {
|
28056
27947
|
this.detachMedia();
|
28057
27948
|
this.attachMedia(media);
|
@@ -28070,7 +27961,7 @@ class Hls {
|
|
28070
27961
|
* Defaults to -1 (None: starts from earliest point)
|
28071
27962
|
*/
|
28072
27963
|
startLoad(startPosition = -1) {
|
28073
|
-
|
27964
|
+
logger.log(`startLoad(${startPosition})`);
|
28074
27965
|
this.started = true;
|
28075
27966
|
this.networkControllers.forEach(controller => {
|
28076
27967
|
controller.startLoad(startPosition);
|
@@ -28081,7 +27972,7 @@ class Hls {
|
|
28081
27972
|
* Stop loading of any stream data.
|
28082
27973
|
*/
|
28083
27974
|
stopLoad() {
|
28084
|
-
|
27975
|
+
logger.log('stopLoad');
|
28085
27976
|
this.started = false;
|
28086
27977
|
this.networkControllers.forEach(controller => {
|
28087
27978
|
controller.stopLoad();
|
@@ -28117,7 +28008,7 @@ class Hls {
|
|
28117
28008
|
* Swap through possible audio codecs in the stream (for example to switch from stereo to 5.1)
|
28118
28009
|
*/
|
28119
28010
|
swapAudioCodec() {
|
28120
|
-
|
28011
|
+
logger.log('swapAudioCodec');
|
28121
28012
|
this.streamController.swapAudioCodec();
|
28122
28013
|
}
|
28123
28014
|
|
@@ -28128,7 +28019,7 @@ class Hls {
|
|
28128
28019
|
* Automatic recovery of media-errors by this process is configurable.
|
28129
28020
|
*/
|
28130
28021
|
recoverMediaError() {
|
28131
|
-
|
28022
|
+
logger.log('recoverMediaError');
|
28132
28023
|
const media = this._media;
|
28133
28024
|
this.detachMedia();
|
28134
28025
|
if (media) {
|
@@ -28158,7 +28049,7 @@ class Hls {
|
|
28158
28049
|
* Set quality level index immediately. This will flush the current buffer to replace the quality asap. That means playback will interrupt at least shortly to re-buffer and re-sync eventually. Set to -1 for automatic level selection.
|
28159
28050
|
*/
|
28160
28051
|
set currentLevel(newLevel) {
|
28161
|
-
|
28052
|
+
logger.log(`set currentLevel:${newLevel}`);
|
28162
28053
|
this.levelController.manualLevel = newLevel;
|
28163
28054
|
this.streamController.immediateLevelSwitch();
|
28164
28055
|
}
|
@@ -28177,7 +28068,7 @@ class Hls {
|
|
28177
28068
|
* @param newLevel - Pass -1 for automatic level selection
|
28178
28069
|
*/
|
28179
28070
|
set nextLevel(newLevel) {
|
28180
|
-
|
28071
|
+
logger.log(`set nextLevel:${newLevel}`);
|
28181
28072
|
this.levelController.manualLevel = newLevel;
|
28182
28073
|
this.streamController.nextLevelSwitch();
|
28183
28074
|
}
|
@@ -28196,7 +28087,7 @@ class Hls {
|
|
28196
28087
|
* @param newLevel - Pass -1 for automatic level selection
|
28197
28088
|
*/
|
28198
28089
|
set loadLevel(newLevel) {
|
28199
|
-
|
28090
|
+
logger.log(`set loadLevel:${newLevel}`);
|
28200
28091
|
this.levelController.manualLevel = newLevel;
|
28201
28092
|
}
|
28202
28093
|
|
@@ -28227,7 +28118,7 @@ class Hls {
|
|
28227
28118
|
* Sets "first-level", see getter.
|
28228
28119
|
*/
|
28229
28120
|
set firstLevel(newLevel) {
|
28230
|
-
|
28121
|
+
logger.log(`set firstLevel:${newLevel}`);
|
28231
28122
|
this.levelController.firstLevel = newLevel;
|
28232
28123
|
}
|
28233
28124
|
|
@@ -28252,7 +28143,7 @@ class Hls {
|
|
28252
28143
|
* (determined from download of first segment)
|
28253
28144
|
*/
|
28254
28145
|
set startLevel(newLevel) {
|
28255
|
-
|
28146
|
+
logger.log(`set startLevel:${newLevel}`);
|
28256
28147
|
// if not in automatic start level detection, ensure startLevel is greater than minAutoLevel
|
28257
28148
|
if (newLevel !== -1) {
|
28258
28149
|
newLevel = Math.max(newLevel, this.minAutoLevel);
|
@@ -28327,7 +28218,7 @@ class Hls {
|
|
28327
28218
|
*/
|
28328
28219
|
set autoLevelCapping(newLevel) {
|
28329
28220
|
if (this._autoLevelCapping !== newLevel) {
|
28330
|
-
|
28221
|
+
logger.log(`set autoLevelCapping:${newLevel}`);
|
28331
28222
|
this._autoLevelCapping = newLevel;
|
28332
28223
|
this.levelController.checkMaxAutoUpdated();
|
28333
28224
|
}
|