hls.js 1.5.9 → 1.5.10-0.canary.10320
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 +4 -3
- package/dist/hls-demo.js +41 -38
- package/dist/hls-demo.js.map +1 -1
- package/dist/hls.js +3477 -2194
- package/dist/hls.js.d.ts +108 -85
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +2401 -1754
- 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 +1989 -1315
- 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 +2863 -1557
- 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 +35 -35
- package/src/config.ts +3 -2
- package/src/controller/abr-controller.ts +24 -20
- package/src/controller/audio-stream-controller.ts +68 -74
- package/src/controller/audio-track-controller.ts +1 -1
- package/src/controller/base-playlist-controller.ts +27 -10
- package/src/controller/base-stream-controller.ts +160 -38
- package/src/controller/buffer-controller.ts +230 -92
- package/src/controller/buffer-operation-queue.ts +16 -19
- package/src/controller/cap-level-controller.ts +3 -2
- package/src/controller/cmcd-controller.ts +51 -14
- package/src/controller/content-steering-controller.ts +29 -15
- package/src/controller/eme-controller.ts +10 -23
- package/src/controller/error-controller.ts +6 -8
- package/src/controller/fps-controller.ts +8 -3
- package/src/controller/fragment-tracker.ts +15 -11
- package/src/controller/gap-controller.ts +43 -16
- package/src/controller/id3-track-controller.ts +7 -7
- package/src/controller/latency-controller.ts +9 -11
- package/src/controller/level-controller.ts +37 -19
- package/src/controller/stream-controller.ts +37 -32
- package/src/controller/subtitle-stream-controller.ts +28 -40
- package/src/controller/subtitle-track-controller.ts +5 -3
- package/src/controller/timeline-controller.ts +19 -21
- package/src/crypt/aes-crypto.ts +21 -2
- package/src/crypt/decrypter-aes-mode.ts +4 -0
- package/src/crypt/decrypter.ts +32 -16
- package/src/crypt/fast-aes-key.ts +28 -5
- package/src/demux/audio/aacdemuxer.ts +2 -2
- package/src/demux/audio/ac3-demuxer.ts +4 -3
- package/src/demux/audio/adts.ts +9 -4
- package/src/demux/audio/base-audio-demuxer.ts +16 -14
- package/src/demux/audio/mp3demuxer.ts +4 -3
- package/src/demux/audio/mpegaudio.ts +1 -1
- package/src/demux/mp4demuxer.ts +7 -7
- package/src/demux/sample-aes.ts +2 -0
- package/src/demux/transmuxer-interface.ts +4 -12
- package/src/demux/transmuxer-worker.ts +4 -4
- package/src/demux/transmuxer.ts +16 -3
- package/src/demux/tsdemuxer.ts +71 -37
- package/src/demux/video/avc-video-parser.ts +208 -119
- package/src/demux/video/base-video-parser.ts +147 -18
- package/src/demux/video/exp-golomb.ts +0 -208
- package/src/demux/video/hevc-video-parser.ts +749 -0
- package/src/empty-es.js +5 -0
- package/src/events.ts +8 -1
- package/src/exports-named.ts +1 -1
- package/src/hls.ts +61 -38
- package/src/loader/fragment-loader.ts +10 -3
- package/src/loader/key-loader.ts +3 -1
- package/src/loader/level-key.ts +10 -9
- package/src/loader/playlist-loader.ts +4 -5
- package/src/remux/mp4-generator.ts +196 -1
- package/src/remux/mp4-remuxer.ts +24 -8
- package/src/task-loop.ts +5 -2
- package/src/types/component-api.ts +3 -1
- package/src/types/demuxer.ts +4 -0
- package/src/types/events.ts +4 -0
- package/src/types/remuxer.ts +1 -1
- package/src/utils/buffer-helper.ts +12 -31
- package/src/utils/cea-608-parser.ts +1 -3
- package/src/utils/codecs.ts +34 -5
- package/src/utils/encryption-methods-util.ts +21 -0
- package/src/utils/fetch-loader.ts +1 -1
- package/src/utils/imsc1-ttml-parser.ts +1 -1
- package/src/utils/keysystem-util.ts +1 -6
- package/src/utils/logger.ts +58 -23
- package/src/utils/mp4-tools.ts +5 -3
- package/src/utils/utf8-utils.ts +18 -0
- package/src/utils/webvtt-parser.ts +1 -1
- package/src/demux/id3.ts +0 -411
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"
|
62
62
|
},
|
63
63
|
"devDependencies": {
|
64
|
-
"@babel/core": "7.
|
65
|
-
"@babel/helper-module-imports": "7.
|
64
|
+
"@babel/core": "7.24.6",
|
65
|
+
"@babel/helper-module-imports": "7.24.6",
|
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.
|
72
|
-
"@babel/register": "7.
|
73
|
-
"@microsoft/api-documenter": "7.
|
74
|
-
"@microsoft/api-extractor": "7.
|
69
|
+
"@babel/plugin-transform-object-assign": "7.24.6",
|
70
|
+
"@babel/preset-env": "7.24.6",
|
71
|
+
"@babel/preset-typescript": "7.24.6",
|
72
|
+
"@babel/register": "7.24.6",
|
73
|
+
"@microsoft/api-documenter": "7.25.2",
|
74
|
+
"@microsoft/api-extractor": "7.46.2",
|
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.8",
|
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.6",
|
82
|
+
"@svta/common-media-library": "0.6.4",
|
83
|
+
"@types/chai": "4.3.16",
|
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": "7.10.0",
|
88
|
+
"@typescript-eslint/parser": "7.10.0",
|
89
89
|
"babel-loader": "9.1.3",
|
90
90
|
"babel-plugin-transform-remove-console": "6.9.4",
|
91
|
-
"chai": "4.
|
91
|
+
"chai": "4.4.1",
|
92
92
|
"chart.js": "2.9.4",
|
93
|
-
"chromedriver": "
|
93
|
+
"chromedriver": "125.0.2",
|
94
94
|
"doctoc": "2.2.1",
|
95
|
-
"es-check": "7.
|
96
|
-
"eslint": "8.
|
95
|
+
"es-check": "7.2.1",
|
96
|
+
"eslint": "8.57.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.4.3",
|
100
|
+
"eslint-plugin-n": "17.7.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": "9.0.11",
|
105
105
|
"jsonpack": "1.1.5",
|
106
|
-
"karma": "6.4.
|
106
|
+
"karma": "6.4.3",
|
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.5",
|
115
115
|
"markdown-styles": "3.2.0",
|
116
|
-
"micromatch": "4.0.
|
117
|
-
"mocha": "10.
|
116
|
+
"micromatch": "4.0.7",
|
117
|
+
"mocha": "10.4.0",
|
118
118
|
"node-fetch": "3.3.2",
|
119
|
-
"npm-run-
|
120
|
-
"prettier": "3.
|
119
|
+
"npm-run-all2": "6.2.0",
|
120
|
+
"prettier": "3.2.5",
|
121
121
|
"promise-polyfill": "8.3.0",
|
122
|
-
"rollup": "4.
|
122
|
+
"rollup": "4.18.0",
|
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.21.0",
|
126
|
+
"semver": "7.6.2",
|
127
|
+
"sinon": "18.0.0",
|
128
128
|
"sinon-chai": "3.7.0",
|
129
|
-
"typescript": "5.
|
129
|
+
"typescript": "5.4.5",
|
130
130
|
"url-toolkit": "2.2.5",
|
131
|
-
"wrangler": "3.
|
131
|
+
"wrangler": "3.57.1"
|
132
132
|
},
|
133
|
-
"version": "1.5.
|
133
|
+
"version": "1.5.10-0.canary.10320"
|
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';
|
21
20
|
|
22
21
|
import type Hls from './hls';
|
23
22
|
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,6 +558,7 @@ function timelineConfig(): TimelineControllerConfig {
|
|
558
558
|
export function mergeConfig(
|
559
559
|
defaultConfig: HlsConfig,
|
560
560
|
userConfig: Partial<HlsConfig>,
|
561
|
+
logger: ILogger,
|
561
562
|
): HlsConfig {
|
562
563
|
if (
|
563
564
|
(userConfig.liveSyncDurationCount ||
|
@@ -664,7 +665,7 @@ function deepCpy(obj: any): any {
|
|
664
665
|
/**
|
665
666
|
* @ignore
|
666
667
|
*/
|
667
|
-
export function enableStreamingMode(config) {
|
668
|
+
export function enableStreamingMode(config: HlsConfig, logger: ILogger) {
|
668
669
|
const currentLoader = config.loader;
|
669
670
|
if (currentLoader !== FetchLoader && currentLoader !== XhrLoader) {
|
670
671
|
// 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 implements AbrComponentAPI {
|
34
|
+
class AbrController extends Logger implements AbrComponentAPI {
|
35
35
|
protected hls: Hls;
|
36
36
|
private lastLevelLoadSec: number = 0;
|
37
37
|
private lastLoadedFragLevel: number = -1;
|
@@ -48,6 +48,7 @@ class AbrController implements AbrComponentAPI {
|
|
48
48
|
public bwEstimator: EwmaBandWidthEstimator;
|
49
49
|
|
50
50
|
constructor(hls: Hls) {
|
51
|
+
super('abr', hls.logger);
|
51
52
|
this.hls = hls;
|
52
53
|
this.bwEstimator = this.initEstimator();
|
53
54
|
this.registerListeners();
|
@@ -55,7 +56,7 @@ class AbrController implements AbrComponentAPI {
|
|
55
56
|
|
56
57
|
public resetEstimator(abrEwmaDefaultEstimate?: number) {
|
57
58
|
if (abrEwmaDefaultEstimate) {
|
58
|
-
|
59
|
+
this.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
|
59
60
|
this.hls.config.abrEwmaDefaultEstimate = abrEwmaDefaultEstimate;
|
60
61
|
}
|
61
62
|
this.firstSelection = -1;
|
@@ -355,7 +356,7 @@ class AbrController implements AbrComponentAPI {
|
|
355
356
|
}
|
356
357
|
|
357
358
|
this.clearTimer();
|
358
|
-
|
359
|
+
this.warn(`Fragment ${frag.sn}${
|
359
360
|
part ? ' part ' + part.index : ''
|
360
361
|
} of level ${frag.level} is loading too slowly;
|
361
362
|
Time to underbuffer: ${bufferStarvationDelay.toFixed(3)} s
|
@@ -479,8 +480,8 @@ class AbrController implements AbrComponentAPI {
|
|
479
480
|
}
|
480
481
|
const firstLevel = this.hls.firstLevel;
|
481
482
|
const clamped = Math.min(Math.max(firstLevel, minAutoLevel), maxAutoLevel);
|
482
|
-
|
483
|
-
`
|
483
|
+
this.warn(
|
484
|
+
`Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`,
|
484
485
|
);
|
485
486
|
return clamped;
|
486
487
|
}
|
@@ -538,6 +539,9 @@ class AbrController implements AbrComponentAPI {
|
|
538
539
|
|
539
540
|
private getNextABRAutoLevel(): number {
|
540
541
|
const { fragCurrent, partCurrent, hls } = this;
|
542
|
+
if (hls.levels.length <= 1) {
|
543
|
+
return hls.loadLevel;
|
544
|
+
}
|
541
545
|
const { maxAutoLevel, config, minAutoLevel } = hls;
|
542
546
|
const currentFragDuration = partCurrent
|
543
547
|
? partCurrent.duration
|
@@ -584,8 +588,8 @@ class AbrController implements AbrComponentAPI {
|
|
584
588
|
? Math.min(currentFragDuration, config.maxLoadingDelay)
|
585
589
|
: config.maxLoadingDelay;
|
586
590
|
maxStarvationDelay = maxLoadingDelay - bitrateTestDelay;
|
587
|
-
|
588
|
-
`
|
591
|
+
this.info(
|
592
|
+
`bitrate test took ${Math.round(
|
589
593
|
1000 * bitrateTestDelay,
|
590
594
|
)}ms, set first fragment max fetchDuration to ${Math.round(
|
591
595
|
1000 * maxStarvationDelay,
|
@@ -604,8 +608,8 @@ class AbrController implements AbrComponentAPI {
|
|
604
608
|
bwFactor,
|
605
609
|
bwUpFactor,
|
606
610
|
);
|
607
|
-
|
608
|
-
|
611
|
+
this.info(
|
612
|
+
`${
|
609
613
|
bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'
|
610
614
|
}, optimal quality level ${bestLevel}`,
|
611
615
|
);
|
@@ -698,7 +702,7 @@ class AbrController implements AbrComponentAPI {
|
|
698
702
|
: videoRanges[0];
|
699
703
|
currentFrameRate = minFramerate;
|
700
704
|
currentBw = Math.max(currentBw, minBitrate);
|
701
|
-
|
705
|
+
this.log(`picked start tier ${JSON.stringify(startTier)}`);
|
702
706
|
} else {
|
703
707
|
currentCodecSet = level?.codecSet;
|
704
708
|
currentVideoRange = level?.videoRange;
|
@@ -751,19 +755,19 @@ class AbrController implements AbrComponentAPI {
|
|
751
755
|
const levels = this.hls.levels;
|
752
756
|
const index = levels.indexOf(levelInfo);
|
753
757
|
if (decodingInfo.error) {
|
754
|
-
|
755
|
-
`
|
758
|
+
this.warn(
|
759
|
+
`MediaCapabilities decodingInfo error: "${
|
756
760
|
decodingInfo.error
|
757
761
|
}" for level ${index} ${JSON.stringify(decodingInfo)}`,
|
758
762
|
);
|
759
763
|
} else if (!decodingInfo.supported) {
|
760
|
-
|
761
|
-
`
|
764
|
+
this.warn(
|
765
|
+
`Unsupported MediaCapabilities decodingInfo result for level ${index} ${JSON.stringify(
|
762
766
|
decodingInfo,
|
763
767
|
)}`,
|
764
768
|
);
|
765
769
|
if (index > -1 && levels.length > 1) {
|
766
|
-
|
770
|
+
this.log(`Removing unsupported level ${index}`);
|
767
771
|
this.hls.removeLevel(index);
|
768
772
|
}
|
769
773
|
}
|
@@ -842,8 +846,8 @@ class AbrController implements AbrComponentAPI {
|
|
842
846
|
(forcedAutoLevel === -1 || forcedAutoLevel !== loadLevel)
|
843
847
|
) {
|
844
848
|
if (levelsSkipped.length) {
|
845
|
-
|
846
|
-
`
|
849
|
+
this.trace(
|
850
|
+
`Skipped level(s) ${levelsSkipped.join(
|
847
851
|
',',
|
848
852
|
)} of ${maxAutoLevel} max with CODECS and VIDEO-RANGE:"${
|
849
853
|
levels[levelsSkipped[0]].codecs
|
@@ -852,8 +856,8 @@ class AbrController implements AbrComponentAPI {
|
|
852
856
|
}" ${currentVideoRange}`,
|
853
857
|
);
|
854
858
|
}
|
855
|
-
|
856
|
-
`
|
859
|
+
this.info(
|
860
|
+
`switch candidate:${selectionBaseLevel}->${i} adjustedbw(${Math.round(
|
857
861
|
adjustedbw,
|
858
862
|
)})-bitrate=${Math.round(
|
859
863
|
adjustedbw - bitrate,
|
@@ -71,30 +71,27 @@ class AudioStreamController
|
|
71
71
|
hls,
|
72
72
|
fragmentTracker,
|
73
73
|
keyLoader,
|
74
|
-
'
|
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
|
-
|
88
|
+
protected registerListeners() {
|
89
|
+
super.registerListeners();
|
89
90
|
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);
|
93
91
|
hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
94
92
|
hls.on(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
|
95
93
|
hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
96
94
|
hls.on(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
|
97
|
-
hls.on(Events.ERROR, this.onError, this);
|
98
95
|
hls.on(Events.BUFFER_RESET, this.onBufferReset, this);
|
99
96
|
hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
100
97
|
hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
|
@@ -103,16 +100,16 @@ class AudioStreamController
|
|
103
100
|
hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
104
101
|
}
|
105
102
|
|
106
|
-
|
103
|
+
protected unregisterListeners() {
|
107
104
|
const { hls } = this;
|
108
|
-
hls
|
109
|
-
|
110
|
-
|
105
|
+
if (!hls) {
|
106
|
+
return;
|
107
|
+
}
|
108
|
+
super.unregisterListeners();
|
111
109
|
hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
112
110
|
hls.off(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
|
113
111
|
hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
114
112
|
hls.off(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
|
115
|
-
hls.off(Events.ERROR, this.onError, this);
|
116
113
|
hls.off(Events.BUFFER_RESET, this.onBufferReset, this);
|
117
114
|
hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
118
115
|
hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
|
@@ -258,7 +255,9 @@ class AudioStreamController
|
|
258
255
|
this.fragmentTracker.removeFragment(waitingData.frag);
|
259
256
|
this.waitingData = null;
|
260
257
|
this.waitingVideoCC = -1;
|
261
|
-
this.state
|
258
|
+
if (this.state !== State.STOPPED) {
|
259
|
+
this.state = State.IDLE;
|
260
|
+
}
|
262
261
|
}
|
263
262
|
}
|
264
263
|
|
@@ -281,12 +280,14 @@ class AudioStreamController
|
|
281
280
|
const { hls, levels, media, trackId } = this;
|
282
281
|
const config = hls.config;
|
283
282
|
|
284
|
-
// 1. if
|
283
|
+
// 1. if buffering is suspended
|
284
|
+
// 2. if video not attached AND
|
285
285
|
// start fragment already requested OR start frag prefetch not enabled
|
286
|
-
//
|
286
|
+
// 3. 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 ||
|
290
291
|
(!media && (this.startFragRequested || !config.startFragPrefetch)) ||
|
291
292
|
!levels?.[trackId]
|
292
293
|
) {
|
@@ -330,12 +331,8 @@ class AudioStreamController
|
|
330
331
|
return;
|
331
332
|
}
|
332
333
|
|
333
|
-
const mainBufferInfo = this.getFwdBufferInfo(
|
334
|
-
this.videoBuffer ? this.videoBuffer : this.media,
|
335
|
-
PlaylistLevelType.MAIN,
|
336
|
-
);
|
337
334
|
const bufferLen = bufferInfo.len;
|
338
|
-
const maxBufLen =
|
335
|
+
const maxBufLen = hls.maxBufferLength;
|
339
336
|
|
340
337
|
const fragments = trackDetails.fragments;
|
341
338
|
const start = fragments[0].start;
|
@@ -391,52 +388,44 @@ class AudioStreamController
|
|
391
388
|
return;
|
392
389
|
}
|
393
390
|
|
394
|
-
|
395
|
-
|
396
|
-
mainBufferInfo
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
(
|
414
|
-
|
415
|
-
|
391
|
+
if (!trackDetails.live || targetBufferTime < this.hls.liveSyncPosition!) {
|
392
|
+
// Request audio segments up to one fragment ahead of main buffer
|
393
|
+
const mainBufferInfo = this.getFwdBufferInfo(
|
394
|
+
this.videoBuffer ? this.videoBuffer : this.media,
|
395
|
+
PlaylistLevelType.MAIN,
|
396
|
+
);
|
397
|
+
const atBufferSyncLimit =
|
398
|
+
!!mainBufferInfo && frag.start > mainBufferInfo.end + frag.duration;
|
399
|
+
if (atBufferSyncLimit) {
|
400
|
+
// Check fragment-tracker for main fragments since GAP segments do not show up in bufferInfo
|
401
|
+
const mainFrag = this.fragmentTracker.getFragAtPos(
|
402
|
+
frag.start,
|
403
|
+
PlaylistLevelType.MAIN,
|
404
|
+
);
|
405
|
+
if (mainFrag === null) {
|
406
|
+
return;
|
407
|
+
}
|
408
|
+
// Bridge gaps in main buffer (also prevents loop loading at gaps)
|
409
|
+
atGap ||= !!mainFrag.gap || mainBufferInfo.len === 0;
|
410
|
+
if (
|
411
|
+
!atGap ||
|
412
|
+
(bufferInfo.nextStart && bufferInfo.nextStart < mainFrag.end)
|
413
|
+
) {
|
414
|
+
return;
|
415
|
+
}
|
416
416
|
}
|
417
417
|
}
|
418
418
|
|
419
419
|
this.loadFragment(frag, levelInfo, targetBufferTime);
|
420
420
|
}
|
421
421
|
|
422
|
-
protected
|
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() {
|
422
|
+
protected onMediaDetaching() {
|
434
423
|
this.videoBuffer = null;
|
435
424
|
this.bufferFlushed = this.flushing = false;
|
436
425
|
super.onMediaDetaching();
|
437
426
|
}
|
438
427
|
|
439
|
-
onAudioTracksUpdated(
|
428
|
+
private onAudioTracksUpdated(
|
440
429
|
event: Events.AUDIO_TRACKS_UPDATED,
|
441
430
|
{ audioTracks }: AudioTracksUpdatedData,
|
442
431
|
) {
|
@@ -445,7 +434,7 @@ class AudioStreamController
|
|
445
434
|
this.levels = audioTracks.map((mediaPlaylist) => new Level(mediaPlaylist));
|
446
435
|
}
|
447
436
|
|
448
|
-
onAudioTrackSwitching(
|
437
|
+
private onAudioTrackSwitching(
|
449
438
|
event: Events.AUDIO_TRACK_SWITCHING,
|
450
439
|
data: AudioTrackSwitchingData,
|
451
440
|
) {
|
@@ -459,29 +448,28 @@ class AudioStreamController
|
|
459
448
|
this.removeUnbufferedFrags(fragCurrent.start);
|
460
449
|
}
|
461
450
|
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
|
-
}
|
469
451
|
|
470
452
|
// should we switch tracks ?
|
471
453
|
if (altAudio) {
|
472
454
|
this.switchingTrack = data;
|
473
455
|
// main audio track are handled by stream-controller, just do something if switching to alt audio track
|
474
|
-
this.state = State.IDLE;
|
475
456
|
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
|
+
}
|
476
463
|
} else {
|
464
|
+
// destroy useless transmuxer when switching audio to main
|
465
|
+
this.resetTransmuxer();
|
477
466
|
this.switchingTrack = null;
|
478
467
|
this.bufferedTrack = data;
|
479
|
-
this.
|
468
|
+
this.clearInterval();
|
480
469
|
}
|
481
|
-
this.tick();
|
482
470
|
}
|
483
471
|
|
484
|
-
onManifestLoading() {
|
472
|
+
protected onManifestLoading() {
|
485
473
|
this.fragmentTracker.removeAllFragments();
|
486
474
|
this.startPosition = this.lastCurrentTime = 0;
|
487
475
|
this.bufferFlushed = this.flushing = false;
|
@@ -496,7 +484,7 @@ class AudioStreamController
|
|
496
484
|
this.trackId = this.videoTrackCC = this.waitingVideoCC = -1;
|
497
485
|
}
|
498
486
|
|
499
|
-
onLevelLoaded(event: Events.LEVEL_LOADED, data: LevelLoadedData) {
|
487
|
+
private onLevelLoaded(event: Events.LEVEL_LOADED, data: LevelLoadedData) {
|
500
488
|
this.mainDetails = data.details;
|
501
489
|
if (this.cachedTrackLoadedData !== null) {
|
502
490
|
this.hls.trigger(Events.AUDIO_TRACK_LOADED, this.cachedTrackLoadedData);
|
@@ -504,7 +492,10 @@ class AudioStreamController
|
|
504
492
|
}
|
505
493
|
}
|
506
494
|
|
507
|
-
onAudioTrackLoaded(
|
495
|
+
private onAudioTrackLoaded(
|
496
|
+
event: Events.AUDIO_TRACK_LOADED,
|
497
|
+
data: TrackLoadedData,
|
498
|
+
) {
|
508
499
|
if (this.mainDetails == null) {
|
509
500
|
this.cachedTrackLoadedData = data;
|
510
501
|
return;
|
@@ -657,13 +648,16 @@ class AudioStreamController
|
|
657
648
|
super._handleFragmentLoadComplete(fragLoadedData);
|
658
649
|
}
|
659
650
|
|
660
|
-
onBufferReset(/* event: Events.BUFFER_RESET */) {
|
651
|
+
private onBufferReset(/* event: Events.BUFFER_RESET */) {
|
661
652
|
// reset reference to sourcebuffers
|
662
653
|
this.mediaBuffer = this.videoBuffer = null;
|
663
654
|
this.loadedmetadata = false;
|
664
655
|
}
|
665
656
|
|
666
|
-
onBufferCreated(
|
657
|
+
private onBufferCreated(
|
658
|
+
event: Events.BUFFER_CREATED,
|
659
|
+
data: BufferCreatedData,
|
660
|
+
) {
|
667
661
|
const audioTrack = data.tracks.audio;
|
668
662
|
if (audioTrack) {
|
669
663
|
this.mediaBuffer = audioTrack.buffer || null;
|
@@ -673,7 +667,7 @@ class AudioStreamController
|
|
673
667
|
}
|
674
668
|
}
|
675
669
|
|
676
|
-
onFragBuffered(event: Events.FRAG_BUFFERED, data: FragBufferedData) {
|
670
|
+
private onFragBuffered(event: Events.FRAG_BUFFERED, data: FragBufferedData) {
|
677
671
|
const { frag, part } = data;
|
678
672
|
if (frag.type !== PlaylistLevelType.AUDIO) {
|
679
673
|
if (!this.loadedmetadata && frag.type === PlaylistLevelType.MAIN) {
|
@@ -713,7 +707,7 @@ class AudioStreamController
|
|
713
707
|
this.fragBufferedComplete(frag, part);
|
714
708
|
}
|
715
709
|
|
716
|
-
|
710
|
+
protected onError(event: Events.ERROR, data: ErrorData) {
|
717
711
|
if (data.fatal) {
|
718
712
|
this.state = State.ERROR;
|
719
713
|
return;
|
@@ -1,11 +1,16 @@
|
|
1
1
|
import type Hls from '../hls';
|
2
2
|
import type { NetworkComponentAPI } from '../types/component-api';
|
3
|
-
import {
|
3
|
+
import {
|
4
|
+
getSkipValue,
|
5
|
+
HlsSkip,
|
6
|
+
HlsUrlParameters,
|
7
|
+
type Level,
|
8
|
+
} from '../types/level';
|
4
9
|
import { computeReloadInterval, mergeDetails } from '../utils/level-helper';
|
5
|
-
import { ErrorData } from '../types/events';
|
10
|
+
import type { ErrorData } from '../types/events';
|
6
11
|
import { getRetryDelay, isTimeoutError } from '../utils/error-helper';
|
7
12
|
import { NetworkErrorAction } from './error-controller';
|
8
|
-
import {
|
13
|
+
import { Logger } from '../utils/logger';
|
9
14
|
import type { LevelDetails } from '../loader/level-details';
|
10
15
|
import type { MediaPlaylist } from '../types/media-playlist';
|
11
16
|
import type {
|
@@ -14,17 +19,17 @@ import type {
|
|
14
19
|
TrackLoadedData,
|
15
20
|
} from '../types/events';
|
16
21
|
|
17
|
-
export default class BasePlaylistController
|
22
|
+
export default class BasePlaylistController
|
23
|
+
extends Logger
|
24
|
+
implements NetworkComponentAPI
|
25
|
+
{
|
18
26
|
protected hls: Hls;
|
19
27
|
protected timer: number = -1;
|
20
28
|
protected requestScheduled: number = -1;
|
21
29
|
protected canLoad: boolean = false;
|
22
|
-
protected log: (msg: any) => void;
|
23
|
-
protected warn: (msg: any) => void;
|
24
30
|
|
25
31
|
constructor(hls: Hls, logPrefix: string) {
|
26
|
-
|
27
|
-
this.warn = logger.warn.bind(logger, `${logPrefix}:`);
|
32
|
+
super(logPrefix, hls.logger);
|
28
33
|
this.hls = hls;
|
29
34
|
}
|
30
35
|
|
@@ -66,7 +71,7 @@ export default class BasePlaylistController implements NetworkComponentAPI {
|
|
66
71
|
try {
|
67
72
|
uri = new self.URL(attr.URI, previous.url).href;
|
68
73
|
} catch (error) {
|
69
|
-
|
74
|
+
this.warn(
|
70
75
|
`Could not construct new URL for Rendition Report: ${error}`,
|
71
76
|
);
|
72
77
|
uri = attr.URI || '';
|
@@ -190,7 +195,19 @@ export default class BasePlaylistController implements NetworkComponentAPI {
|
|
190
195
|
details.targetduration * 1.5,
|
191
196
|
);
|
192
197
|
if (currentGoal > 0) {
|
193
|
-
if (
|
198
|
+
if (cdnAge > details.targetduration * 3) {
|
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
|
+
) {
|
194
211
|
// If we attempted to get the next or latest playlist update, but currentGoal increased,
|
195
212
|
// then we either can't catchup, or the "age" header cannot be trusted.
|
196
213
|
this.warn(
|