hls.js 1.5.7 → 1.5.8-0.canary.10044
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 +2 -1
- package/dist/hls-demo.js +10 -0
- package/dist/hls-demo.js.map +1 -1
- package/dist/hls.js +2314 -1298
- package/dist/hls.js.d.ts +97 -84
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +1486 -1075
- 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 +1195 -789
- 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 +1979 -982
- 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 +22 -22
- 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 +20 -8
- package/src/controller/base-stream-controller.ts +157 -36
- package/src/controller/buffer-controller.ts +203 -67
- package/src/controller/buffer-operation-queue.ts +16 -19
- package/src/controller/cap-level-controller.ts +2 -2
- package/src/controller/cmcd-controller.ts +27 -6
- 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/fragment-tracker.ts +15 -11
- package/src/controller/gap-controller.ts +43 -16
- package/src/controller/latency-controller.ts +9 -11
- package/src/controller/level-controller.ts +12 -18
- package/src/controller/stream-controller.ts +36 -31
- package/src/controller/subtitle-stream-controller.ts +28 -40
- 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 +71 -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 +49 -37
- 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/loader/playlist-loader.ts +4 -5
- 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/buffer-helper.ts +12 -31
- package/src/utils/codecs.ts +34 -5
- package/src/utils/encryption-methods-util.ts +21 -0
- package/src/utils/logger.ts +54 -24
- package/src/utils/mp4-tools.ts +4 -2
@@ -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,8 @@ export default class BaseStreamController
|
|
97
100
|
protected startFragRequested: boolean = false;
|
98
101
|
protected decrypter: Decrypter;
|
99
102
|
protected initPTS: RationalTimestamp[] = [];
|
100
|
-
protected
|
101
|
-
|
102
|
-
|
103
|
-
private readonly logPrefix: string = '';
|
104
|
-
protected log: (msg: any) => void;
|
105
|
-
protected warn: (msg: any) => void;
|
103
|
+
protected buffering: boolean = true;
|
104
|
+
private loadingParts: boolean = false;
|
106
105
|
|
107
106
|
constructor(
|
108
107
|
hls: Hls,
|
@@ -111,18 +110,32 @@ export default class BaseStreamController
|
|
111
110
|
logPrefix: string,
|
112
111
|
playlistType: PlaylistLevelType,
|
113
112
|
) {
|
114
|
-
super();
|
113
|
+
super(logPrefix, hls.logger);
|
115
114
|
this.playlistType = playlistType;
|
116
|
-
this.logPrefix = logPrefix;
|
117
|
-
this.log = logger.log.bind(logger, `${logPrefix}:`);
|
118
|
-
this.warn = logger.warn.bind(logger, `${logPrefix}:`);
|
119
115
|
this.hls = hls;
|
120
116
|
this.fragmentLoader = new FragmentLoader(hls.config);
|
121
117
|
this.keyLoader = keyLoader;
|
122
118
|
this.fragmentTracker = fragmentTracker;
|
123
119
|
this.config = hls.config;
|
124
120
|
this.decrypter = new Decrypter(hls.config);
|
121
|
+
}
|
122
|
+
|
123
|
+
protected registerListeners() {
|
124
|
+
const { hls } = this;
|
125
|
+
hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
126
|
+
hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
127
|
+
hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
125
128
|
hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
|
129
|
+
hls.on(Events.ERROR, this.onError, this);
|
130
|
+
}
|
131
|
+
|
132
|
+
protected unregisterListeners() {
|
133
|
+
const { hls } = this;
|
134
|
+
hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
135
|
+
hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
136
|
+
hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
137
|
+
hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
|
138
|
+
hls.off(Events.ERROR, this.onError, this);
|
126
139
|
}
|
127
140
|
|
128
141
|
protected doTick() {
|
@@ -150,6 +163,14 @@ export default class BaseStreamController
|
|
150
163
|
this.state = State.STOPPED;
|
151
164
|
}
|
152
165
|
|
166
|
+
public pauseBuffering() {
|
167
|
+
this.buffering = false;
|
168
|
+
}
|
169
|
+
|
170
|
+
public resumeBuffering() {
|
171
|
+
this.buffering = true;
|
172
|
+
}
|
173
|
+
|
153
174
|
protected _streamEnded(
|
154
175
|
bufferInfo: BufferInfo,
|
155
176
|
levelDetails: LevelDetails,
|
@@ -197,10 +218,8 @@ export default class BaseStreamController
|
|
197
218
|
data: MediaAttachedData,
|
198
219
|
) {
|
199
220
|
const media = (this.media = this.mediaBuffer = data.media);
|
200
|
-
|
201
|
-
|
202
|
-
media.addEventListener('seeking', this.onvseeking);
|
203
|
-
media.addEventListener('ended', this.onvended);
|
221
|
+
media.addEventListener('seeking', this.onMediaSeeking);
|
222
|
+
media.addEventListener('ended', this.onMediaEnded);
|
204
223
|
const config = this.config;
|
205
224
|
if (this.levels && config.autoStartLoad && this.state === State.STOPPED) {
|
206
225
|
this.startLoad(config.startPosition);
|
@@ -215,10 +234,9 @@ export default class BaseStreamController
|
|
215
234
|
}
|
216
235
|
|
217
236
|
// remove video listeners
|
218
|
-
if (media
|
219
|
-
media.removeEventListener('seeking', this.
|
220
|
-
media.removeEventListener('ended', this.
|
221
|
-
this.onvseeking = this.onvended = null;
|
237
|
+
if (media) {
|
238
|
+
media.removeEventListener('seeking', this.onMediaSeeking);
|
239
|
+
media.removeEventListener('ended', this.onMediaEnded);
|
222
240
|
}
|
223
241
|
if (this.keyLoader) {
|
224
242
|
this.keyLoader.detach();
|
@@ -229,7 +247,11 @@ export default class BaseStreamController
|
|
229
247
|
this.stopLoad();
|
230
248
|
}
|
231
249
|
|
232
|
-
protected
|
250
|
+
protected onManifestLoading() {}
|
251
|
+
|
252
|
+
protected onError(event: Events.ERROR, data: ErrorData) {}
|
253
|
+
|
254
|
+
protected onMediaSeeking = () => {
|
233
255
|
const { config, fragCurrent, media, mediaBuffer, state } = this;
|
234
256
|
const currentTime: number = media ? media.currentTime : 0;
|
235
257
|
const bufferInfo = BufferHelper.bufferInfo(
|
@@ -283,6 +305,21 @@ export default class BaseStreamController
|
|
283
305
|
);
|
284
306
|
|
285
307
|
this.lastCurrentTime = currentTime;
|
308
|
+
if (!this.loadingParts) {
|
309
|
+
const bufferEnd = Math.max(bufferInfo.end, currentTime);
|
310
|
+
const shouldLoadParts = this.shouldLoadParts(
|
311
|
+
this.getLevelDetails(),
|
312
|
+
bufferEnd,
|
313
|
+
);
|
314
|
+
if (shouldLoadParts) {
|
315
|
+
this.log(
|
316
|
+
`LL-Part loading ON after seeking to ${currentTime.toFixed(
|
317
|
+
2,
|
318
|
+
)} with buffer @${bufferEnd.toFixed(2)}`,
|
319
|
+
);
|
320
|
+
this.loadingParts = shouldLoadParts;
|
321
|
+
}
|
322
|
+
}
|
286
323
|
}
|
287
324
|
|
288
325
|
// in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
|
@@ -292,12 +329,17 @@ export default class BaseStreamController
|
|
292
329
|
|
293
330
|
// Async tick to speed up processing
|
294
331
|
this.tickImmediate();
|
295
|
-
}
|
332
|
+
};
|
296
333
|
|
297
|
-
protected onMediaEnded() {
|
334
|
+
protected onMediaEnded = () => {
|
298
335
|
// reset startPosition and lastCurrentTime to restart playback @ stream beginning
|
299
336
|
this.startPosition = this.lastCurrentTime = 0;
|
300
|
-
|
337
|
+
if (this.playlistType === PlaylistLevelType.MAIN) {
|
338
|
+
this.hls.trigger(Events.MEDIA_ENDED, {
|
339
|
+
stalled: false,
|
340
|
+
});
|
341
|
+
}
|
342
|
+
};
|
301
343
|
|
302
344
|
protected onManifestLoaded(
|
303
345
|
event: Events.MANIFEST_LOADED,
|
@@ -312,7 +354,7 @@ export default class BaseStreamController
|
|
312
354
|
this.stopLoad();
|
313
355
|
super.onHandlerDestroying();
|
314
356
|
// @ts-ignore
|
315
|
-
this.hls = null;
|
357
|
+
this.hls = this.onMediaSeeking = this.onMediaEnded = null;
|
316
358
|
}
|
317
359
|
|
318
360
|
protected onHandlerDestroyed() {
|
@@ -486,7 +528,7 @@ export default class BaseStreamController
|
|
486
528
|
payload.byteLength > 0 &&
|
487
529
|
decryptData?.key &&
|
488
530
|
decryptData.iv &&
|
489
|
-
decryptData.method
|
531
|
+
isFullSegmentEncryption(decryptData.method)
|
490
532
|
) {
|
491
533
|
const startTime = self.performance.now();
|
492
534
|
// decrypt init segment data
|
@@ -495,6 +537,7 @@ export default class BaseStreamController
|
|
495
537
|
new Uint8Array(payload),
|
496
538
|
decryptData.key.buffer,
|
497
539
|
decryptData.iv.buffer,
|
540
|
+
getAesModeFromFullSegmentMethod(decryptData.method),
|
498
541
|
)
|
499
542
|
.catch((err) => {
|
500
543
|
hls.trigger(Events.ERROR, {
|
@@ -649,7 +692,7 @@ export default class BaseStreamController
|
|
649
692
|
if (frag.encrypted && !frag.decryptdata?.key) {
|
650
693
|
this.log(
|
651
694
|
`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${
|
652
|
-
this.
|
695
|
+
this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'
|
653
696
|
} ${frag.level}`,
|
654
697
|
);
|
655
698
|
this.state = State.KEY_LOADING;
|
@@ -673,8 +716,23 @@ export default class BaseStreamController
|
|
673
716
|
this.keyLoader.loadClear(frag, details.encryptedFragments);
|
674
717
|
}
|
675
718
|
|
719
|
+
const fragPrevious = this.fragPrevious;
|
720
|
+
if (
|
721
|
+
frag.sn !== 'initSegment' &&
|
722
|
+
(!fragPrevious || frag.sn !== fragPrevious.sn)
|
723
|
+
) {
|
724
|
+
const shouldLoadParts = this.shouldLoadParts(level.details, frag.end);
|
725
|
+
if (shouldLoadParts !== this.loadingParts) {
|
726
|
+
this.log(
|
727
|
+
`LL-Part loading ${
|
728
|
+
shouldLoadParts ? 'ON' : 'OFF'
|
729
|
+
} loading sn ${fragPrevious?.sn}->${frag.sn}`,
|
730
|
+
);
|
731
|
+
this.loadingParts = shouldLoadParts;
|
732
|
+
}
|
733
|
+
}
|
676
734
|
targetBufferTime = Math.max(frag.start, targetBufferTime || 0);
|
677
|
-
if (this.
|
735
|
+
if (this.loadingParts && frag.sn !== 'initSegment') {
|
678
736
|
const partList = details.partList;
|
679
737
|
if (partList && progressCallback) {
|
680
738
|
if (targetBufferTime > frag.end && details.fragmentHint) {
|
@@ -689,7 +747,7 @@ export default class BaseStreamController
|
|
689
747
|
} of playlist [${details.startSN}-${
|
690
748
|
details.endSN
|
691
749
|
}] parts [0-${partIndex}-${partList.length - 1}] ${
|
692
|
-
this.
|
750
|
+
this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'
|
693
751
|
}: ${frag.level}, target: ${parseFloat(
|
694
752
|
targetBufferTime.toFixed(3),
|
695
753
|
)}`,
|
@@ -745,10 +803,22 @@ export default class BaseStreamController
|
|
745
803
|
}
|
746
804
|
}
|
747
805
|
|
806
|
+
if (frag.sn !== 'initSegment' && this.loadingParts) {
|
807
|
+
this.log(
|
808
|
+
`LL-Part loading OFF after next part miss @${targetBufferTime.toFixed(
|
809
|
+
2,
|
810
|
+
)}`,
|
811
|
+
);
|
812
|
+
this.loadingParts = false;
|
813
|
+
} else if (!frag.url) {
|
814
|
+
// Selected fragment hint for part but not loading parts
|
815
|
+
return Promise.resolve(null);
|
816
|
+
}
|
817
|
+
|
748
818
|
this.log(
|
749
819
|
`Loading fragment ${frag.sn} cc: ${frag.cc} ${
|
750
820
|
details ? 'of [' + details.startSN + '-' + details.endSN + '] ' : ''
|
751
|
-
}${this.
|
821
|
+
}${this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'}: ${
|
752
822
|
frag.level
|
753
823
|
}, target: ${parseFloat(targetBufferTime.toFixed(3))}`,
|
754
824
|
);
|
@@ -872,9 +942,50 @@ export default class BaseStreamController
|
|
872
942
|
if (part) {
|
873
943
|
part.stats.parsing.end = now;
|
874
944
|
}
|
945
|
+
// See if part loading should be disabled/enabled based on buffer and playback position.
|
946
|
+
if (frag.sn !== 'initSegment') {
|
947
|
+
const levelDetails = this.getLevelDetails();
|
948
|
+
const loadingPartsAtEdge = levelDetails && frag.sn > levelDetails.endSN;
|
949
|
+
const shouldLoadParts =
|
950
|
+
loadingPartsAtEdge || this.shouldLoadParts(levelDetails, frag.end);
|
951
|
+
if (shouldLoadParts !== this.loadingParts) {
|
952
|
+
this.log(
|
953
|
+
`LL-Part loading ${
|
954
|
+
shouldLoadParts ? 'ON' : 'OFF'
|
955
|
+
} after parsing segment ending @${frag.end.toFixed(2)}`,
|
956
|
+
);
|
957
|
+
this.loadingParts = shouldLoadParts;
|
958
|
+
}
|
959
|
+
}
|
960
|
+
|
875
961
|
this.updateLevelTiming(frag, part, level, chunkMeta.partial);
|
876
962
|
}
|
877
963
|
|
964
|
+
private shouldLoadParts(
|
965
|
+
details: LevelDetails | undefined,
|
966
|
+
bufferEnd: number,
|
967
|
+
): boolean {
|
968
|
+
if (this.config.lowLatencyMode) {
|
969
|
+
if (!details) {
|
970
|
+
return this.loadingParts;
|
971
|
+
}
|
972
|
+
if (details?.partList) {
|
973
|
+
// Buffer must be ahead of first part + duration of parts after last segment
|
974
|
+
// and playback must be at or past segment adjacent to part list
|
975
|
+
const firstPart = details.partList[0];
|
976
|
+
const safePartStart =
|
977
|
+
firstPart.end + (details.fragmentHint?.duration || 0);
|
978
|
+
if (
|
979
|
+
bufferEnd >= safePartStart &&
|
980
|
+
this.lastCurrentTime > firstPart.start - firstPart.fragment.duration
|
981
|
+
) {
|
982
|
+
return true;
|
983
|
+
}
|
984
|
+
}
|
985
|
+
}
|
986
|
+
return false;
|
987
|
+
}
|
988
|
+
|
878
989
|
protected getCurrentContext(
|
879
990
|
chunkMeta: ChunkMetadata,
|
880
991
|
): { frag: Fragment; part: Part | null; level: Level } | null {
|
@@ -991,7 +1102,10 @@ export default class BaseStreamController
|
|
991
1102
|
// Workaround flaw in getting forward buffer when maxBufferHole is smaller than gap at current pos
|
992
1103
|
if (bufferInfo.len === 0 && bufferInfo.nextStart !== undefined) {
|
993
1104
|
const bufferedFragAtPos = this.fragmentTracker.getBufferedFrag(pos, type);
|
994
|
-
if (
|
1105
|
+
if (
|
1106
|
+
bufferedFragAtPos &&
|
1107
|
+
(bufferInfo.nextStart <= bufferedFragAtPos.end || bufferedFragAtPos.gap)
|
1108
|
+
) {
|
995
1109
|
return BufferHelper.bufferInfo(
|
996
1110
|
bufferable,
|
997
1111
|
pos,
|
@@ -1004,7 +1118,7 @@ export default class BaseStreamController
|
|
1004
1118
|
|
1005
1119
|
protected getMaxBufferLength(levelBitrate?: number): number {
|
1006
1120
|
const { config } = this;
|
1007
|
-
let maxBufLen;
|
1121
|
+
let maxBufLen: number;
|
1008
1122
|
if (levelBitrate) {
|
1009
1123
|
maxBufLen = Math.max(
|
1010
1124
|
(8 * config.maxBufferSize) / levelBitrate,
|
@@ -1056,7 +1170,8 @@ export default class BaseStreamController
|
|
1056
1170
|
// find fragment index, contiguous with end of buffer position
|
1057
1171
|
const { config } = this;
|
1058
1172
|
const start = fragments[0].start;
|
1059
|
-
|
1173
|
+
const canLoadParts = config.lowLatencyMode && !!levelDetails.partList;
|
1174
|
+
let frag: Fragment | null = null;
|
1060
1175
|
|
1061
1176
|
if (levelDetails.live) {
|
1062
1177
|
const initialLiveManifestSize = config.initialLiveManifestSize;
|
@@ -1076,6 +1191,10 @@ export default class BaseStreamController
|
|
1076
1191
|
this.startPosition === -1) ||
|
1077
1192
|
pos < start
|
1078
1193
|
) {
|
1194
|
+
if (canLoadParts && !this.loadingParts) {
|
1195
|
+
this.log(`LL-Part loading ON for initial live fragment`);
|
1196
|
+
this.loadingParts = true;
|
1197
|
+
}
|
1079
1198
|
frag = this.getInitialLiveFragment(levelDetails, fragments);
|
1080
1199
|
this.startPosition = this.nextLoadPosition = frag
|
1081
1200
|
? this.hls.liveSyncPosition || frag.start
|
@@ -1088,7 +1207,7 @@ export default class BaseStreamController
|
|
1088
1207
|
|
1089
1208
|
// If we haven't run into any special cases already, just load the fragment most closely matching the requested position
|
1090
1209
|
if (!frag) {
|
1091
|
-
const end =
|
1210
|
+
const end = this.loadingParts
|
1092
1211
|
? levelDetails.partEnd
|
1093
1212
|
: levelDetails.fragmentEnd;
|
1094
1213
|
frag = this.getFragmentAtPosition(pos, end, levelDetails);
|
@@ -1271,7 +1390,7 @@ export default class BaseStreamController
|
|
1271
1390
|
const partList = levelDetails.partList;
|
1272
1391
|
|
1273
1392
|
const loadingParts = !!(
|
1274
|
-
|
1393
|
+
this.loadingParts &&
|
1275
1394
|
partList?.length &&
|
1276
1395
|
fragmentHint
|
1277
1396
|
);
|
@@ -1550,7 +1669,7 @@ export default class BaseStreamController
|
|
1550
1669
|
errorAction.resolved = true;
|
1551
1670
|
}
|
1552
1671
|
} else {
|
1553
|
-
|
1672
|
+
this.warn(
|
1554
1673
|
`${data.details} reached or exceeded max retry (${retryCount})`,
|
1555
1674
|
);
|
1556
1675
|
return;
|
@@ -1639,7 +1758,9 @@ export default class BaseStreamController
|
|
1639
1758
|
this.log('Reset loading state');
|
1640
1759
|
this.fragCurrent = null;
|
1641
1760
|
this.fragPrevious = null;
|
1642
|
-
this.state
|
1761
|
+
if (this.state !== State.STOPPED) {
|
1762
|
+
this.state = State.IDLE;
|
1763
|
+
}
|
1643
1764
|
}
|
1644
1765
|
|
1645
1766
|
protected resetStartWhenNotLoaded(level: Level | null): void {
|