hls.js 1.5.14-0.canary.10515 → 1.5.14
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 +2903 -4542
- package/dist/hls.js.d.ts +112 -186
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +2284 -3295
- 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 +1804 -2817
- 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 +4652 -6293
- 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 +136 -156
- 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 +107 -263
- package/src/controller/buffer-controller.ts +98 -252
- 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 +5 -5
- package/src/demux/audio/ac3-demuxer.ts +4 -5
- 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/inject-worker.ts +4 -38
- package/src/demux/mp4demuxer.ts +7 -7
- package/src/demux/sample-aes.ts +0 -2
- package/src/demux/transmuxer-interface.ts +83 -106
- package/src/demux/transmuxer-worker.ts +77 -111
- package/src/demux/transmuxer.ts +22 -46
- package/src/demux/tsdemuxer.ts +62 -122
- 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/errors.ts +0 -2
- package/src/events.ts +1 -8
- package/src/exports-named.ts +1 -1
- package/src/hls.ts +48 -97
- 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 +84 -55
- package/src/remux/passthrough-remuxer.ts +8 -23
- 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/discontinuities.ts +47 -21
- 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
- package/src/version.ts +0 -1
@@ -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
|
-
protected
|
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,34 +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
|
-
if (this.transmuxer) {
|
369
|
-
this.transmuxer.destroy();
|
370
|
-
this.transmuxer = null;
|
371
|
-
}
|
372
313
|
super.onHandlerDestroying();
|
373
314
|
// @ts-ignore
|
374
|
-
this.hls =
|
315
|
+
this.hls = null;
|
375
316
|
}
|
376
317
|
|
377
318
|
protected onHandlerDestroyed() {
|
@@ -402,7 +343,6 @@ export default class BaseStreamController
|
|
402
343
|
level: Level,
|
403
344
|
targetBufferTime: number,
|
404
345
|
) {
|
405
|
-
this.startFragRequested = true;
|
406
346
|
this._loadFragForPlayback(frag, level, targetBufferTime);
|
407
347
|
}
|
408
348
|
|
@@ -446,9 +386,7 @@ export default class BaseStreamController
|
|
446
386
|
}
|
447
387
|
|
448
388
|
if ('payload' in data) {
|
449
|
-
this.log(
|
450
|
-
`Loaded ${frag.type} sn: ${frag.sn} of ${this.playlistLabel()} ${frag.level}`,
|
451
|
-
);
|
389
|
+
this.log(`Loaded fragment ${frag.sn} of level ${frag.level}`);
|
452
390
|
this.hls.trigger(Events.FRAG_LOADED, data);
|
453
391
|
}
|
454
392
|
|
@@ -558,7 +496,7 @@ export default class BaseStreamController
|
|
558
496
|
payload.byteLength > 0 &&
|
559
497
|
decryptData?.key &&
|
560
498
|
decryptData.iv &&
|
561
|
-
|
499
|
+
decryptData.method === 'AES-128'
|
562
500
|
) {
|
563
501
|
const startTime = self.performance.now();
|
564
502
|
// decrypt init segment data
|
@@ -567,7 +505,6 @@ export default class BaseStreamController
|
|
567
505
|
new Uint8Array(payload),
|
568
506
|
decryptData.key.buffer,
|
569
507
|
decryptData.iv.buffer,
|
570
|
-
getAesModeFromFullSegmentMethod(decryptData.method),
|
571
508
|
)
|
572
509
|
.catch((err) => {
|
573
510
|
hls.trigger(Events.ERROR, {
|
@@ -611,9 +548,7 @@ export default class BaseStreamController
|
|
611
548
|
throw new Error('init load aborted, missing levels');
|
612
549
|
}
|
613
550
|
const stats = data.frag.stats;
|
614
|
-
|
615
|
-
this.state = State.IDLE;
|
616
|
-
}
|
551
|
+
this.state = State.IDLE;
|
617
552
|
data.frag.data = new Uint8Array(data.payload);
|
618
553
|
stats.parsing.start = stats.buffering.start = self.performance.now();
|
619
554
|
stats.parsing.end = stats.buffering.end = self.performance.now();
|
@@ -635,7 +570,11 @@ export default class BaseStreamController
|
|
635
570
|
this.log(
|
636
571
|
`Buffered ${frag.type} sn: ${frag.sn}${
|
637
572
|
part ? ' part: ' + part.index : ''
|
638
|
-
} of ${
|
573
|
+
} of ${
|
574
|
+
this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'
|
575
|
+
} ${frag.level} (frag:[${(frag.startPTS ?? NaN).toFixed(3)}-${(
|
576
|
+
frag.endPTS ?? NaN
|
577
|
+
).toFixed(3)}] > buffer:${
|
639
578
|
media
|
640
579
|
? TimeRanges.toString(BufferHelper.getBuffered(media))
|
641
580
|
: '(detached)'
|
@@ -709,7 +648,6 @@ export default class BaseStreamController
|
|
709
648
|
targetBufferTime: number | null = null,
|
710
649
|
progressCallback?: FragmentLoadProgressCallback,
|
711
650
|
): Promise<PartsLoadedData | FragLoadedData | null> {
|
712
|
-
this.fragCurrent = frag;
|
713
651
|
const details = level?.details;
|
714
652
|
if (!this.levels || !details) {
|
715
653
|
throw new Error(
|
@@ -720,7 +658,9 @@ export default class BaseStreamController
|
|
720
658
|
let keyLoadingPromise: Promise<KeyLoadedData | void> | null = null;
|
721
659
|
if (frag.encrypted && !frag.decryptdata?.key) {
|
722
660
|
this.log(
|
723
|
-
`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${
|
661
|
+
`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${
|
662
|
+
this.logPrefix === '[stream-controller]' ? 'level' : 'track'
|
663
|
+
} ${frag.level}`,
|
724
664
|
);
|
725
665
|
this.state = State.KEY_LOADING;
|
726
666
|
this.fragCurrent = frag;
|
@@ -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) {
|
@@ -773,7 +698,9 @@ export default class BaseStreamController
|
|
773
698
|
frag.cc
|
774
699
|
} of playlist [${details.startSN}-${
|
775
700
|
details.endSN
|
776
|
-
}] parts [0-${partIndex}-${partList.length - 1}] ${
|
701
|
+
}] parts [0-${partIndex}-${partList.length - 1}] ${
|
702
|
+
this.logPrefix === '[stream-controller]' ? 'level' : 'track'
|
703
|
+
}: ${frag.level}, target: ${parseFloat(
|
777
704
|
targetBufferTime.toFixed(3),
|
778
705
|
)}`,
|
779
706
|
);
|
@@ -828,21 +755,11 @@ export default class BaseStreamController
|
|
828
755
|
}
|
829
756
|
}
|
830
757
|
|
831
|
-
if (frag.sn !== 'initSegment' && this.loadingParts) {
|
832
|
-
this.log(
|
833
|
-
`LL-Part loading OFF after next part miss @${targetBufferTime.toFixed(
|
834
|
-
2,
|
835
|
-
)}`,
|
836
|
-
);
|
837
|
-
this.loadingParts = false;
|
838
|
-
} else if (!frag.url) {
|
839
|
-
// Selected fragment hint for part but not loading parts
|
840
|
-
return Promise.resolve(null);
|
841
|
-
}
|
842
|
-
|
843
758
|
this.log(
|
844
|
-
`Loading
|
845
|
-
details ? '[' + details.startSN + '-' + details.endSN + ']' : ''
|
759
|
+
`Loading fragment ${frag.sn} cc: ${frag.cc} ${
|
760
|
+
details ? 'of [' + details.startSN + '-' + details.endSN + '] ' : ''
|
761
|
+
}${this.logPrefix === '[stream-controller]' ? 'level' : 'track'}: ${
|
762
|
+
frag.level
|
846
763
|
}, target: ${parseFloat(targetBufferTime.toFixed(3))}`,
|
847
764
|
);
|
848
765
|
// Don't update nextLoadPosition for fragments which are not buffered
|
@@ -908,7 +825,7 @@ export default class BaseStreamController
|
|
908
825
|
const loadedPart = partLoadedData.part as Part;
|
909
826
|
this.hls.trigger(Events.FRAG_LOADED, partLoadedData);
|
910
827
|
const nextPart =
|
911
|
-
getPartWith(level
|
828
|
+
getPartWith(level, frag.sn as number, part.index + 1) ||
|
912
829
|
findPart(initialPartList, frag.sn as number, part.index + 1);
|
913
830
|
if (nextPart) {
|
914
831
|
loadPart(nextPart);
|
@@ -965,50 +882,12 @@ export default class BaseStreamController
|
|
965
882
|
if (part) {
|
966
883
|
part.stats.parsing.end = now;
|
967
884
|
}
|
968
|
-
// See if part loading should be disabled/enabled based on buffer and playback position.
|
969
|
-
const levelDetails = this.getLevelDetails();
|
970
|
-
const loadingPartsAtEdge = levelDetails && frag.sn > levelDetails.endSN;
|
971
|
-
const shouldLoadParts =
|
972
|
-
loadingPartsAtEdge || this.shouldLoadParts(levelDetails, frag.end);
|
973
|
-
if (shouldLoadParts !== this.loadingParts) {
|
974
|
-
this.log(
|
975
|
-
`LL-Part loading ${
|
976
|
-
shouldLoadParts ? 'ON' : 'OFF'
|
977
|
-
} after parsing segment ending @${frag.end.toFixed(2)}`,
|
978
|
-
);
|
979
|
-
this.loadingParts = shouldLoadParts;
|
980
|
-
}
|
981
885
|
this.updateLevelTiming(frag, part, level, chunkMeta.partial);
|
982
886
|
}
|
983
887
|
|
984
|
-
private shouldLoadParts(
|
985
|
-
details: LevelDetails | undefined,
|
986
|
-
bufferEnd: number,
|
987
|
-
): boolean {
|
988
|
-
if (this.config.lowLatencyMode) {
|
989
|
-
if (!details) {
|
990
|
-
return this.loadingParts;
|
991
|
-
}
|
992
|
-
if (details?.partList) {
|
993
|
-
// Buffer must be ahead of first part + duration of parts after last segment
|
994
|
-
// and playback must be at or past segment adjacent to part list
|
995
|
-
const firstPart = details.partList[0];
|
996
|
-
const safePartStart =
|
997
|
-
firstPart.end + (details.fragmentHint?.duration || 0);
|
998
|
-
if (
|
999
|
-
bufferEnd >= safePartStart &&
|
1000
|
-
this.lastCurrentTime > firstPart.start - firstPart.fragment.duration
|
1001
|
-
) {
|
1002
|
-
return true;
|
1003
|
-
}
|
1004
|
-
}
|
1005
|
-
}
|
1006
|
-
return false;
|
1007
|
-
}
|
1008
|
-
|
1009
888
|
protected getCurrentContext(
|
1010
889
|
chunkMeta: ChunkMetadata,
|
1011
|
-
): { frag:
|
890
|
+
): { frag: Fragment; part: Part | null; level: Level } | null {
|
1012
891
|
const { levels, fragCurrent } = this;
|
1013
892
|
const { level: levelIndex, sn, part: partIndex } = chunkMeta;
|
1014
893
|
if (!levels?.[levelIndex]) {
|
@@ -1018,13 +897,10 @@ export default class BaseStreamController
|
|
1018
897
|
return null;
|
1019
898
|
}
|
1020
899
|
const level = levels[levelIndex];
|
1021
|
-
const
|
1022
|
-
|
1023
|
-
const part =
|
1024
|
-
partIndex > -1 ? getPartWith(levelDetails, sn, partIndex) : null;
|
900
|
+
const part = partIndex > -1 ? getPartWith(level, sn, partIndex) : null;
|
1025
901
|
const frag = part
|
1026
902
|
? part.fragment
|
1027
|
-
: getFragmentWithSN(
|
903
|
+
: getFragmentWithSN(level, sn, fragCurrent);
|
1028
904
|
if (!frag) {
|
1029
905
|
return null;
|
1030
906
|
}
|
@@ -1110,26 +986,22 @@ export default class BaseStreamController
|
|
1110
986
|
if (!Number.isFinite(pos)) {
|
1111
987
|
return null;
|
1112
988
|
}
|
1113
|
-
|
1114
|
-
const maxBufferHole =
|
1115
|
-
backwardSeek || this.media?.paused ? 0 : this.config.maxBufferHole;
|
1116
|
-
return this.getFwdBufferInfoAtPos(bufferable, pos, type, maxBufferHole);
|
989
|
+
return this.getFwdBufferInfoAtPos(bufferable, pos, type);
|
1117
990
|
}
|
1118
991
|
|
1119
|
-
|
992
|
+
protected getFwdBufferInfoAtPos(
|
1120
993
|
bufferable: Bufferable | null,
|
1121
994
|
pos: number,
|
1122
995
|
type: PlaylistLevelType,
|
1123
|
-
maxBufferHole: number,
|
1124
996
|
): BufferInfo | null {
|
997
|
+
const {
|
998
|
+
config: { maxBufferHole },
|
999
|
+
} = this;
|
1125
1000
|
const bufferInfo = BufferHelper.bufferInfo(bufferable, pos, maxBufferHole);
|
1126
1001
|
// Workaround flaw in getting forward buffer when maxBufferHole is smaller than gap at current pos
|
1127
1002
|
if (bufferInfo.len === 0 && bufferInfo.nextStart !== undefined) {
|
1128
1003
|
const bufferedFragAtPos = this.fragmentTracker.getBufferedFrag(pos, type);
|
1129
|
-
if (
|
1130
|
-
bufferedFragAtPos &&
|
1131
|
-
(bufferInfo.nextStart <= bufferedFragAtPos.end || bufferedFragAtPos.gap)
|
1132
|
-
) {
|
1004
|
+
if (bufferedFragAtPos && bufferInfo.nextStart < bufferedFragAtPos.end) {
|
1133
1005
|
return BufferHelper.bufferInfo(
|
1134
1006
|
bufferable,
|
1135
1007
|
pos,
|
@@ -1142,7 +1014,7 @@ export default class BaseStreamController
|
|
1142
1014
|
|
1143
1015
|
protected getMaxBufferLength(levelBitrate?: number): number {
|
1144
1016
|
const { config } = this;
|
1145
|
-
let maxBufLen
|
1017
|
+
let maxBufLen;
|
1146
1018
|
if (levelBitrate) {
|
1147
1019
|
maxBufLen = Math.max(
|
1148
1020
|
(8 * config.maxBufferSize) / levelBitrate,
|
@@ -1178,9 +1050,9 @@ export default class BaseStreamController
|
|
1178
1050
|
position: number,
|
1179
1051
|
playlistType: PlaylistLevelType = PlaylistLevelType.MAIN,
|
1180
1052
|
): Fragment | null {
|
1181
|
-
const fragOrPart = this.fragmentTracker
|
1053
|
+
const fragOrPart = this.fragmentTracker.getAppendedFrag(
|
1182
1054
|
position,
|
1183
|
-
|
1055
|
+
PlaylistLevelType.MAIN,
|
1184
1056
|
);
|
1185
1057
|
if (fragOrPart && 'fragment' in fragOrPart) {
|
1186
1058
|
return fragOrPart.fragment;
|
@@ -1202,8 +1074,7 @@ export default class BaseStreamController
|
|
1202
1074
|
// find fragment index, contiguous with end of buffer position
|
1203
1075
|
const { config } = this;
|
1204
1076
|
const start = fragments[0].start;
|
1205
|
-
|
1206
|
-
let frag: MediaFragment | null = null;
|
1077
|
+
let frag;
|
1207
1078
|
|
1208
1079
|
if (levelDetails.live) {
|
1209
1080
|
const initialLiveManifestSize = config.initialLiveManifestSize;
|
@@ -1223,10 +1094,6 @@ export default class BaseStreamController
|
|
1223
1094
|
this.startPosition === -1) ||
|
1224
1095
|
pos < start
|
1225
1096
|
) {
|
1226
|
-
if (canLoadParts && !this.loadingParts) {
|
1227
|
-
this.log(`LL-Part loading ON for initial live fragment`);
|
1228
|
-
this.loadingParts = true;
|
1229
|
-
}
|
1230
1097
|
frag = this.getInitialLiveFragment(levelDetails, fragments);
|
1231
1098
|
this.startPosition = this.nextLoadPosition = frag
|
1232
1099
|
? this.hls.liveSyncPosition || frag.start
|
@@ -1239,7 +1106,7 @@ export default class BaseStreamController
|
|
1239
1106
|
|
1240
1107
|
// If we haven't run into any special cases already, just load the fragment most closely matching the requested position
|
1241
1108
|
if (!frag) {
|
1242
|
-
const end =
|
1109
|
+
const end = config.lowLatencyMode
|
1243
1110
|
? levelDetails.partEnd
|
1244
1111
|
: levelDetails.fragmentEnd;
|
1245
1112
|
frag = this.getFragmentAtPosition(pos, end, levelDetails);
|
@@ -1264,35 +1131,34 @@ export default class BaseStreamController
|
|
1264
1131
|
playlistType: PlaylistLevelType,
|
1265
1132
|
maxBufLen: number,
|
1266
1133
|
): Fragment | null {
|
1267
|
-
|
1268
|
-
|
1269
|
-
|
1270
|
-
|
1271
|
-
|
1272
|
-
|
1273
|
-
|
1274
|
-
|
1275
|
-
|
1276
|
-
|
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}`,
|
1277
1157
|
);
|
1278
|
-
|
1279
|
-
nextbufferInfo !== null &&
|
1280
|
-
bufferInfo.len + nextbufferInfo.len >= maxBufLen
|
1281
|
-
) {
|
1282
|
-
// Returning here might result in not finding an audio and video candiate to skip to
|
1283
|
-
const sn = nextFragment.sn;
|
1284
|
-
if (this.loopSn !== sn) {
|
1285
|
-
this.log(
|
1286
|
-
`buffer full after gaps in "${playlistType}" playlist starting at sn: ${sn}`,
|
1287
|
-
);
|
1288
|
-
this.loopSn = sn;
|
1289
|
-
}
|
1290
|
-
return null;
|
1291
|
-
}
|
1158
|
+
return null;
|
1292
1159
|
}
|
1293
1160
|
}
|
1294
|
-
|
1295
|
-
return nextFragment;
|
1161
|
+
return frag;
|
1296
1162
|
}
|
1297
1163
|
|
1298
1164
|
mapToInitFragWhenRequired(frag: Fragment | null): typeof frag {
|
@@ -1347,10 +1213,10 @@ export default class BaseStreamController
|
|
1347
1213
|
*/
|
1348
1214
|
protected getInitialLiveFragment(
|
1349
1215
|
levelDetails: LevelDetails,
|
1350
|
-
fragments:
|
1351
|
-
):
|
1216
|
+
fragments: Array<Fragment>,
|
1217
|
+
): Fragment | null {
|
1352
1218
|
const fragPrevious = this.fragPrevious;
|
1353
|
-
let frag:
|
1219
|
+
let frag: Fragment | null = null;
|
1354
1220
|
if (fragPrevious) {
|
1355
1221
|
if (levelDetails.hasProgramDateTime) {
|
1356
1222
|
// Prefer using PDT, because it can be accurate enough to choose the correct fragment without knowing the level sliding
|
@@ -1414,7 +1280,7 @@ export default class BaseStreamController
|
|
1414
1280
|
bufferEnd: number,
|
1415
1281
|
end: number,
|
1416
1282
|
levelDetails: LevelDetails,
|
1417
|
-
):
|
1283
|
+
): Fragment | null {
|
1418
1284
|
const { config } = this;
|
1419
1285
|
let { fragPrevious } = this;
|
1420
1286
|
let { fragments, endSN } = levelDetails;
|
@@ -1423,25 +1289,20 @@ export default class BaseStreamController
|
|
1423
1289
|
const partList = levelDetails.partList;
|
1424
1290
|
|
1425
1291
|
const loadingParts = !!(
|
1426
|
-
|
1292
|
+
config.lowLatencyMode &&
|
1427
1293
|
partList?.length &&
|
1428
1294
|
fragmentHint
|
1429
1295
|
);
|
1430
1296
|
if (loadingParts && fragmentHint && !this.bitrateTest) {
|
1431
1297
|
// Include incomplete fragment with parts at end
|
1432
1298
|
fragments = fragments.concat(fragmentHint);
|
1433
|
-
endSN = fragmentHint.sn;
|
1299
|
+
endSN = fragmentHint.sn as number;
|
1434
1300
|
}
|
1435
1301
|
|
1436
|
-
let frag
|
1302
|
+
let frag;
|
1437
1303
|
if (bufferEnd < end) {
|
1438
|
-
const backwardSeek = bufferEnd < this.lastCurrentTime;
|
1439
1304
|
const lookupTolerance =
|
1440
|
-
|
1441
|
-
bufferEnd > end - maxFragLookUpTolerance ||
|
1442
|
-
this.media?.paused
|
1443
|
-
? 0
|
1444
|
-
: maxFragLookUpTolerance;
|
1305
|
+
bufferEnd > end - maxFragLookUpTolerance ? 0 : maxFragLookUpTolerance;
|
1445
1306
|
// Remove the tolerance if it would put the bufferEnd past the actual end of stream
|
1446
1307
|
// Uses buffer and sequence number to calculate switch segment (required if using EXT-X-DISCONTINUITY-SEQUENCE)
|
1447
1308
|
frag = findFragmentByPTS(
|
@@ -1594,7 +1455,7 @@ export default class BaseStreamController
|
|
1594
1455
|
if (startTimeOffset !== null && Number.isFinite(startTimeOffset)) {
|
1595
1456
|
startPosition = sliding + startTimeOffset;
|
1596
1457
|
if (startTimeOffset < 0) {
|
1597
|
-
startPosition += details.
|
1458
|
+
startPosition += details.totalduration;
|
1598
1459
|
}
|
1599
1460
|
startPosition = Math.min(
|
1600
1461
|
Math.max(sliding, startPosition),
|
@@ -1675,7 +1536,7 @@ export default class BaseStreamController
|
|
1675
1536
|
}
|
1676
1537
|
const gapTagEncountered = data.details === ErrorDetails.FRAG_GAP;
|
1677
1538
|
if (gapTagEncountered) {
|
1678
|
-
this.fragmentTracker.fragBuffered(frag
|
1539
|
+
this.fragmentTracker.fragBuffered(frag, true);
|
1679
1540
|
}
|
1680
1541
|
// keep retrying until the limit will be reached
|
1681
1542
|
const errorAction = data.errorAction;
|
@@ -1708,7 +1569,7 @@ export default class BaseStreamController
|
|
1708
1569
|
errorAction.resolved = true;
|
1709
1570
|
}
|
1710
1571
|
} else {
|
1711
|
-
|
1572
|
+
logger.warn(
|
1712
1573
|
`${data.details} reached or exceeded max retry (${retryCount})`,
|
1713
1574
|
);
|
1714
1575
|
return;
|
@@ -1798,9 +1659,7 @@ export default class BaseStreamController
|
|
1798
1659
|
this.log('Reset loading state');
|
1799
1660
|
this.fragCurrent = null;
|
1800
1661
|
this.fragPrevious = null;
|
1801
|
-
|
1802
|
-
this.state = State.IDLE;
|
1803
|
-
}
|
1662
|
+
this.state = State.IDLE;
|
1804
1663
|
}
|
1805
1664
|
|
1806
1665
|
protected resetStartWhenNotLoaded(level: Level | null): void {
|
@@ -1840,7 +1699,7 @@ export default class BaseStreamController
|
|
1840
1699
|
}
|
1841
1700
|
|
1842
1701
|
private updateLevelTiming(
|
1843
|
-
frag:
|
1702
|
+
frag: Fragment,
|
1844
1703
|
part: Part | null,
|
1845
1704
|
level: Level,
|
1846
1705
|
partial: boolean,
|
@@ -1916,35 +1775,20 @@ export default class BaseStreamController
|
|
1916
1775
|
// For this error fallthrough. Marking parsed will allow advancing to next fragment.
|
1917
1776
|
}
|
1918
1777
|
this.state = State.PARSED;
|
1919
|
-
this.log(
|
1920
|
-
`Parsed ${frag.type} sn: ${frag.sn}${
|
1921
|
-
part ? ' part: ' + part.index : ''
|
1922
|
-
} of ${this.fragInfo(frag)})`,
|
1923
|
-
);
|
1924
1778
|
this.hls.trigger(Events.FRAG_PARSED, { frag, part });
|
1925
1779
|
}
|
1926
1780
|
|
1927
|
-
private playlistLabel() {
|
1928
|
-
return this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track';
|
1929
|
-
}
|
1930
|
-
|
1931
|
-
private fragInfo(frag: Fragment, pts: boolean = true): string {
|
1932
|
-
return `${this.playlistLabel()} ${frag.level} (frag:[${((pts ? frag.startPTS : frag.start) ?? NaN).toFixed(3)}-${(
|
1933
|
-
(pts ? frag.endPTS : frag.end) ?? NaN
|
1934
|
-
).toFixed(3)}]`;
|
1935
|
-
}
|
1936
|
-
|
1937
1781
|
protected resetTransmuxer() {
|
1938
|
-
this.transmuxer
|
1782
|
+
if (this.transmuxer) {
|
1783
|
+
this.transmuxer.destroy();
|
1784
|
+
this.transmuxer = null;
|
1785
|
+
}
|
1939
1786
|
}
|
1940
1787
|
|
1941
1788
|
protected recoverWorkerError(data: ErrorData) {
|
1942
1789
|
if (data.event === 'demuxerWorker') {
|
1943
1790
|
this.fragmentTracker.removeAllFragments();
|
1944
|
-
|
1945
|
-
this.transmuxer.destroy();
|
1946
|
-
this.transmuxer = null;
|
1947
|
-
}
|
1791
|
+
this.resetTransmuxer();
|
1948
1792
|
this.resetStartWhenNotLoaded(this.levelLastLoaded);
|
1949
1793
|
this.resetLoadingState();
|
1950
1794
|
}
|