hls.js 1.5.4 → 1.5.5-0.canary.9977
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 +1 -0
- package/dist/hls-demo.js +10 -0
- package/dist/hls-demo.js.map +1 -1
- package/dist/hls.js +1930 -1095
- package/dist/hls.js.d.ts +63 -50
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +1609 -778
- 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 +1363 -542
- 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 +1635 -815
- 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 +18 -18
- package/src/config.ts +3 -2
- package/src/controller/abr-controller.ts +21 -20
- package/src/controller/audio-stream-controller.ts +15 -16
- 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 +56 -29
- package/src/controller/buffer-controller.ts +11 -11
- package/src/controller/cap-level-controller.ts +1 -2
- package/src/controller/cmcd-controller.ts +25 -3
- package/src/controller/content-steering-controller.ts +8 -6
- package/src/controller/eme-controller.ts +9 -22
- package/src/controller/error-controller.ts +6 -8
- package/src/controller/fps-controller.ts +2 -3
- package/src/controller/gap-controller.ts +43 -16
- package/src/controller/latency-controller.ts +9 -11
- package/src/controller/level-controller.ts +5 -17
- package/src/controller/stream-controller.ts +25 -32
- package/src/controller/subtitle-stream-controller.ts +13 -14
- package/src/controller/subtitle-track-controller.ts +5 -3
- package/src/controller/timeline-controller.ts +23 -30
- package/src/crypt/aes-crypto.ts +21 -2
- package/src/crypt/decrypter-aes-mode.ts +4 -0
- package/src/crypt/decrypter.ts +32 -18
- package/src/crypt/fast-aes-key.ts +24 -5
- package/src/demux/audio/adts.ts +9 -4
- 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 +63 -37
- package/src/demux/video/avc-video-parser.ts +208 -119
- package/src/demux/video/base-video-parser.ts +134 -2
- package/src/demux/video/exp-golomb.ts +0 -208
- package/src/demux/video/hevc-video-parser.ts +746 -0
- package/src/events.ts +7 -0
- package/src/hls.ts +42 -34
- package/src/loader/fragment-loader.ts +9 -2
- package/src/loader/key-loader.ts +2 -0
- package/src/loader/level-key.ts +10 -9
- package/src/remux/mp4-generator.ts +196 -1
- package/src/remux/mp4-remuxer.ts +23 -7
- package/src/task-loop.ts +5 -2
- package/src/types/component-api.ts +2 -0
- package/src/types/demuxer.ts +3 -0
- package/src/types/events.ts +4 -0
- package/src/utils/codecs.ts +33 -4
- package/src/utils/encryption-methods-util.ts +21 -0
- package/src/utils/logger.ts +53 -24
package/package.json
CHANGED
@@ -58,39 +58,39 @@
|
|
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.23.
|
64
|
+
"@babel/core": "7.23.9",
|
65
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
69
|
"@babel/plugin-transform-object-assign": "7.23.3",
|
70
|
-
"@babel/preset-env": "7.23.
|
70
|
+
"@babel/preset-env": "7.23.9",
|
71
71
|
"@babel/preset-typescript": "7.23.3",
|
72
72
|
"@babel/register": "7.23.7",
|
73
|
-
"@microsoft/api-documenter": "7.23.
|
74
|
-
"@microsoft/api-extractor": "7.39.
|
73
|
+
"@microsoft/api-documenter": "7.23.20",
|
74
|
+
"@microsoft/api-extractor": "7.39.4",
|
75
75
|
"@rollup/plugin-alias": "5.1.0",
|
76
76
|
"@rollup/plugin-babel": "6.0.4",
|
77
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.
|
81
|
+
"@rollup/plugin-typescript": "11.1.6",
|
82
|
+
"@svta/common-media-library": "0.6.2",
|
83
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": "6.
|
88
|
-
"@typescript-eslint/parser": "6.
|
87
|
+
"@typescript-eslint/eslint-plugin": "6.21.0",
|
88
|
+
"@typescript-eslint/parser": "6.21.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": "121.0.0",
|
94
94
|
"doctoc": "2.2.1",
|
95
95
|
"es-check": "7.1.1",
|
96
96
|
"eslint": "8.56.0",
|
@@ -101,7 +101,7 @@
|
|
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.10",
|
105
105
|
"jsonpack": "1.1.5",
|
106
106
|
"karma": "6.4.2",
|
107
107
|
"karma-chrome-launcher": "3.2.0",
|
@@ -116,19 +116,19 @@
|
|
116
116
|
"micromatch": "4.0.5",
|
117
117
|
"mocha": "10.2.0",
|
118
118
|
"node-fetch": "3.3.2",
|
119
|
-
"npm-run-
|
120
|
-
"prettier": "3.
|
119
|
+
"npm-run-all2": "5.0.2",
|
120
|
+
"prettier": "3.2.4",
|
121
121
|
"promise-polyfill": "8.3.0",
|
122
|
-
"rollup": "4.9.
|
122
|
+
"rollup": "4.9.6",
|
123
123
|
"rollup-plugin-istanbul": "5.0.0",
|
124
124
|
"sauce-connect-launcher": "1.3.2",
|
125
|
-
"selenium-webdriver": "4.
|
125
|
+
"selenium-webdriver": "4.17.0",
|
126
126
|
"semver": "7.5.4",
|
127
127
|
"sinon": "17.0.1",
|
128
128
|
"sinon-chai": "3.7.0",
|
129
129
|
"typescript": "5.3.3",
|
130
130
|
"url-toolkit": "2.2.5",
|
131
|
-
"wrangler": "3.
|
131
|
+
"wrangler": "3.26.0"
|
132
132
|
},
|
133
|
-
"version": "1.5.
|
133
|
+
"version": "1.5.5-0.canary.9977"
|
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
|
}
|
@@ -591,8 +592,8 @@ class AbrController implements AbrComponentAPI {
|
|
591
592
|
? Math.min(currentFragDuration, config.maxLoadingDelay)
|
592
593
|
: config.maxLoadingDelay;
|
593
594
|
maxStarvationDelay = maxLoadingDelay - bitrateTestDelay;
|
594
|
-
|
595
|
-
`
|
595
|
+
this.info(
|
596
|
+
`bitrate test took ${Math.round(
|
596
597
|
1000 * bitrateTestDelay,
|
597
598
|
)}ms, set first fragment max fetchDuration to ${Math.round(
|
598
599
|
1000 * maxStarvationDelay,
|
@@ -611,8 +612,8 @@ class AbrController implements AbrComponentAPI {
|
|
611
612
|
bwFactor,
|
612
613
|
bwUpFactor,
|
613
614
|
);
|
614
|
-
|
615
|
-
|
615
|
+
this.info(
|
616
|
+
`${
|
616
617
|
bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'
|
617
618
|
}, optimal quality level ${bestLevel}`,
|
618
619
|
);
|
@@ -691,7 +692,7 @@ class AbrController implements AbrComponentAPI {
|
|
691
692
|
: videoRanges[0];
|
692
693
|
currentFrameRate = minFramerate;
|
693
694
|
currentBw = Math.max(currentBw, minBitrate);
|
694
|
-
|
695
|
+
this.log(`picked start tier ${JSON.stringify(startTier)}`);
|
695
696
|
} else {
|
696
697
|
currentCodecSet = level?.codecSet;
|
697
698
|
currentVideoRange = level?.videoRange;
|
@@ -741,19 +742,19 @@ class AbrController implements AbrComponentAPI {
|
|
741
742
|
const levels = this.hls.levels;
|
742
743
|
const index = levels.indexOf(levelInfo);
|
743
744
|
if (decodingInfo.error) {
|
744
|
-
|
745
|
-
`
|
745
|
+
this.warn(
|
746
|
+
`MediaCapabilities decodingInfo error: "${
|
746
747
|
decodingInfo.error
|
747
748
|
}" for level ${index} ${JSON.stringify(decodingInfo)}`,
|
748
749
|
);
|
749
750
|
} else if (!decodingInfo.supported) {
|
750
|
-
|
751
|
-
`
|
751
|
+
this.warn(
|
752
|
+
`Unsupported MediaCapabilities decodingInfo result for level ${index} ${JSON.stringify(
|
752
753
|
decodingInfo,
|
753
754
|
)}`,
|
754
755
|
);
|
755
756
|
if (index > -1 && levels.length > 1) {
|
756
|
-
|
757
|
+
this.log(`Removing unsupported level ${index}`);
|
757
758
|
this.hls.removeLevel(index);
|
758
759
|
}
|
759
760
|
}
|
@@ -832,8 +833,8 @@ class AbrController implements AbrComponentAPI {
|
|
832
833
|
(forcedAutoLevel === -1 || forcedAutoLevel !== loadLevel)
|
833
834
|
) {
|
834
835
|
if (levelsSkipped.length) {
|
835
|
-
|
836
|
-
`
|
836
|
+
this.trace(
|
837
|
+
`Skipped level(s) ${levelsSkipped.join(
|
837
838
|
',',
|
838
839
|
)} of ${maxAutoLevel} max with CODECS and VIDEO-RANGE:"${
|
839
840
|
levels[levelsSkipped[0]].codecs
|
@@ -842,8 +843,8 @@ class AbrController implements AbrComponentAPI {
|
|
842
843
|
}" ${currentVideoRange}`,
|
843
844
|
);
|
844
845
|
}
|
845
|
-
|
846
|
-
`
|
846
|
+
this.info(
|
847
|
+
`switch candidate:${selectionBaseLevel}->${i} adjustedbw(${Math.round(
|
847
848
|
adjustedbw,
|
848
849
|
)})-bitrate=${Math.round(
|
849
850
|
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);
|
@@ -281,12 +278,14 @@ class AudioStreamController
|
|
281
278
|
const { hls, levels, media, trackId } = this;
|
282
279
|
const config = hls.config;
|
283
280
|
|
284
|
-
// 1. if
|
281
|
+
// 1. if buffering is suspended
|
282
|
+
// 2. if video not attached AND
|
285
283
|
// start fragment already requested OR start frag prefetch not enabled
|
286
|
-
//
|
284
|
+
// 3. if tracks or track not loaded and selected
|
287
285
|
// then exit loop
|
288
286
|
// => if media not attached but start frag prefetch is enabled and start frag not requested yet, we will not exit loop
|
289
287
|
if (
|
288
|
+
!this.buffering ||
|
290
289
|
(!media && (this.startFragRequested || !config.startFragPrefetch)) ||
|
291
290
|
!levels?.[trackId]
|
292
291
|
) {
|
@@ -713,7 +712,7 @@ class AudioStreamController
|
|
713
712
|
this.fragBufferedComplete(frag, part);
|
714
713
|
}
|
715
714
|
|
716
|
-
|
715
|
+
protected onError(event: Events.ERROR, data: ErrorData) {
|
717
716
|
if (data.fatal) {
|
718
717
|
this.state = State.ERROR;
|
719
718
|
return;
|
@@ -5,7 +5,7 @@ import { computeReloadInterval, mergeDetails } from '../utils/level-helper';
|
|
5
5
|
import { ErrorData } from '../types/events';
|
6
6
|
import { getRetryDelay, isTimeoutError } from '../utils/error-helper';
|
7
7
|
import { NetworkErrorAction } from './error-controller';
|
8
|
-
import {
|
8
|
+
import { Logger } from '../utils/logger';
|
9
9
|
import type { LevelDetails } from '../loader/level-details';
|
10
10
|
import type { MediaPlaylist } from '../types/media-playlist';
|
11
11
|
import type {
|
@@ -14,17 +14,17 @@ import type {
|
|
14
14
|
TrackLoadedData,
|
15
15
|
} from '../types/events';
|
16
16
|
|
17
|
-
export default class BasePlaylistController
|
17
|
+
export default class BasePlaylistController
|
18
|
+
extends Logger
|
19
|
+
implements NetworkComponentAPI
|
20
|
+
{
|
18
21
|
protected hls: Hls;
|
19
22
|
protected timer: number = -1;
|
20
23
|
protected requestScheduled: number = -1;
|
21
24
|
protected canLoad: boolean = false;
|
22
|
-
protected log: (msg: any) => void;
|
23
|
-
protected warn: (msg: any) => void;
|
24
25
|
|
25
26
|
constructor(hls: Hls, logPrefix: string) {
|
26
|
-
|
27
|
-
this.warn = logger.warn.bind(logger, `${logPrefix}:`);
|
27
|
+
super(logPrefix, hls.logger);
|
28
28
|
this.hls = hls;
|
29
29
|
}
|
30
30
|
|
@@ -65,7 +65,7 @@ export default class BasePlaylistController implements NetworkComponentAPI {
|
|
65
65
|
try {
|
66
66
|
uri = new self.URL(attr.URI, previous.url).href;
|
67
67
|
} catch (error) {
|
68
|
-
|
68
|
+
this.warn(
|
69
69
|
`Could not construct new URL for Rendition Report: ${error}`,
|
70
70
|
);
|
71
71
|
uri = attr.URI || '';
|
@@ -1,12 +1,15 @@
|
|
1
1
|
import TaskLoop from '../task-loop';
|
2
2
|
import { FragmentState } from './fragment-tracker';
|
3
3
|
import { Bufferable, BufferHelper, BufferInfo } from '../utils/buffer-helper';
|
4
|
-
import { logger } from '../utils/logger';
|
5
4
|
import { Events } from '../events';
|
6
5
|
import { ErrorDetails, ErrorTypes } from '../errors';
|
7
6
|
import { ChunkMetadata } from '../types/transmuxer';
|
8
7
|
import { appendUint8Array } from '../utils/mp4-tools';
|
9
8
|
import { alignStream } from '../utils/discontinuities';
|
9
|
+
import {
|
10
|
+
isFullSegmentEncryption,
|
11
|
+
getAesModeFromFullSegmentMethod,
|
12
|
+
} from '../utils/encryption-methods-util';
|
10
13
|
import {
|
11
14
|
findFragmentByPDT,
|
12
15
|
findFragmentByPTS,
|
@@ -97,12 +100,7 @@ export default class BaseStreamController
|
|
97
100
|
protected startFragRequested: boolean = false;
|
98
101
|
protected decrypter: Decrypter;
|
99
102
|
protected initPTS: RationalTimestamp[] = [];
|
100
|
-
protected
|
101
|
-
protected onvended: EventListener | null = null;
|
102
|
-
|
103
|
-
private readonly logPrefix: string = '';
|
104
|
-
protected log: (msg: any) => void;
|
105
|
-
protected warn: (msg: any) => void;
|
103
|
+
protected buffering: boolean = true;
|
106
104
|
|
107
105
|
constructor(
|
108
106
|
hls: Hls,
|
@@ -111,18 +109,32 @@ export default class BaseStreamController
|
|
111
109
|
logPrefix: string,
|
112
110
|
playlistType: PlaylistLevelType,
|
113
111
|
) {
|
114
|
-
super();
|
112
|
+
super(logPrefix, hls.logger);
|
115
113
|
this.playlistType = playlistType;
|
116
|
-
this.logPrefix = logPrefix;
|
117
|
-
this.log = logger.log.bind(logger, `${logPrefix}:`);
|
118
|
-
this.warn = logger.warn.bind(logger, `${logPrefix}:`);
|
119
114
|
this.hls = hls;
|
120
115
|
this.fragmentLoader = new FragmentLoader(hls.config);
|
121
116
|
this.keyLoader = keyLoader;
|
122
117
|
this.fragmentTracker = fragmentTracker;
|
123
118
|
this.config = hls.config;
|
124
119
|
this.decrypter = new Decrypter(hls.config);
|
120
|
+
}
|
121
|
+
|
122
|
+
protected registerListeners() {
|
123
|
+
const { hls } = this;
|
124
|
+
hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
125
|
+
hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
126
|
+
hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
125
127
|
hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
|
128
|
+
hls.on(Events.ERROR, this.onError, this);
|
129
|
+
}
|
130
|
+
|
131
|
+
protected unregisterListeners() {
|
132
|
+
const { hls } = this;
|
133
|
+
hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
134
|
+
hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
135
|
+
hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
136
|
+
hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
|
137
|
+
hls.off(Events.ERROR, this.onError, this);
|
126
138
|
}
|
127
139
|
|
128
140
|
protected doTick() {
|
@@ -150,6 +162,14 @@ export default class BaseStreamController
|
|
150
162
|
this.state = State.STOPPED;
|
151
163
|
}
|
152
164
|
|
165
|
+
public pauseBuffering() {
|
166
|
+
this.buffering = false;
|
167
|
+
}
|
168
|
+
|
169
|
+
public resumeBuffering() {
|
170
|
+
this.buffering = true;
|
171
|
+
}
|
172
|
+
|
153
173
|
protected _streamEnded(
|
154
174
|
bufferInfo: BufferInfo,
|
155
175
|
levelDetails: LevelDetails,
|
@@ -197,10 +217,8 @@ export default class BaseStreamController
|
|
197
217
|
data: MediaAttachedData,
|
198
218
|
) {
|
199
219
|
const media = (this.media = this.mediaBuffer = data.media);
|
200
|
-
|
201
|
-
|
202
|
-
media.addEventListener('seeking', this.onvseeking);
|
203
|
-
media.addEventListener('ended', this.onvended);
|
220
|
+
media.addEventListener('seeking', this.onMediaSeeking);
|
221
|
+
media.addEventListener('ended', this.onMediaEnded);
|
204
222
|
const config = this.config;
|
205
223
|
if (this.levels && config.autoStartLoad && this.state === State.STOPPED) {
|
206
224
|
this.startLoad(config.startPosition);
|
@@ -215,10 +233,9 @@ export default class BaseStreamController
|
|
215
233
|
}
|
216
234
|
|
217
235
|
// remove video listeners
|
218
|
-
if (media
|
219
|
-
media.removeEventListener('seeking', this.
|
220
|
-
media.removeEventListener('ended', this.
|
221
|
-
this.onvseeking = this.onvended = null;
|
236
|
+
if (media) {
|
237
|
+
media.removeEventListener('seeking', this.onMediaSeeking);
|
238
|
+
media.removeEventListener('ended', this.onMediaEnded);
|
222
239
|
}
|
223
240
|
if (this.keyLoader) {
|
224
241
|
this.keyLoader.detach();
|
@@ -229,7 +246,11 @@ export default class BaseStreamController
|
|
229
246
|
this.stopLoad();
|
230
247
|
}
|
231
248
|
|
232
|
-
protected
|
249
|
+
protected onManifestLoading() {}
|
250
|
+
|
251
|
+
protected onError(event: Events.ERROR, data: ErrorData) {}
|
252
|
+
|
253
|
+
protected onMediaSeeking = () => {
|
233
254
|
const { config, fragCurrent, media, mediaBuffer, state } = this;
|
234
255
|
const currentTime: number = media ? media.currentTime : 0;
|
235
256
|
const bufferInfo = BufferHelper.bufferInfo(
|
@@ -292,12 +313,17 @@ export default class BaseStreamController
|
|
292
313
|
|
293
314
|
// Async tick to speed up processing
|
294
315
|
this.tickImmediate();
|
295
|
-
}
|
316
|
+
};
|
296
317
|
|
297
|
-
protected onMediaEnded() {
|
318
|
+
protected onMediaEnded = () => {
|
298
319
|
// reset startPosition and lastCurrentTime to restart playback @ stream beginning
|
299
320
|
this.startPosition = this.lastCurrentTime = 0;
|
300
|
-
|
321
|
+
if (this.playlistType === PlaylistLevelType.MAIN) {
|
322
|
+
this.hls.trigger(Events.MEDIA_ENDED, {
|
323
|
+
stalled: false,
|
324
|
+
});
|
325
|
+
}
|
326
|
+
};
|
301
327
|
|
302
328
|
protected onManifestLoaded(
|
303
329
|
event: Events.MANIFEST_LOADED,
|
@@ -312,7 +338,7 @@ export default class BaseStreamController
|
|
312
338
|
this.stopLoad();
|
313
339
|
super.onHandlerDestroying();
|
314
340
|
// @ts-ignore
|
315
|
-
this.hls = null;
|
341
|
+
this.hls = this.onMediaSeeking = this.onMediaEnded = null;
|
316
342
|
}
|
317
343
|
|
318
344
|
protected onHandlerDestroyed() {
|
@@ -486,7 +512,7 @@ export default class BaseStreamController
|
|
486
512
|
payload.byteLength > 0 &&
|
487
513
|
decryptData?.key &&
|
488
514
|
decryptData.iv &&
|
489
|
-
decryptData.method
|
515
|
+
isFullSegmentEncryption(decryptData.method)
|
490
516
|
) {
|
491
517
|
const startTime = self.performance.now();
|
492
518
|
// decrypt init segment data
|
@@ -495,6 +521,7 @@ export default class BaseStreamController
|
|
495
521
|
new Uint8Array(payload),
|
496
522
|
decryptData.key.buffer,
|
497
523
|
decryptData.iv.buffer,
|
524
|
+
getAesModeFromFullSegmentMethod(decryptData.method),
|
498
525
|
)
|
499
526
|
.catch((err) => {
|
500
527
|
hls.trigger(Events.ERROR, {
|
@@ -649,7 +676,7 @@ export default class BaseStreamController
|
|
649
676
|
if (frag.encrypted && !frag.decryptdata?.key) {
|
650
677
|
this.log(
|
651
678
|
`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${
|
652
|
-
this.
|
679
|
+
this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'
|
653
680
|
} ${frag.level}`,
|
654
681
|
);
|
655
682
|
this.state = State.KEY_LOADING;
|
@@ -689,7 +716,7 @@ export default class BaseStreamController
|
|
689
716
|
} of playlist [${details.startSN}-${
|
690
717
|
details.endSN
|
691
718
|
}] parts [0-${partIndex}-${partList.length - 1}] ${
|
692
|
-
this.
|
719
|
+
this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'
|
693
720
|
}: ${frag.level}, target: ${parseFloat(
|
694
721
|
targetBufferTime.toFixed(3),
|
695
722
|
)}`,
|
@@ -748,7 +775,7 @@ export default class BaseStreamController
|
|
748
775
|
this.log(
|
749
776
|
`Loading fragment ${frag.sn} cc: ${frag.cc} ${
|
750
777
|
details ? 'of [' + details.startSN + '-' + details.endSN + '] ' : ''
|
751
|
-
}${this.
|
778
|
+
}${this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'}: ${
|
752
779
|
frag.level
|
753
780
|
}, target: ${parseFloat(targetBufferTime.toFixed(3))}`,
|
754
781
|
);
|
@@ -1550,7 +1577,7 @@ export default class BaseStreamController
|
|
1550
1577
|
errorAction.resolved = true;
|
1551
1578
|
}
|
1552
1579
|
} else {
|
1553
|
-
|
1580
|
+
this.warn(
|
1554
1581
|
`${data.details} reached or exceeded max retry (${retryCount})`,
|
1555
1582
|
);
|
1556
1583
|
return;
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import { Events } from '../events';
|
2
|
-
import {
|
2
|
+
import { Logger } from '../utils/logger';
|
3
3
|
import { ErrorDetails, ErrorTypes } from '../errors';
|
4
4
|
import { BufferHelper } from '../utils/buffer-helper';
|
5
5
|
import {
|
@@ -42,7 +42,7 @@ interface BufferedChangeEvent extends Event {
|
|
42
42
|
readonly removedRanges?: TimeRanges;
|
43
43
|
}
|
44
44
|
|
45
|
-
export default class BufferController implements ComponentAPI {
|
45
|
+
export default class BufferController extends Logger implements ComponentAPI {
|
46
46
|
// The level details used to determine duration, target-duration and live
|
47
47
|
private details: LevelDetails | null = null;
|
48
48
|
// cache the self generated object url to detect hijack of video tag
|
@@ -82,17 +82,10 @@ export default class BufferController implements ComponentAPI {
|
|
82
82
|
public pendingTracks: TrackSet = {};
|
83
83
|
public sourceBuffer!: SourceBuffers;
|
84
84
|
|
85
|
-
protected log: (msg: any) => void;
|
86
|
-
protected warn: (msg: any, obj?: any) => void;
|
87
|
-
protected error: (msg: any, obj?: any) => void;
|
88
|
-
|
89
85
|
constructor(hls: Hls) {
|
86
|
+
super('buffer-controller', hls.logger);
|
90
87
|
this.hls = hls;
|
91
|
-
const logPrefix = '[buffer-controller]';
|
92
88
|
this.appendSource = hls.config.preferManagedMediaSource;
|
93
|
-
this.log = logger.log.bind(logger, logPrefix);
|
94
|
-
this.warn = logger.warn.bind(logger, logPrefix);
|
95
|
-
this.error = logger.error.bind(logger, logPrefix);
|
96
89
|
this._initSourceBuffer();
|
97
90
|
this.registerListeners();
|
98
91
|
}
|
@@ -110,6 +103,12 @@ export default class BufferController implements ComponentAPI {
|
|
110
103
|
this.lastMpegAudioChunk = null;
|
111
104
|
// @ts-ignore
|
112
105
|
this.hls = null;
|
106
|
+
// @ts-ignore
|
107
|
+
this._onMediaSourceOpen = this._onMediaSourceClose = null;
|
108
|
+
// @ts-ignore
|
109
|
+
this._onMediaSourceEnded = null;
|
110
|
+
// @ts-ignore
|
111
|
+
this._onStartStreaming = this._onEndStreaming = null;
|
113
112
|
}
|
114
113
|
|
115
114
|
protected registerListeners() {
|
@@ -296,6 +295,7 @@ export default class BufferController implements ComponentAPI {
|
|
296
295
|
this.resetBuffer(type);
|
297
296
|
});
|
298
297
|
this._initSourceBuffer();
|
298
|
+
this.hls.resumeBuffering();
|
299
299
|
}
|
300
300
|
|
301
301
|
private resetBuffer(type: SourceBufferName) {
|
@@ -1004,7 +1004,7 @@ export default class BufferController implements ComponentAPI {
|
|
1004
1004
|
private _onMediaEmptied = () => {
|
1005
1005
|
const { mediaSrc, _objectUrl } = this;
|
1006
1006
|
if (mediaSrc !== _objectUrl) {
|
1007
|
-
|
1007
|
+
this.error(
|
1008
1008
|
`Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`,
|
1009
1009
|
);
|
1010
1010
|
}
|
@@ -12,7 +12,6 @@ import type {
|
|
12
12
|
LevelsUpdatedData,
|
13
13
|
} from '../types/events';
|
14
14
|
import StreamController from './stream-controller';
|
15
|
-
import { logger } from '../utils/logger';
|
16
15
|
import type { ComponentAPI } from '../types/component-api';
|
17
16
|
import type Hls from '../hls';
|
18
17
|
|
@@ -152,7 +151,7 @@ class CapLevelController implements ComponentAPI {
|
|
152
151
|
const hls = this.hls;
|
153
152
|
const maxLevel = this.getMaxLevel(levels.length - 1);
|
154
153
|
if (maxLevel !== this.autoLevelCapping) {
|
155
|
-
logger.log(
|
154
|
+
hls.logger.log(
|
156
155
|
`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`,
|
157
156
|
);
|
158
157
|
}
|