hls.js 1.5.12-0.canary.10398 → 1.5.12
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 +38 -41
- package/dist/hls-demo.js.map +1 -1
- package/dist/hls.js +2678 -4226
- package/dist/hls.js.d.ts +109 -174
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +1964 -2900
- 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 +3409 -4360
- 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 +4451 -6014
- 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 +38 -38
- package/src/config.ts +2 -5
- package/src/controller/abr-controller.ts +25 -39
- package/src/controller/audio-stream-controller.ts +137 -141
- 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 +97 -223
- package/src/controller/buffer-controller.ts +97 -250
- 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 +22 -28
- package/src/controller/fps-controller.ts +3 -8
- package/src/controller/fragment-finders.ts +16 -44
- package/src/controller/fragment-tracker.ts +25 -58
- package/src/controller/gap-controller.ts +16 -43
- package/src/controller/id3-track-controller.ts +35 -45
- package/src/controller/latency-controller.ts +13 -18
- package/src/controller/level-controller.ts +19 -37
- package/src/controller/stream-controller.ts +83 -100
- package/src/controller/subtitle-stream-controller.ts +47 -35
- package/src/controller/subtitle-track-controller.ts +3 -5
- package/src/controller/timeline-controller.ts +22 -20
- package/src/crypt/aes-crypto.ts +2 -21
- package/src/crypt/decrypter.ts +16 -32
- package/src/crypt/fast-aes-key.ts +5 -28
- 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 +16 -8
- package/src/demux/transmuxer-worker.ts +4 -4
- package/src/demux/transmuxer.ts +3 -16
- package/src/demux/tsdemuxer.ts +38 -75
- package/src/demux/video/avc-video-parser.ts +121 -210
- package/src/demux/video/base-video-parser.ts +2 -135
- 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 +43 -73
- package/src/loader/date-range.ts +5 -71
- package/src/loader/fragment-loader.ts +21 -23
- package/src/loader/fragment.ts +4 -8
- package/src/loader/key-loader.ts +1 -3
- package/src/loader/level-details.ts +6 -6
- package/src/loader/level-key.ts +9 -10
- package/src/loader/m3u8-parser.ts +144 -138
- package/src/loader/playlist-loader.ts +7 -5
- package/src/remux/mp4-generator.ts +1 -196
- package/src/remux/mp4-remuxer.ts +16 -36
- package/src/remux/passthrough-remuxer.ts +1 -1
- package/src/task-loop.ts +2 -5
- package/src/types/component-api.ts +1 -3
- package/src/types/demuxer.ts +0 -3
- package/src/types/events.ts +6 -19
- package/src/types/fragment-tracker.ts +2 -2
- package/src/types/general.ts +6 -0
- package/src/types/media-playlist.ts +1 -9
- package/src/types/remuxer.ts +1 -1
- package/src/utils/attr-list.ts +9 -96
- 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/hdr.ts +7 -4
- package/src/utils/imsc1-ttml-parser.ts +1 -1
- package/src/utils/keysystem-util.ts +6 -1
- package/src/utils/level-helper.ts +44 -71
- package/src/utils/logger.ts +23 -58
- package/src/utils/mp4-tools.ts +3 -5
- package/src/utils/rendition-helper.ts +74 -100
- package/src/utils/variable-substitution.ts +19 -0
- package/src/utils/webvtt-parser.ts +12 -2
- package/src/crypt/decrypter-aes-mode.ts +0 -4
- package/src/demux/video/hevc-video-parser.ts +0 -749
- package/src/utils/encryption-methods-util.ts +0 -21
- package/src/utils/hash.ts +0 -10
- package/src/utils/utf8-utils.ts +0 -18
@@ -1,15 +1,12 @@
|
|
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';
|
4
5
|
import { Events } from '../events';
|
5
6
|
import { ErrorDetails, ErrorTypes } from '../errors';
|
6
7
|
import { ChunkMetadata } from '../types/transmuxer';
|
7
8
|
import { appendUint8Array } from '../utils/mp4-tools';
|
8
9
|
import { alignStream } from '../utils/discontinuities';
|
9
|
-
import {
|
10
|
-
isFullSegmentEncryption,
|
11
|
-
getAesModeFromFullSegmentMethod,
|
12
|
-
} from '../utils/encryption-methods-util';
|
13
10
|
import {
|
14
11
|
findFragmentByPDT,
|
15
12
|
findFragmentByPTS,
|
@@ -22,7 +19,7 @@ import {
|
|
22
19
|
updateFragPTSDTS,
|
23
20
|
} from '../utils/level-helper';
|
24
21
|
import TransmuxerInterface from '../demux/transmuxer-interface';
|
25
|
-
import { Fragment,
|
22
|
+
import { Fragment, Part } from '../loader/fragment';
|
26
23
|
import FragmentLoader, {
|
27
24
|
FragmentLoadProgressCallback,
|
28
25
|
LoadError,
|
@@ -77,7 +74,7 @@ export default class BaseStreamController
|
|
77
74
|
{
|
78
75
|
protected hls: Hls;
|
79
76
|
|
80
|
-
protected fragPrevious:
|
77
|
+
protected fragPrevious: Fragment | null = null;
|
81
78
|
protected fragCurrent: Fragment | null = null;
|
82
79
|
protected fragmentTracker: FragmentTracker;
|
83
80
|
protected transmuxer: TransmuxerInterface | null = null;
|
@@ -100,9 +97,12 @@ export default class BaseStreamController
|
|
100
97
|
protected startFragRequested: boolean = false;
|
101
98
|
protected decrypter: Decrypter;
|
102
99
|
protected initPTS: RationalTimestamp[] = [];
|
103
|
-
protected
|
104
|
-
|
105
|
-
|
100
|
+
protected onvseeking: EventListener | null = null;
|
101
|
+
protected onvended: EventListener | null = null;
|
102
|
+
|
103
|
+
private readonly logPrefix: string = '';
|
104
|
+
protected log: (msg: any) => void;
|
105
|
+
protected warn: (msg: any) => void;
|
106
106
|
|
107
107
|
constructor(
|
108
108
|
hls: Hls,
|
@@ -111,32 +111,18 @@ export default class BaseStreamController
|
|
111
111
|
logPrefix: string,
|
112
112
|
playlistType: PlaylistLevelType,
|
113
113
|
) {
|
114
|
-
super(
|
114
|
+
super();
|
115
115
|
this.playlistType = playlistType;
|
116
|
+
this.logPrefix = logPrefix;
|
117
|
+
this.log = logger.log.bind(logger, `${logPrefix}:`);
|
118
|
+
this.warn = logger.warn.bind(logger, `${logPrefix}:`);
|
116
119
|
this.hls = hls;
|
117
120
|
this.fragmentLoader = new FragmentLoader(hls.config);
|
118
121
|
this.keyLoader = keyLoader;
|
119
122
|
this.fragmentTracker = fragmentTracker;
|
120
123
|
this.config = hls.config;
|
121
124
|
this.decrypter = new Decrypter(hls.config);
|
122
|
-
}
|
123
|
-
|
124
|
-
protected registerListeners() {
|
125
|
-
const { hls } = this;
|
126
|
-
hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
127
|
-
hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
128
|
-
hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
129
125
|
hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
|
130
|
-
hls.on(Events.ERROR, this.onError, this);
|
131
|
-
}
|
132
|
-
|
133
|
-
protected unregisterListeners() {
|
134
|
-
const { hls } = this;
|
135
|
-
hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
136
|
-
hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
137
|
-
hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
138
|
-
hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
|
139
|
-
hls.off(Events.ERROR, this.onError, this);
|
140
126
|
}
|
141
127
|
|
142
128
|
protected doTick() {
|
@@ -149,9 +135,6 @@ export default class BaseStreamController
|
|
149
135
|
public startLoad(startPosition: number): void {}
|
150
136
|
|
151
137
|
public stopLoad() {
|
152
|
-
if (this.state === State.STOPPED) {
|
153
|
-
return;
|
154
|
-
}
|
155
138
|
this.fragmentLoader.abort();
|
156
139
|
this.keyLoader.abort(this.playlistType);
|
157
140
|
const frag = this.fragCurrent;
|
@@ -167,14 +150,6 @@ export default class BaseStreamController
|
|
167
150
|
this.state = State.STOPPED;
|
168
151
|
}
|
169
152
|
|
170
|
-
public pauseBuffering() {
|
171
|
-
this.buffering = false;
|
172
|
-
}
|
173
|
-
|
174
|
-
public resumeBuffering() {
|
175
|
-
this.buffering = true;
|
176
|
-
}
|
177
|
-
|
178
153
|
protected _streamEnded(
|
179
154
|
bufferInfo: BufferInfo,
|
180
155
|
levelDetails: LevelDetails,
|
@@ -222,8 +197,10 @@ export default class BaseStreamController
|
|
222
197
|
data: MediaAttachedData,
|
223
198
|
) {
|
224
199
|
const media = (this.media = this.mediaBuffer = data.media);
|
225
|
-
|
226
|
-
|
200
|
+
this.onvseeking = this.onMediaSeeking.bind(this) as EventListener;
|
201
|
+
this.onvended = this.onMediaEnded.bind(this) as EventListener;
|
202
|
+
media.addEventListener('seeking', this.onvseeking);
|
203
|
+
media.addEventListener('ended', this.onvended);
|
227
204
|
const config = this.config;
|
228
205
|
if (this.levels && config.autoStartLoad && this.state === State.STOPPED) {
|
229
206
|
this.startLoad(config.startPosition);
|
@@ -238,30 +215,21 @@ export default class BaseStreamController
|
|
238
215
|
}
|
239
216
|
|
240
217
|
// remove video listeners
|
241
|
-
if (media) {
|
242
|
-
media.removeEventListener('seeking', this.
|
243
|
-
media.removeEventListener('ended', this.
|
218
|
+
if (media && this.onvseeking && this.onvended) {
|
219
|
+
media.removeEventListener('seeking', this.onvseeking);
|
220
|
+
media.removeEventListener('ended', this.onvended);
|
221
|
+
this.onvseeking = this.onvended = null;
|
244
222
|
}
|
245
223
|
if (this.keyLoader) {
|
246
224
|
this.keyLoader.detach();
|
247
225
|
}
|
248
226
|
this.media = this.mediaBuffer = null;
|
249
|
-
this.
|
250
|
-
this.startFragRequested = this.loadedmetadata = this.loadingParts = false;
|
227
|
+
this.loadedmetadata = false;
|
251
228
|
this.fragmentTracker.removeAllFragments();
|
252
229
|
this.stopLoad();
|
253
230
|
}
|
254
231
|
|
255
|
-
protected
|
256
|
-
this.initPTS = [];
|
257
|
-
this.levels = this.levelLastLoaded = this.fragCurrent = null;
|
258
|
-
this.lastCurrentTime = this.startPosition = 0;
|
259
|
-
this.startFragRequested = false;
|
260
|
-
}
|
261
|
-
|
262
|
-
protected onError(event: Events.ERROR, data: ErrorData) {}
|
263
|
-
|
264
|
-
protected onMediaSeeking = () => {
|
232
|
+
protected onMediaSeeking() {
|
265
233
|
const { config, fragCurrent, media, mediaBuffer, state } = this;
|
266
234
|
const currentTime: number = media ? media.currentTime : 0;
|
267
235
|
const bufferInfo = BufferHelper.bufferInfo(
|
@@ -315,21 +283,6 @@ export default class BaseStreamController
|
|
315
283
|
);
|
316
284
|
|
317
285
|
this.lastCurrentTime = currentTime;
|
318
|
-
if (!this.loadingParts) {
|
319
|
-
const bufferEnd = Math.max(bufferInfo.end, currentTime);
|
320
|
-
const shouldLoadParts = this.shouldLoadParts(
|
321
|
-
this.getLevelDetails(),
|
322
|
-
bufferEnd,
|
323
|
-
);
|
324
|
-
if (shouldLoadParts) {
|
325
|
-
this.log(
|
326
|
-
`LL-Part loading ON after seeking to ${currentTime.toFixed(
|
327
|
-
2,
|
328
|
-
)} with buffer @${bufferEnd.toFixed(2)}`,
|
329
|
-
);
|
330
|
-
this.loadingParts = shouldLoadParts;
|
331
|
-
}
|
332
|
-
}
|
333
286
|
}
|
334
287
|
|
335
288
|
// in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
|
@@ -339,30 +292,27 @@ export default class BaseStreamController
|
|
339
292
|
|
340
293
|
// Async tick to speed up processing
|
341
294
|
this.tickImmediate();
|
342
|
-
}
|
295
|
+
}
|
343
296
|
|
344
|
-
protected onMediaEnded
|
297
|
+
protected onMediaEnded() {
|
345
298
|
// reset startPosition and lastCurrentTime to restart playback @ stream beginning
|
346
299
|
this.startPosition = this.lastCurrentTime = 0;
|
347
|
-
|
348
|
-
this.hls.trigger(Events.MEDIA_ENDED, {
|
349
|
-
stalled: false,
|
350
|
-
});
|
351
|
-
}
|
352
|
-
};
|
300
|
+
}
|
353
301
|
|
354
302
|
protected onManifestLoaded(
|
355
303
|
event: Events.MANIFEST_LOADED,
|
356
304
|
data: ManifestLoadedData,
|
357
305
|
): void {
|
358
306
|
this.startTimeOffset = data.startTimeOffset;
|
307
|
+
this.initPTS = [];
|
359
308
|
}
|
360
309
|
|
361
310
|
protected onHandlerDestroying() {
|
311
|
+
this.hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
|
362
312
|
this.stopLoad();
|
363
313
|
super.onHandlerDestroying();
|
364
314
|
// @ts-ignore
|
365
|
-
this.hls =
|
315
|
+
this.hls = null;
|
366
316
|
}
|
367
317
|
|
368
318
|
protected onHandlerDestroyed() {
|
@@ -393,7 +343,6 @@ export default class BaseStreamController
|
|
393
343
|
level: Level,
|
394
344
|
targetBufferTime: number,
|
395
345
|
) {
|
396
|
-
this.startFragRequested = true;
|
397
346
|
this._loadFragForPlayback(frag, level, targetBufferTime);
|
398
347
|
}
|
399
348
|
|
@@ -476,7 +425,7 @@ export default class BaseStreamController
|
|
476
425
|
: 0;
|
477
426
|
if (
|
478
427
|
backtracked === 1 ||
|
479
|
-
this.reduceMaxBufferLength(minForwardBufferLength)
|
428
|
+
this.reduceMaxBufferLength(minForwardBufferLength, frag.duration)
|
480
429
|
) {
|
481
430
|
fragmentTracker.removeFragment(frag);
|
482
431
|
}
|
@@ -547,7 +496,7 @@ export default class BaseStreamController
|
|
547
496
|
payload.byteLength > 0 &&
|
548
497
|
decryptData?.key &&
|
549
498
|
decryptData.iv &&
|
550
|
-
|
499
|
+
decryptData.method === 'AES-128'
|
551
500
|
) {
|
552
501
|
const startTime = self.performance.now();
|
553
502
|
// decrypt init segment data
|
@@ -556,7 +505,6 @@ export default class BaseStreamController
|
|
556
505
|
new Uint8Array(payload),
|
557
506
|
decryptData.key.buffer,
|
558
507
|
decryptData.iv.buffer,
|
559
|
-
getAesModeFromFullSegmentMethod(decryptData.method),
|
560
508
|
)
|
561
509
|
.catch((err) => {
|
562
510
|
hls.trigger(Events.ERROR, {
|
@@ -600,9 +548,7 @@ export default class BaseStreamController
|
|
600
548
|
throw new Error('init load aborted, missing levels');
|
601
549
|
}
|
602
550
|
const stats = data.frag.stats;
|
603
|
-
|
604
|
-
this.state = State.IDLE;
|
605
|
-
}
|
551
|
+
this.state = State.IDLE;
|
606
552
|
data.frag.data = new Uint8Array(data.payload);
|
607
553
|
stats.parsing.start = stats.buffering.start = self.performance.now();
|
608
554
|
stats.parsing.end = stats.buffering.end = self.performance.now();
|
@@ -702,7 +648,6 @@ export default class BaseStreamController
|
|
702
648
|
targetBufferTime: number | null = null,
|
703
649
|
progressCallback?: FragmentLoadProgressCallback,
|
704
650
|
): Promise<PartsLoadedData | FragLoadedData | null> {
|
705
|
-
this.fragCurrent = frag;
|
706
651
|
const details = level?.details;
|
707
652
|
if (!this.levels || !details) {
|
708
653
|
throw new Error(
|
@@ -714,7 +659,7 @@ export default class BaseStreamController
|
|
714
659
|
if (frag.encrypted && !frag.decryptdata?.key) {
|
715
660
|
this.log(
|
716
661
|
`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${
|
717
|
-
this.
|
662
|
+
this.logPrefix === '[stream-controller]' ? 'level' : 'track'
|
718
663
|
} ${frag.level}`,
|
719
664
|
);
|
720
665
|
this.state = State.KEY_LOADING;
|
@@ -738,23 +683,8 @@ export default class BaseStreamController
|
|
738
683
|
this.keyLoader.loadClear(frag, details.encryptedFragments);
|
739
684
|
}
|
740
685
|
|
741
|
-
const fragPrevious = this.fragPrevious;
|
742
|
-
if (
|
743
|
-
frag.sn !== 'initSegment' &&
|
744
|
-
(!fragPrevious || frag.sn !== fragPrevious.sn)
|
745
|
-
) {
|
746
|
-
const shouldLoadParts = this.shouldLoadParts(level.details, frag.end);
|
747
|
-
if (shouldLoadParts !== this.loadingParts) {
|
748
|
-
this.log(
|
749
|
-
`LL-Part loading ${
|
750
|
-
shouldLoadParts ? 'ON' : 'OFF'
|
751
|
-
} loading sn ${fragPrevious?.sn}->${frag.sn}`,
|
752
|
-
);
|
753
|
-
this.loadingParts = shouldLoadParts;
|
754
|
-
}
|
755
|
-
}
|
756
686
|
targetBufferTime = Math.max(frag.start, targetBufferTime || 0);
|
757
|
-
if (this.
|
687
|
+
if (this.config.lowLatencyMode && frag.sn !== 'initSegment') {
|
758
688
|
const partList = details.partList;
|
759
689
|
if (partList && progressCallback) {
|
760
690
|
if (targetBufferTime > frag.end && details.fragmentHint) {
|
@@ -769,7 +699,7 @@ export default class BaseStreamController
|
|
769
699
|
} of playlist [${details.startSN}-${
|
770
700
|
details.endSN
|
771
701
|
}] parts [0-${partIndex}-${partList.length - 1}] ${
|
772
|
-
this.
|
702
|
+
this.logPrefix === '[stream-controller]' ? 'level' : 'track'
|
773
703
|
}: ${frag.level}, target: ${parseFloat(
|
774
704
|
targetBufferTime.toFixed(3),
|
775
705
|
)}`,
|
@@ -825,22 +755,10 @@ export default class BaseStreamController
|
|
825
755
|
}
|
826
756
|
}
|
827
757
|
|
828
|
-
if (frag.sn !== 'initSegment' && this.loadingParts) {
|
829
|
-
this.log(
|
830
|
-
`LL-Part loading OFF after next part miss @${targetBufferTime.toFixed(
|
831
|
-
2,
|
832
|
-
)}`,
|
833
|
-
);
|
834
|
-
this.loadingParts = false;
|
835
|
-
} else if (!frag.url) {
|
836
|
-
// Selected fragment hint for part but not loading parts
|
837
|
-
return Promise.resolve(null);
|
838
|
-
}
|
839
|
-
|
840
758
|
this.log(
|
841
759
|
`Loading fragment ${frag.sn} cc: ${frag.cc} ${
|
842
760
|
details ? 'of [' + details.startSN + '-' + details.endSN + '] ' : ''
|
843
|
-
}${this.
|
761
|
+
}${this.logPrefix === '[stream-controller]' ? 'level' : 'track'}: ${
|
844
762
|
frag.level
|
845
763
|
}, target: ${parseFloat(targetBufferTime.toFixed(3))}`,
|
846
764
|
);
|
@@ -907,7 +825,7 @@ export default class BaseStreamController
|
|
907
825
|
const loadedPart = partLoadedData.part as Part;
|
908
826
|
this.hls.trigger(Events.FRAG_LOADED, partLoadedData);
|
909
827
|
const nextPart =
|
910
|
-
getPartWith(level
|
828
|
+
getPartWith(level, frag.sn as number, part.index + 1) ||
|
911
829
|
findPart(initialPartList, frag.sn as number, part.index + 1);
|
912
830
|
if (nextPart) {
|
913
831
|
loadPart(nextPart);
|
@@ -964,50 +882,12 @@ export default class BaseStreamController
|
|
964
882
|
if (part) {
|
965
883
|
part.stats.parsing.end = now;
|
966
884
|
}
|
967
|
-
// See if part loading should be disabled/enabled based on buffer and playback position.
|
968
|
-
const levelDetails = this.getLevelDetails();
|
969
|
-
const loadingPartsAtEdge = levelDetails && frag.sn > levelDetails.endSN;
|
970
|
-
const shouldLoadParts =
|
971
|
-
loadingPartsAtEdge || this.shouldLoadParts(levelDetails, frag.end);
|
972
|
-
if (shouldLoadParts !== this.loadingParts) {
|
973
|
-
this.log(
|
974
|
-
`LL-Part loading ${
|
975
|
-
shouldLoadParts ? 'ON' : 'OFF'
|
976
|
-
} after parsing segment ending @${frag.end.toFixed(2)}`,
|
977
|
-
);
|
978
|
-
this.loadingParts = shouldLoadParts;
|
979
|
-
}
|
980
885
|
this.updateLevelTiming(frag, part, level, chunkMeta.partial);
|
981
886
|
}
|
982
887
|
|
983
|
-
private shouldLoadParts(
|
984
|
-
details: LevelDetails | undefined,
|
985
|
-
bufferEnd: number,
|
986
|
-
): boolean {
|
987
|
-
if (this.config.lowLatencyMode) {
|
988
|
-
if (!details) {
|
989
|
-
return this.loadingParts;
|
990
|
-
}
|
991
|
-
if (details?.partList) {
|
992
|
-
// Buffer must be ahead of first part + duration of parts after last segment
|
993
|
-
// and playback must be at or past segment adjacent to part list
|
994
|
-
const firstPart = details.partList[0];
|
995
|
-
const safePartStart =
|
996
|
-
firstPart.end + (details.fragmentHint?.duration || 0);
|
997
|
-
if (
|
998
|
-
bufferEnd >= safePartStart &&
|
999
|
-
this.lastCurrentTime > firstPart.start - firstPart.fragment.duration
|
1000
|
-
) {
|
1001
|
-
return true;
|
1002
|
-
}
|
1003
|
-
}
|
1004
|
-
}
|
1005
|
-
return false;
|
1006
|
-
}
|
1007
|
-
|
1008
888
|
protected getCurrentContext(
|
1009
889
|
chunkMeta: ChunkMetadata,
|
1010
|
-
): { frag:
|
890
|
+
): { frag: Fragment; part: Part | null; level: Level } | null {
|
1011
891
|
const { levels, fragCurrent } = this;
|
1012
892
|
const { level: levelIndex, sn, part: partIndex } = chunkMeta;
|
1013
893
|
if (!levels?.[levelIndex]) {
|
@@ -1017,13 +897,10 @@ export default class BaseStreamController
|
|
1017
897
|
return null;
|
1018
898
|
}
|
1019
899
|
const level = levels[levelIndex];
|
1020
|
-
const
|
1021
|
-
|
1022
|
-
const part =
|
1023
|
-
partIndex > -1 ? getPartWith(levelDetails, sn, partIndex) : null;
|
900
|
+
const part = partIndex > -1 ? getPartWith(level, sn, partIndex) : null;
|
1024
901
|
const frag = part
|
1025
902
|
? part.fragment
|
1026
|
-
: getFragmentWithSN(
|
903
|
+
: getFragmentWithSN(level, sn, fragCurrent);
|
1027
904
|
if (!frag) {
|
1028
905
|
return null;
|
1029
906
|
}
|
@@ -1124,10 +1001,7 @@ export default class BaseStreamController
|
|
1124
1001
|
// Workaround flaw in getting forward buffer when maxBufferHole is smaller than gap at current pos
|
1125
1002
|
if (bufferInfo.len === 0 && bufferInfo.nextStart !== undefined) {
|
1126
1003
|
const bufferedFragAtPos = this.fragmentTracker.getBufferedFrag(pos, type);
|
1127
|
-
if (
|
1128
|
-
bufferedFragAtPos &&
|
1129
|
-
(bufferInfo.nextStart <= bufferedFragAtPos.end || bufferedFragAtPos.gap)
|
1130
|
-
) {
|
1004
|
+
if (bufferedFragAtPos && bufferInfo.nextStart < bufferedFragAtPos.end) {
|
1131
1005
|
return BufferHelper.bufferInfo(
|
1132
1006
|
bufferable,
|
1133
1007
|
pos,
|
@@ -1140,7 +1014,7 @@ export default class BaseStreamController
|
|
1140
1014
|
|
1141
1015
|
protected getMaxBufferLength(levelBitrate?: number): number {
|
1142
1016
|
const { config } = this;
|
1143
|
-
let maxBufLen
|
1017
|
+
let maxBufLen;
|
1144
1018
|
if (levelBitrate) {
|
1145
1019
|
maxBufLen = Math.max(
|
1146
1020
|
(8 * config.maxBufferSize) / levelBitrate,
|
@@ -1152,10 +1026,16 @@ export default class BaseStreamController
|
|
1152
1026
|
return Math.min(maxBufLen, config.maxMaxBufferLength);
|
1153
1027
|
}
|
1154
1028
|
|
1155
|
-
protected reduceMaxBufferLength(threshold: number) {
|
1029
|
+
protected reduceMaxBufferLength(threshold: number, fragDuration: number) {
|
1156
1030
|
const config = this.config;
|
1157
|
-
const minLength =
|
1158
|
-
|
1031
|
+
const minLength = Math.max(
|
1032
|
+
Math.min(threshold, config.maxBufferLength),
|
1033
|
+
fragDuration,
|
1034
|
+
);
|
1035
|
+
const reducedLength = Math.max(
|
1036
|
+
threshold - fragDuration * 3,
|
1037
|
+
config.maxMaxBufferLength / 2,
|
1038
|
+
);
|
1159
1039
|
if (reducedLength >= minLength) {
|
1160
1040
|
// reduce max buffer length as it might be too high. we do this to avoid loop flushing ...
|
1161
1041
|
config.maxMaxBufferLength = reducedLength;
|
@@ -1169,9 +1049,9 @@ export default class BaseStreamController
|
|
1169
1049
|
position: number,
|
1170
1050
|
playlistType: PlaylistLevelType = PlaylistLevelType.MAIN,
|
1171
1051
|
): Fragment | null {
|
1172
|
-
const fragOrPart = this.fragmentTracker
|
1052
|
+
const fragOrPart = this.fragmentTracker.getAppendedFrag(
|
1173
1053
|
position,
|
1174
|
-
|
1054
|
+
PlaylistLevelType.MAIN,
|
1175
1055
|
);
|
1176
1056
|
if (fragOrPart && 'fragment' in fragOrPart) {
|
1177
1057
|
return fragOrPart.fragment;
|
@@ -1193,8 +1073,7 @@ export default class BaseStreamController
|
|
1193
1073
|
// find fragment index, contiguous with end of buffer position
|
1194
1074
|
const { config } = this;
|
1195
1075
|
const start = fragments[0].start;
|
1196
|
-
|
1197
|
-
let frag: MediaFragment | null = null;
|
1076
|
+
let frag;
|
1198
1077
|
|
1199
1078
|
if (levelDetails.live) {
|
1200
1079
|
const initialLiveManifestSize = config.initialLiveManifestSize;
|
@@ -1214,10 +1093,6 @@ export default class BaseStreamController
|
|
1214
1093
|
this.startPosition === -1) ||
|
1215
1094
|
pos < start
|
1216
1095
|
) {
|
1217
|
-
if (canLoadParts && !this.loadingParts) {
|
1218
|
-
this.log(`LL-Part loading ON for initial live fragment`);
|
1219
|
-
this.loadingParts = true;
|
1220
|
-
}
|
1221
1096
|
frag = this.getInitialLiveFragment(levelDetails, fragments);
|
1222
1097
|
this.startPosition = this.nextLoadPosition = frag
|
1223
1098
|
? this.hls.liveSyncPosition || frag.start
|
@@ -1230,7 +1105,7 @@ export default class BaseStreamController
|
|
1230
1105
|
|
1231
1106
|
// If we haven't run into any special cases already, just load the fragment most closely matching the requested position
|
1232
1107
|
if (!frag) {
|
1233
|
-
const end =
|
1108
|
+
const end = config.lowLatencyMode
|
1234
1109
|
? levelDetails.partEnd
|
1235
1110
|
: levelDetails.fragmentEnd;
|
1236
1111
|
frag = this.getFragmentAtPosition(pos, end, levelDetails);
|
@@ -1255,34 +1130,34 @@ export default class BaseStreamController
|
|
1255
1130
|
playlistType: PlaylistLevelType,
|
1256
1131
|
maxBufLen: number,
|
1257
1132
|
): Fragment | null {
|
1258
|
-
|
1259
|
-
|
1260
|
-
|
1261
|
-
|
1262
|
-
|
1263
|
-
|
1264
|
-
|
1265
|
-
|
1266
|
-
|
1133
|
+
const gapStart = frag.gap;
|
1134
|
+
const nextFragment = this.getNextFragment(
|
1135
|
+
this.nextLoadPosition,
|
1136
|
+
levelDetails,
|
1137
|
+
);
|
1138
|
+
if (nextFragment === null) {
|
1139
|
+
return nextFragment;
|
1140
|
+
}
|
1141
|
+
frag = nextFragment;
|
1142
|
+
if (gapStart && frag && !frag.gap && bufferInfo.nextStart) {
|
1143
|
+
// Media buffered after GAP tags should not make the next buffer timerange exceed forward buffer length
|
1144
|
+
const nextbufferInfo = this.getFwdBufferInfoAtPos(
|
1145
|
+
this.mediaBuffer ? this.mediaBuffer : this.media,
|
1146
|
+
bufferInfo.nextStart,
|
1147
|
+
playlistType,
|
1148
|
+
);
|
1149
|
+
if (
|
1150
|
+
nextbufferInfo !== null &&
|
1151
|
+
bufferInfo.len + nextbufferInfo.len >= maxBufLen
|
1152
|
+
) {
|
1153
|
+
// Returning here might result in not finding an audio and video candiate to skip to
|
1154
|
+
this.log(
|
1155
|
+
`buffer full after gaps in "${playlistType}" playlist starting at sn: ${frag.sn}`,
|
1267
1156
|
);
|
1268
|
-
|
1269
|
-
nextbufferInfo !== null &&
|
1270
|
-
bufferInfo.len + nextbufferInfo.len >= maxBufLen
|
1271
|
-
) {
|
1272
|
-
// Returning here might result in not finding an audio and video candiate to skip to
|
1273
|
-
const sn = nextFragment.sn;
|
1274
|
-
if (this.loopSn !== sn) {
|
1275
|
-
this.log(
|
1276
|
-
`buffer full after gaps in "${playlistType}" playlist starting at sn: ${sn}`,
|
1277
|
-
);
|
1278
|
-
this.loopSn = sn;
|
1279
|
-
}
|
1280
|
-
return null;
|
1281
|
-
}
|
1157
|
+
return null;
|
1282
1158
|
}
|
1283
1159
|
}
|
1284
|
-
|
1285
|
-
return nextFragment;
|
1160
|
+
return frag;
|
1286
1161
|
}
|
1287
1162
|
|
1288
1163
|
mapToInitFragWhenRequired(frag: Fragment | null): typeof frag {
|
@@ -1337,10 +1212,10 @@ export default class BaseStreamController
|
|
1337
1212
|
*/
|
1338
1213
|
protected getInitialLiveFragment(
|
1339
1214
|
levelDetails: LevelDetails,
|
1340
|
-
fragments:
|
1341
|
-
):
|
1215
|
+
fragments: Array<Fragment>,
|
1216
|
+
): Fragment | null {
|
1342
1217
|
const fragPrevious = this.fragPrevious;
|
1343
|
-
let frag:
|
1218
|
+
let frag: Fragment | null = null;
|
1344
1219
|
if (fragPrevious) {
|
1345
1220
|
if (levelDetails.hasProgramDateTime) {
|
1346
1221
|
// Prefer using PDT, because it can be accurate enough to choose the correct fragment without knowing the level sliding
|
@@ -1404,7 +1279,7 @@ export default class BaseStreamController
|
|
1404
1279
|
bufferEnd: number,
|
1405
1280
|
end: number,
|
1406
1281
|
levelDetails: LevelDetails,
|
1407
|
-
):
|
1282
|
+
): Fragment | null {
|
1408
1283
|
const { config } = this;
|
1409
1284
|
let { fragPrevious } = this;
|
1410
1285
|
let { fragments, endSN } = levelDetails;
|
@@ -1413,17 +1288,17 @@ export default class BaseStreamController
|
|
1413
1288
|
const partList = levelDetails.partList;
|
1414
1289
|
|
1415
1290
|
const loadingParts = !!(
|
1416
|
-
|
1291
|
+
config.lowLatencyMode &&
|
1417
1292
|
partList?.length &&
|
1418
1293
|
fragmentHint
|
1419
1294
|
);
|
1420
1295
|
if (loadingParts && fragmentHint && !this.bitrateTest) {
|
1421
1296
|
// Include incomplete fragment with parts at end
|
1422
1297
|
fragments = fragments.concat(fragmentHint);
|
1423
|
-
endSN = fragmentHint.sn;
|
1298
|
+
endSN = fragmentHint.sn as number;
|
1424
1299
|
}
|
1425
1300
|
|
1426
|
-
let frag
|
1301
|
+
let frag;
|
1427
1302
|
if (bufferEnd < end) {
|
1428
1303
|
const lookupTolerance =
|
1429
1304
|
bufferEnd > end - maxFragLookUpTolerance ? 0 : maxFragLookUpTolerance;
|
@@ -1579,7 +1454,7 @@ export default class BaseStreamController
|
|
1579
1454
|
if (startTimeOffset !== null && Number.isFinite(startTimeOffset)) {
|
1580
1455
|
startPosition = sliding + startTimeOffset;
|
1581
1456
|
if (startTimeOffset < 0) {
|
1582
|
-
startPosition += details.
|
1457
|
+
startPosition += details.totalduration;
|
1583
1458
|
}
|
1584
1459
|
startPosition = Math.min(
|
1585
1460
|
Math.max(sliding, startPosition),
|
@@ -1660,7 +1535,7 @@ export default class BaseStreamController
|
|
1660
1535
|
}
|
1661
1536
|
const gapTagEncountered = data.details === ErrorDetails.FRAG_GAP;
|
1662
1537
|
if (gapTagEncountered) {
|
1663
|
-
this.fragmentTracker.fragBuffered(frag
|
1538
|
+
this.fragmentTracker.fragBuffered(frag, true);
|
1664
1539
|
}
|
1665
1540
|
// keep retrying until the limit will be reached
|
1666
1541
|
const errorAction = data.errorAction;
|
@@ -1693,7 +1568,7 @@ export default class BaseStreamController
|
|
1693
1568
|
errorAction.resolved = true;
|
1694
1569
|
}
|
1695
1570
|
} else {
|
1696
|
-
|
1571
|
+
logger.warn(
|
1697
1572
|
`${data.details} reached or exceeded max retry (${retryCount})`,
|
1698
1573
|
);
|
1699
1574
|
return;
|
@@ -1712,6 +1587,7 @@ export default class BaseStreamController
|
|
1712
1587
|
protected reduceLengthAndFlushBuffer(data: ErrorData): boolean {
|
1713
1588
|
// if in appending state
|
1714
1589
|
if (this.state === State.PARSING || this.state === State.PARSED) {
|
1590
|
+
const frag = data.frag;
|
1715
1591
|
const playlistType = data.parent as PlaylistLevelType;
|
1716
1592
|
const bufferedInfo = this.getFwdBufferInfo(
|
1717
1593
|
this.mediaBuffer,
|
@@ -1721,7 +1597,7 @@ export default class BaseStreamController
|
|
1721
1597
|
// reduce max buf len if current position is buffered
|
1722
1598
|
const buffered = bufferedInfo && bufferedInfo.len > 0.5;
|
1723
1599
|
if (buffered) {
|
1724
|
-
this.reduceMaxBufferLength(bufferedInfo.len);
|
1600
|
+
this.reduceMaxBufferLength(bufferedInfo.len, frag?.duration || 10);
|
1725
1601
|
}
|
1726
1602
|
const flushBuffer = !buffered;
|
1727
1603
|
if (flushBuffer) {
|
@@ -1732,9 +1608,9 @@ export default class BaseStreamController
|
|
1732
1608
|
`Buffer full error while media.currentTime is not buffered, flush ${playlistType} buffer`,
|
1733
1609
|
);
|
1734
1610
|
}
|
1735
|
-
if (
|
1736
|
-
this.fragmentTracker.removeFragment(
|
1737
|
-
this.nextLoadPosition =
|
1611
|
+
if (frag) {
|
1612
|
+
this.fragmentTracker.removeFragment(frag);
|
1613
|
+
this.nextLoadPosition = frag.start;
|
1738
1614
|
}
|
1739
1615
|
this.resetLoadingState();
|
1740
1616
|
return flushBuffer;
|
@@ -1782,9 +1658,7 @@ export default class BaseStreamController
|
|
1782
1658
|
this.log('Reset loading state');
|
1783
1659
|
this.fragCurrent = null;
|
1784
1660
|
this.fragPrevious = null;
|
1785
|
-
|
1786
|
-
this.state = State.IDLE;
|
1787
|
-
}
|
1661
|
+
this.state = State.IDLE;
|
1788
1662
|
}
|
1789
1663
|
|
1790
1664
|
protected resetStartWhenNotLoaded(level: Level | null): void {
|
@@ -1824,7 +1698,7 @@ export default class BaseStreamController
|
|
1824
1698
|
}
|
1825
1699
|
|
1826
1700
|
private updateLevelTiming(
|
1827
|
-
frag:
|
1701
|
+
frag: Fragment,
|
1828
1702
|
part: Part | null,
|
1829
1703
|
level: Level,
|
1830
1704
|
partial: boolean,
|