hls.js 1.5.2-0.canary.9924 → 1.5.2
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 +686 -762
- package/dist/hls.js.d.ts +47 -49
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +471 -563
- 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 +329 -409
- 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 +500 -559
- 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 +9 -9
- package/src/config.ts +2 -3
- package/src/controller/abr-controller.ts +22 -23
- 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 -42
- package/src/controller/buffer-controller.ts +11 -10
- package/src/controller/cap-level-controller.ts +2 -1
- 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 +10 -16
- package/src/controller/latency-controller.ts +11 -9
- package/src/controller/level-controller.ts +19 -8
- package/src/controller/stream-controller.ts +29 -20
- 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/hls.ts +20 -32
- 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 +3 -4
- package/src/task-loop.ts +2 -5
- package/src/types/demuxer.ts +0 -1
- 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
@@ -369,23 +369,6 @@ let ErrorDetails = /*#__PURE__*/function (ErrorDetails) {
|
|
369
369
|
return ErrorDetails;
|
370
370
|
}({});
|
371
371
|
|
372
|
-
class Logger {
|
373
|
-
constructor(label, logger) {
|
374
|
-
this.trace = void 0;
|
375
|
-
this.debug = void 0;
|
376
|
-
this.log = void 0;
|
377
|
-
this.warn = void 0;
|
378
|
-
this.info = void 0;
|
379
|
-
this.error = void 0;
|
380
|
-
const lb = `[${label}]:`;
|
381
|
-
this.trace = noop;
|
382
|
-
this.debug = logger.debug.bind(null, lb);
|
383
|
-
this.log = logger.log.bind(null, lb);
|
384
|
-
this.warn = logger.warn.bind(null, lb);
|
385
|
-
this.info = logger.info.bind(null, lb);
|
386
|
-
this.error = logger.error.bind(null, lb);
|
387
|
-
}
|
388
|
-
}
|
389
372
|
const noop = function noop() {};
|
390
373
|
const fakeLogger = {
|
391
374
|
trace: noop,
|
@@ -395,9 +378,7 @@ const fakeLogger = {
|
|
395
378
|
info: noop,
|
396
379
|
error: noop
|
397
380
|
};
|
398
|
-
|
399
|
-
return _extends({}, fakeLogger);
|
400
|
-
}
|
381
|
+
let exportedLogger = fakeLogger;
|
401
382
|
|
402
383
|
// let lastCallTime;
|
403
384
|
// function formatMsgWithTimeInfo(type, msg) {
|
@@ -408,36 +389,35 @@ function createLogger() {
|
|
408
389
|
// return msg;
|
409
390
|
// }
|
410
391
|
|
411
|
-
function consolePrintFn(type
|
392
|
+
function consolePrintFn(type) {
|
412
393
|
const func = self.console[type];
|
413
|
-
|
394
|
+
if (func) {
|
395
|
+
return func.bind(self.console, `[${type}] >`);
|
396
|
+
}
|
397
|
+
return noop;
|
414
398
|
}
|
415
|
-
function
|
416
|
-
|
399
|
+
function exportLoggerFunctions(debugConfig, ...functions) {
|
400
|
+
functions.forEach(function (type) {
|
401
|
+
exportedLogger[type] = debugConfig[type] ? debugConfig[type].bind(debugConfig) : consolePrintFn(type);
|
402
|
+
});
|
417
403
|
}
|
418
|
-
|
419
|
-
function enableLogs(debugConfig, context, id) {
|
404
|
+
function enableLogs(debugConfig, id) {
|
420
405
|
// check that console is available
|
421
|
-
const newLogger = createLogger();
|
422
406
|
if (typeof console === 'object' && debugConfig === true || typeof debugConfig === 'object') {
|
423
|
-
|
407
|
+
exportLoggerFunctions(debugConfig,
|
424
408
|
// Remove out from list here to hard-disable a log-level
|
425
409
|
// 'trace',
|
426
|
-
'debug', 'log', 'info', 'warn', 'error'
|
427
|
-
keys.forEach(key => {
|
428
|
-
newLogger[key] = getLoggerFn(key, debugConfig, id);
|
429
|
-
});
|
410
|
+
'debug', 'log', 'info', 'warn', 'error');
|
430
411
|
// Some browsers don't allow to use bind on console object anyway
|
431
412
|
// fallback to default if needed
|
432
413
|
try {
|
433
|
-
|
414
|
+
exportedLogger.log(`Debug logs enabled for "${id}" in hls.js version ${"1.5.2"}`);
|
434
415
|
} catch (e) {
|
435
|
-
|
436
|
-
return createLogger();
|
416
|
+
exportedLogger = fakeLogger;
|
437
417
|
}
|
418
|
+
} else {
|
419
|
+
exportedLogger = fakeLogger;
|
438
420
|
}
|
439
|
-
exportedLogger = newLogger;
|
440
|
-
return newLogger;
|
441
421
|
}
|
442
422
|
const logger = exportedLogger;
|
443
423
|
|
@@ -1056,26 +1036,6 @@ function strToUtf8array(str) {
|
|
1056
1036
|
return Uint8Array.from(unescape(encodeURIComponent(str)), c => c.charCodeAt(0));
|
1057
1037
|
}
|
1058
1038
|
|
1059
|
-
var DecrypterAesMode = {
|
1060
|
-
cbc: 0,
|
1061
|
-
ctr: 1
|
1062
|
-
};
|
1063
|
-
|
1064
|
-
function isFullSegmentEncryption(method) {
|
1065
|
-
return method === 'AES-128' || method === 'AES-256' || method === 'AES-256-CTR';
|
1066
|
-
}
|
1067
|
-
function getAesModeFromFullSegmentMethod(method) {
|
1068
|
-
switch (method) {
|
1069
|
-
case 'AES-128':
|
1070
|
-
case 'AES-256':
|
1071
|
-
return DecrypterAesMode.cbc;
|
1072
|
-
case 'AES-256-CTR':
|
1073
|
-
return DecrypterAesMode.ctr;
|
1074
|
-
default:
|
1075
|
-
throw new Error(`invalid full segment method ${method}`);
|
1076
|
-
}
|
1077
|
-
}
|
1078
|
-
|
1079
1039
|
/** returns `undefined` is `self` is missing, e.g. in node */
|
1080
1040
|
const optionalSelf = typeof self !== 'undefined' ? self : undefined;
|
1081
1041
|
|
@@ -2714,12 +2674,12 @@ class LevelKey {
|
|
2714
2674
|
this.keyFormatVersions = formatversions;
|
2715
2675
|
this.iv = iv;
|
2716
2676
|
this.encrypted = method ? method !== 'NONE' : false;
|
2717
|
-
this.isCommonEncryption = this.encrypted &&
|
2677
|
+
this.isCommonEncryption = this.encrypted && method !== 'AES-128';
|
2718
2678
|
}
|
2719
2679
|
isSupported() {
|
2720
2680
|
// If it's Segment encryption or No encryption, just select that key system
|
2721
2681
|
if (this.method) {
|
2722
|
-
if (
|
2682
|
+
if (this.method === 'AES-128' || this.method === 'NONE') {
|
2723
2683
|
return true;
|
2724
2684
|
}
|
2725
2685
|
if (this.keyFormat === 'identity') {
|
@@ -2741,13 +2701,14 @@ class LevelKey {
|
|
2741
2701
|
if (!this.encrypted || !this.uri) {
|
2742
2702
|
return null;
|
2743
2703
|
}
|
2744
|
-
if (
|
2704
|
+
if (this.method === 'AES-128' && this.uri && !this.iv) {
|
2745
2705
|
if (typeof sn !== 'number') {
|
2746
2706
|
// We are fetching decryption data for a initialization segment
|
2747
|
-
// If the segment was encrypted with AES-128
|
2707
|
+
// If the segment was encrypted with AES-128
|
2748
2708
|
// It must have an IV defined. We cannot substitute the Segment Number in.
|
2749
|
-
|
2750
|
-
|
2709
|
+
if (this.method === 'AES-128' && !this.iv) {
|
2710
|
+
logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`);
|
2711
|
+
}
|
2751
2712
|
// Explicitly set sn to resulting value from implicit conversions 'initSegment' values for IV generation.
|
2752
2713
|
sn = 0;
|
2753
2714
|
}
|
@@ -3026,28 +2987,23 @@ function getCodecCompatibleNameLower(lowerCaseCodec, preferManagedMediaSource =
|
|
3026
2987
|
if (CODEC_COMPATIBLE_NAMES[lowerCaseCodec]) {
|
3027
2988
|
return CODEC_COMPATIBLE_NAMES[lowerCaseCodec];
|
3028
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
|
3029
2994
|
const codecsToCheck = {
|
3030
|
-
// Idealy fLaC and Opus would be first (spec-compliant) but
|
3031
|
-
// some browsers will report that fLaC is supported then fail.
|
3032
|
-
// see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
|
3033
2995
|
flac: ['flac', 'fLaC', 'FLAC'],
|
3034
|
-
opus: ['opus', 'Opus']
|
3035
|
-
// Replace audio codec info if browser does not support mp4a.40.34,
|
3036
|
-
// and demuxer can fallback to 'audio/mpeg' or 'audio/mp4;codecs="mp3"'
|
3037
|
-
'mp4a.40.34': ['mp3']
|
2996
|
+
opus: ['opus', 'Opus']
|
3038
2997
|
}[lowerCaseCodec];
|
3039
2998
|
for (let i = 0; i < codecsToCheck.length; i++) {
|
3040
|
-
var _getMediaSource;
|
3041
2999
|
if (isCodecMediaSourceSupported(codecsToCheck[i], 'audio', preferManagedMediaSource)) {
|
3042
3000
|
CODEC_COMPATIBLE_NAMES[lowerCaseCodec] = codecsToCheck[i];
|
3043
3001
|
return codecsToCheck[i];
|
3044
|
-
} else if (codecsToCheck[i] === 'mp3' && (_getMediaSource = getMediaSource(preferManagedMediaSource)) != null && _getMediaSource.isTypeSupported('audio/mpeg')) {
|
3045
|
-
return '';
|
3046
3002
|
}
|
3047
3003
|
}
|
3048
3004
|
return lowerCaseCodec;
|
3049
3005
|
}
|
3050
|
-
const AUDIO_CODEC_REGEXP = /flac|opus
|
3006
|
+
const AUDIO_CODEC_REGEXP = /flac|opus/i;
|
3051
3007
|
function getCodecCompatibleName(codec, preferManagedMediaSource = true) {
|
3052
3008
|
return codec.replace(AUDIO_CODEC_REGEXP, m => getCodecCompatibleNameLower(m.toLowerCase(), preferManagedMediaSource));
|
3053
3009
|
}
|
@@ -3070,16 +3026,6 @@ function convertAVC1ToAVCOTI(codec) {
|
|
3070
3026
|
}
|
3071
3027
|
return codec;
|
3072
3028
|
}
|
3073
|
-
function getM2TSSupportedAudioTypes(preferManagedMediaSource) {
|
3074
|
-
const MediaSource = getMediaSource(preferManagedMediaSource) || {
|
3075
|
-
isTypeSupported: () => false
|
3076
|
-
};
|
3077
|
-
return {
|
3078
|
-
mpeg: MediaSource.isTypeSupported('audio/mpeg'),
|
3079
|
-
mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
|
3080
|
-
ac3: MediaSource.isTypeSupported('audio/mp4; codecs="ac-3"')
|
3081
|
-
};
|
3082
|
-
}
|
3083
3029
|
|
3084
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;
|
3085
3031
|
const MASTER_PLAYLIST_MEDIA_REGEX = /#EXT-X-MEDIA:(.*)/g;
|
@@ -4746,47 +4692,7 @@ class LatencyController {
|
|
4746
4692
|
this.currentTime = 0;
|
4747
4693
|
this.stallCount = 0;
|
4748
4694
|
this._latency = null;
|
4749
|
-
this.
|
4750
|
-
const {
|
4751
|
-
media,
|
4752
|
-
levelDetails
|
4753
|
-
} = this;
|
4754
|
-
if (!media || !levelDetails) {
|
4755
|
-
return;
|
4756
|
-
}
|
4757
|
-
this.currentTime = media.currentTime;
|
4758
|
-
const latency = this.computeLatency();
|
4759
|
-
if (latency === null) {
|
4760
|
-
return;
|
4761
|
-
}
|
4762
|
-
this._latency = latency;
|
4763
|
-
|
4764
|
-
// Adapt playbackRate to meet target latency in low-latency mode
|
4765
|
-
const {
|
4766
|
-
lowLatencyMode,
|
4767
|
-
maxLiveSyncPlaybackRate
|
4768
|
-
} = this.config;
|
4769
|
-
if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
|
4770
|
-
return;
|
4771
|
-
}
|
4772
|
-
const targetLatency = this.targetLatency;
|
4773
|
-
if (targetLatency === null) {
|
4774
|
-
return;
|
4775
|
-
}
|
4776
|
-
const distanceFromTarget = latency - targetLatency;
|
4777
|
-
// Only adjust playbackRate when within one target duration of targetLatency
|
4778
|
-
// and more than one second from under-buffering.
|
4779
|
-
// Playback further than one target duration from target can be considered DVR playback.
|
4780
|
-
const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
|
4781
|
-
const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
|
4782
|
-
if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
|
4783
|
-
const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
|
4784
|
-
const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
|
4785
|
-
media.playbackRate = Math.min(max, Math.max(1, rate));
|
4786
|
-
} else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
|
4787
|
-
media.playbackRate = 1;
|
4788
|
-
}
|
4789
|
-
};
|
4695
|
+
this.timeupdateHandler = () => this.timeupdate();
|
4790
4696
|
this.hls = hls;
|
4791
4697
|
this.config = hls.config;
|
4792
4698
|
this.registerListeners();
|
@@ -4878,7 +4784,7 @@ class LatencyController {
|
|
4878
4784
|
this.onMediaDetaching();
|
4879
4785
|
this.levelDetails = null;
|
4880
4786
|
// @ts-ignore
|
4881
|
-
this.hls = null;
|
4787
|
+
this.hls = this.timeupdateHandler = null;
|
4882
4788
|
}
|
4883
4789
|
registerListeners() {
|
4884
4790
|
this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
@@ -4896,11 +4802,11 @@ class LatencyController {
|
|
4896
4802
|
}
|
4897
4803
|
onMediaAttached(event, data) {
|
4898
4804
|
this.media = data.media;
|
4899
|
-
this.media.addEventListener('timeupdate', this.
|
4805
|
+
this.media.addEventListener('timeupdate', this.timeupdateHandler);
|
4900
4806
|
}
|
4901
4807
|
onMediaDetaching() {
|
4902
4808
|
if (this.media) {
|
4903
|
-
this.media.removeEventListener('timeupdate', this.
|
4809
|
+
this.media.removeEventListener('timeupdate', this.timeupdateHandler);
|
4904
4810
|
this.media = null;
|
4905
4811
|
}
|
4906
4812
|
}
|
@@ -4914,10 +4820,10 @@ class LatencyController {
|
|
4914
4820
|
}) {
|
4915
4821
|
this.levelDetails = details;
|
4916
4822
|
if (details.advanced) {
|
4917
|
-
this.
|
4823
|
+
this.timeupdate();
|
4918
4824
|
}
|
4919
4825
|
if (!details.live && this.media) {
|
4920
|
-
this.media.removeEventListener('timeupdate', this.
|
4826
|
+
this.media.removeEventListener('timeupdate', this.timeupdateHandler);
|
4921
4827
|
}
|
4922
4828
|
}
|
4923
4829
|
onError(event, data) {
|
@@ -4927,7 +4833,48 @@ class LatencyController {
|
|
4927
4833
|
}
|
4928
4834
|
this.stallCount++;
|
4929
4835
|
if ((_this$levelDetails = this.levelDetails) != null && _this$levelDetails.live) {
|
4930
|
-
|
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;
|
4931
4878
|
}
|
4932
4879
|
}
|
4933
4880
|
estimateLiveEdge() {
|
@@ -5699,13 +5646,18 @@ var ErrorActionFlags = {
|
|
5699
5646
|
MoveAllAlternatesMatchingHDCP: 2,
|
5700
5647
|
SwitchToSDR: 4
|
5701
5648
|
}; // Reserved for future use
|
5702
|
-
class ErrorController
|
5649
|
+
class ErrorController {
|
5703
5650
|
constructor(hls) {
|
5704
|
-
super('error-controller', hls.logger);
|
5705
5651
|
this.hls = void 0;
|
5706
5652
|
this.playlistError = 0;
|
5707
5653
|
this.penalizedRenditions = {};
|
5654
|
+
this.log = void 0;
|
5655
|
+
this.warn = void 0;
|
5656
|
+
this.error = void 0;
|
5708
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]:`);
|
5709
5661
|
this.registerListeners();
|
5710
5662
|
}
|
5711
5663
|
registerListeners() {
|
@@ -6057,13 +6009,16 @@ class ErrorController extends Logger {
|
|
6057
6009
|
}
|
6058
6010
|
}
|
6059
6011
|
|
6060
|
-
class BasePlaylistController
|
6012
|
+
class BasePlaylistController {
|
6061
6013
|
constructor(hls, logPrefix) {
|
6062
|
-
super(logPrefix, hls.logger);
|
6063
6014
|
this.hls = void 0;
|
6064
6015
|
this.timer = -1;
|
6065
6016
|
this.requestScheduled = -1;
|
6066
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}:`);
|
6067
6022
|
this.hls = hls;
|
6068
6023
|
}
|
6069
6024
|
destroy() {
|
@@ -6096,7 +6051,7 @@ class BasePlaylistController extends Logger {
|
|
6096
6051
|
try {
|
6097
6052
|
uri = new self.URL(attr.URI, previous.url).href;
|
6098
6053
|
} catch (error) {
|
6099
|
-
|
6054
|
+
logger.warn(`Could not construct new URL for Rendition Report: ${error}`);
|
6100
6055
|
uri = attr.URI || '';
|
6101
6056
|
}
|
6102
6057
|
// Use exact match. Otherwise, the last partial match, if any, will be used
|
@@ -6855,9 +6810,8 @@ function searchDownAndUpList(arr, searchIndex, predicate) {
|
|
6855
6810
|
return -1;
|
6856
6811
|
}
|
6857
6812
|
|
6858
|
-
class AbrController
|
6813
|
+
class AbrController {
|
6859
6814
|
constructor(_hls) {
|
6860
|
-
super('abr', _hls.logger);
|
6861
6815
|
this.hls = void 0;
|
6862
6816
|
this.lastLevelLoadSec = 0;
|
6863
6817
|
this.lastLoadedFragLevel = -1;
|
@@ -6923,7 +6877,7 @@ class AbrController extends Logger {
|
|
6923
6877
|
const bwEstimate = this.getBwEstimate();
|
6924
6878
|
const levels = hls.levels;
|
6925
6879
|
const level = levels[frag.level];
|
6926
|
-
const expectedLen = stats.total || Math.max(stats.loaded, Math.round(duration * level.
|
6880
|
+
const expectedLen = stats.total || Math.max(stats.loaded, Math.round(duration * level.averageBitrate / 8));
|
6927
6881
|
let timeStreaming = loadedFirstByte ? timeLoading - ttfb : timeLoading;
|
6928
6882
|
if (timeStreaming < 1 && loadedFirstByte) {
|
6929
6883
|
timeStreaming = Math.min(timeLoading, stats.loaded * 8 / bwEstimate);
|
@@ -6966,12 +6920,12 @@ class AbrController extends Logger {
|
|
6966
6920
|
// If there has been no loading progress, sample TTFB
|
6967
6921
|
this.bwEstimator.sampleTTFB(timeLoading);
|
6968
6922
|
}
|
6969
|
-
const nextLoadLevelBitrate = levels[nextLoadLevel].
|
6923
|
+
const nextLoadLevelBitrate = levels[nextLoadLevel].maxBitrate;
|
6970
6924
|
if (this.getBwEstimate() * this.hls.config.abrBandWidthUpFactor > nextLoadLevelBitrate) {
|
6971
6925
|
this.resetEstimator(nextLoadLevelBitrate);
|
6972
6926
|
}
|
6973
6927
|
this.clearTimer();
|
6974
|
-
|
6928
|
+
logger.warn(`[abr] Fragment ${frag.sn}${part ? ' part ' + part.index : ''} of level ${frag.level} is loading too slowly;
|
6975
6929
|
Time to underbuffer: ${bufferStarvationDelay.toFixed(3)} s
|
6976
6930
|
Estimated load time for current fragment: ${fragLoadedDelay.toFixed(3)} s
|
6977
6931
|
Estimated load time for down switch fragment: ${fragLevelNextLoadedDelay.toFixed(3)} s
|
@@ -6991,7 +6945,7 @@ class AbrController extends Logger {
|
|
6991
6945
|
}
|
6992
6946
|
resetEstimator(abrEwmaDefaultEstimate) {
|
6993
6947
|
if (abrEwmaDefaultEstimate) {
|
6994
|
-
|
6948
|
+
logger.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
|
6995
6949
|
this.hls.config.abrEwmaDefaultEstimate = abrEwmaDefaultEstimate;
|
6996
6950
|
}
|
6997
6951
|
this.firstSelection = -1;
|
@@ -7223,7 +7177,7 @@ class AbrController extends Logger {
|
|
7223
7177
|
}
|
7224
7178
|
const firstLevel = this.hls.firstLevel;
|
7225
7179
|
const clamped = Math.min(Math.max(firstLevel, minAutoLevel), maxAutoLevel);
|
7226
|
-
|
7180
|
+
logger.warn(`[abr] Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`);
|
7227
7181
|
return clamped;
|
7228
7182
|
}
|
7229
7183
|
get forcedAutoLevel() {
|
@@ -7308,13 +7262,13 @@ class AbrController extends Logger {
|
|
7308
7262
|
// cap maxLoadingDelay and ensure it is not bigger 'than bitrate test' frag duration
|
7309
7263
|
const maxLoadingDelay = currentFragDuration ? Math.min(currentFragDuration, config.maxLoadingDelay) : config.maxLoadingDelay;
|
7310
7264
|
maxStarvationDelay = maxLoadingDelay - bitrateTestDelay;
|
7311
|
-
|
7265
|
+
logger.info(`[abr] bitrate test took ${Math.round(1000 * bitrateTestDelay)}ms, set first fragment max fetchDuration to ${Math.round(1000 * maxStarvationDelay)} ms`);
|
7312
7266
|
// don't use conservative factor on bitrate test
|
7313
7267
|
bwFactor = bwUpFactor = 1;
|
7314
7268
|
}
|
7315
7269
|
}
|
7316
7270
|
const bestLevel = this.findBestLevel(avgbw, minAutoLevel, maxAutoLevel, bufferStarvationDelay, maxStarvationDelay, bwFactor, bwUpFactor);
|
7317
|
-
|
7271
|
+
logger.info(`[abr] ${bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'}, optimal quality level ${bestLevel}`);
|
7318
7272
|
if (bestLevel > -1) {
|
7319
7273
|
return bestLevel;
|
7320
7274
|
}
|
@@ -7376,7 +7330,7 @@ class AbrController extends Logger {
|
|
7376
7330
|
currentVideoRange = preferHDR ? videoRanges[videoRanges.length - 1] : videoRanges[0];
|
7377
7331
|
currentFrameRate = minFramerate;
|
7378
7332
|
currentBw = Math.max(currentBw, minBitrate);
|
7379
|
-
|
7333
|
+
logger.log(`[abr] picked start tier ${JSON.stringify(startTier)}`);
|
7380
7334
|
} else {
|
7381
7335
|
currentCodecSet = level == null ? void 0 : level.codecSet;
|
7382
7336
|
currentVideoRange = level == null ? void 0 : level.videoRange;
|
@@ -7400,11 +7354,11 @@ class AbrController extends Logger {
|
|
7400
7354
|
const levels = this.hls.levels;
|
7401
7355
|
const index = levels.indexOf(levelInfo);
|
7402
7356
|
if (decodingInfo.error) {
|
7403
|
-
|
7357
|
+
logger.warn(`[abr] MediaCapabilities decodingInfo error: "${decodingInfo.error}" for level ${index} ${JSON.stringify(decodingInfo)}`);
|
7404
7358
|
} else if (!decodingInfo.supported) {
|
7405
|
-
|
7359
|
+
logger.warn(`[abr] Unsupported MediaCapabilities decodingInfo result for level ${index} ${JSON.stringify(decodingInfo)}`);
|
7406
7360
|
if (index > -1 && levels.length > 1) {
|
7407
|
-
|
7361
|
+
logger.log(`[abr] Removing unsupported level ${index}`);
|
7408
7362
|
this.hls.removeLevel(index);
|
7409
7363
|
}
|
7410
7364
|
}
|
@@ -7451,9 +7405,9 @@ class AbrController extends Logger {
|
|
7451
7405
|
const forcedAutoLevel = this.forcedAutoLevel;
|
7452
7406
|
if (i !== loadLevel && (forcedAutoLevel === -1 || forcedAutoLevel !== loadLevel)) {
|
7453
7407
|
if (levelsSkipped.length) {
|
7454
|
-
|
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}`);
|
7455
7409
|
}
|
7456
|
-
|
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}`);
|
7457
7411
|
}
|
7458
7412
|
if (firstSelection) {
|
7459
7413
|
this.firstSelection = i;
|
@@ -7503,9 +7457,8 @@ class AbrController extends Logger {
|
|
7503
7457
|
* we are limiting the task execution per call stack to exactly one, but scheduling/post-poning further
|
7504
7458
|
* task processing on the next main loop iteration (also known as "next tick" in the Node/JS runtime lingo).
|
7505
7459
|
*/
|
7506
|
-
class TaskLoop
|
7507
|
-
constructor(
|
7508
|
-
super(label, logger);
|
7460
|
+
class TaskLoop {
|
7461
|
+
constructor() {
|
7509
7462
|
this._boundTick = void 0;
|
7510
7463
|
this._tickTimer = null;
|
7511
7464
|
this._tickInterval = null;
|
@@ -8596,8 +8549,8 @@ function createLoaderContext(frag, part = null) {
|
|
8596
8549
|
var _frag$decryptdata;
|
8597
8550
|
let byteRangeStart = start;
|
8598
8551
|
let byteRangeEnd = end;
|
8599
|
-
if (frag.sn === 'initSegment' &&
|
8600
|
-
// 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,
|
8601
8554
|
// has the unencrypted size specified in the range.
|
8602
8555
|
// Ref: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-6.3.6
|
8603
8556
|
const fragmentLen = end - start;
|
@@ -8630,9 +8583,6 @@ function createGapLoadError(frag, part) {
|
|
8630
8583
|
(part ? part : frag).stats.aborted = true;
|
8631
8584
|
return new LoadError(errorData);
|
8632
8585
|
}
|
8633
|
-
function isMethodFullSegmentAesCbc(method) {
|
8634
|
-
return method === 'AES-128' || method === 'AES-256';
|
8635
|
-
}
|
8636
8586
|
class LoadError extends Error {
|
8637
8587
|
constructor(data) {
|
8638
8588
|
super(data.error.message);
|
@@ -8642,61 +8592,33 @@ class LoadError extends Error {
|
|
8642
8592
|
}
|
8643
8593
|
|
8644
8594
|
class AESCrypto {
|
8645
|
-
constructor(subtle, iv
|
8595
|
+
constructor(subtle, iv) {
|
8646
8596
|
this.subtle = void 0;
|
8647
8597
|
this.aesIV = void 0;
|
8648
|
-
this.aesMode = void 0;
|
8649
8598
|
this.subtle = subtle;
|
8650
8599
|
this.aesIV = iv;
|
8651
|
-
this.aesMode = aesMode;
|
8652
8600
|
}
|
8653
8601
|
decrypt(data, key) {
|
8654
|
-
|
8655
|
-
|
8656
|
-
|
8657
|
-
|
8658
|
-
iv: this.aesIV
|
8659
|
-
}, key, data);
|
8660
|
-
case DecrypterAesMode.ctr:
|
8661
|
-
return this.subtle.decrypt({
|
8662
|
-
name: 'AES-CTR',
|
8663
|
-
counter: this.aesIV,
|
8664
|
-
length: 64
|
8665
|
-
},
|
8666
|
-
//64 : NIST SP800-38A standard suggests that the counter should occupy half of the counter block
|
8667
|
-
key, data);
|
8668
|
-
default:
|
8669
|
-
throw new Error(`[AESCrypto] invalid aes mode ${this.aesMode}`);
|
8670
|
-
}
|
8602
|
+
return this.subtle.decrypt({
|
8603
|
+
name: 'AES-CBC',
|
8604
|
+
iv: this.aesIV
|
8605
|
+
}, key, data);
|
8671
8606
|
}
|
8672
8607
|
}
|
8673
8608
|
|
8674
8609
|
class FastAESKey {
|
8675
|
-
constructor(subtle, key
|
8610
|
+
constructor(subtle, key) {
|
8676
8611
|
this.subtle = void 0;
|
8677
8612
|
this.key = void 0;
|
8678
|
-
this.aesMode = void 0;
|
8679
8613
|
this.subtle = subtle;
|
8680
8614
|
this.key = key;
|
8681
|
-
this.aesMode = aesMode;
|
8682
8615
|
}
|
8683
8616
|
expandKey() {
|
8684
|
-
const subtleAlgoName = getSubtleAlgoName(this.aesMode);
|
8685
8617
|
return this.subtle.importKey('raw', this.key, {
|
8686
|
-
name:
|
8618
|
+
name: 'AES-CBC'
|
8687
8619
|
}, false, ['encrypt', 'decrypt']);
|
8688
8620
|
}
|
8689
8621
|
}
|
8690
|
-
function getSubtleAlgoName(aesMode) {
|
8691
|
-
switch (aesMode) {
|
8692
|
-
case DecrypterAesMode.cbc:
|
8693
|
-
return 'AES-CBC';
|
8694
|
-
case DecrypterAesMode.ctr:
|
8695
|
-
return 'AES-CTR';
|
8696
|
-
default:
|
8697
|
-
throw new Error(`[FastAESKey] invalid aes mode ${aesMode}`);
|
8698
|
-
}
|
8699
|
-
}
|
8700
8622
|
|
8701
8623
|
// PKCS7
|
8702
8624
|
function removePadding(array) {
|
@@ -8946,8 +8868,7 @@ class Decrypter {
|
|
8946
8868
|
this.currentIV = null;
|
8947
8869
|
this.currentResult = null;
|
8948
8870
|
this.useSoftware = void 0;
|
8949
|
-
this.
|
8950
|
-
this.enableSoftwareAES = config.enableSoftwareAES;
|
8871
|
+
this.useSoftware = config.enableSoftwareAES;
|
8951
8872
|
this.removePKCS7Padding = removePKCS7Padding;
|
8952
8873
|
// built in decryptor expects PKCS7 padding
|
8953
8874
|
if (removePKCS7Padding) {
|
@@ -8960,7 +8881,9 @@ class Decrypter {
|
|
8960
8881
|
/* no-op */
|
8961
8882
|
}
|
8962
8883
|
}
|
8963
|
-
|
8884
|
+
if (this.subtle === null) {
|
8885
|
+
this.useSoftware = true;
|
8886
|
+
}
|
8964
8887
|
}
|
8965
8888
|
destroy() {
|
8966
8889
|
this.subtle = null;
|
@@ -8998,10 +8921,10 @@ class Decrypter {
|
|
8998
8921
|
this.softwareDecrypter = null;
|
8999
8922
|
}
|
9000
8923
|
}
|
9001
|
-
decrypt(data, key, iv
|
8924
|
+
decrypt(data, key, iv) {
|
9002
8925
|
if (this.useSoftware) {
|
9003
8926
|
return new Promise((resolve, reject) => {
|
9004
|
-
this.softwareDecrypt(new Uint8Array(data), key, iv
|
8927
|
+
this.softwareDecrypt(new Uint8Array(data), key, iv);
|
9005
8928
|
const decryptResult = this.flush();
|
9006
8929
|
if (decryptResult) {
|
9007
8930
|
resolve(decryptResult.buffer);
|
@@ -9010,21 +8933,17 @@ class Decrypter {
|
|
9010
8933
|
}
|
9011
8934
|
});
|
9012
8935
|
}
|
9013
|
-
return this.webCryptoDecrypt(new Uint8Array(data), key, iv
|
8936
|
+
return this.webCryptoDecrypt(new Uint8Array(data), key, iv);
|
9014
8937
|
}
|
9015
8938
|
|
9016
8939
|
// Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
|
9017
8940
|
// data is handled in the flush() call
|
9018
|
-
softwareDecrypt(data, key, iv
|
8941
|
+
softwareDecrypt(data, key, iv) {
|
9019
8942
|
const {
|
9020
8943
|
currentIV,
|
9021
8944
|
currentResult,
|
9022
8945
|
remainderData
|
9023
8946
|
} = this;
|
9024
|
-
if (aesMode !== DecrypterAesMode.cbc || key.byteLength !== 16) {
|
9025
|
-
logger.warn('SoftwareDecrypt: can only handle AES-128-CBC');
|
9026
|
-
return null;
|
9027
|
-
}
|
9028
8947
|
this.logOnce('JS AES decrypt');
|
9029
8948
|
// The output is staggered during progressive parsing - the current result is cached, and emitted on the next call
|
9030
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
|
@@ -9057,11 +8976,11 @@ class Decrypter {
|
|
9057
8976
|
}
|
9058
8977
|
return result;
|
9059
8978
|
}
|
9060
|
-
webCryptoDecrypt(data, key, iv
|
8979
|
+
webCryptoDecrypt(data, key, iv) {
|
9061
8980
|
const subtle = this.subtle;
|
9062
8981
|
if (this.key !== key || !this.fastAesKey) {
|
9063
8982
|
this.key = key;
|
9064
|
-
this.fastAesKey = new FastAESKey(subtle, key
|
8983
|
+
this.fastAesKey = new FastAESKey(subtle, key);
|
9065
8984
|
}
|
9066
8985
|
return this.fastAesKey.expandKey().then(aesKey => {
|
9067
8986
|
// decrypt using web crypto
|
@@ -9069,25 +8988,22 @@ class Decrypter {
|
|
9069
8988
|
return Promise.reject(new Error('web crypto not initialized'));
|
9070
8989
|
}
|
9071
8990
|
this.logOnce('WebCrypto AES decrypt');
|
9072
|
-
const crypto = new AESCrypto(subtle, new Uint8Array(iv)
|
8991
|
+
const crypto = new AESCrypto(subtle, new Uint8Array(iv));
|
9073
8992
|
return crypto.decrypt(data.buffer, aesKey);
|
9074
8993
|
}).catch(err => {
|
9075
8994
|
logger.warn(`[decrypter]: WebCrypto Error, disable WebCrypto API, ${err.name}: ${err.message}`);
|
9076
|
-
return this.onWebCryptoError(data, key, iv
|
8995
|
+
return this.onWebCryptoError(data, key, iv);
|
9077
8996
|
});
|
9078
8997
|
}
|
9079
|
-
onWebCryptoError(data, key, iv
|
9080
|
-
|
9081
|
-
|
9082
|
-
|
9083
|
-
|
9084
|
-
|
9085
|
-
|
9086
|
-
if (decryptResult) {
|
9087
|
-
return decryptResult.buffer;
|
9088
|
-
}
|
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;
|
9089
9005
|
}
|
9090
|
-
throw new Error('WebCrypto
|
9006
|
+
throw new Error('WebCrypto and softwareDecrypt: failed to decrypt data');
|
9091
9007
|
}
|
9092
9008
|
getValidChunk(data) {
|
9093
9009
|
let currentChunk = data;
|
@@ -9138,7 +9054,7 @@ const State = {
|
|
9138
9054
|
};
|
9139
9055
|
class BaseStreamController extends TaskLoop {
|
9140
9056
|
constructor(hls, fragmentTracker, keyLoader, logPrefix, playlistType) {
|
9141
|
-
super(
|
9057
|
+
super();
|
9142
9058
|
this.hls = void 0;
|
9143
9059
|
this.fragPrevious = null;
|
9144
9060
|
this.fragCurrent = null;
|
@@ -9163,83 +9079,22 @@ class BaseStreamController extends TaskLoop {
|
|
9163
9079
|
this.startFragRequested = false;
|
9164
9080
|
this.decrypter = void 0;
|
9165
9081
|
this.initPTS = [];
|
9166
|
-
this.
|
9167
|
-
|
9168
|
-
|
9169
|
-
|
9170
|
-
|
9171
|
-
mediaBuffer,
|
9172
|
-
state
|
9173
|
-
} = this;
|
9174
|
-
const currentTime = media ? media.currentTime : 0;
|
9175
|
-
const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
|
9176
|
-
this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
|
9177
|
-
if (this.state === State.ENDED) {
|
9178
|
-
this.resetLoadingState();
|
9179
|
-
} else if (fragCurrent) {
|
9180
|
-
// Seeking while frag load is in progress
|
9181
|
-
const tolerance = config.maxFragLookUpTolerance;
|
9182
|
-
const fragStartOffset = fragCurrent.start - tolerance;
|
9183
|
-
const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
|
9184
|
-
// if seeking out of buffered range or into new one
|
9185
|
-
if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
|
9186
|
-
const pastFragment = currentTime > fragEndOffset;
|
9187
|
-
// if the seek position is outside the current fragment range
|
9188
|
-
if (currentTime < fragStartOffset || pastFragment) {
|
9189
|
-
if (pastFragment && fragCurrent.loader) {
|
9190
|
-
this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
|
9191
|
-
fragCurrent.abortRequests();
|
9192
|
-
this.resetLoadingState();
|
9193
|
-
}
|
9194
|
-
this.fragPrevious = null;
|
9195
|
-
}
|
9196
|
-
}
|
9197
|
-
}
|
9198
|
-
if (media) {
|
9199
|
-
// Remove gap fragments
|
9200
|
-
this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
|
9201
|
-
this.lastCurrentTime = currentTime;
|
9202
|
-
}
|
9203
|
-
|
9204
|
-
// in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
|
9205
|
-
if (!this.loadedmetadata && !bufferInfo.len) {
|
9206
|
-
this.nextLoadPosition = this.startPosition = currentTime;
|
9207
|
-
}
|
9208
|
-
|
9209
|
-
// Async tick to speed up processing
|
9210
|
-
this.tickImmediate();
|
9211
|
-
};
|
9212
|
-
this.onMediaEnded = () => {
|
9213
|
-
// reset startPosition and lastCurrentTime to restart playback @ stream beginning
|
9214
|
-
this.startPosition = this.lastCurrentTime = 0;
|
9215
|
-
};
|
9082
|
+
this.onvseeking = null;
|
9083
|
+
this.onvended = null;
|
9084
|
+
this.logPrefix = '';
|
9085
|
+
this.log = void 0;
|
9086
|
+
this.warn = void 0;
|
9216
9087
|
this.playlistType = playlistType;
|
9088
|
+
this.logPrefix = logPrefix;
|
9089
|
+
this.log = logger.log.bind(logger, `${logPrefix}:`);
|
9090
|
+
this.warn = logger.warn.bind(logger, `${logPrefix}:`);
|
9217
9091
|
this.hls = hls;
|
9218
9092
|
this.fragmentLoader = new FragmentLoader(hls.config);
|
9219
9093
|
this.keyLoader = keyLoader;
|
9220
9094
|
this.fragmentTracker = fragmentTracker;
|
9221
9095
|
this.config = hls.config;
|
9222
9096
|
this.decrypter = new Decrypter(hls.config);
|
9223
|
-
}
|
9224
|
-
registerListeners() {
|
9225
|
-
const {
|
9226
|
-
hls
|
9227
|
-
} = this;
|
9228
|
-
hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
9229
|
-
hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
9230
|
-
hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
9231
9097
|
hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
|
9232
|
-
hls.on(Events.ERROR, this.onError, this);
|
9233
|
-
}
|
9234
|
-
unregisterListeners() {
|
9235
|
-
const {
|
9236
|
-
hls
|
9237
|
-
} = this;
|
9238
|
-
hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
9239
|
-
hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
9240
|
-
hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
9241
|
-
hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
|
9242
|
-
hls.off(Events.ERROR, this.onError, this);
|
9243
9098
|
}
|
9244
9099
|
doTick() {
|
9245
9100
|
this.onTickEnd();
|
@@ -9293,8 +9148,10 @@ class BaseStreamController extends TaskLoop {
|
|
9293
9148
|
}
|
9294
9149
|
onMediaAttached(event, data) {
|
9295
9150
|
const media = this.media = this.mediaBuffer = data.media;
|
9296
|
-
|
9297
|
-
|
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);
|
9298
9155
|
const config = this.config;
|
9299
9156
|
if (this.levels && config.autoStartLoad && this.state === State.STOPPED) {
|
9300
9157
|
this.startLoad(config.startPosition);
|
@@ -9308,9 +9165,10 @@ class BaseStreamController extends TaskLoop {
|
|
9308
9165
|
}
|
9309
9166
|
|
9310
9167
|
// remove video listeners
|
9311
|
-
if (media) {
|
9312
|
-
media.removeEventListener('seeking', this.
|
9313
|
-
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;
|
9314
9172
|
}
|
9315
9173
|
if (this.keyLoader) {
|
9316
9174
|
this.keyLoader.detach();
|
@@ -9320,8 +9178,56 @@ class BaseStreamController extends TaskLoop {
|
|
9320
9178
|
this.fragmentTracker.removeAllFragments();
|
9321
9179
|
this.stopLoad();
|
9322
9180
|
}
|
9323
|
-
|
9324
|
-
|
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
|
+
}
|
9325
9231
|
onManifestLoaded(event, data) {
|
9326
9232
|
this.startTimeOffset = data.startTimeOffset;
|
9327
9233
|
this.initPTS = [];
|
@@ -9331,7 +9237,7 @@ class BaseStreamController extends TaskLoop {
|
|
9331
9237
|
this.stopLoad();
|
9332
9238
|
super.onHandlerDestroying();
|
9333
9239
|
// @ts-ignore
|
9334
|
-
this.hls =
|
9240
|
+
this.hls = null;
|
9335
9241
|
}
|
9336
9242
|
onHandlerDestroyed() {
|
9337
9243
|
this.state = State.STOPPED;
|
@@ -9462,10 +9368,10 @@ class BaseStreamController extends TaskLoop {
|
|
9462
9368
|
const decryptData = frag.decryptdata;
|
9463
9369
|
|
9464
9370
|
// check to see if the payload needs to be decrypted
|
9465
|
-
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') {
|
9466
9372
|
const startTime = self.performance.now();
|
9467
9373
|
// decrypt init segment data
|
9468
|
-
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 => {
|
9469
9375
|
hls.trigger(Events.ERROR, {
|
9470
9376
|
type: ErrorTypes.MEDIA_ERROR,
|
9471
9377
|
details: ErrorDetails.FRAG_DECRYPT_ERROR,
|
@@ -9577,7 +9483,7 @@ class BaseStreamController extends TaskLoop {
|
|
9577
9483
|
}
|
9578
9484
|
let keyLoadingPromise = null;
|
9579
9485
|
if (frag.encrypted && !((_frag$decryptdata = frag.decryptdata) != null && _frag$decryptdata.key)) {
|
9580
|
-
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}`);
|
9581
9487
|
this.state = State.KEY_LOADING;
|
9582
9488
|
this.fragCurrent = frag;
|
9583
9489
|
keyLoadingPromise = this.keyLoader.load(frag).then(keyLoadedData => {
|
@@ -9608,7 +9514,7 @@ class BaseStreamController extends TaskLoop {
|
|
9608
9514
|
const partIndex = this.getNextPart(partList, frag, targetBufferTime);
|
9609
9515
|
if (partIndex > -1) {
|
9610
9516
|
const part = partList[partIndex];
|
9611
|
-
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))}`);
|
9612
9518
|
this.nextLoadPosition = part.start + part.duration;
|
9613
9519
|
this.state = State.FRAG_LOADING;
|
9614
9520
|
let _result;
|
@@ -9637,7 +9543,7 @@ class BaseStreamController extends TaskLoop {
|
|
9637
9543
|
}
|
9638
9544
|
}
|
9639
9545
|
}
|
9640
|
-
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))}`);
|
9641
9547
|
// Don't update nextLoadPosition for fragments which are not buffered
|
9642
9548
|
if (isFiniteNumber(frag.sn) && !this.bitrateTest) {
|
9643
9549
|
this.nextLoadPosition = frag.start + frag.duration;
|
@@ -10222,7 +10128,7 @@ class BaseStreamController extends TaskLoop {
|
|
10222
10128
|
errorAction.resolved = true;
|
10223
10129
|
}
|
10224
10130
|
} else {
|
10225
|
-
|
10131
|
+
logger.warn(`${data.details} reached or exceeded max retry (${retryCount})`);
|
10226
10132
|
return;
|
10227
10133
|
}
|
10228
10134
|
} else if ((errorAction == null ? void 0 : errorAction.action) === NetworkErrorAction.SendAlternateToPenaltyBox) {
|
@@ -10631,7 +10537,6 @@ const initPTSFn = (timestamp, timeOffset, initPTS) => {
|
|
10631
10537
|
*/
|
10632
10538
|
function getAudioConfig(observer, data, offset, audioCodec) {
|
10633
10539
|
let adtsObjectType;
|
10634
|
-
let originalAdtsObjectType;
|
10635
10540
|
let adtsExtensionSamplingIndex;
|
10636
10541
|
let adtsChannelConfig;
|
10637
10542
|
let config;
|
@@ -10639,7 +10544,7 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
10639
10544
|
const manifestCodec = audioCodec;
|
10640
10545
|
const adtsSamplingRates = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
|
10641
10546
|
// byte 2
|
10642
|
-
adtsObjectType =
|
10547
|
+
adtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
|
10643
10548
|
const adtsSamplingIndex = (data[offset + 2] & 0x3c) >>> 2;
|
10644
10549
|
if (adtsSamplingIndex > adtsSamplingRates.length - 1) {
|
10645
10550
|
const error = new Error(`invalid ADTS sampling index:${adtsSamplingIndex}`);
|
@@ -10656,8 +10561,8 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
10656
10561
|
// byte 3
|
10657
10562
|
adtsChannelConfig |= (data[offset + 3] & 0xc0) >>> 6;
|
10658
10563
|
logger.log(`manifest codec:${audioCodec}, ADTS type:${adtsObjectType}, samplingIndex:${adtsSamplingIndex}`);
|
10659
|
-
//
|
10660
|
-
if (/firefox
|
10564
|
+
// firefox: freq less than 24kHz = AAC SBR (HE-AAC)
|
10565
|
+
if (/firefox/i.test(userAgent)) {
|
10661
10566
|
if (adtsSamplingIndex >= 6) {
|
10662
10567
|
adtsObjectType = 5;
|
10663
10568
|
config = new Array(4);
|
@@ -10751,7 +10656,6 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
10751
10656
|
samplerate: adtsSamplingRates[adtsSamplingIndex],
|
10752
10657
|
channelCount: adtsChannelConfig,
|
10753
10658
|
codec: 'mp4a.40.' + adtsObjectType,
|
10754
|
-
parsedCodec: 'mp4a.40.' + originalAdtsObjectType,
|
10755
10659
|
manifestCodec
|
10756
10660
|
};
|
10757
10661
|
}
|
@@ -10806,8 +10710,7 @@ function initTrackConfig(track, observer, data, offset, audioCodec) {
|
|
10806
10710
|
track.channelCount = config.channelCount;
|
10807
10711
|
track.codec = config.codec;
|
10808
10712
|
track.manifestCodec = config.manifestCodec;
|
10809
|
-
track.
|
10810
|
-
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}`);
|
10811
10714
|
}
|
10812
10715
|
}
|
10813
10716
|
function getFrameDuration(samplerate) {
|
@@ -11983,7 +11886,7 @@ class SampleAesDecrypter {
|
|
11983
11886
|
});
|
11984
11887
|
}
|
11985
11888
|
decryptBuffer(encryptedData) {
|
11986
|
-
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);
|
11987
11890
|
}
|
11988
11891
|
|
11989
11892
|
// AAC - encrypt all full 16 bytes blocks starting from offset 16
|
@@ -14227,7 +14130,7 @@ class MP4Remuxer {
|
|
14227
14130
|
logger.warn(`[mp4-remuxer]: Injecting ${missing} audio frame @ ${(nextPts / inputTimeScale).toFixed(3)}s due to ${Math.round(1000 * delta / inputTimeScale)} ms gap.`);
|
14228
14131
|
for (let j = 0; j < missing; j++) {
|
14229
14132
|
const newStamp = Math.max(nextPts, 0);
|
14230
|
-
let fillFrame = AAC.getSilentFrame(track.
|
14133
|
+
let fillFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
|
14231
14134
|
if (!fillFrame) {
|
14232
14135
|
logger.log('[mp4-remuxer]: Unable to get silent frame for given audio codec; duplicating last frame instead.');
|
14233
14136
|
fillFrame = sample.unit.subarray();
|
@@ -14355,7 +14258,7 @@ class MP4Remuxer {
|
|
14355
14258
|
// samples count of this segment's duration
|
14356
14259
|
const nbSamples = Math.ceil((endDTS - startDTS) / frameDuration);
|
14357
14260
|
// silent frame
|
14358
|
-
const silentFrame = AAC.getSilentFrame(track.
|
14261
|
+
const silentFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
|
14359
14262
|
logger.warn('[mp4-remuxer]: remux empty Audio');
|
14360
14263
|
// Can't remux if we can't generate a silent frame...
|
14361
14264
|
if (!silentFrame) {
|
@@ -14749,15 +14652,13 @@ class Transmuxer {
|
|
14749
14652
|
initSegmentData
|
14750
14653
|
} = transmuxConfig;
|
14751
14654
|
const keyData = getEncryptionType(uintData, decryptdata);
|
14752
|
-
if (keyData &&
|
14655
|
+
if (keyData && keyData.method === 'AES-128') {
|
14753
14656
|
const decrypter = this.getDecrypter();
|
14754
|
-
const aesMode = getAesModeFromFullSegmentMethod(keyData.method);
|
14755
|
-
|
14756
14657
|
// Software decryption is synchronous; webCrypto is not
|
14757
14658
|
if (decrypter.isSync()) {
|
14758
14659
|
// Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
|
14759
14660
|
// data is handled in the flush() call
|
14760
|
-
let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer
|
14661
|
+
let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer);
|
14761
14662
|
// For Low-Latency HLS Parts, decrypt in place, since part parsing is expected on push progress
|
14762
14663
|
const loadingParts = chunkMeta.part > -1;
|
14763
14664
|
if (loadingParts) {
|
@@ -14769,7 +14670,7 @@ class Transmuxer {
|
|
14769
14670
|
}
|
14770
14671
|
uintData = new Uint8Array(decryptedData);
|
14771
14672
|
} else {
|
14772
|
-
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 => {
|
14773
14674
|
// Calling push here is important; if flush() is called while this is still resolving, this ensures that
|
14774
14675
|
// the decrypted data has been transmuxed
|
14775
14676
|
const result = this.push(decryptedData, null, chunkMeta);
|
@@ -15423,7 +15324,14 @@ class TransmuxerInterface {
|
|
15423
15324
|
this.observer = new EventEmitter();
|
15424
15325
|
this.observer.on(Events.FRAG_DECRYPTED, forwardMessage);
|
15425
15326
|
this.observer.on(Events.ERROR, forwardMessage);
|
15426
|
-
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
|
+
};
|
15427
15335
|
|
15428
15336
|
// navigator.vendor is not always available in Web Worker
|
15429
15337
|
// refer to https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/navigator
|
@@ -15711,7 +15619,7 @@ const TICK_INTERVAL$2 = 100; // how often to tick in ms
|
|
15711
15619
|
|
15712
15620
|
class AudioStreamController extends BaseStreamController {
|
15713
15621
|
constructor(hls, fragmentTracker, keyLoader) {
|
15714
|
-
super(hls, fragmentTracker, keyLoader, 'audio-stream-controller', PlaylistLevelType.AUDIO);
|
15622
|
+
super(hls, fragmentTracker, keyLoader, '[audio-stream-controller]', PlaylistLevelType.AUDIO);
|
15715
15623
|
this.videoBuffer = null;
|
15716
15624
|
this.videoTrackCC = -1;
|
15717
15625
|
this.waitingVideoCC = -1;
|
@@ -15723,24 +15631,27 @@ class AudioStreamController extends BaseStreamController {
|
|
15723
15631
|
this.flushing = false;
|
15724
15632
|
this.bufferFlushed = false;
|
15725
15633
|
this.cachedTrackLoadedData = null;
|
15726
|
-
this.
|
15634
|
+
this._registerListeners();
|
15727
15635
|
}
|
15728
15636
|
onHandlerDestroying() {
|
15729
|
-
this.
|
15637
|
+
this._unregisterListeners();
|
15730
15638
|
super.onHandlerDestroying();
|
15731
15639
|
this.mainDetails = null;
|
15732
15640
|
this.bufferedTrack = null;
|
15733
15641
|
this.switchingTrack = null;
|
15734
15642
|
}
|
15735
|
-
|
15736
|
-
super.registerListeners();
|
15643
|
+
_registerListeners() {
|
15737
15644
|
const {
|
15738
15645
|
hls
|
15739
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);
|
15740
15650
|
hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
15741
15651
|
hls.on(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
|
15742
15652
|
hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
15743
15653
|
hls.on(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
|
15654
|
+
hls.on(Events.ERROR, this.onError, this);
|
15744
15655
|
hls.on(Events.BUFFER_RESET, this.onBufferReset, this);
|
15745
15656
|
hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
15746
15657
|
hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
|
@@ -15748,18 +15659,18 @@ class AudioStreamController extends BaseStreamController {
|
|
15748
15659
|
hls.on(Events.INIT_PTS_FOUND, this.onInitPtsFound, this);
|
15749
15660
|
hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
15750
15661
|
}
|
15751
|
-
|
15662
|
+
_unregisterListeners() {
|
15752
15663
|
const {
|
15753
15664
|
hls
|
15754
15665
|
} = this;
|
15755
|
-
|
15756
|
-
|
15757
|
-
|
15758
|
-
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);
|
15759
15669
|
hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
15760
15670
|
hls.off(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
|
15761
15671
|
hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
15762
15672
|
hls.off(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
|
15673
|
+
hls.off(Events.ERROR, this.onError, this);
|
15763
15674
|
hls.off(Events.BUFFER_RESET, this.onBufferReset, this);
|
15764
15675
|
hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
15765
15676
|
hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
|
@@ -16491,7 +16402,7 @@ class AudioStreamController extends BaseStreamController {
|
|
16491
16402
|
|
16492
16403
|
class AudioTrackController extends BasePlaylistController {
|
16493
16404
|
constructor(hls) {
|
16494
|
-
super(hls, 'audio-track-controller');
|
16405
|
+
super(hls, '[audio-track-controller]');
|
16495
16406
|
this.tracks = [];
|
16496
16407
|
this.groupIds = null;
|
16497
16408
|
this.tracksInGroup = [];
|
@@ -16810,23 +16721,26 @@ const TICK_INTERVAL$1 = 500; // how often to tick in ms
|
|
16810
16721
|
|
16811
16722
|
class SubtitleStreamController extends BaseStreamController {
|
16812
16723
|
constructor(hls, fragmentTracker, keyLoader) {
|
16813
|
-
super(hls, fragmentTracker, keyLoader, 'subtitle-stream-controller', PlaylistLevelType.SUBTITLE);
|
16724
|
+
super(hls, fragmentTracker, keyLoader, '[subtitle-stream-controller]', PlaylistLevelType.SUBTITLE);
|
16814
16725
|
this.currentTrackId = -1;
|
16815
16726
|
this.tracksBuffered = [];
|
16816
16727
|
this.mainDetails = null;
|
16817
|
-
this.
|
16728
|
+
this._registerListeners();
|
16818
16729
|
}
|
16819
16730
|
onHandlerDestroying() {
|
16820
|
-
this.
|
16731
|
+
this._unregisterListeners();
|
16821
16732
|
super.onHandlerDestroying();
|
16822
16733
|
this.mainDetails = null;
|
16823
16734
|
}
|
16824
|
-
|
16825
|
-
super.registerListeners();
|
16735
|
+
_registerListeners() {
|
16826
16736
|
const {
|
16827
16737
|
hls
|
16828
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);
|
16829
16742
|
hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
16743
|
+
hls.on(Events.ERROR, this.onError, this);
|
16830
16744
|
hls.on(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
|
16831
16745
|
hls.on(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
|
16832
16746
|
hls.on(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
|
@@ -16834,12 +16748,15 @@ class SubtitleStreamController extends BaseStreamController {
|
|
16834
16748
|
hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
|
16835
16749
|
hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
16836
16750
|
}
|
16837
|
-
|
16838
|
-
super.unregisterListeners();
|
16751
|
+
_unregisterListeners() {
|
16839
16752
|
const {
|
16840
16753
|
hls
|
16841
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);
|
16842
16758
|
hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
16759
|
+
hls.off(Events.ERROR, this.onError, this);
|
16843
16760
|
hls.off(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
|
16844
16761
|
hls.off(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
|
16845
16762
|
hls.off(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
|
@@ -17066,10 +16983,10 @@ class SubtitleStreamController extends BaseStreamController {
|
|
17066
16983
|
return;
|
17067
16984
|
}
|
17068
16985
|
// check to see if the payload needs to be decrypted
|
17069
|
-
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') {
|
17070
16987
|
const startTime = performance.now();
|
17071
16988
|
// decrypt the subtitles
|
17072
|
-
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 => {
|
17073
16990
|
hls.trigger(Events.ERROR, {
|
17074
16991
|
type: ErrorTypes.MEDIA_ERROR,
|
17075
16992
|
details: ErrorDetails.FRAG_DECRYPT_ERROR,
|
@@ -17203,7 +17120,7 @@ class BufferableInstance {
|
|
17203
17120
|
|
17204
17121
|
class SubtitleTrackController extends BasePlaylistController {
|
17205
17122
|
constructor(hls) {
|
17206
|
-
super(hls, 'subtitle-track-controller');
|
17123
|
+
super(hls, '[subtitle-track-controller]');
|
17207
17124
|
this.media = null;
|
17208
17125
|
this.tracks = [];
|
17209
17126
|
this.groupIds = null;
|
@@ -17212,10 +17129,10 @@ class SubtitleTrackController extends BasePlaylistController {
|
|
17212
17129
|
this.currentTrack = null;
|
17213
17130
|
this.selectDefaultTrack = true;
|
17214
17131
|
this.queuedDefaultTrack = -1;
|
17132
|
+
this.asyncPollTrackChange = () => this.pollTrackChange(0);
|
17215
17133
|
this.useTextTrackPolling = false;
|
17216
17134
|
this.subtitlePollingInterval = -1;
|
17217
17135
|
this._subtitleDisplay = true;
|
17218
|
-
this.asyncPollTrackChange = () => this.pollTrackChange(0);
|
17219
17136
|
this.onTextTracksChanged = () => {
|
17220
17137
|
if (!this.useTextTrackPolling) {
|
17221
17138
|
self.clearInterval(this.subtitlePollingInterval);
|
@@ -17249,7 +17166,6 @@ class SubtitleTrackController extends BasePlaylistController {
|
|
17249
17166
|
this.tracks.length = 0;
|
17250
17167
|
this.tracksInGroup.length = 0;
|
17251
17168
|
this.currentTrack = null;
|
17252
|
-
// @ts-ignore
|
17253
17169
|
this.onTextTracksChanged = this.asyncPollTrackChange = null;
|
17254
17170
|
super.destroy();
|
17255
17171
|
}
|
@@ -17710,9 +17626,8 @@ class BufferOperationQueue {
|
|
17710
17626
|
}
|
17711
17627
|
|
17712
17628
|
const VIDEO_CODEC_PROFILE_REPLACE = /(avc[1234]|hvc1|hev1|dvh[1e]|vp09|av01)(?:\.[^.,]+)+/;
|
17713
|
-
class BufferController
|
17629
|
+
class BufferController {
|
17714
17630
|
constructor(hls) {
|
17715
|
-
super('buffer-controller', hls.logger);
|
17716
17631
|
// The level details used to determine duration, target-duration and live
|
17717
17632
|
this.details = null;
|
17718
17633
|
// cache the self generated object url to detect hijack of video tag
|
@@ -17742,6 +17657,9 @@ class BufferController extends Logger {
|
|
17742
17657
|
this.tracks = {};
|
17743
17658
|
this.pendingTracks = {};
|
17744
17659
|
this.sourceBuffer = void 0;
|
17660
|
+
this.log = void 0;
|
17661
|
+
this.warn = void 0;
|
17662
|
+
this.error = void 0;
|
17745
17663
|
this._onEndStreaming = event => {
|
17746
17664
|
if (!this.hls) {
|
17747
17665
|
return;
|
@@ -17787,11 +17705,15 @@ class BufferController extends Logger {
|
|
17787
17705
|
_objectUrl
|
17788
17706
|
} = this;
|
17789
17707
|
if (mediaSrc !== _objectUrl) {
|
17790
|
-
|
17708
|
+
logger.error(`Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`);
|
17791
17709
|
}
|
17792
17710
|
};
|
17793
17711
|
this.hls = hls;
|
17712
|
+
const logPrefix = '[buffer-controller]';
|
17794
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);
|
17795
17717
|
this._initSourceBuffer();
|
17796
17718
|
this.registerListeners();
|
17797
17719
|
}
|
@@ -17804,12 +17726,6 @@ class BufferController extends Logger {
|
|
17804
17726
|
this.lastMpegAudioChunk = null;
|
17805
17727
|
// @ts-ignore
|
17806
17728
|
this.hls = null;
|
17807
|
-
// @ts-ignore
|
17808
|
-
this._onMediaSourceOpen = this._onMediaSourceClose = null;
|
17809
|
-
// @ts-ignore
|
17810
|
-
this._onMediaSourceEnded = null;
|
17811
|
-
// @ts-ignore
|
17812
|
-
this._onStartStreaming = this._onEndStreaming = null;
|
17813
17729
|
}
|
17814
17730
|
registerListeners() {
|
17815
17731
|
const {
|
@@ -21074,12 +20990,14 @@ class TimelineController {
|
|
21074
20990
|
this.cea608Parser1 = this.cea608Parser2 = undefined;
|
21075
20991
|
}
|
21076
20992
|
initCea608Parsers() {
|
21077
|
-
|
21078
|
-
|
21079
|
-
|
21080
|
-
|
21081
|
-
|
21082
|
-
|
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
|
+
}
|
21083
21001
|
}
|
21084
21002
|
addCues(trackName, startTime, endTime, screen, cueRanges) {
|
21085
21003
|
// skip cues which overlap more than 50% with previously parsed time ranges
|
@@ -21317,7 +21235,7 @@ class TimelineController {
|
|
21317
21235
|
if (inUseTracks != null && inUseTracks.length) {
|
21318
21236
|
const unusedTextTracks = inUseTracks.filter(t => t !== null).map(t => t.label);
|
21319
21237
|
if (unusedTextTracks.length) {
|
21320
|
-
|
21238
|
+
logger.warn(`Media element contains unused subtitle tracks: ${unusedTextTracks.join(', ')}. Replace media element for each source to clear TextTracks and captions menu.`);
|
21321
21239
|
}
|
21322
21240
|
}
|
21323
21241
|
} else if (this.tracks.length) {
|
@@ -21362,23 +21280,26 @@ class TimelineController {
|
|
21362
21280
|
return level == null ? void 0 : level.attrs['CLOSED-CAPTIONS'];
|
21363
21281
|
}
|
21364
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
|
+
}
|
21365
21294
|
// if this frag isn't contiguous, clear the parser so cues with bad start/end times aren't added to the textTrack
|
21366
|
-
if (
|
21295
|
+
if (data.frag.type === PlaylistLevelType.MAIN) {
|
21367
21296
|
var _data$part$index, _data$part;
|
21368
|
-
const {
|
21369
|
-
cea608Parser1,
|
21370
|
-
cea608Parser2,
|
21371
|
-
lastSn
|
21372
|
-
} = this;
|
21373
|
-
if (!cea608Parser1 || !cea608Parser2) {
|
21374
|
-
return;
|
21375
|
-
}
|
21376
21297
|
const {
|
21377
21298
|
cc,
|
21378
21299
|
sn
|
21379
21300
|
} = data.frag;
|
21380
|
-
const partIndex = (_data$part$index = (_data$part = data.part) == null ? void 0 : _data$part.index) != null ? _data$part$index : -1;
|
21381
|
-
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)) {
|
21382
21303
|
cea608Parser1.reset();
|
21383
21304
|
cea608Parser2.reset();
|
21384
21305
|
}
|
@@ -21435,7 +21356,7 @@ class TimelineController {
|
|
21435
21356
|
frag: frag
|
21436
21357
|
});
|
21437
21358
|
}, error => {
|
21438
|
-
|
21359
|
+
logger.log(`Failed to parse IMSC1: ${error}`);
|
21439
21360
|
hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {
|
21440
21361
|
success: false,
|
21441
21362
|
frag: frag,
|
@@ -21476,7 +21397,7 @@ class TimelineController {
|
|
21476
21397
|
this._fallbackToIMSC1(frag, payload);
|
21477
21398
|
}
|
21478
21399
|
// Something went wrong while parsing. Trigger event with success false.
|
21479
|
-
|
21400
|
+
logger.log(`Failed to parse VTT cue: ${error}`);
|
21480
21401
|
if (missingInitPTS && maxAvCC > frag.cc) {
|
21481
21402
|
return;
|
21482
21403
|
}
|
@@ -21537,7 +21458,12 @@ class TimelineController {
|
|
21537
21458
|
this.captionsTracks = {};
|
21538
21459
|
}
|
21539
21460
|
onFragParsingUserdata(event, data) {
|
21540
|
-
|
21461
|
+
this.initCea608Parsers();
|
21462
|
+
const {
|
21463
|
+
cea608Parser1,
|
21464
|
+
cea608Parser2
|
21465
|
+
} = this;
|
21466
|
+
if (!this.enabled || !cea608Parser1 || !cea608Parser2) {
|
21541
21467
|
return;
|
21542
21468
|
}
|
21543
21469
|
const {
|
@@ -21552,12 +21478,9 @@ class TimelineController {
|
|
21552
21478
|
for (let i = 0; i < samples.length; i++) {
|
21553
21479
|
const ccBytes = samples[i].bytes;
|
21554
21480
|
if (ccBytes) {
|
21555
|
-
if (!this.cea608Parser1) {
|
21556
|
-
this.initCea608Parsers();
|
21557
|
-
}
|
21558
21481
|
const ccdatas = this.extractCea608Data(ccBytes);
|
21559
|
-
|
21560
|
-
|
21482
|
+
cea608Parser1.addData(samples[i].pts, ccdatas[0]);
|
21483
|
+
cea608Parser2.addData(samples[i].pts, ccdatas[1]);
|
21561
21484
|
}
|
21562
21485
|
}
|
21563
21486
|
}
|
@@ -21753,7 +21676,7 @@ class CapLevelController {
|
|
21753
21676
|
const hls = this.hls;
|
21754
21677
|
const maxLevel = this.getMaxLevel(levels.length - 1);
|
21755
21678
|
if (maxLevel !== this.autoLevelCapping) {
|
21756
|
-
|
21679
|
+
logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
|
21757
21680
|
}
|
21758
21681
|
hls.autoLevelCapping = maxLevel;
|
21759
21682
|
if (hls.autoLevelCapping > this.autoLevelCapping && this.streamController) {
|
@@ -21931,10 +21854,10 @@ class FPSController {
|
|
21931
21854
|
totalDroppedFrames: droppedFrames
|
21932
21855
|
});
|
21933
21856
|
if (droppedFPS > 0) {
|
21934
|
-
//
|
21857
|
+
// logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
|
21935
21858
|
if (currentDropped > hls.config.fpsDroppedMonitoringThreshold * currentDecoded) {
|
21936
21859
|
let currentLevel = hls.currentLevel;
|
21937
|
-
|
21860
|
+
logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
|
21938
21861
|
if (currentLevel > 0 && (hls.autoLevelCapping === -1 || hls.autoLevelCapping >= currentLevel)) {
|
21939
21862
|
currentLevel = currentLevel - 1;
|
21940
21863
|
hls.trigger(Events.FPS_DROP_LEVEL_CAPPING, {
|
@@ -21966,6 +21889,7 @@ class FPSController {
|
|
21966
21889
|
}
|
21967
21890
|
}
|
21968
21891
|
|
21892
|
+
const LOGGER_PREFIX = '[eme]';
|
21969
21893
|
/**
|
21970
21894
|
* Controller to deal with encrypted media extensions (EME)
|
21971
21895
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/Encrypted_Media_Extensions_API
|
@@ -21973,9 +21897,8 @@ class FPSController {
|
|
21973
21897
|
* @class
|
21974
21898
|
* @constructor
|
21975
21899
|
*/
|
21976
|
-
class EMEController
|
21900
|
+
class EMEController {
|
21977
21901
|
constructor(hls) {
|
21978
|
-
super('eme', hls.logger);
|
21979
21902
|
this.hls = void 0;
|
21980
21903
|
this.config = void 0;
|
21981
21904
|
this.media = null;
|
@@ -21985,100 +21908,12 @@ class EMEController extends Logger {
|
|
21985
21908
|
this.mediaKeySessions = [];
|
21986
21909
|
this.keyIdToKeySessionPromise = {};
|
21987
21910
|
this.setMediaKeysQueue = EMEController.CDMCleanupPromise ? [EMEController.CDMCleanupPromise] : [];
|
21988
|
-
this.onMediaEncrypted =
|
21989
|
-
|
21990
|
-
|
21991
|
-
|
21992
|
-
|
21993
|
-
|
21994
|
-
|
21995
|
-
// Ignore event when initData is null
|
21996
|
-
if (initData === null) {
|
21997
|
-
return;
|
21998
|
-
}
|
21999
|
-
let keyId;
|
22000
|
-
let keySystemDomain;
|
22001
|
-
if (initDataType === 'sinf' && this.config.drmSystems[KeySystems.FAIRPLAY]) {
|
22002
|
-
// Match sinf keyId to playlist skd://keyId=
|
22003
|
-
const json = bin2str(new Uint8Array(initData));
|
22004
|
-
try {
|
22005
|
-
const sinf = base64Decode(JSON.parse(json).sinf);
|
22006
|
-
const tenc = parseSinf(new Uint8Array(sinf));
|
22007
|
-
if (!tenc) {
|
22008
|
-
return;
|
22009
|
-
}
|
22010
|
-
keyId = tenc.subarray(8, 24);
|
22011
|
-
keySystemDomain = KeySystems.FAIRPLAY;
|
22012
|
-
} catch (error) {
|
22013
|
-
this.warn('Failed to parse sinf "encrypted" event message initData');
|
22014
|
-
return;
|
22015
|
-
}
|
22016
|
-
} else {
|
22017
|
-
// Support clear-lead key-session creation (otherwise depend on playlist keys)
|
22018
|
-
const psshInfo = parsePssh(initData);
|
22019
|
-
if (psshInfo === null) {
|
22020
|
-
return;
|
22021
|
-
}
|
22022
|
-
if (psshInfo.version === 0 && psshInfo.systemId === KeySystemIds.WIDEVINE && psshInfo.data) {
|
22023
|
-
keyId = psshInfo.data.subarray(8, 24);
|
22024
|
-
}
|
22025
|
-
keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId);
|
22026
|
-
}
|
22027
|
-
if (!keySystemDomain || !keyId) {
|
22028
|
-
return;
|
22029
|
-
}
|
22030
|
-
const keyIdHex = Hex.hexDump(keyId);
|
22031
|
-
const {
|
22032
|
-
keyIdToKeySessionPromise,
|
22033
|
-
mediaKeySessions
|
22034
|
-
} = this;
|
22035
|
-
let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex];
|
22036
|
-
for (let i = 0; i < mediaKeySessions.length; i++) {
|
22037
|
-
// Match playlist key
|
22038
|
-
const keyContext = mediaKeySessions[i];
|
22039
|
-
const decryptdata = keyContext.decryptdata;
|
22040
|
-
if (decryptdata.pssh || !decryptdata.keyId) {
|
22041
|
-
continue;
|
22042
|
-
}
|
22043
|
-
const oldKeyIdHex = Hex.hexDump(decryptdata.keyId);
|
22044
|
-
if (keyIdHex === oldKeyIdHex || decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1) {
|
22045
|
-
keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex];
|
22046
|
-
delete keyIdToKeySessionPromise[oldKeyIdHex];
|
22047
|
-
decryptdata.pssh = new Uint8Array(initData);
|
22048
|
-
decryptdata.keyId = keyId;
|
22049
|
-
keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = keySessionContextPromise.then(() => {
|
22050
|
-
return this.generateRequestWithPreferredKeySession(keyContext, initDataType, initData, 'encrypted-event-key-match');
|
22051
|
-
});
|
22052
|
-
break;
|
22053
|
-
}
|
22054
|
-
}
|
22055
|
-
if (!keySessionContextPromise) {
|
22056
|
-
// Clear-lead key (not encountered in playlist)
|
22057
|
-
keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = this.getKeySystemSelectionPromise([keySystemDomain]).then(({
|
22058
|
-
keySystem,
|
22059
|
-
mediaKeys
|
22060
|
-
}) => {
|
22061
|
-
var _keySystemToKeySystem;
|
22062
|
-
this.throwIfDestroyed();
|
22063
|
-
const decryptdata = new LevelKey('ISO-23001-7', keyIdHex, (_keySystemToKeySystem = keySystemDomainToKeySystemFormat(keySystem)) != null ? _keySystemToKeySystem : '');
|
22064
|
-
decryptdata.pssh = new Uint8Array(initData);
|
22065
|
-
decryptdata.keyId = keyId;
|
22066
|
-
return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
|
22067
|
-
this.throwIfDestroyed();
|
22068
|
-
const keySessionContext = this.createMediaKeySessionContext({
|
22069
|
-
decryptdata,
|
22070
|
-
keySystem,
|
22071
|
-
mediaKeys
|
22072
|
-
});
|
22073
|
-
return this.generateRequestWithPreferredKeySession(keySessionContext, initDataType, initData, 'encrypted-event-no-match');
|
22074
|
-
});
|
22075
|
-
});
|
22076
|
-
}
|
22077
|
-
keySessionContextPromise.catch(error => this.handleError(error));
|
22078
|
-
};
|
22079
|
-
this.onWaitingForKey = event => {
|
22080
|
-
this.log(`"${event.type}" event`);
|
22081
|
-
};
|
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);
|
22082
21917
|
this.hls = hls;
|
22083
21918
|
this.config = hls.config;
|
22084
21919
|
this.registerListeners();
|
@@ -22092,9 +21927,9 @@ class EMEController extends Logger {
|
|
22092
21927
|
config.licenseXhrSetup = config.licenseResponseCallback = undefined;
|
22093
21928
|
config.drmSystems = config.drmSystemOptions = {};
|
22094
21929
|
// @ts-ignore
|
22095
|
-
this.hls = this.
|
21930
|
+
this.hls = this.onMediaEncrypted = this.onWaitingForKey = this.keyIdToKeySessionPromise = null;
|
22096
21931
|
// @ts-ignore
|
22097
|
-
this.
|
21932
|
+
this.config = null;
|
22098
21933
|
}
|
22099
21934
|
registerListeners() {
|
22100
21935
|
this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
@@ -22358,6 +22193,100 @@ class EMEController extends Logger {
|
|
22358
22193
|
}
|
22359
22194
|
return this.attemptKeySystemAccess(keySystemsToAttempt);
|
22360
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
|
+
}
|
22361
22290
|
attemptSetMediaKeys(keySystem, mediaKeys) {
|
22362
22291
|
const queue = this.setMediaKeysQueue.slice();
|
22363
22292
|
this.log(`Setting media-keys for "${keySystem}"`);
|
@@ -24090,10 +24019,10 @@ class CMCDController {
|
|
24090
24019
|
}
|
24091
24020
|
|
24092
24021
|
const PATHWAY_PENALTY_DURATION_MS = 300000;
|
24093
|
-
class ContentSteeringController
|
24022
|
+
class ContentSteeringController {
|
24094
24023
|
constructor(hls) {
|
24095
|
-
super('content-steering', hls.logger);
|
24096
24024
|
this.hls = void 0;
|
24025
|
+
this.log = void 0;
|
24097
24026
|
this.loader = null;
|
24098
24027
|
this.uri = null;
|
24099
24028
|
this.pathwayId = '.';
|
@@ -24108,6 +24037,7 @@ class ContentSteeringController extends Logger {
|
|
24108
24037
|
this.subtitleTracks = null;
|
24109
24038
|
this.penalizedPathways = {};
|
24110
24039
|
this.hls = hls;
|
24040
|
+
this.log = logger.log.bind(logger, `[content-steering]:`);
|
24111
24041
|
this.registerListeners();
|
24112
24042
|
}
|
24113
24043
|
registerListeners() {
|
@@ -24231,7 +24161,7 @@ class ContentSteeringController extends Logger {
|
|
24231
24161
|
errorAction.resolved = this.pathwayId !== errorPathway;
|
24232
24162
|
}
|
24233
24163
|
if (!errorAction.resolved) {
|
24234
|
-
|
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)}`);
|
24235
24165
|
}
|
24236
24166
|
}
|
24237
24167
|
}
|
@@ -24402,7 +24332,7 @@ class ContentSteeringController extends Logger {
|
|
24402
24332
|
onSuccess: (response, stats, context, networkDetails) => {
|
24403
24333
|
this.log(`Loaded steering manifest: "${url}"`);
|
24404
24334
|
const steeringData = response.data;
|
24405
|
-
if (
|
24335
|
+
if (steeringData.VERSION !== 1) {
|
24406
24336
|
this.log(`Steering VERSION ${steeringData.VERSION} not supported!`);
|
24407
24337
|
return;
|
24408
24338
|
}
|
@@ -25372,7 +25302,7 @@ function timelineConfig() {
|
|
25372
25302
|
/**
|
25373
25303
|
* @ignore
|
25374
25304
|
*/
|
25375
|
-
function mergeConfig(defaultConfig, userConfig
|
25305
|
+
function mergeConfig(defaultConfig, userConfig) {
|
25376
25306
|
if ((userConfig.liveSyncDurationCount || userConfig.liveMaxLatencyDurationCount) && (userConfig.liveSyncDuration || userConfig.liveMaxLatencyDuration)) {
|
25377
25307
|
throw new Error("Illegal hls.js config: don't mix up liveSyncDurationCount/liveMaxLatencyDurationCount and liveSyncDuration/liveMaxLatencyDuration");
|
25378
25308
|
}
|
@@ -25442,7 +25372,7 @@ function deepCpy(obj) {
|
|
25442
25372
|
/**
|
25443
25373
|
* @ignore
|
25444
25374
|
*/
|
25445
|
-
function enableStreamingMode(config
|
25375
|
+
function enableStreamingMode(config) {
|
25446
25376
|
const currentLoader = config.loader;
|
25447
25377
|
if (currentLoader !== FetchLoader && currentLoader !== XhrLoader) {
|
25448
25378
|
// If a developer has configured their own loader, respect that choice
|
@@ -25459,9 +25389,10 @@ function enableStreamingMode(config, logger) {
|
|
25459
25389
|
}
|
25460
25390
|
}
|
25461
25391
|
|
25392
|
+
let chromeOrFirefox;
|
25462
25393
|
class LevelController extends BasePlaylistController {
|
25463
25394
|
constructor(hls, contentSteeringController) {
|
25464
|
-
super(hls, 'level-controller');
|
25395
|
+
super(hls, '[level-controller]');
|
25465
25396
|
this._levels = [];
|
25466
25397
|
this._firstLevel = -1;
|
25467
25398
|
this._maxAutoLevel = -1;
|
@@ -25532,15 +25463,23 @@ class LevelController extends BasePlaylistController {
|
|
25532
25463
|
let videoCodecFound = false;
|
25533
25464
|
let audioCodecFound = false;
|
25534
25465
|
data.levels.forEach(levelParsed => {
|
25535
|
-
var _videoCodec;
|
25466
|
+
var _audioCodec, _videoCodec;
|
25536
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
|
25537
25471
|
let {
|
25538
25472
|
audioCodec,
|
25539
25473
|
videoCodec
|
25540
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
|
+
}
|
25541
25481
|
if (audioCodec) {
|
25542
|
-
|
25543
|
-
levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource) || undefined;
|
25482
|
+
levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource);
|
25544
25483
|
}
|
25545
25484
|
if (((_videoCodec = videoCodec) == null ? void 0 : _videoCodec.indexOf('avc1')) === 0) {
|
25546
25485
|
videoCodec = levelParsed.videoCodec = convertAVC1ToAVCOTI(videoCodec);
|
@@ -25665,8 +25604,8 @@ class LevelController extends BasePlaylistController {
|
|
25665
25604
|
return valueB - valueA;
|
25666
25605
|
}
|
25667
25606
|
}
|
25668
|
-
if (a.
|
25669
|
-
return a.
|
25607
|
+
if (a.averageBitrate !== b.averageBitrate) {
|
25608
|
+
return a.averageBitrate - b.averageBitrate;
|
25670
25609
|
}
|
25671
25610
|
return 0;
|
25672
25611
|
});
|
@@ -26126,8 +26065,6 @@ class KeyLoader {
|
|
26126
26065
|
}
|
26127
26066
|
return this.loadKeyEME(keyInfo, frag);
|
26128
26067
|
case 'AES-128':
|
26129
|
-
case 'AES-256':
|
26130
|
-
case 'AES-256-CTR':
|
26131
26068
|
return this.loadKeyHTTP(keyInfo, frag);
|
26132
26069
|
default:
|
26133
26070
|
return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`Key supplied with unsupported METHOD: "${decryptdata.method}"`)));
|
@@ -26265,9 +26202,8 @@ const STALL_MINIMUM_DURATION_MS = 250;
|
|
26265
26202
|
const MAX_START_GAP_JUMP = 2.0;
|
26266
26203
|
const SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1;
|
26267
26204
|
const SKIP_BUFFER_RANGE_START = 0.05;
|
26268
|
-
class GapController
|
26205
|
+
class GapController {
|
26269
26206
|
constructor(config, media, fragmentTracker, hls) {
|
26270
|
-
super('gap-controller', hls.logger);
|
26271
26207
|
this.config = void 0;
|
26272
26208
|
this.media = null;
|
26273
26209
|
this.fragmentTracker = void 0;
|
@@ -26321,7 +26257,7 @@ class GapController extends Logger {
|
|
26321
26257
|
// The playhead is now moving, but was previously stalled
|
26322
26258
|
if (this.stallReported) {
|
26323
26259
|
const _stalledDuration = self.performance.now() - stalled;
|
26324
|
-
|
26260
|
+
logger.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(_stalledDuration)}ms`);
|
26325
26261
|
this.stallReported = false;
|
26326
26262
|
}
|
26327
26263
|
this.stalled = null;
|
@@ -26432,7 +26368,7 @@ class GapController extends Logger {
|
|
26432
26368
|
// needs to cross some sort of threshold covering all source-buffers content
|
26433
26369
|
// to start playing properly.
|
26434
26370
|
if ((bufferInfo.len > config.maxBufferHole || bufferInfo.nextStart && bufferInfo.nextStart - currentTime < config.maxBufferHole) && stalledDurationMs > config.highBufferWatchdogPeriod * 1000) {
|
26435
|
-
|
26371
|
+
logger.warn('Trying to nudge playhead over buffer-hole');
|
26436
26372
|
// Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds
|
26437
26373
|
// We only try to jump the hole if it's under the configured size
|
26438
26374
|
// Reset stalled so to rearm watchdog timer
|
@@ -26456,7 +26392,7 @@ class GapController extends Logger {
|
|
26456
26392
|
// Report stalled error once
|
26457
26393
|
this.stallReported = true;
|
26458
26394
|
const error = new Error(`Playback stalling at @${media.currentTime} due to low buffer (${JSON.stringify(bufferInfo)})`);
|
26459
|
-
|
26395
|
+
logger.warn(error.message);
|
26460
26396
|
hls.trigger(Events.ERROR, {
|
26461
26397
|
type: ErrorTypes.MEDIA_ERROR,
|
26462
26398
|
details: ErrorDetails.BUFFER_STALLED_ERROR,
|
@@ -26524,7 +26460,7 @@ class GapController extends Logger {
|
|
26524
26460
|
}
|
26525
26461
|
}
|
26526
26462
|
const targetTime = Math.max(startTime + SKIP_BUFFER_RANGE_START, currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS);
|
26527
|
-
|
26463
|
+
logger.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`);
|
26528
26464
|
this.moved = true;
|
26529
26465
|
this.stalled = null;
|
26530
26466
|
media.currentTime = targetTime;
|
@@ -26565,7 +26501,7 @@ class GapController extends Logger {
|
|
26565
26501
|
const targetTime = currentTime + (nudgeRetry + 1) * config.nudgeOffset;
|
26566
26502
|
// playback stalled in buffered area ... let's nudge currentTime to try to overcome this
|
26567
26503
|
const error = new Error(`Nudging 'currentTime' from ${currentTime} to ${targetTime}`);
|
26568
|
-
|
26504
|
+
logger.warn(error.message);
|
26569
26505
|
media.currentTime = targetTime;
|
26570
26506
|
hls.trigger(Events.ERROR, {
|
26571
26507
|
type: ErrorTypes.MEDIA_ERROR,
|
@@ -26575,7 +26511,7 @@ class GapController extends Logger {
|
|
26575
26511
|
});
|
26576
26512
|
} else {
|
26577
26513
|
const error = new Error(`Playhead still not moving while enough data buffered @${currentTime} after ${config.nudgeMaxRetry} nudges`);
|
26578
|
-
|
26514
|
+
logger.error(error.message);
|
26579
26515
|
hls.trigger(Events.ERROR, {
|
26580
26516
|
type: ErrorTypes.MEDIA_ERROR,
|
26581
26517
|
details: ErrorDetails.BUFFER_STALLED_ERROR,
|
@@ -26590,7 +26526,7 @@ const TICK_INTERVAL = 100; // how often to tick in ms
|
|
26590
26526
|
|
26591
26527
|
class StreamController extends BaseStreamController {
|
26592
26528
|
constructor(hls, fragmentTracker, keyLoader) {
|
26593
|
-
super(hls, fragmentTracker, keyLoader, 'stream-controller', PlaylistLevelType.MAIN);
|
26529
|
+
super(hls, fragmentTracker, keyLoader, '[stream-controller]', PlaylistLevelType.MAIN);
|
26594
26530
|
this.audioCodecSwap = false;
|
26595
26531
|
this.gapController = null;
|
26596
26532
|
this.level = -1;
|
@@ -26598,43 +26534,27 @@ class StreamController extends BaseStreamController {
|
|
26598
26534
|
this.altAudio = false;
|
26599
26535
|
this.audioOnly = false;
|
26600
26536
|
this.fragPlaying = null;
|
26537
|
+
this.onvplaying = null;
|
26538
|
+
this.onvseeked = null;
|
26601
26539
|
this.fragLastKbps = 0;
|
26602
26540
|
this.couldBacktrack = false;
|
26603
26541
|
this.backtrackFragment = null;
|
26604
26542
|
this.audioCodecSwitch = false;
|
26605
26543
|
this.videoBuffer = null;
|
26606
|
-
this.
|
26607
|
-
// tick to speed up FRAG_CHANGED triggering
|
26608
|
-
this.tick();
|
26609
|
-
};
|
26610
|
-
this.onMediaSeeked = () => {
|
26611
|
-
const media = this.media;
|
26612
|
-
const currentTime = media ? media.currentTime : null;
|
26613
|
-
if (isFiniteNumber(currentTime)) {
|
26614
|
-
this.log(`Media seeked to ${currentTime.toFixed(3)}`);
|
26615
|
-
}
|
26616
|
-
|
26617
|
-
// If seeked was issued before buffer was appended do not tick immediately
|
26618
|
-
const bufferInfo = this.getMainFwdBufferInfo();
|
26619
|
-
if (bufferInfo === null || bufferInfo.len === 0) {
|
26620
|
-
this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
|
26621
|
-
return;
|
26622
|
-
}
|
26623
|
-
|
26624
|
-
// tick to speed up FRAG_CHANGED triggering
|
26625
|
-
this.tick();
|
26626
|
-
};
|
26627
|
-
this.registerListeners();
|
26544
|
+
this._registerListeners();
|
26628
26545
|
}
|
26629
|
-
|
26630
|
-
super.registerListeners();
|
26546
|
+
_registerListeners() {
|
26631
26547
|
const {
|
26632
26548
|
hls
|
26633
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);
|
26634
26553
|
hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
|
26635
26554
|
hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
|
26636
26555
|
hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
26637
26556
|
hls.on(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
|
26557
|
+
hls.on(Events.ERROR, this.onError, this);
|
26638
26558
|
hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
26639
26559
|
hls.on(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
|
26640
26560
|
hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
@@ -26642,14 +26562,17 @@ class StreamController extends BaseStreamController {
|
|
26642
26562
|
hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
|
26643
26563
|
hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
26644
26564
|
}
|
26645
|
-
|
26646
|
-
super.unregisterListeners();
|
26565
|
+
_unregisterListeners() {
|
26647
26566
|
const {
|
26648
26567
|
hls
|
26649
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);
|
26650
26572
|
hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
|
26651
26573
|
hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
26652
26574
|
hls.off(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
|
26575
|
+
hls.off(Events.ERROR, this.onError, this);
|
26653
26576
|
hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
26654
26577
|
hls.off(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
|
26655
26578
|
hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
@@ -26658,9 +26581,7 @@ class StreamController extends BaseStreamController {
|
|
26658
26581
|
hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
26659
26582
|
}
|
26660
26583
|
onHandlerDestroying() {
|
26661
|
-
|
26662
|
-
this.onMediaPlaying = this.onMediaSeeked = null;
|
26663
|
-
this.unregisterListeners();
|
26584
|
+
this._unregisterListeners();
|
26664
26585
|
super.onHandlerDestroying();
|
26665
26586
|
}
|
26666
26587
|
startLoad(startPosition) {
|
@@ -26987,17 +26908,20 @@ class StreamController extends BaseStreamController {
|
|
26987
26908
|
onMediaAttached(event, data) {
|
26988
26909
|
super.onMediaAttached(event, data);
|
26989
26910
|
const media = data.media;
|
26990
|
-
|
26991
|
-
|
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);
|
26992
26915
|
this.gapController = new GapController(this.config, media, this.fragmentTracker, this.hls);
|
26993
26916
|
}
|
26994
26917
|
onMediaDetaching() {
|
26995
26918
|
const {
|
26996
26919
|
media
|
26997
26920
|
} = this;
|
26998
|
-
if (media) {
|
26999
|
-
media.removeEventListener('playing', this.
|
27000
|
-
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;
|
27001
26925
|
this.videoBuffer = null;
|
27002
26926
|
}
|
27003
26927
|
this.fragPlaying = null;
|
@@ -27007,6 +26931,27 @@ class StreamController extends BaseStreamController {
|
|
27007
26931
|
}
|
27008
26932
|
super.onMediaDetaching();
|
27009
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
|
+
}
|
27010
26955
|
onManifestLoading() {
|
27011
26956
|
// reset buffer on manifest loading
|
27012
26957
|
this.log('Trigger BUFFER_RESET');
|
@@ -27737,7 +27682,7 @@ class Hls {
|
|
27737
27682
|
* Get the video-dev/hls.js package version.
|
27738
27683
|
*/
|
27739
27684
|
static get version() {
|
27740
|
-
return "1.5.2
|
27685
|
+
return "1.5.2";
|
27741
27686
|
}
|
27742
27687
|
|
27743
27688
|
/**
|
@@ -27800,10 +27745,6 @@ class Hls {
|
|
27800
27745
|
* The configuration object provided on player instantiation.
|
27801
27746
|
*/
|
27802
27747
|
this.userConfig = void 0;
|
27803
|
-
/**
|
27804
|
-
* The logger functions used by this player instance, configured on player instantiation.
|
27805
|
-
*/
|
27806
|
-
this.logger = void 0;
|
27807
27748
|
this.coreComponents = void 0;
|
27808
27749
|
this.networkControllers = void 0;
|
27809
27750
|
this.started = false;
|
@@ -27823,11 +27764,11 @@ class Hls {
|
|
27823
27764
|
this._media = null;
|
27824
27765
|
this.url = null;
|
27825
27766
|
this.triggeringException = void 0;
|
27826
|
-
|
27827
|
-
const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig
|
27767
|
+
enableLogs(userConfig.debug || false, 'Hls instance');
|
27768
|
+
const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig);
|
27828
27769
|
this.userConfig = userConfig;
|
27829
27770
|
if (config.progressive) {
|
27830
|
-
enableStreamingMode(config
|
27771
|
+
enableStreamingMode(config);
|
27831
27772
|
}
|
27832
27773
|
|
27833
27774
|
// core controllers and network loaders
|
@@ -27926,7 +27867,7 @@ class Hls {
|
|
27926
27867
|
try {
|
27927
27868
|
return this.emit(event, event, eventObject);
|
27928
27869
|
} catch (error) {
|
27929
|
-
|
27870
|
+
logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
|
27930
27871
|
// Prevent recursion in error event handlers that throw #5497
|
27931
27872
|
if (!this.triggeringException) {
|
27932
27873
|
this.triggeringException = true;
|
@@ -27952,7 +27893,7 @@ class Hls {
|
|
27952
27893
|
* Dispose of the instance
|
27953
27894
|
*/
|
27954
27895
|
destroy() {
|
27955
|
-
|
27896
|
+
logger.log('destroy');
|
27956
27897
|
this.trigger(Events.DESTROYING, undefined);
|
27957
27898
|
this.detachMedia();
|
27958
27899
|
this.removeAllListeners();
|
@@ -27973,7 +27914,7 @@ class Hls {
|
|
27973
27914
|
* Attaches Hls.js to a media element
|
27974
27915
|
*/
|
27975
27916
|
attachMedia(media) {
|
27976
|
-
|
27917
|
+
logger.log('attachMedia');
|
27977
27918
|
this._media = media;
|
27978
27919
|
this.trigger(Events.MEDIA_ATTACHING, {
|
27979
27920
|
media: media
|
@@ -27984,7 +27925,7 @@ class Hls {
|
|
27984
27925
|
* Detach Hls.js from the media
|
27985
27926
|
*/
|
27986
27927
|
detachMedia() {
|
27987
|
-
|
27928
|
+
logger.log('detachMedia');
|
27988
27929
|
this.trigger(Events.MEDIA_DETACHING, undefined);
|
27989
27930
|
this._media = null;
|
27990
27931
|
}
|
@@ -28001,7 +27942,7 @@ class Hls {
|
|
28001
27942
|
});
|
28002
27943
|
this._autoLevelCapping = -1;
|
28003
27944
|
this._maxHdcpLevel = null;
|
28004
|
-
|
27945
|
+
logger.log(`loadSource:${loadingSource}`);
|
28005
27946
|
if (media && loadedSource && (loadedSource !== loadingSource || this.bufferController.hasSourceTypes())) {
|
28006
27947
|
this.detachMedia();
|
28007
27948
|
this.attachMedia(media);
|
@@ -28020,7 +27961,7 @@ class Hls {
|
|
28020
27961
|
* Defaults to -1 (None: starts from earliest point)
|
28021
27962
|
*/
|
28022
27963
|
startLoad(startPosition = -1) {
|
28023
|
-
|
27964
|
+
logger.log(`startLoad(${startPosition})`);
|
28024
27965
|
this.started = true;
|
28025
27966
|
this.networkControllers.forEach(controller => {
|
28026
27967
|
controller.startLoad(startPosition);
|
@@ -28031,7 +27972,7 @@ class Hls {
|
|
28031
27972
|
* Stop loading of any stream data.
|
28032
27973
|
*/
|
28033
27974
|
stopLoad() {
|
28034
|
-
|
27975
|
+
logger.log('stopLoad');
|
28035
27976
|
this.started = false;
|
28036
27977
|
this.networkControllers.forEach(controller => {
|
28037
27978
|
controller.stopLoad();
|
@@ -28067,7 +28008,7 @@ class Hls {
|
|
28067
28008
|
* Swap through possible audio codecs in the stream (for example to switch from stereo to 5.1)
|
28068
28009
|
*/
|
28069
28010
|
swapAudioCodec() {
|
28070
|
-
|
28011
|
+
logger.log('swapAudioCodec');
|
28071
28012
|
this.streamController.swapAudioCodec();
|
28072
28013
|
}
|
28073
28014
|
|
@@ -28078,7 +28019,7 @@ class Hls {
|
|
28078
28019
|
* Automatic recovery of media-errors by this process is configurable.
|
28079
28020
|
*/
|
28080
28021
|
recoverMediaError() {
|
28081
|
-
|
28022
|
+
logger.log('recoverMediaError');
|
28082
28023
|
const media = this._media;
|
28083
28024
|
this.detachMedia();
|
28084
28025
|
if (media) {
|
@@ -28108,7 +28049,7 @@ class Hls {
|
|
28108
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.
|
28109
28050
|
*/
|
28110
28051
|
set currentLevel(newLevel) {
|
28111
|
-
|
28052
|
+
logger.log(`set currentLevel:${newLevel}`);
|
28112
28053
|
this.levelController.manualLevel = newLevel;
|
28113
28054
|
this.streamController.immediateLevelSwitch();
|
28114
28055
|
}
|
@@ -28127,7 +28068,7 @@ class Hls {
|
|
28127
28068
|
* @param newLevel - Pass -1 for automatic level selection
|
28128
28069
|
*/
|
28129
28070
|
set nextLevel(newLevel) {
|
28130
|
-
|
28071
|
+
logger.log(`set nextLevel:${newLevel}`);
|
28131
28072
|
this.levelController.manualLevel = newLevel;
|
28132
28073
|
this.streamController.nextLevelSwitch();
|
28133
28074
|
}
|
@@ -28146,7 +28087,7 @@ class Hls {
|
|
28146
28087
|
* @param newLevel - Pass -1 for automatic level selection
|
28147
28088
|
*/
|
28148
28089
|
set loadLevel(newLevel) {
|
28149
|
-
|
28090
|
+
logger.log(`set loadLevel:${newLevel}`);
|
28150
28091
|
this.levelController.manualLevel = newLevel;
|
28151
28092
|
}
|
28152
28093
|
|
@@ -28177,7 +28118,7 @@ class Hls {
|
|
28177
28118
|
* Sets "first-level", see getter.
|
28178
28119
|
*/
|
28179
28120
|
set firstLevel(newLevel) {
|
28180
|
-
|
28121
|
+
logger.log(`set firstLevel:${newLevel}`);
|
28181
28122
|
this.levelController.firstLevel = newLevel;
|
28182
28123
|
}
|
28183
28124
|
|
@@ -28202,7 +28143,7 @@ class Hls {
|
|
28202
28143
|
* (determined from download of first segment)
|
28203
28144
|
*/
|
28204
28145
|
set startLevel(newLevel) {
|
28205
|
-
|
28146
|
+
logger.log(`set startLevel:${newLevel}`);
|
28206
28147
|
// if not in automatic start level detection, ensure startLevel is greater than minAutoLevel
|
28207
28148
|
if (newLevel !== -1) {
|
28208
28149
|
newLevel = Math.max(newLevel, this.minAutoLevel);
|
@@ -28277,7 +28218,7 @@ class Hls {
|
|
28277
28218
|
*/
|
28278
28219
|
set autoLevelCapping(newLevel) {
|
28279
28220
|
if (this._autoLevelCapping !== newLevel) {
|
28280
|
-
|
28221
|
+
logger.log(`set autoLevelCapping:${newLevel}`);
|
28281
28222
|
this._autoLevelCapping = newLevel;
|
28282
28223
|
this.levelController.checkMaxAutoUpdated();
|
28283
28224
|
}
|