hls.js 1.5.12-0.canary.10399 → 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 +2671 -4220
- package/dist/hls.js.d.ts +108 -173
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +1955 -2892
- 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 +3402 -4354
- 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 +4445 -6009
- 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 +82 -215
- 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
|
|
@@ -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,
|
@@ -1175,9 +1049,9 @@ export default class BaseStreamController
|
|
1175
1049
|
position: number,
|
1176
1050
|
playlistType: PlaylistLevelType = PlaylistLevelType.MAIN,
|
1177
1051
|
): Fragment | null {
|
1178
|
-
const fragOrPart = this.fragmentTracker
|
1052
|
+
const fragOrPart = this.fragmentTracker.getAppendedFrag(
|
1179
1053
|
position,
|
1180
|
-
|
1054
|
+
PlaylistLevelType.MAIN,
|
1181
1055
|
);
|
1182
1056
|
if (fragOrPart && 'fragment' in fragOrPart) {
|
1183
1057
|
return fragOrPart.fragment;
|
@@ -1199,8 +1073,7 @@ export default class BaseStreamController
|
|
1199
1073
|
// find fragment index, contiguous with end of buffer position
|
1200
1074
|
const { config } = this;
|
1201
1075
|
const start = fragments[0].start;
|
1202
|
-
|
1203
|
-
let frag: MediaFragment | null = null;
|
1076
|
+
let frag;
|
1204
1077
|
|
1205
1078
|
if (levelDetails.live) {
|
1206
1079
|
const initialLiveManifestSize = config.initialLiveManifestSize;
|
@@ -1220,10 +1093,6 @@ export default class BaseStreamController
|
|
1220
1093
|
this.startPosition === -1) ||
|
1221
1094
|
pos < start
|
1222
1095
|
) {
|
1223
|
-
if (canLoadParts && !this.loadingParts) {
|
1224
|
-
this.log(`LL-Part loading ON for initial live fragment`);
|
1225
|
-
this.loadingParts = true;
|
1226
|
-
}
|
1227
1096
|
frag = this.getInitialLiveFragment(levelDetails, fragments);
|
1228
1097
|
this.startPosition = this.nextLoadPosition = frag
|
1229
1098
|
? this.hls.liveSyncPosition || frag.start
|
@@ -1236,7 +1105,7 @@ export default class BaseStreamController
|
|
1236
1105
|
|
1237
1106
|
// If we haven't run into any special cases already, just load the fragment most closely matching the requested position
|
1238
1107
|
if (!frag) {
|
1239
|
-
const end =
|
1108
|
+
const end = config.lowLatencyMode
|
1240
1109
|
? levelDetails.partEnd
|
1241
1110
|
: levelDetails.fragmentEnd;
|
1242
1111
|
frag = this.getFragmentAtPosition(pos, end, levelDetails);
|
@@ -1261,34 +1130,34 @@ export default class BaseStreamController
|
|
1261
1130
|
playlistType: PlaylistLevelType,
|
1262
1131
|
maxBufLen: number,
|
1263
1132
|
): Fragment | null {
|
1264
|
-
|
1265
|
-
|
1266
|
-
|
1267
|
-
|
1268
|
-
|
1269
|
-
|
1270
|
-
|
1271
|
-
|
1272
|
-
|
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}`,
|
1273
1156
|
);
|
1274
|
-
|
1275
|
-
nextbufferInfo !== null &&
|
1276
|
-
bufferInfo.len + nextbufferInfo.len >= maxBufLen
|
1277
|
-
) {
|
1278
|
-
// Returning here might result in not finding an audio and video candiate to skip to
|
1279
|
-
const sn = nextFragment.sn;
|
1280
|
-
if (this.loopSn !== sn) {
|
1281
|
-
this.log(
|
1282
|
-
`buffer full after gaps in "${playlistType}" playlist starting at sn: ${sn}`,
|
1283
|
-
);
|
1284
|
-
this.loopSn = sn;
|
1285
|
-
}
|
1286
|
-
return null;
|
1287
|
-
}
|
1157
|
+
return null;
|
1288
1158
|
}
|
1289
1159
|
}
|
1290
|
-
|
1291
|
-
return nextFragment;
|
1160
|
+
return frag;
|
1292
1161
|
}
|
1293
1162
|
|
1294
1163
|
mapToInitFragWhenRequired(frag: Fragment | null): typeof frag {
|
@@ -1343,10 +1212,10 @@ export default class BaseStreamController
|
|
1343
1212
|
*/
|
1344
1213
|
protected getInitialLiveFragment(
|
1345
1214
|
levelDetails: LevelDetails,
|
1346
|
-
fragments:
|
1347
|
-
):
|
1215
|
+
fragments: Array<Fragment>,
|
1216
|
+
): Fragment | null {
|
1348
1217
|
const fragPrevious = this.fragPrevious;
|
1349
|
-
let frag:
|
1218
|
+
let frag: Fragment | null = null;
|
1350
1219
|
if (fragPrevious) {
|
1351
1220
|
if (levelDetails.hasProgramDateTime) {
|
1352
1221
|
// Prefer using PDT, because it can be accurate enough to choose the correct fragment without knowing the level sliding
|
@@ -1410,7 +1279,7 @@ export default class BaseStreamController
|
|
1410
1279
|
bufferEnd: number,
|
1411
1280
|
end: number,
|
1412
1281
|
levelDetails: LevelDetails,
|
1413
|
-
):
|
1282
|
+
): Fragment | null {
|
1414
1283
|
const { config } = this;
|
1415
1284
|
let { fragPrevious } = this;
|
1416
1285
|
let { fragments, endSN } = levelDetails;
|
@@ -1419,17 +1288,17 @@ export default class BaseStreamController
|
|
1419
1288
|
const partList = levelDetails.partList;
|
1420
1289
|
|
1421
1290
|
const loadingParts = !!(
|
1422
|
-
|
1291
|
+
config.lowLatencyMode &&
|
1423
1292
|
partList?.length &&
|
1424
1293
|
fragmentHint
|
1425
1294
|
);
|
1426
1295
|
if (loadingParts && fragmentHint && !this.bitrateTest) {
|
1427
1296
|
// Include incomplete fragment with parts at end
|
1428
1297
|
fragments = fragments.concat(fragmentHint);
|
1429
|
-
endSN = fragmentHint.sn;
|
1298
|
+
endSN = fragmentHint.sn as number;
|
1430
1299
|
}
|
1431
1300
|
|
1432
|
-
let frag
|
1301
|
+
let frag;
|
1433
1302
|
if (bufferEnd < end) {
|
1434
1303
|
const lookupTolerance =
|
1435
1304
|
bufferEnd > end - maxFragLookUpTolerance ? 0 : maxFragLookUpTolerance;
|
@@ -1585,7 +1454,7 @@ export default class BaseStreamController
|
|
1585
1454
|
if (startTimeOffset !== null && Number.isFinite(startTimeOffset)) {
|
1586
1455
|
startPosition = sliding + startTimeOffset;
|
1587
1456
|
if (startTimeOffset < 0) {
|
1588
|
-
startPosition += details.
|
1457
|
+
startPosition += details.totalduration;
|
1589
1458
|
}
|
1590
1459
|
startPosition = Math.min(
|
1591
1460
|
Math.max(sliding, startPosition),
|
@@ -1666,7 +1535,7 @@ export default class BaseStreamController
|
|
1666
1535
|
}
|
1667
1536
|
const gapTagEncountered = data.details === ErrorDetails.FRAG_GAP;
|
1668
1537
|
if (gapTagEncountered) {
|
1669
|
-
this.fragmentTracker.fragBuffered(frag
|
1538
|
+
this.fragmentTracker.fragBuffered(frag, true);
|
1670
1539
|
}
|
1671
1540
|
// keep retrying until the limit will be reached
|
1672
1541
|
const errorAction = data.errorAction;
|
@@ -1699,7 +1568,7 @@ export default class BaseStreamController
|
|
1699
1568
|
errorAction.resolved = true;
|
1700
1569
|
}
|
1701
1570
|
} else {
|
1702
|
-
|
1571
|
+
logger.warn(
|
1703
1572
|
`${data.details} reached or exceeded max retry (${retryCount})`,
|
1704
1573
|
);
|
1705
1574
|
return;
|
@@ -1789,9 +1658,7 @@ export default class BaseStreamController
|
|
1789
1658
|
this.log('Reset loading state');
|
1790
1659
|
this.fragCurrent = null;
|
1791
1660
|
this.fragPrevious = null;
|
1792
|
-
|
1793
|
-
this.state = State.IDLE;
|
1794
|
-
}
|
1661
|
+
this.state = State.IDLE;
|
1795
1662
|
}
|
1796
1663
|
|
1797
1664
|
protected resetStartWhenNotLoaded(level: Level | null): void {
|
@@ -1831,7 +1698,7 @@ export default class BaseStreamController
|
|
1831
1698
|
}
|
1832
1699
|
|
1833
1700
|
private updateLevelTiming(
|
1834
|
-
frag:
|
1701
|
+
frag: Fragment,
|
1835
1702
|
part: Part | null,
|
1836
1703
|
level: Level,
|
1837
1704
|
partial: boolean,
|