hls.js 1.5.9-0.canary.10308 → 1.5.9
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/README.md +3 -4
- package/dist/hls-demo.js +8 -21
- package/dist/hls-demo.js.map +1 -1
- package/dist/hls.js +4743 -6054
- package/dist/hls.js.d.ts +85 -108
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +1708 -2381
- 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 +1293 -1978
- 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 +1549 -2866
- 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 +34 -34
- package/src/config.ts +2 -3
- package/src/controller/abr-controller.ts +20 -24
- package/src/controller/audio-stream-controller.ts +74 -68
- package/src/controller/audio-track-controller.ts +1 -1
- package/src/controller/base-playlist-controller.ts +10 -27
- package/src/controller/base-stream-controller.ts +38 -160
- package/src/controller/buffer-controller.ts +92 -230
- package/src/controller/buffer-operation-queue.ts +19 -16
- package/src/controller/cap-level-controller.ts +2 -3
- package/src/controller/cmcd-controller.ts +14 -51
- package/src/controller/content-steering-controller.ts +15 -29
- package/src/controller/eme-controller.ts +23 -10
- package/src/controller/error-controller.ts +8 -6
- package/src/controller/fps-controller.ts +3 -8
- package/src/controller/fragment-tracker.ts +11 -15
- package/src/controller/gap-controller.ts +16 -43
- package/src/controller/id3-track-controller.ts +7 -7
- package/src/controller/latency-controller.ts +11 -9
- package/src/controller/level-controller.ts +19 -37
- package/src/controller/stream-controller.ts +32 -37
- package/src/controller/subtitle-stream-controller.ts +40 -28
- package/src/controller/subtitle-track-controller.ts +3 -5
- package/src/controller/timeline-controller.ts +21 -19
- package/src/crypt/aes-crypto.ts +2 -21
- package/src/crypt/decrypter.ts +16 -32
- package/src/crypt/fast-aes-key.ts +5 -24
- package/src/demux/audio/aacdemuxer.ts +2 -2
- package/src/demux/audio/ac3-demuxer.ts +3 -4
- package/src/demux/audio/adts.ts +4 -9
- package/src/demux/audio/base-audio-demuxer.ts +14 -16
- package/src/demux/audio/mp3demuxer.ts +3 -4
- package/src/demux/audio/mpegaudio.ts +1 -1
- package/src/demux/id3.ts +411 -0
- package/src/demux/mp4demuxer.ts +7 -7
- 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 +37 -71
- package/src/demux/video/avc-video-parser.ts +119 -208
- package/src/demux/video/base-video-parser.ts +18 -147
- package/src/demux/video/exp-golomb.ts +208 -0
- package/src/events.ts +1 -8
- package/src/exports-named.ts +1 -1
- package/src/hls.ts +38 -61
- package/src/loader/fragment-loader.ts +3 -10
- package/src/loader/key-loader.ts +1 -3
- package/src/loader/level-key.ts +9 -10
- package/src/loader/playlist-loader.ts +5 -4
- package/src/remux/mp4-generator.ts +1 -196
- package/src/remux/mp4-remuxer.ts +8 -24
- package/src/task-loop.ts +2 -5
- package/src/types/component-api.ts +1 -3
- package/src/types/demuxer.ts +0 -4
- package/src/types/events.ts +0 -4
- package/src/types/remuxer.ts +1 -1
- package/src/utils/buffer-helper.ts +31 -12
- package/src/utils/cea-608-parser.ts +3 -1
- package/src/utils/codecs.ts +5 -34
- package/src/utils/fetch-loader.ts +1 -1
- package/src/utils/imsc1-ttml-parser.ts +1 -1
- package/src/utils/keysystem-util.ts +6 -1
- package/src/utils/logger.ts +23 -58
- package/src/utils/mp4-tools.ts +3 -5
- package/src/utils/webvtt-parser.ts +1 -1
- package/src/crypt/decrypter-aes-mode.ts +0 -4
- package/src/demux/video/hevc-video-parser.ts +0 -749
- package/src/empty-es.js +0 -5
- package/src/utils/encryption-methods-util.ts +0 -21
- package/src/utils/utf8-utils.ts +0 -18
package/package.json
CHANGED
@@ -58,52 +58,52 @@
|
|
58
58
|
"test:func:sauce": "SAUCE=1 UA=safari OS='OS X 10.15' BABEL_ENV=development mocha --require @babel/register tests/functional/auto/setup.js --timeout 40000 --exit",
|
59
59
|
"type-check": "tsc --noEmit",
|
60
60
|
"type-check:watch": "npm run type-check -- --watch",
|
61
|
-
"prepare": "husky"
|
61
|
+
"prepare": "husky install"
|
62
62
|
},
|
63
63
|
"devDependencies": {
|
64
|
-
"@babel/core": "7.
|
65
|
-
"@babel/helper-module-imports": "7.
|
64
|
+
"@babel/core": "7.23.7",
|
65
|
+
"@babel/helper-module-imports": "7.22.15",
|
66
66
|
"@babel/plugin-proposal-class-properties": "7.18.6",
|
67
67
|
"@babel/plugin-proposal-object-rest-spread": "7.20.7",
|
68
68
|
"@babel/plugin-proposal-optional-chaining": "7.21.0",
|
69
|
-
"@babel/plugin-transform-object-assign": "7.
|
70
|
-
"@babel/preset-env": "7.
|
71
|
-
"@babel/preset-typescript": "7.
|
69
|
+
"@babel/plugin-transform-object-assign": "7.23.3",
|
70
|
+
"@babel/preset-env": "7.23.7",
|
71
|
+
"@babel/preset-typescript": "7.23.3",
|
72
72
|
"@babel/register": "7.23.7",
|
73
|
-
"@microsoft/api-documenter": "7.
|
74
|
-
"@microsoft/api-extractor": "7.
|
73
|
+
"@microsoft/api-documenter": "7.23.16",
|
74
|
+
"@microsoft/api-extractor": "7.39.1",
|
75
75
|
"@rollup/plugin-alias": "5.1.0",
|
76
76
|
"@rollup/plugin-babel": "6.0.4",
|
77
|
-
"@rollup/plugin-commonjs": "25.0.
|
77
|
+
"@rollup/plugin-commonjs": "25.0.7",
|
78
78
|
"@rollup/plugin-node-resolve": "15.2.3",
|
79
79
|
"@rollup/plugin-replace": "5.0.5",
|
80
80
|
"@rollup/plugin-terser": "0.4.4",
|
81
|
-
"@rollup/plugin-typescript": "11.1.
|
82
|
-
"@svta/common-media-library": "0.6.
|
83
|
-
"@types/chai": "4.3.
|
81
|
+
"@rollup/plugin-typescript": "11.1.5",
|
82
|
+
"@svta/common-media-library": "0.6.1",
|
83
|
+
"@types/chai": "4.3.11",
|
84
84
|
"@types/chart.js": "2.9.41",
|
85
85
|
"@types/mocha": "10.0.6",
|
86
86
|
"@types/sinon-chai": "3.2.12",
|
87
|
-
"@typescript-eslint/eslint-plugin": "
|
88
|
-
"@typescript-eslint/parser": "
|
87
|
+
"@typescript-eslint/eslint-plugin": "6.17.0",
|
88
|
+
"@typescript-eslint/parser": "6.17.0",
|
89
89
|
"babel-loader": "9.1.3",
|
90
90
|
"babel-plugin-transform-remove-console": "6.9.4",
|
91
|
-
"chai": "4.
|
91
|
+
"chai": "4.3.10",
|
92
92
|
"chart.js": "2.9.4",
|
93
|
-
"chromedriver": "
|
93
|
+
"chromedriver": "120.0.1",
|
94
94
|
"doctoc": "2.2.1",
|
95
|
-
"es-check": "7.
|
96
|
-
"eslint": "8.
|
95
|
+
"es-check": "7.1.1",
|
96
|
+
"eslint": "8.56.0",
|
97
97
|
"eslint-config-prettier": "9.1.0",
|
98
98
|
"eslint-plugin-import": "2.29.1",
|
99
|
-
"eslint-plugin-mocha": "10.
|
100
|
-
"eslint-plugin-
|
99
|
+
"eslint-plugin-mocha": "10.2.0",
|
100
|
+
"eslint-plugin-node": "11.1.0",
|
101
101
|
"eslint-plugin-promise": "6.1.1",
|
102
102
|
"eventemitter3": "5.0.1",
|
103
103
|
"http-server": "14.1.1",
|
104
|
-
"husky": "
|
104
|
+
"husky": "8.0.3",
|
105
105
|
"jsonpack": "1.1.5",
|
106
|
-
"karma": "6.4.
|
106
|
+
"karma": "6.4.2",
|
107
107
|
"karma-chrome-launcher": "3.2.0",
|
108
108
|
"karma-coverage": "2.2.1",
|
109
109
|
"karma-mocha": "2.0.1",
|
@@ -111,24 +111,24 @@
|
|
111
111
|
"karma-rollup-preprocessor": "github:jlmakes/karma-rollup-preprocessor#7a7268d91149307b3cf2888ee4e65ccd079955a3",
|
112
112
|
"karma-sinon-chai": "2.0.2",
|
113
113
|
"karma-sourcemap-loader": "0.4.0",
|
114
|
-
"lint-staged": "15.2.
|
114
|
+
"lint-staged": "15.2.0",
|
115
115
|
"markdown-styles": "3.2.0",
|
116
|
-
"micromatch": "4.0.
|
117
|
-
"mocha": "10.
|
116
|
+
"micromatch": "4.0.5",
|
117
|
+
"mocha": "10.2.0",
|
118
118
|
"node-fetch": "3.3.2",
|
119
|
-
"npm-run-
|
120
|
-
"prettier": "3.
|
119
|
+
"npm-run-all": "4.1.5",
|
120
|
+
"prettier": "3.1.1",
|
121
121
|
"promise-polyfill": "8.3.0",
|
122
|
-
"rollup": "4.
|
122
|
+
"rollup": "4.9.4",
|
123
123
|
"rollup-plugin-istanbul": "5.0.0",
|
124
124
|
"sauce-connect-launcher": "1.3.2",
|
125
|
-
"selenium-webdriver": "4.
|
126
|
-
"semver": "7.
|
127
|
-
"sinon": "
|
125
|
+
"selenium-webdriver": "4.16.0",
|
126
|
+
"semver": "7.5.4",
|
127
|
+
"sinon": "17.0.1",
|
128
128
|
"sinon-chai": "3.7.0",
|
129
|
-
"typescript": "5.
|
129
|
+
"typescript": "5.3.3",
|
130
130
|
"url-toolkit": "2.2.5",
|
131
|
-
"wrangler": "3.
|
131
|
+
"wrangler": "3.22.4"
|
132
132
|
},
|
133
|
-
"version": "1.5.9
|
133
|
+
"version": "1.5.9"
|
134
134
|
}
|
package/src/config.ts
CHANGED
@@ -17,10 +17,10 @@ import XhrLoader from './utils/xhr-loader';
|
|
17
17
|
import FetchLoader, { fetchSupported } from './utils/fetch-loader';
|
18
18
|
import Cues from './utils/cues';
|
19
19
|
import { requestMediaKeySystemAccess } from './utils/mediakeys-helper';
|
20
|
+
import { ILogger, logger } from './utils/logger';
|
20
21
|
|
21
22
|
import type Hls from './hls';
|
22
23
|
import type { CuesInterface } from './utils/cues';
|
23
|
-
import type { ILogger } from './utils/logger';
|
24
24
|
import type { MediaKeyFunc, KeySystems } from './utils/mediakeys-helper';
|
25
25
|
import type {
|
26
26
|
FragmentLoaderContext,
|
@@ -558,7 +558,6 @@ function timelineConfig(): TimelineControllerConfig {
|
|
558
558
|
export function mergeConfig(
|
559
559
|
defaultConfig: HlsConfig,
|
560
560
|
userConfig: Partial<HlsConfig>,
|
561
|
-
logger: ILogger,
|
562
561
|
): HlsConfig {
|
563
562
|
if (
|
564
563
|
(userConfig.liveSyncDurationCount ||
|
@@ -665,7 +664,7 @@ function deepCpy(obj: any): any {
|
|
665
664
|
/**
|
666
665
|
* @ignore
|
667
666
|
*/
|
668
|
-
export function enableStreamingMode(config
|
667
|
+
export function enableStreamingMode(config) {
|
669
668
|
const currentLoader = config.loader;
|
670
669
|
if (currentLoader !== FetchLoader && currentLoader !== XhrLoader) {
|
671
670
|
// If a developer has configured their own loader, respect that choice
|
@@ -2,7 +2,7 @@ import EwmaBandWidthEstimator from '../utils/ewma-bandwidth-estimator';
|
|
2
2
|
import { Events } from '../events';
|
3
3
|
import { ErrorDetails } from '../errors';
|
4
4
|
import { PlaylistLevelType } from '../types/loader';
|
5
|
-
import {
|
5
|
+
import { logger } from '../utils/logger';
|
6
6
|
import {
|
7
7
|
SUPPORTED_INFO_DEFAULT,
|
8
8
|
getMediaDecodingInfoPromise,
|
@@ -31,7 +31,7 @@ import type {
|
|
31
31
|
} from '../types/events';
|
32
32
|
import type { AbrComponentAPI } from '../types/component-api';
|
33
33
|
|
34
|
-
class AbrController
|
34
|
+
class AbrController implements AbrComponentAPI {
|
35
35
|
protected hls: Hls;
|
36
36
|
private lastLevelLoadSec: number = 0;
|
37
37
|
private lastLoadedFragLevel: number = -1;
|
@@ -48,7 +48,6 @@ class AbrController extends Logger implements AbrComponentAPI {
|
|
48
48
|
public bwEstimator: EwmaBandWidthEstimator;
|
49
49
|
|
50
50
|
constructor(hls: Hls) {
|
51
|
-
super('abr', hls.logger);
|
52
51
|
this.hls = hls;
|
53
52
|
this.bwEstimator = this.initEstimator();
|
54
53
|
this.registerListeners();
|
@@ -56,7 +55,7 @@ class AbrController extends Logger implements AbrComponentAPI {
|
|
56
55
|
|
57
56
|
public resetEstimator(abrEwmaDefaultEstimate?: number) {
|
58
57
|
if (abrEwmaDefaultEstimate) {
|
59
|
-
|
58
|
+
logger.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
|
60
59
|
this.hls.config.abrEwmaDefaultEstimate = abrEwmaDefaultEstimate;
|
61
60
|
}
|
62
61
|
this.firstSelection = -1;
|
@@ -356,7 +355,7 @@ class AbrController extends Logger implements AbrComponentAPI {
|
|
356
355
|
}
|
357
356
|
|
358
357
|
this.clearTimer();
|
359
|
-
|
358
|
+
logger.warn(`[abr] Fragment ${frag.sn}${
|
360
359
|
part ? ' part ' + part.index : ''
|
361
360
|
} of level ${frag.level} is loading too slowly;
|
362
361
|
Time to underbuffer: ${bufferStarvationDelay.toFixed(3)} s
|
@@ -480,8 +479,8 @@ class AbrController extends Logger implements AbrComponentAPI {
|
|
480
479
|
}
|
481
480
|
const firstLevel = this.hls.firstLevel;
|
482
481
|
const clamped = Math.min(Math.max(firstLevel, minAutoLevel), maxAutoLevel);
|
483
|
-
|
484
|
-
`Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`,
|
482
|
+
logger.warn(
|
483
|
+
`[abr] Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`,
|
485
484
|
);
|
486
485
|
return clamped;
|
487
486
|
}
|
@@ -539,9 +538,6 @@ class AbrController extends Logger implements AbrComponentAPI {
|
|
539
538
|
|
540
539
|
private getNextABRAutoLevel(): number {
|
541
540
|
const { fragCurrent, partCurrent, hls } = this;
|
542
|
-
if (hls.levels.length <= 1) {
|
543
|
-
return hls.loadLevel;
|
544
|
-
}
|
545
541
|
const { maxAutoLevel, config, minAutoLevel } = hls;
|
546
542
|
const currentFragDuration = partCurrent
|
547
543
|
? partCurrent.duration
|
@@ -588,8 +584,8 @@ class AbrController extends Logger implements AbrComponentAPI {
|
|
588
584
|
? Math.min(currentFragDuration, config.maxLoadingDelay)
|
589
585
|
: config.maxLoadingDelay;
|
590
586
|
maxStarvationDelay = maxLoadingDelay - bitrateTestDelay;
|
591
|
-
|
592
|
-
`bitrate test took ${Math.round(
|
587
|
+
logger.info(
|
588
|
+
`[abr] bitrate test took ${Math.round(
|
593
589
|
1000 * bitrateTestDelay,
|
594
590
|
)}ms, set first fragment max fetchDuration to ${Math.round(
|
595
591
|
1000 * maxStarvationDelay,
|
@@ -608,8 +604,8 @@ class AbrController extends Logger implements AbrComponentAPI {
|
|
608
604
|
bwFactor,
|
609
605
|
bwUpFactor,
|
610
606
|
);
|
611
|
-
|
612
|
-
|
607
|
+
logger.info(
|
608
|
+
`[abr] ${
|
613
609
|
bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'
|
614
610
|
}, optimal quality level ${bestLevel}`,
|
615
611
|
);
|
@@ -702,7 +698,7 @@ class AbrController extends Logger implements AbrComponentAPI {
|
|
702
698
|
: videoRanges[0];
|
703
699
|
currentFrameRate = minFramerate;
|
704
700
|
currentBw = Math.max(currentBw, minBitrate);
|
705
|
-
|
701
|
+
logger.log(`[abr] picked start tier ${JSON.stringify(startTier)}`);
|
706
702
|
} else {
|
707
703
|
currentCodecSet = level?.codecSet;
|
708
704
|
currentVideoRange = level?.videoRange;
|
@@ -755,19 +751,19 @@ class AbrController extends Logger implements AbrComponentAPI {
|
|
755
751
|
const levels = this.hls.levels;
|
756
752
|
const index = levels.indexOf(levelInfo);
|
757
753
|
if (decodingInfo.error) {
|
758
|
-
|
759
|
-
`MediaCapabilities decodingInfo error: "${
|
754
|
+
logger.warn(
|
755
|
+
`[abr] MediaCapabilities decodingInfo error: "${
|
760
756
|
decodingInfo.error
|
761
757
|
}" for level ${index} ${JSON.stringify(decodingInfo)}`,
|
762
758
|
);
|
763
759
|
} else if (!decodingInfo.supported) {
|
764
|
-
|
765
|
-
`Unsupported MediaCapabilities decodingInfo result for level ${index} ${JSON.stringify(
|
760
|
+
logger.warn(
|
761
|
+
`[abr] Unsupported MediaCapabilities decodingInfo result for level ${index} ${JSON.stringify(
|
766
762
|
decodingInfo,
|
767
763
|
)}`,
|
768
764
|
);
|
769
765
|
if (index > -1 && levels.length > 1) {
|
770
|
-
|
766
|
+
logger.log(`[abr] Removing unsupported level ${index}`);
|
771
767
|
this.hls.removeLevel(index);
|
772
768
|
}
|
773
769
|
}
|
@@ -846,8 +842,8 @@ class AbrController extends Logger implements AbrComponentAPI {
|
|
846
842
|
(forcedAutoLevel === -1 || forcedAutoLevel !== loadLevel)
|
847
843
|
) {
|
848
844
|
if (levelsSkipped.length) {
|
849
|
-
|
850
|
-
`Skipped level(s) ${levelsSkipped.join(
|
845
|
+
logger.trace(
|
846
|
+
`[abr] Skipped level(s) ${levelsSkipped.join(
|
851
847
|
',',
|
852
848
|
)} of ${maxAutoLevel} max with CODECS and VIDEO-RANGE:"${
|
853
849
|
levels[levelsSkipped[0]].codecs
|
@@ -856,8 +852,8 @@ class AbrController extends Logger implements AbrComponentAPI {
|
|
856
852
|
}" ${currentVideoRange}`,
|
857
853
|
);
|
858
854
|
}
|
859
|
-
|
860
|
-
`switch candidate:${selectionBaseLevel}->${i} adjustedbw(${Math.round(
|
855
|
+
logger.info(
|
856
|
+
`[abr] switch candidate:${selectionBaseLevel}->${i} adjustedbw(${Math.round(
|
861
857
|
adjustedbw,
|
862
858
|
)})-bitrate=${Math.round(
|
863
859
|
adjustedbw - bitrate,
|
@@ -71,27 +71,30 @@ class AudioStreamController
|
|
71
71
|
hls,
|
72
72
|
fragmentTracker,
|
73
73
|
keyLoader,
|
74
|
-
'audio-stream-controller',
|
74
|
+
'[audio-stream-controller]',
|
75
75
|
PlaylistLevelType.AUDIO,
|
76
76
|
);
|
77
|
-
this.
|
77
|
+
this._registerListeners();
|
78
78
|
}
|
79
79
|
|
80
80
|
protected onHandlerDestroying() {
|
81
|
-
this.
|
81
|
+
this._unregisterListeners();
|
82
82
|
super.onHandlerDestroying();
|
83
83
|
this.mainDetails = null;
|
84
84
|
this.bufferedTrack = null;
|
85
85
|
this.switchingTrack = null;
|
86
86
|
}
|
87
87
|
|
88
|
-
|
89
|
-
super.registerListeners();
|
88
|
+
private _registerListeners() {
|
90
89
|
const { hls } = this;
|
90
|
+
hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
91
|
+
hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
92
|
+
hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
91
93
|
hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
92
94
|
hls.on(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
|
93
95
|
hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
94
96
|
hls.on(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
|
97
|
+
hls.on(Events.ERROR, this.onError, this);
|
95
98
|
hls.on(Events.BUFFER_RESET, this.onBufferReset, this);
|
96
99
|
hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
97
100
|
hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
|
@@ -100,16 +103,16 @@ class AudioStreamController
|
|
100
103
|
hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
101
104
|
}
|
102
105
|
|
103
|
-
|
106
|
+
private _unregisterListeners() {
|
104
107
|
const { hls } = this;
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
super.unregisterListeners();
|
108
|
+
hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
109
|
+
hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
110
|
+
hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
109
111
|
hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
110
112
|
hls.off(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
|
111
113
|
hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
112
114
|
hls.off(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
|
115
|
+
hls.off(Events.ERROR, this.onError, this);
|
113
116
|
hls.off(Events.BUFFER_RESET, this.onBufferReset, this);
|
114
117
|
hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
115
118
|
hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
|
@@ -255,9 +258,7 @@ class AudioStreamController
|
|
255
258
|
this.fragmentTracker.removeFragment(waitingData.frag);
|
256
259
|
this.waitingData = null;
|
257
260
|
this.waitingVideoCC = -1;
|
258
|
-
|
259
|
-
this.state = State.IDLE;
|
260
|
-
}
|
261
|
+
this.state = State.IDLE;
|
261
262
|
}
|
262
263
|
}
|
263
264
|
|
@@ -280,14 +281,12 @@ class AudioStreamController
|
|
280
281
|
const { hls, levels, media, trackId } = this;
|
281
282
|
const config = hls.config;
|
282
283
|
|
283
|
-
// 1. if
|
284
|
-
// 2. if video not attached AND
|
284
|
+
// 1. if video not attached AND
|
285
285
|
// start fragment already requested OR start frag prefetch not enabled
|
286
|
-
//
|
286
|
+
// 2. if tracks or track not loaded and selected
|
287
287
|
// then exit loop
|
288
288
|
// => if media not attached but start frag prefetch is enabled and start frag not requested yet, we will not exit loop
|
289
289
|
if (
|
290
|
-
!this.buffering ||
|
291
290
|
(!media && (this.startFragRequested || !config.startFragPrefetch)) ||
|
292
291
|
!levels?.[trackId]
|
293
292
|
) {
|
@@ -331,8 +330,12 @@ class AudioStreamController
|
|
331
330
|
return;
|
332
331
|
}
|
333
332
|
|
333
|
+
const mainBufferInfo = this.getFwdBufferInfo(
|
334
|
+
this.videoBuffer ? this.videoBuffer : this.media,
|
335
|
+
PlaylistLevelType.MAIN,
|
336
|
+
);
|
334
337
|
const bufferLen = bufferInfo.len;
|
335
|
-
const maxBufLen =
|
338
|
+
const maxBufLen = this.getMaxBufferLength(mainBufferInfo?.len);
|
336
339
|
|
337
340
|
const fragments = trackDetails.fragments;
|
338
341
|
const start = fragments[0].start;
|
@@ -388,44 +391,52 @@ class AudioStreamController
|
|
388
391
|
return;
|
389
392
|
}
|
390
393
|
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
) {
|
414
|
-
return;
|
415
|
-
}
|
394
|
+
// Buffer audio up to one target duration ahead of main buffer
|
395
|
+
const atBufferSyncLimit =
|
396
|
+
mainBufferInfo &&
|
397
|
+
frag.start > mainBufferInfo.end + trackDetails.targetduration;
|
398
|
+
if (
|
399
|
+
atBufferSyncLimit ||
|
400
|
+
// Or wait for main buffer after buffing some audio
|
401
|
+
(!mainBufferInfo?.len && bufferInfo.len)
|
402
|
+
) {
|
403
|
+
// Check fragment-tracker for main fragments since GAP segments do not show up in bufferInfo
|
404
|
+
const mainFrag = this.getAppendedFrag(frag.start, PlaylistLevelType.MAIN);
|
405
|
+
if (mainFrag === null) {
|
406
|
+
return;
|
407
|
+
}
|
408
|
+
// Bridge gaps in main buffer
|
409
|
+
atGap ||=
|
410
|
+
!!mainFrag.gap || (!!atBufferSyncLimit && mainBufferInfo.len === 0);
|
411
|
+
if (
|
412
|
+
(atBufferSyncLimit && !atGap) ||
|
413
|
+
(atGap && bufferInfo.nextStart && bufferInfo.nextStart < mainFrag.end)
|
414
|
+
) {
|
415
|
+
return;
|
416
416
|
}
|
417
417
|
}
|
418
418
|
|
419
419
|
this.loadFragment(frag, levelInfo, targetBufferTime);
|
420
420
|
}
|
421
421
|
|
422
|
-
protected
|
422
|
+
protected getMaxBufferLength(mainBufferLength?: number): number {
|
423
|
+
const maxConfigBuffer = super.getMaxBufferLength();
|
424
|
+
if (!mainBufferLength) {
|
425
|
+
return maxConfigBuffer;
|
426
|
+
}
|
427
|
+
return Math.min(
|
428
|
+
Math.max(maxConfigBuffer, mainBufferLength),
|
429
|
+
this.config.maxMaxBufferLength,
|
430
|
+
);
|
431
|
+
}
|
432
|
+
|
433
|
+
onMediaDetaching() {
|
423
434
|
this.videoBuffer = null;
|
424
435
|
this.bufferFlushed = this.flushing = false;
|
425
436
|
super.onMediaDetaching();
|
426
437
|
}
|
427
438
|
|
428
|
-
|
439
|
+
onAudioTracksUpdated(
|
429
440
|
event: Events.AUDIO_TRACKS_UPDATED,
|
430
441
|
{ audioTracks }: AudioTracksUpdatedData,
|
431
442
|
) {
|
@@ -434,7 +445,7 @@ class AudioStreamController
|
|
434
445
|
this.levels = audioTracks.map((mediaPlaylist) => new Level(mediaPlaylist));
|
435
446
|
}
|
436
447
|
|
437
|
-
|
448
|
+
onAudioTrackSwitching(
|
438
449
|
event: Events.AUDIO_TRACK_SWITCHING,
|
439
450
|
data: AudioTrackSwitchingData,
|
440
451
|
) {
|
@@ -448,28 +459,29 @@ class AudioStreamController
|
|
448
459
|
this.removeUnbufferedFrags(fragCurrent.start);
|
449
460
|
}
|
450
461
|
this.resetLoadingState();
|
462
|
+
// destroy useless transmuxer when switching audio to main
|
463
|
+
if (!altAudio) {
|
464
|
+
this.resetTransmuxer();
|
465
|
+
} else {
|
466
|
+
// switching to audio track, start timer if not already started
|
467
|
+
this.setInterval(TICK_INTERVAL);
|
468
|
+
}
|
451
469
|
|
452
470
|
// should we switch tracks ?
|
453
471
|
if (altAudio) {
|
454
472
|
this.switchingTrack = data;
|
455
473
|
// main audio track are handled by stream-controller, just do something if switching to alt audio track
|
474
|
+
this.state = State.IDLE;
|
456
475
|
this.flushAudioIfNeeded(data);
|
457
|
-
if (this.state !== State.STOPPED) {
|
458
|
-
// switching to audio track, start timer if not already started
|
459
|
-
this.setInterval(TICK_INTERVAL);
|
460
|
-
this.state = State.IDLE;
|
461
|
-
this.tick();
|
462
|
-
}
|
463
476
|
} else {
|
464
|
-
// destroy useless transmuxer when switching audio to main
|
465
|
-
this.resetTransmuxer();
|
466
477
|
this.switchingTrack = null;
|
467
478
|
this.bufferedTrack = data;
|
468
|
-
this.
|
479
|
+
this.state = State.STOPPED;
|
469
480
|
}
|
481
|
+
this.tick();
|
470
482
|
}
|
471
483
|
|
472
|
-
|
484
|
+
onManifestLoading() {
|
473
485
|
this.fragmentTracker.removeAllFragments();
|
474
486
|
this.startPosition = this.lastCurrentTime = 0;
|
475
487
|
this.bufferFlushed = this.flushing = false;
|
@@ -484,7 +496,7 @@ class AudioStreamController
|
|
484
496
|
this.trackId = this.videoTrackCC = this.waitingVideoCC = -1;
|
485
497
|
}
|
486
498
|
|
487
|
-
|
499
|
+
onLevelLoaded(event: Events.LEVEL_LOADED, data: LevelLoadedData) {
|
488
500
|
this.mainDetails = data.details;
|
489
501
|
if (this.cachedTrackLoadedData !== null) {
|
490
502
|
this.hls.trigger(Events.AUDIO_TRACK_LOADED, this.cachedTrackLoadedData);
|
@@ -492,10 +504,7 @@ class AudioStreamController
|
|
492
504
|
}
|
493
505
|
}
|
494
506
|
|
495
|
-
|
496
|
-
event: Events.AUDIO_TRACK_LOADED,
|
497
|
-
data: TrackLoadedData,
|
498
|
-
) {
|
507
|
+
onAudioTrackLoaded(event: Events.AUDIO_TRACK_LOADED, data: TrackLoadedData) {
|
499
508
|
if (this.mainDetails == null) {
|
500
509
|
this.cachedTrackLoadedData = data;
|
501
510
|
return;
|
@@ -648,16 +657,13 @@ class AudioStreamController
|
|
648
657
|
super._handleFragmentLoadComplete(fragLoadedData);
|
649
658
|
}
|
650
659
|
|
651
|
-
|
660
|
+
onBufferReset(/* event: Events.BUFFER_RESET */) {
|
652
661
|
// reset reference to sourcebuffers
|
653
662
|
this.mediaBuffer = this.videoBuffer = null;
|
654
663
|
this.loadedmetadata = false;
|
655
664
|
}
|
656
665
|
|
657
|
-
|
658
|
-
event: Events.BUFFER_CREATED,
|
659
|
-
data: BufferCreatedData,
|
660
|
-
) {
|
666
|
+
onBufferCreated(event: Events.BUFFER_CREATED, data: BufferCreatedData) {
|
661
667
|
const audioTrack = data.tracks.audio;
|
662
668
|
if (audioTrack) {
|
663
669
|
this.mediaBuffer = audioTrack.buffer || null;
|
@@ -667,7 +673,7 @@ class AudioStreamController
|
|
667
673
|
}
|
668
674
|
}
|
669
675
|
|
670
|
-
|
676
|
+
onFragBuffered(event: Events.FRAG_BUFFERED, data: FragBufferedData) {
|
671
677
|
const { frag, part } = data;
|
672
678
|
if (frag.type !== PlaylistLevelType.AUDIO) {
|
673
679
|
if (!this.loadedmetadata && frag.type === PlaylistLevelType.MAIN) {
|
@@ -707,7 +713,7 @@ class AudioStreamController
|
|
707
713
|
this.fragBufferedComplete(frag, part);
|
708
714
|
}
|
709
715
|
|
710
|
-
|
716
|
+
private onError(event: Events.ERROR, data: ErrorData) {
|
711
717
|
if (data.fatal) {
|
712
718
|
this.state = State.ERROR;
|
713
719
|
return;
|
@@ -1,16 +1,11 @@
|
|
1
1
|
import type Hls from '../hls';
|
2
2
|
import type { NetworkComponentAPI } from '../types/component-api';
|
3
|
-
import {
|
4
|
-
getSkipValue,
|
5
|
-
HlsSkip,
|
6
|
-
HlsUrlParameters,
|
7
|
-
type Level,
|
8
|
-
} from '../types/level';
|
3
|
+
import { getSkipValue, HlsSkip, HlsUrlParameters, Level } from '../types/level';
|
9
4
|
import { computeReloadInterval, mergeDetails } from '../utils/level-helper';
|
10
|
-
import
|
5
|
+
import { ErrorData } from '../types/events';
|
11
6
|
import { getRetryDelay, isTimeoutError } from '../utils/error-helper';
|
12
7
|
import { NetworkErrorAction } from './error-controller';
|
13
|
-
import {
|
8
|
+
import { logger } from '../utils/logger';
|
14
9
|
import type { LevelDetails } from '../loader/level-details';
|
15
10
|
import type { MediaPlaylist } from '../types/media-playlist';
|
16
11
|
import type {
|
@@ -19,17 +14,17 @@ import type {
|
|
19
14
|
TrackLoadedData,
|
20
15
|
} from '../types/events';
|
21
16
|
|
22
|
-
export default class BasePlaylistController
|
23
|
-
extends Logger
|
24
|
-
implements NetworkComponentAPI
|
25
|
-
{
|
17
|
+
export default class BasePlaylistController implements NetworkComponentAPI {
|
26
18
|
protected hls: Hls;
|
27
19
|
protected timer: number = -1;
|
28
20
|
protected requestScheduled: number = -1;
|
29
21
|
protected canLoad: boolean = false;
|
22
|
+
protected log: (msg: any) => void;
|
23
|
+
protected warn: (msg: any) => void;
|
30
24
|
|
31
25
|
constructor(hls: Hls, logPrefix: string) {
|
32
|
-
|
26
|
+
this.log = logger.log.bind(logger, `${logPrefix}:`);
|
27
|
+
this.warn = logger.warn.bind(logger, `${logPrefix}:`);
|
33
28
|
this.hls = hls;
|
34
29
|
}
|
35
30
|
|
@@ -71,7 +66,7 @@ export default class BasePlaylistController
|
|
71
66
|
try {
|
72
67
|
uri = new self.URL(attr.URI, previous.url).href;
|
73
68
|
} catch (error) {
|
74
|
-
|
69
|
+
logger.warn(
|
75
70
|
`Could not construct new URL for Rendition Report: ${error}`,
|
76
71
|
);
|
77
72
|
uri = attr.URI || '';
|
@@ -195,19 +190,7 @@ export default class BasePlaylistController
|
|
195
190
|
details.targetduration * 1.5,
|
196
191
|
);
|
197
192
|
if (currentGoal > 0) {
|
198
|
-
if (
|
199
|
-
// Omit segment and part directives when the last response was more than 3 target durations ago,
|
200
|
-
this.log(
|
201
|
-
`Playlist last advanced ${lastAdvanced.toFixed(
|
202
|
-
2,
|
203
|
-
)}s ago. Omitting segment and part directives.`,
|
204
|
-
);
|
205
|
-
msn = undefined;
|
206
|
-
part = undefined;
|
207
|
-
} else if (
|
208
|
-
previousDetails?.tuneInGoal &&
|
209
|
-
cdnAge - details.partTarget > previousDetails.tuneInGoal
|
210
|
-
) {
|
193
|
+
if (previousDetails && currentGoal > previousDetails.tuneInGoal) {
|
211
194
|
// If we attempted to get the next or latest playlist update, but currentGoal increased,
|
212
195
|
// then we either can't catchup, or the "age" header cannot be trusted.
|
213
196
|
this.warn(
|