hls.js 1.5.13-0.canary.10410 → 1.5.13
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 +2704 -4236
- package/dist/hls.js.d.ts +110 -179
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +1994 -2914
- 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 +3458 -4388
- 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 +4506 -6048
- 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 +91 -235
- 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 +47 -84
- 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 +62 -32
- 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(
|
@@ -314,27 +282,7 @@ export default class BaseStreamController
|
|
314
282
|
true,
|
315
283
|
);
|
316
284
|
|
317
|
-
|
318
|
-
const lastCurrentTime = this.lastCurrentTime;
|
319
|
-
if (currentTime > lastCurrentTime) {
|
320
|
-
this.lastCurrentTime = currentTime;
|
321
|
-
}
|
322
|
-
|
323
|
-
if (!this.loadingParts) {
|
324
|
-
const bufferEnd = Math.max(bufferInfo.end, currentTime);
|
325
|
-
const shouldLoadParts = this.shouldLoadParts(
|
326
|
-
this.getLevelDetails(),
|
327
|
-
bufferEnd,
|
328
|
-
);
|
329
|
-
if (shouldLoadParts) {
|
330
|
-
this.log(
|
331
|
-
`LL-Part loading ON after seeking to ${currentTime.toFixed(
|
332
|
-
2,
|
333
|
-
)} with buffer @${bufferEnd.toFixed(2)}`,
|
334
|
-
);
|
335
|
-
this.loadingParts = shouldLoadParts;
|
336
|
-
}
|
337
|
-
}
|
285
|
+
this.lastCurrentTime = currentTime;
|
338
286
|
}
|
339
287
|
|
340
288
|
// in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
|
@@ -344,30 +292,27 @@ export default class BaseStreamController
|
|
344
292
|
|
345
293
|
// Async tick to speed up processing
|
346
294
|
this.tickImmediate();
|
347
|
-
}
|
295
|
+
}
|
348
296
|
|
349
|
-
protected onMediaEnded
|
297
|
+
protected onMediaEnded() {
|
350
298
|
// reset startPosition and lastCurrentTime to restart playback @ stream beginning
|
351
299
|
this.startPosition = this.lastCurrentTime = 0;
|
352
|
-
|
353
|
-
this.hls.trigger(Events.MEDIA_ENDED, {
|
354
|
-
stalled: false,
|
355
|
-
});
|
356
|
-
}
|
357
|
-
};
|
300
|
+
}
|
358
301
|
|
359
302
|
protected onManifestLoaded(
|
360
303
|
event: Events.MANIFEST_LOADED,
|
361
304
|
data: ManifestLoadedData,
|
362
305
|
): void {
|
363
306
|
this.startTimeOffset = data.startTimeOffset;
|
307
|
+
this.initPTS = [];
|
364
308
|
}
|
365
309
|
|
366
310
|
protected onHandlerDestroying() {
|
311
|
+
this.hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
|
367
312
|
this.stopLoad();
|
368
313
|
super.onHandlerDestroying();
|
369
314
|
// @ts-ignore
|
370
|
-
this.hls =
|
315
|
+
this.hls = null;
|
371
316
|
}
|
372
317
|
|
373
318
|
protected onHandlerDestroyed() {
|
@@ -398,7 +343,6 @@ export default class BaseStreamController
|
|
398
343
|
level: Level,
|
399
344
|
targetBufferTime: number,
|
400
345
|
) {
|
401
|
-
this.startFragRequested = true;
|
402
346
|
this._loadFragForPlayback(frag, level, targetBufferTime);
|
403
347
|
}
|
404
348
|
|
@@ -552,7 +496,7 @@ export default class BaseStreamController
|
|
552
496
|
payload.byteLength > 0 &&
|
553
497
|
decryptData?.key &&
|
554
498
|
decryptData.iv &&
|
555
|
-
|
499
|
+
decryptData.method === 'AES-128'
|
556
500
|
) {
|
557
501
|
const startTime = self.performance.now();
|
558
502
|
// decrypt init segment data
|
@@ -561,7 +505,6 @@ export default class BaseStreamController
|
|
561
505
|
new Uint8Array(payload),
|
562
506
|
decryptData.key.buffer,
|
563
507
|
decryptData.iv.buffer,
|
564
|
-
getAesModeFromFullSegmentMethod(decryptData.method),
|
565
508
|
)
|
566
509
|
.catch((err) => {
|
567
510
|
hls.trigger(Events.ERROR, {
|
@@ -605,9 +548,7 @@ export default class BaseStreamController
|
|
605
548
|
throw new Error('init load aborted, missing levels');
|
606
549
|
}
|
607
550
|
const stats = data.frag.stats;
|
608
|
-
|
609
|
-
this.state = State.IDLE;
|
610
|
-
}
|
551
|
+
this.state = State.IDLE;
|
611
552
|
data.frag.data = new Uint8Array(data.payload);
|
612
553
|
stats.parsing.start = stats.buffering.start = self.performance.now();
|
613
554
|
stats.parsing.end = stats.buffering.end = self.performance.now();
|
@@ -707,7 +648,6 @@ export default class BaseStreamController
|
|
707
648
|
targetBufferTime: number | null = null,
|
708
649
|
progressCallback?: FragmentLoadProgressCallback,
|
709
650
|
): Promise<PartsLoadedData | FragLoadedData | null> {
|
710
|
-
this.fragCurrent = frag;
|
711
651
|
const details = level?.details;
|
712
652
|
if (!this.levels || !details) {
|
713
653
|
throw new Error(
|
@@ -719,7 +659,7 @@ export default class BaseStreamController
|
|
719
659
|
if (frag.encrypted && !frag.decryptdata?.key) {
|
720
660
|
this.log(
|
721
661
|
`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${
|
722
|
-
this.
|
662
|
+
this.logPrefix === '[stream-controller]' ? 'level' : 'track'
|
723
663
|
} ${frag.level}`,
|
724
664
|
);
|
725
665
|
this.state = State.KEY_LOADING;
|
@@ -743,23 +683,8 @@ export default class BaseStreamController
|
|
743
683
|
this.keyLoader.loadClear(frag, details.encryptedFragments);
|
744
684
|
}
|
745
685
|
|
746
|
-
const fragPrevious = this.fragPrevious;
|
747
|
-
if (
|
748
|
-
frag.sn !== 'initSegment' &&
|
749
|
-
(!fragPrevious || frag.sn !== fragPrevious.sn)
|
750
|
-
) {
|
751
|
-
const shouldLoadParts = this.shouldLoadParts(level.details, frag.end);
|
752
|
-
if (shouldLoadParts !== this.loadingParts) {
|
753
|
-
this.log(
|
754
|
-
`LL-Part loading ${
|
755
|
-
shouldLoadParts ? 'ON' : 'OFF'
|
756
|
-
} loading sn ${fragPrevious?.sn}->${frag.sn}`,
|
757
|
-
);
|
758
|
-
this.loadingParts = shouldLoadParts;
|
759
|
-
}
|
760
|
-
}
|
761
686
|
targetBufferTime = Math.max(frag.start, targetBufferTime || 0);
|
762
|
-
if (this.
|
687
|
+
if (this.config.lowLatencyMode && frag.sn !== 'initSegment') {
|
763
688
|
const partList = details.partList;
|
764
689
|
if (partList && progressCallback) {
|
765
690
|
if (targetBufferTime > frag.end && details.fragmentHint) {
|
@@ -774,7 +699,7 @@ export default class BaseStreamController
|
|
774
699
|
} of playlist [${details.startSN}-${
|
775
700
|
details.endSN
|
776
701
|
}] parts [0-${partIndex}-${partList.length - 1}] ${
|
777
|
-
this.
|
702
|
+
this.logPrefix === '[stream-controller]' ? 'level' : 'track'
|
778
703
|
}: ${frag.level}, target: ${parseFloat(
|
779
704
|
targetBufferTime.toFixed(3),
|
780
705
|
)}`,
|
@@ -830,22 +755,10 @@ export default class BaseStreamController
|
|
830
755
|
}
|
831
756
|
}
|
832
757
|
|
833
|
-
if (frag.sn !== 'initSegment' && this.loadingParts) {
|
834
|
-
this.log(
|
835
|
-
`LL-Part loading OFF after next part miss @${targetBufferTime.toFixed(
|
836
|
-
2,
|
837
|
-
)}`,
|
838
|
-
);
|
839
|
-
this.loadingParts = false;
|
840
|
-
} else if (!frag.url) {
|
841
|
-
// Selected fragment hint for part but not loading parts
|
842
|
-
return Promise.resolve(null);
|
843
|
-
}
|
844
|
-
|
845
758
|
this.log(
|
846
759
|
`Loading fragment ${frag.sn} cc: ${frag.cc} ${
|
847
760
|
details ? 'of [' + details.startSN + '-' + details.endSN + '] ' : ''
|
848
|
-
}${this.
|
761
|
+
}${this.logPrefix === '[stream-controller]' ? 'level' : 'track'}: ${
|
849
762
|
frag.level
|
850
763
|
}, target: ${parseFloat(targetBufferTime.toFixed(3))}`,
|
851
764
|
);
|
@@ -912,7 +825,7 @@ export default class BaseStreamController
|
|
912
825
|
const loadedPart = partLoadedData.part as Part;
|
913
826
|
this.hls.trigger(Events.FRAG_LOADED, partLoadedData);
|
914
827
|
const nextPart =
|
915
|
-
getPartWith(level
|
828
|
+
getPartWith(level, frag.sn as number, part.index + 1) ||
|
916
829
|
findPart(initialPartList, frag.sn as number, part.index + 1);
|
917
830
|
if (nextPart) {
|
918
831
|
loadPart(nextPart);
|
@@ -969,50 +882,12 @@ export default class BaseStreamController
|
|
969
882
|
if (part) {
|
970
883
|
part.stats.parsing.end = now;
|
971
884
|
}
|
972
|
-
// See if part loading should be disabled/enabled based on buffer and playback position.
|
973
|
-
const levelDetails = this.getLevelDetails();
|
974
|
-
const loadingPartsAtEdge = levelDetails && frag.sn > levelDetails.endSN;
|
975
|
-
const shouldLoadParts =
|
976
|
-
loadingPartsAtEdge || this.shouldLoadParts(levelDetails, frag.end);
|
977
|
-
if (shouldLoadParts !== this.loadingParts) {
|
978
|
-
this.log(
|
979
|
-
`LL-Part loading ${
|
980
|
-
shouldLoadParts ? 'ON' : 'OFF'
|
981
|
-
} after parsing segment ending @${frag.end.toFixed(2)}`,
|
982
|
-
);
|
983
|
-
this.loadingParts = shouldLoadParts;
|
984
|
-
}
|
985
885
|
this.updateLevelTiming(frag, part, level, chunkMeta.partial);
|
986
886
|
}
|
987
887
|
|
988
|
-
private shouldLoadParts(
|
989
|
-
details: LevelDetails | undefined,
|
990
|
-
bufferEnd: number,
|
991
|
-
): boolean {
|
992
|
-
if (this.config.lowLatencyMode) {
|
993
|
-
if (!details) {
|
994
|
-
return this.loadingParts;
|
995
|
-
}
|
996
|
-
if (details?.partList) {
|
997
|
-
// Buffer must be ahead of first part + duration of parts after last segment
|
998
|
-
// and playback must be at or past segment adjacent to part list
|
999
|
-
const firstPart = details.partList[0];
|
1000
|
-
const safePartStart =
|
1001
|
-
firstPart.end + (details.fragmentHint?.duration || 0);
|
1002
|
-
if (
|
1003
|
-
bufferEnd >= safePartStart &&
|
1004
|
-
this.lastCurrentTime > firstPart.start - firstPart.fragment.duration
|
1005
|
-
) {
|
1006
|
-
return true;
|
1007
|
-
}
|
1008
|
-
}
|
1009
|
-
}
|
1010
|
-
return false;
|
1011
|
-
}
|
1012
|
-
|
1013
888
|
protected getCurrentContext(
|
1014
889
|
chunkMeta: ChunkMetadata,
|
1015
|
-
): { frag:
|
890
|
+
): { frag: Fragment; part: Part | null; level: Level } | null {
|
1016
891
|
const { levels, fragCurrent } = this;
|
1017
892
|
const { level: levelIndex, sn, part: partIndex } = chunkMeta;
|
1018
893
|
if (!levels?.[levelIndex]) {
|
@@ -1022,13 +897,10 @@ export default class BaseStreamController
|
|
1022
897
|
return null;
|
1023
898
|
}
|
1024
899
|
const level = levels[levelIndex];
|
1025
|
-
const
|
1026
|
-
|
1027
|
-
const part =
|
1028
|
-
partIndex > -1 ? getPartWith(levelDetails, sn, partIndex) : null;
|
900
|
+
const part = partIndex > -1 ? getPartWith(level, sn, partIndex) : null;
|
1029
901
|
const frag = part
|
1030
902
|
? part.fragment
|
1031
|
-
: getFragmentWithSN(
|
903
|
+
: getFragmentWithSN(level, sn, fragCurrent);
|
1032
904
|
if (!frag) {
|
1033
905
|
return null;
|
1034
906
|
}
|
@@ -1114,26 +986,22 @@ export default class BaseStreamController
|
|
1114
986
|
if (!Number.isFinite(pos)) {
|
1115
987
|
return null;
|
1116
988
|
}
|
1117
|
-
|
1118
|
-
const maxBufferHole =
|
1119
|
-
backwardSeek || this.media?.paused ? 0 : this.config.maxBufferHole;
|
1120
|
-
return this.getFwdBufferInfoAtPos(bufferable, pos, type, maxBufferHole);
|
989
|
+
return this.getFwdBufferInfoAtPos(bufferable, pos, type);
|
1121
990
|
}
|
1122
991
|
|
1123
|
-
|
992
|
+
protected getFwdBufferInfoAtPos(
|
1124
993
|
bufferable: Bufferable | null,
|
1125
994
|
pos: number,
|
1126
995
|
type: PlaylistLevelType,
|
1127
|
-
maxBufferHole: number,
|
1128
996
|
): BufferInfo | null {
|
997
|
+
const {
|
998
|
+
config: { maxBufferHole },
|
999
|
+
} = this;
|
1129
1000
|
const bufferInfo = BufferHelper.bufferInfo(bufferable, pos, maxBufferHole);
|
1130
1001
|
// Workaround flaw in getting forward buffer when maxBufferHole is smaller than gap at current pos
|
1131
1002
|
if (bufferInfo.len === 0 && bufferInfo.nextStart !== undefined) {
|
1132
1003
|
const bufferedFragAtPos = this.fragmentTracker.getBufferedFrag(pos, type);
|
1133
|
-
if (
|
1134
|
-
bufferedFragAtPos &&
|
1135
|
-
(bufferInfo.nextStart <= bufferedFragAtPos.end || bufferedFragAtPos.gap)
|
1136
|
-
) {
|
1004
|
+
if (bufferedFragAtPos && bufferInfo.nextStart < bufferedFragAtPos.end) {
|
1137
1005
|
return BufferHelper.bufferInfo(
|
1138
1006
|
bufferable,
|
1139
1007
|
pos,
|
@@ -1146,7 +1014,7 @@ export default class BaseStreamController
|
|
1146
1014
|
|
1147
1015
|
protected getMaxBufferLength(levelBitrate?: number): number {
|
1148
1016
|
const { config } = this;
|
1149
|
-
let maxBufLen
|
1017
|
+
let maxBufLen;
|
1150
1018
|
if (levelBitrate) {
|
1151
1019
|
maxBufLen = Math.max(
|
1152
1020
|
(8 * config.maxBufferSize) / levelBitrate,
|
@@ -1161,12 +1029,13 @@ export default class BaseStreamController
|
|
1161
1029
|
protected reduceMaxBufferLength(threshold: number, fragDuration: number) {
|
1162
1030
|
const config = this.config;
|
1163
1031
|
const minLength = Math.max(
|
1164
|
-
Math.min(threshold, config.maxBufferLength),
|
1032
|
+
Math.min(threshold - fragDuration, config.maxBufferLength),
|
1165
1033
|
fragDuration,
|
1166
1034
|
);
|
1167
1035
|
const reducedLength = Math.max(
|
1168
1036
|
threshold - fragDuration * 3,
|
1169
1037
|
config.maxMaxBufferLength / 2,
|
1038
|
+
minLength,
|
1170
1039
|
);
|
1171
1040
|
if (reducedLength >= minLength) {
|
1172
1041
|
// reduce max buffer length as it might be too high. we do this to avoid loop flushing ...
|
@@ -1181,9 +1050,9 @@ export default class BaseStreamController
|
|
1181
1050
|
position: number,
|
1182
1051
|
playlistType: PlaylistLevelType = PlaylistLevelType.MAIN,
|
1183
1052
|
): Fragment | null {
|
1184
|
-
const fragOrPart = this.fragmentTracker
|
1053
|
+
const fragOrPart = this.fragmentTracker.getAppendedFrag(
|
1185
1054
|
position,
|
1186
|
-
|
1055
|
+
PlaylistLevelType.MAIN,
|
1187
1056
|
);
|
1188
1057
|
if (fragOrPart && 'fragment' in fragOrPart) {
|
1189
1058
|
return fragOrPart.fragment;
|
@@ -1205,8 +1074,7 @@ export default class BaseStreamController
|
|
1205
1074
|
// find fragment index, contiguous with end of buffer position
|
1206
1075
|
const { config } = this;
|
1207
1076
|
const start = fragments[0].start;
|
1208
|
-
|
1209
|
-
let frag: MediaFragment | null = null;
|
1077
|
+
let frag;
|
1210
1078
|
|
1211
1079
|
if (levelDetails.live) {
|
1212
1080
|
const initialLiveManifestSize = config.initialLiveManifestSize;
|
@@ -1226,10 +1094,6 @@ export default class BaseStreamController
|
|
1226
1094
|
this.startPosition === -1) ||
|
1227
1095
|
pos < start
|
1228
1096
|
) {
|
1229
|
-
if (canLoadParts && !this.loadingParts) {
|
1230
|
-
this.log(`LL-Part loading ON for initial live fragment`);
|
1231
|
-
this.loadingParts = true;
|
1232
|
-
}
|
1233
1097
|
frag = this.getInitialLiveFragment(levelDetails, fragments);
|
1234
1098
|
this.startPosition = this.nextLoadPosition = frag
|
1235
1099
|
? this.hls.liveSyncPosition || frag.start
|
@@ -1242,7 +1106,7 @@ export default class BaseStreamController
|
|
1242
1106
|
|
1243
1107
|
// If we haven't run into any special cases already, just load the fragment most closely matching the requested position
|
1244
1108
|
if (!frag) {
|
1245
|
-
const end =
|
1109
|
+
const end = config.lowLatencyMode
|
1246
1110
|
? levelDetails.partEnd
|
1247
1111
|
: levelDetails.fragmentEnd;
|
1248
1112
|
frag = this.getFragmentAtPosition(pos, end, levelDetails);
|
@@ -1267,35 +1131,34 @@ export default class BaseStreamController
|
|
1267
1131
|
playlistType: PlaylistLevelType,
|
1268
1132
|
maxBufLen: number,
|
1269
1133
|
): Fragment | null {
|
1270
|
-
|
1271
|
-
|
1272
|
-
|
1273
|
-
|
1274
|
-
|
1275
|
-
|
1276
|
-
|
1277
|
-
|
1278
|
-
|
1279
|
-
|
1134
|
+
const gapStart = frag.gap;
|
1135
|
+
const nextFragment = this.getNextFragment(
|
1136
|
+
this.nextLoadPosition,
|
1137
|
+
levelDetails,
|
1138
|
+
);
|
1139
|
+
if (nextFragment === null) {
|
1140
|
+
return nextFragment;
|
1141
|
+
}
|
1142
|
+
frag = nextFragment;
|
1143
|
+
if (gapStart && frag && !frag.gap && bufferInfo.nextStart) {
|
1144
|
+
// Media buffered after GAP tags should not make the next buffer timerange exceed forward buffer length
|
1145
|
+
const nextbufferInfo = this.getFwdBufferInfoAtPos(
|
1146
|
+
this.mediaBuffer ? this.mediaBuffer : this.media,
|
1147
|
+
bufferInfo.nextStart,
|
1148
|
+
playlistType,
|
1149
|
+
);
|
1150
|
+
if (
|
1151
|
+
nextbufferInfo !== null &&
|
1152
|
+
bufferInfo.len + nextbufferInfo.len >= maxBufLen
|
1153
|
+
) {
|
1154
|
+
// Returning here might result in not finding an audio and video candiate to skip to
|
1155
|
+
this.log(
|
1156
|
+
`buffer full after gaps in "${playlistType}" playlist starting at sn: ${frag.sn}`,
|
1280
1157
|
);
|
1281
|
-
|
1282
|
-
nextbufferInfo !== null &&
|
1283
|
-
bufferInfo.len + nextbufferInfo.len >= maxBufLen
|
1284
|
-
) {
|
1285
|
-
// Returning here might result in not finding an audio and video candiate to skip to
|
1286
|
-
const sn = nextFragment.sn;
|
1287
|
-
if (this.loopSn !== sn) {
|
1288
|
-
this.log(
|
1289
|
-
`buffer full after gaps in "${playlistType}" playlist starting at sn: ${sn}`,
|
1290
|
-
);
|
1291
|
-
this.loopSn = sn;
|
1292
|
-
}
|
1293
|
-
return null;
|
1294
|
-
}
|
1158
|
+
return null;
|
1295
1159
|
}
|
1296
1160
|
}
|
1297
|
-
|
1298
|
-
return nextFragment;
|
1161
|
+
return frag;
|
1299
1162
|
}
|
1300
1163
|
|
1301
1164
|
mapToInitFragWhenRequired(frag: Fragment | null): typeof frag {
|
@@ -1350,10 +1213,10 @@ export default class BaseStreamController
|
|
1350
1213
|
*/
|
1351
1214
|
protected getInitialLiveFragment(
|
1352
1215
|
levelDetails: LevelDetails,
|
1353
|
-
fragments:
|
1354
|
-
):
|
1216
|
+
fragments: Array<Fragment>,
|
1217
|
+
): Fragment | null {
|
1355
1218
|
const fragPrevious = this.fragPrevious;
|
1356
|
-
let frag:
|
1219
|
+
let frag: Fragment | null = null;
|
1357
1220
|
if (fragPrevious) {
|
1358
1221
|
if (levelDetails.hasProgramDateTime) {
|
1359
1222
|
// Prefer using PDT, because it can be accurate enough to choose the correct fragment without knowing the level sliding
|
@@ -1417,7 +1280,7 @@ export default class BaseStreamController
|
|
1417
1280
|
bufferEnd: number,
|
1418
1281
|
end: number,
|
1419
1282
|
levelDetails: LevelDetails,
|
1420
|
-
):
|
1283
|
+
): Fragment | null {
|
1421
1284
|
const { config } = this;
|
1422
1285
|
let { fragPrevious } = this;
|
1423
1286
|
let { fragments, endSN } = levelDetails;
|
@@ -1426,25 +1289,20 @@ export default class BaseStreamController
|
|
1426
1289
|
const partList = levelDetails.partList;
|
1427
1290
|
|
1428
1291
|
const loadingParts = !!(
|
1429
|
-
|
1292
|
+
config.lowLatencyMode &&
|
1430
1293
|
partList?.length &&
|
1431
1294
|
fragmentHint
|
1432
1295
|
);
|
1433
1296
|
if (loadingParts && fragmentHint && !this.bitrateTest) {
|
1434
1297
|
// Include incomplete fragment with parts at end
|
1435
1298
|
fragments = fragments.concat(fragmentHint);
|
1436
|
-
endSN = fragmentHint.sn;
|
1299
|
+
endSN = fragmentHint.sn as number;
|
1437
1300
|
}
|
1438
1301
|
|
1439
|
-
let frag
|
1302
|
+
let frag;
|
1440
1303
|
if (bufferEnd < end) {
|
1441
|
-
const backwardSeek = bufferEnd < this.lastCurrentTime;
|
1442
1304
|
const lookupTolerance =
|
1443
|
-
|
1444
|
-
bufferEnd > end - maxFragLookUpTolerance ||
|
1445
|
-
this.media?.paused
|
1446
|
-
? 0
|
1447
|
-
: maxFragLookUpTolerance;
|
1305
|
+
bufferEnd > end - maxFragLookUpTolerance ? 0 : maxFragLookUpTolerance;
|
1448
1306
|
// Remove the tolerance if it would put the bufferEnd past the actual end of stream
|
1449
1307
|
// Uses buffer and sequence number to calculate switch segment (required if using EXT-X-DISCONTINUITY-SEQUENCE)
|
1450
1308
|
frag = findFragmentByPTS(
|
@@ -1597,7 +1455,7 @@ export default class BaseStreamController
|
|
1597
1455
|
if (startTimeOffset !== null && Number.isFinite(startTimeOffset)) {
|
1598
1456
|
startPosition = sliding + startTimeOffset;
|
1599
1457
|
if (startTimeOffset < 0) {
|
1600
|
-
startPosition += details.
|
1458
|
+
startPosition += details.totalduration;
|
1601
1459
|
}
|
1602
1460
|
startPosition = Math.min(
|
1603
1461
|
Math.max(sliding, startPosition),
|
@@ -1678,7 +1536,7 @@ export default class BaseStreamController
|
|
1678
1536
|
}
|
1679
1537
|
const gapTagEncountered = data.details === ErrorDetails.FRAG_GAP;
|
1680
1538
|
if (gapTagEncountered) {
|
1681
|
-
this.fragmentTracker.fragBuffered(frag
|
1539
|
+
this.fragmentTracker.fragBuffered(frag, true);
|
1682
1540
|
}
|
1683
1541
|
// keep retrying until the limit will be reached
|
1684
1542
|
const errorAction = data.errorAction;
|
@@ -1711,7 +1569,7 @@ export default class BaseStreamController
|
|
1711
1569
|
errorAction.resolved = true;
|
1712
1570
|
}
|
1713
1571
|
} else {
|
1714
|
-
|
1572
|
+
logger.warn(
|
1715
1573
|
`${data.details} reached or exceeded max retry (${retryCount})`,
|
1716
1574
|
);
|
1717
1575
|
return;
|
@@ -1801,9 +1659,7 @@ export default class BaseStreamController
|
|
1801
1659
|
this.log('Reset loading state');
|
1802
1660
|
this.fragCurrent = null;
|
1803
1661
|
this.fragPrevious = null;
|
1804
|
-
|
1805
|
-
this.state = State.IDLE;
|
1806
|
-
}
|
1662
|
+
this.state = State.IDLE;
|
1807
1663
|
}
|
1808
1664
|
|
1809
1665
|
protected resetStartWhenNotLoaded(level: Level | null): void {
|
@@ -1843,7 +1699,7 @@ export default class BaseStreamController
|
|
1843
1699
|
}
|
1844
1700
|
|
1845
1701
|
private updateLevelTiming(
|
1846
|
-
frag:
|
1702
|
+
frag: Fragment,
|
1847
1703
|
part: Part | null,
|
1848
1704
|
level: Level,
|
1849
1705
|
partial: boolean,
|