hls.js 1.5.13 → 1.5.14-0.canary.10415
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 +4 -3
- package/dist/hls-demo.js +41 -38
- package/dist/hls-demo.js.map +1 -1
- package/dist/hls.js +4211 -2666
- package/dist/hls.js.d.ts +179 -110
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +2841 -1921
- 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 +2569 -1639
- 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 +3572 -2017
- 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 +5 -2
- package/src/controller/abr-controller.ts +39 -25
- package/src/controller/audio-stream-controller.ts +156 -136
- package/src/controller/audio-track-controller.ts +1 -1
- package/src/controller/base-playlist-controller.ts +27 -10
- package/src/controller/base-stream-controller.ts +234 -89
- package/src/controller/buffer-controller.ts +250 -97
- package/src/controller/buffer-operation-queue.ts +16 -19
- package/src/controller/cap-level-controller.ts +3 -2
- package/src/controller/cmcd-controller.ts +51 -14
- package/src/controller/content-steering-controller.ts +29 -15
- package/src/controller/eme-controller.ts +10 -23
- package/src/controller/error-controller.ts +28 -22
- package/src/controller/fps-controller.ts +8 -3
- package/src/controller/fragment-finders.ts +44 -16
- package/src/controller/fragment-tracker.ts +58 -25
- package/src/controller/gap-controller.ts +43 -16
- package/src/controller/id3-track-controller.ts +45 -35
- package/src/controller/latency-controller.ts +18 -13
- package/src/controller/level-controller.ts +37 -19
- package/src/controller/stream-controller.ts +100 -83
- package/src/controller/subtitle-stream-controller.ts +35 -47
- package/src/controller/subtitle-track-controller.ts +5 -3
- package/src/controller/timeline-controller.ts +20 -22
- package/src/crypt/aes-crypto.ts +21 -2
- package/src/crypt/decrypter-aes-mode.ts +4 -0
- package/src/crypt/decrypter.ts +32 -16
- package/src/crypt/fast-aes-key.ts +28 -5
- package/src/demux/audio/aacdemuxer.ts +2 -2
- package/src/demux/audio/ac3-demuxer.ts +4 -3
- package/src/demux/audio/adts.ts +9 -4
- package/src/demux/audio/base-audio-demuxer.ts +16 -14
- package/src/demux/audio/mp3demuxer.ts +4 -3
- package/src/demux/audio/mpegaudio.ts +1 -1
- package/src/demux/mp4demuxer.ts +7 -7
- package/src/demux/sample-aes.ts +2 -0
- package/src/demux/transmuxer-interface.ts +8 -16
- package/src/demux/transmuxer-worker.ts +4 -4
- package/src/demux/transmuxer.ts +16 -3
- package/src/demux/tsdemuxer.ts +75 -38
- package/src/demux/video/avc-video-parser.ts +210 -121
- package/src/demux/video/base-video-parser.ts +135 -2
- package/src/demux/video/exp-golomb.ts +0 -208
- package/src/demux/video/hevc-video-parser.ts +749 -0
- package/src/events.ts +8 -1
- package/src/exports-named.ts +1 -1
- package/src/hls.ts +84 -47
- package/src/loader/date-range.ts +71 -5
- package/src/loader/fragment-loader.ts +23 -21
- package/src/loader/fragment.ts +8 -4
- package/src/loader/key-loader.ts +3 -1
- package/src/loader/level-details.ts +6 -6
- package/src/loader/level-key.ts +10 -9
- package/src/loader/m3u8-parser.ts +138 -144
- package/src/loader/playlist-loader.ts +5 -7
- package/src/remux/mp4-generator.ts +196 -1
- package/src/remux/mp4-remuxer.ts +32 -62
- package/src/remux/passthrough-remuxer.ts +1 -1
- package/src/task-loop.ts +5 -2
- package/src/types/component-api.ts +3 -1
- package/src/types/demuxer.ts +3 -0
- package/src/types/events.ts +19 -6
- package/src/types/fragment-tracker.ts +2 -2
- package/src/types/media-playlist.ts +9 -1
- package/src/types/remuxer.ts +1 -1
- package/src/utils/attr-list.ts +96 -9
- package/src/utils/buffer-helper.ts +12 -31
- package/src/utils/cea-608-parser.ts +1 -3
- package/src/utils/codecs.ts +34 -5
- package/src/utils/encryption-methods-util.ts +21 -0
- package/src/utils/fetch-loader.ts +1 -1
- package/src/utils/hash.ts +10 -0
- package/src/utils/hdr.ts +4 -7
- package/src/utils/imsc1-ttml-parser.ts +1 -1
- package/src/utils/keysystem-util.ts +1 -6
- package/src/utils/level-helper.ts +71 -44
- package/src/utils/logger.ts +58 -23
- package/src/utils/mp4-tools.ts +5 -3
- package/src/utils/rendition-helper.ts +100 -74
- package/src/utils/utf8-utils.ts +18 -0
- package/src/utils/variable-substitution.ts +0 -19
- package/src/utils/webvtt-parser.ts +2 -12
- package/src/demux/id3.ts +0 -411
- package/src/types/general.ts +0 -6
@@ -1,14 +1,18 @@
|
|
1
1
|
import BaseStreamController, { State } from './base-stream-controller';
|
2
2
|
import { Events } from '../events';
|
3
|
-
import { Bufferable, BufferHelper } from '../utils/buffer-helper';
|
4
3
|
import { FragmentState } from './fragment-tracker';
|
5
4
|
import { Level } from '../types/level';
|
6
5
|
import { PlaylistContextType, PlaylistLevelType } from '../types/loader';
|
7
|
-
import {
|
6
|
+
import {
|
7
|
+
Fragment,
|
8
|
+
ElementaryStreamTypes,
|
9
|
+
Part,
|
10
|
+
MediaFragment,
|
11
|
+
} from '../loader/fragment';
|
8
12
|
import ChunkCache from '../demux/chunk-cache';
|
9
13
|
import TransmuxerInterface from '../demux/transmuxer-interface';
|
10
14
|
import { ChunkMetadata } from '../types/transmuxer';
|
11
|
-
import {
|
15
|
+
import { findFragWithCC, findNearestWithCC } from './fragment-finders';
|
12
16
|
import { alignMediaPlaylistByPDT } from '../utils/discontinuities';
|
13
17
|
import { mediaAttributesIdentical } from '../utils/media-option-attributes';
|
14
18
|
import { ErrorDetails } from '../errors';
|
@@ -34,13 +38,15 @@ import type {
|
|
34
38
|
FragBufferedData,
|
35
39
|
ErrorData,
|
36
40
|
BufferFlushingData,
|
41
|
+
BufferCodecsData,
|
42
|
+
FragLoadingData,
|
37
43
|
} from '../types/events';
|
38
44
|
import type { MediaPlaylist } from '../types/media-playlist';
|
39
45
|
|
40
46
|
const TICK_INTERVAL = 100; // how often to tick in ms
|
41
47
|
|
42
48
|
type WaitingForPTSData = {
|
43
|
-
frag:
|
49
|
+
frag: MediaFragment;
|
44
50
|
part: Part | null;
|
45
51
|
cache: ChunkCache;
|
46
52
|
complete: boolean;
|
@@ -50,9 +56,8 @@ class AudioStreamController
|
|
50
56
|
extends BaseStreamController
|
51
57
|
implements NetworkComponentAPI
|
52
58
|
{
|
53
|
-
private
|
54
|
-
private
|
55
|
-
private waitingVideoCC: number = -1;
|
59
|
+
private videoAnchor: MediaFragment | null = null;
|
60
|
+
private mainFragLoading: FragLoadingData | null = null;
|
56
61
|
private bufferedTrack: MediaPlaylist | null = null;
|
57
62
|
private switchingTrack: MediaPlaylist | null = null;
|
58
63
|
private trackId: number = -1;
|
@@ -71,53 +76,52 @@ class AudioStreamController
|
|
71
76
|
hls,
|
72
77
|
fragmentTracker,
|
73
78
|
keyLoader,
|
74
|
-
'
|
79
|
+
'audio-stream-controller',
|
75
80
|
PlaylistLevelType.AUDIO,
|
76
81
|
);
|
77
|
-
this.
|
82
|
+
this.registerListeners();
|
78
83
|
}
|
79
84
|
|
80
85
|
protected onHandlerDestroying() {
|
81
|
-
this.
|
86
|
+
this.unregisterListeners();
|
82
87
|
super.onHandlerDestroying();
|
83
88
|
this.mainDetails = null;
|
84
89
|
this.bufferedTrack = null;
|
85
90
|
this.switchingTrack = null;
|
86
91
|
}
|
87
92
|
|
88
|
-
|
93
|
+
protected registerListeners() {
|
94
|
+
super.registerListeners();
|
89
95
|
const { hls } = this;
|
90
|
-
hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
91
|
-
hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
92
|
-
hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
93
96
|
hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
94
97
|
hls.on(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
|
95
98
|
hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
96
99
|
hls.on(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
|
97
|
-
hls.on(Events.ERROR, this.onError, this);
|
98
100
|
hls.on(Events.BUFFER_RESET, this.onBufferReset, this);
|
99
101
|
hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
100
102
|
hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
|
101
103
|
hls.on(Events.BUFFER_FLUSHED, this.onBufferFlushed, this);
|
102
104
|
hls.on(Events.INIT_PTS_FOUND, this.onInitPtsFound, this);
|
105
|
+
hls.on(Events.FRAG_LOADING, this.onFragLoading, this);
|
103
106
|
hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
104
107
|
}
|
105
108
|
|
106
|
-
|
109
|
+
protected unregisterListeners() {
|
107
110
|
const { hls } = this;
|
108
|
-
hls
|
109
|
-
|
110
|
-
|
111
|
+
if (!hls) {
|
112
|
+
return;
|
113
|
+
}
|
114
|
+
super.unregisterListeners();
|
111
115
|
hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
112
116
|
hls.off(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
|
113
117
|
hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
114
118
|
hls.off(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
|
115
|
-
hls.off(Events.ERROR, this.onError, this);
|
116
119
|
hls.off(Events.BUFFER_RESET, this.onBufferReset, this);
|
117
120
|
hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
118
121
|
hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
|
119
122
|
hls.off(Events.BUFFER_FLUSHED, this.onBufferFlushed, this);
|
120
123
|
hls.off(Events.INIT_PTS_FOUND, this.onInitPtsFound, this);
|
124
|
+
hls.on(Events.FRAG_LOADING, this.onFragLoading, this);
|
121
125
|
hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
122
126
|
}
|
123
127
|
|
@@ -128,18 +132,44 @@ class AudioStreamController
|
|
128
132
|
) {
|
129
133
|
// Always update the new INIT PTS
|
130
134
|
// Can change due level switch
|
131
|
-
if (id ===
|
135
|
+
if (id === PlaylistLevelType.MAIN) {
|
132
136
|
const cc = frag.cc;
|
133
|
-
|
134
|
-
this.
|
135
|
-
this.
|
137
|
+
const inFlightFrag = this.fragCurrent;
|
138
|
+
this.initPTS[cc] = { baseTime: initPTS, timescale };
|
139
|
+
this.log(
|
140
|
+
`InitPTS for cc: ${cc} found from main: ${initPTS}/${timescale}`,
|
141
|
+
);
|
142
|
+
this.videoAnchor = frag;
|
136
143
|
// If we are waiting, tick immediately to unblock audio fragment transmuxing
|
137
144
|
if (this.state === State.WAITING_INIT_PTS) {
|
145
|
+
const waitingData = this.waitingData;
|
146
|
+
if (!waitingData || waitingData.frag.cc !== cc) {
|
147
|
+
this.nextLoadPosition = this.findSyncFrag(frag).start;
|
148
|
+
}
|
138
149
|
this.tick();
|
150
|
+
} else if (
|
151
|
+
!this.loadedmetadata &&
|
152
|
+
inFlightFrag &&
|
153
|
+
inFlightFrag.cc !== cc
|
154
|
+
) {
|
155
|
+
this.startFragRequested = false;
|
156
|
+
this.nextLoadPosition = this.findSyncFrag(frag).start;
|
157
|
+
inFlightFrag.abortRequests();
|
158
|
+
this.resetLoadingState();
|
139
159
|
}
|
140
160
|
}
|
141
161
|
}
|
142
162
|
|
163
|
+
private findSyncFrag(mainFrag: MediaFragment): MediaFragment {
|
164
|
+
const trackDetails = this.getLevelDetails();
|
165
|
+
const cc = mainFrag.cc;
|
166
|
+
return (
|
167
|
+
findNearestWithCC(trackDetails, cc, mainFrag) ||
|
168
|
+
(trackDetails && findFragWithCC(trackDetails.fragments, cc)) ||
|
169
|
+
mainFrag
|
170
|
+
);
|
171
|
+
}
|
172
|
+
|
143
173
|
startLoad(startPosition: number) {
|
144
174
|
if (!this.levels) {
|
145
175
|
this.startPosition = startPosition;
|
@@ -202,9 +232,9 @@ class AudioStreamController
|
|
202
232
|
const waitingData = this.waitingData;
|
203
233
|
if (waitingData) {
|
204
234
|
const { frag, part, cache, complete } = waitingData;
|
235
|
+
const videoAnchor = this.videoAnchor;
|
205
236
|
if (this.initPTS[frag.cc] !== undefined) {
|
206
237
|
this.waitingData = null;
|
207
|
-
this.waitingVideoCC = -1;
|
208
238
|
this.state = State.FRAG_LOADING;
|
209
239
|
const payload = cache.flush();
|
210
240
|
const data: FragLoadedData = {
|
@@ -217,33 +247,15 @@ class AudioStreamController
|
|
217
247
|
if (complete) {
|
218
248
|
super._handleFragmentLoadComplete(data);
|
219
249
|
}
|
220
|
-
} else if (
|
250
|
+
} else if (videoAnchor && videoAnchor.cc !== waitingData.frag.cc) {
|
221
251
|
// Drop waiting fragment if videoTrackCC has changed since waitingFragment was set and initPTS was not found
|
222
252
|
this.log(
|
223
|
-
`Waiting fragment cc (${frag.cc}) cancelled because video is at cc ${
|
253
|
+
`Waiting fragment cc (${frag.cc}) cancelled because video is at cc ${videoAnchor.cc}`,
|
224
254
|
);
|
255
|
+
this.nextLoadPosition = this.findSyncFrag(videoAnchor).start;
|
225
256
|
this.clearWaitingFragment();
|
226
|
-
} else {
|
227
|
-
// Drop waiting fragment if an earlier fragment is needed
|
228
|
-
const pos = this.getLoadPosition();
|
229
|
-
const bufferInfo = BufferHelper.bufferInfo(
|
230
|
-
this.mediaBuffer,
|
231
|
-
pos,
|
232
|
-
this.config.maxBufferHole,
|
233
|
-
);
|
234
|
-
const waitingFragmentAtPosition = fragmentWithinToleranceTest(
|
235
|
-
bufferInfo.end,
|
236
|
-
this.config.maxFragLookUpTolerance,
|
237
|
-
frag,
|
238
|
-
);
|
239
|
-
if (waitingFragmentAtPosition < 0) {
|
240
|
-
this.log(
|
241
|
-
`Waiting fragment cc (${frag.cc}) @ ${frag.start} cancelled because another fragment at ${bufferInfo.end} is needed`,
|
242
|
-
);
|
243
|
-
this.clearWaitingFragment();
|
244
|
-
}
|
245
257
|
}
|
246
|
-
} else {
|
258
|
+
} else if (this.state !== State.STOPPED) {
|
247
259
|
this.state = State.IDLE;
|
248
260
|
}
|
249
261
|
}
|
@@ -255,10 +267,15 @@ class AudioStreamController
|
|
255
267
|
clearWaitingFragment() {
|
256
268
|
const waitingData = this.waitingData;
|
257
269
|
if (waitingData) {
|
270
|
+
if (!this.loadedmetadata) {
|
271
|
+
// Load overlapping fragment on start when discontinuity start times are not aligned
|
272
|
+
this.startFragRequested = false;
|
273
|
+
}
|
258
274
|
this.fragmentTracker.removeFragment(waitingData.frag);
|
259
275
|
this.waitingData = null;
|
260
|
-
this.
|
261
|
-
|
276
|
+
if (this.state !== State.STOPPED) {
|
277
|
+
this.state = State.IDLE;
|
278
|
+
}
|
262
279
|
}
|
263
280
|
}
|
264
281
|
|
@@ -281,12 +298,14 @@ class AudioStreamController
|
|
281
298
|
const { hls, levels, media, trackId } = this;
|
282
299
|
const config = hls.config;
|
283
300
|
|
284
|
-
// 1. if
|
301
|
+
// 1. if buffering is suspended
|
302
|
+
// 2. if video not attached AND
|
285
303
|
// start fragment already requested OR start frag prefetch not enabled
|
286
|
-
//
|
304
|
+
// 3. if tracks or track not loaded and selected
|
287
305
|
// then exit loop
|
288
306
|
// => if media not attached but start frag prefetch is enabled and start frag not requested yet, we will not exit loop
|
289
307
|
if (
|
308
|
+
!this.buffering ||
|
290
309
|
(!media && (this.startFragRequested || !config.startFragPrefetch)) ||
|
291
310
|
!levels?.[trackId]
|
292
311
|
) {
|
@@ -330,21 +349,16 @@ class AudioStreamController
|
|
330
349
|
return;
|
331
350
|
}
|
332
351
|
|
333
|
-
const mainBufferInfo = this.getFwdBufferInfo(
|
334
|
-
this.videoBuffer ? this.videoBuffer : this.media,
|
335
|
-
PlaylistLevelType.MAIN,
|
336
|
-
);
|
337
352
|
const bufferLen = bufferInfo.len;
|
338
|
-
const maxBufLen =
|
353
|
+
const maxBufLen = hls.maxBufferLength;
|
339
354
|
|
340
355
|
const fragments = trackDetails.fragments;
|
341
356
|
const start = fragments[0].start;
|
342
|
-
|
343
|
-
|
344
|
-
: bufferInfo.end;
|
357
|
+
const loadPosition = this.getLoadPosition();
|
358
|
+
let targetBufferTime = this.flushing ? loadPosition : bufferInfo.end;
|
345
359
|
|
346
360
|
if (switchingTrack && media) {
|
347
|
-
const pos =
|
361
|
+
const pos = loadPosition;
|
348
362
|
// STABLE
|
349
363
|
if (
|
350
364
|
bufferedTrack &&
|
@@ -374,10 +388,8 @@ class AudioStreamController
|
|
374
388
|
}
|
375
389
|
|
376
390
|
let frag = this.getNextFragment(targetBufferTime, trackDetails);
|
377
|
-
let atGap = false;
|
378
391
|
// Avoid loop loading by using nextLoadPosition set for backtracking and skipping consecutive GAP tags
|
379
392
|
if (frag && this.isLoopLoading(frag, targetBufferTime)) {
|
380
|
-
atGap = !!frag.gap;
|
381
393
|
frag = this.getNextFragmentLoopLoading(
|
382
394
|
frag,
|
383
395
|
trackDetails,
|
@@ -391,27 +403,34 @@ class AudioStreamController
|
|
391
403
|
return;
|
392
404
|
}
|
393
405
|
|
394
|
-
//
|
395
|
-
const
|
396
|
-
mainBufferInfo &&
|
397
|
-
frag.start > mainBufferInfo.end + trackDetails.targetduration;
|
406
|
+
// Request audio segments up to one fragment ahead of main stream-controller
|
407
|
+
const mainFragLoading = this.mainFragLoading?.frag;
|
398
408
|
if (
|
399
|
-
|
400
|
-
|
401
|
-
|
409
|
+
this.startFragRequested &&
|
410
|
+
mainFragLoading &&
|
411
|
+
mainFragLoading.sn !== 'initSegment' &&
|
412
|
+
frag.sn !== 'initSegment' &&
|
413
|
+
!frag.endList &&
|
414
|
+
(!trackDetails.live ||
|
415
|
+
(!this.loadingParts && targetBufferTime < this.hls.liveSyncPosition!))
|
402
416
|
) {
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
417
|
+
let mainFrag = mainFragLoading;
|
418
|
+
if (frag.start > mainFrag.end) {
|
419
|
+
// Get buffered frag at target position from tracker (loaded out of sequence)
|
420
|
+
const mainFragAtPos = this.fragmentTracker.getFragAtPos(
|
421
|
+
targetBufferTime,
|
422
|
+
PlaylistLevelType.MAIN,
|
423
|
+
);
|
424
|
+
if (mainFragAtPos && mainFragAtPos.end > mainFragLoading.end) {
|
425
|
+
mainFrag = mainFragAtPos;
|
426
|
+
this.mainFragLoading = {
|
427
|
+
frag: mainFragAtPos,
|
428
|
+
targetBufferTime: null,
|
429
|
+
};
|
430
|
+
}
|
407
431
|
}
|
408
|
-
|
409
|
-
|
410
|
-
!!mainFrag.gap || (!!atBufferSyncLimit && mainBufferInfo.len === 0);
|
411
|
-
if (
|
412
|
-
(atBufferSyncLimit && !atGap) ||
|
413
|
-
(atGap && bufferInfo.nextStart && bufferInfo.nextStart < mainFrag.end)
|
414
|
-
) {
|
432
|
+
const atBufferSyncLimit = frag.start > mainFrag.end;
|
433
|
+
if (atBufferSyncLimit) {
|
415
434
|
return;
|
416
435
|
}
|
417
436
|
}
|
@@ -419,24 +438,12 @@ class AudioStreamController
|
|
419
438
|
this.loadFragment(frag, levelInfo, targetBufferTime);
|
420
439
|
}
|
421
440
|
|
422
|
-
protected
|
423
|
-
const maxConfigBuffer = super.getMaxBufferLength();
|
424
|
-
if (!mainBufferLength) {
|
425
|
-
return maxConfigBuffer;
|
426
|
-
}
|
427
|
-
return Math.min(
|
428
|
-
Math.max(maxConfigBuffer, mainBufferLength),
|
429
|
-
this.config.maxMaxBufferLength,
|
430
|
-
);
|
431
|
-
}
|
432
|
-
|
433
|
-
onMediaDetaching() {
|
434
|
-
this.videoBuffer = null;
|
441
|
+
protected onMediaDetaching() {
|
435
442
|
this.bufferFlushed = this.flushing = false;
|
436
443
|
super.onMediaDetaching();
|
437
444
|
}
|
438
445
|
|
439
|
-
onAudioTracksUpdated(
|
446
|
+
private onAudioTracksUpdated(
|
440
447
|
event: Events.AUDIO_TRACKS_UPDATED,
|
441
448
|
{ audioTracks }: AudioTracksUpdatedData,
|
442
449
|
) {
|
@@ -445,7 +452,7 @@ class AudioStreamController
|
|
445
452
|
this.levels = audioTracks.map((mediaPlaylist) => new Level(mediaPlaylist));
|
446
453
|
}
|
447
454
|
|
448
|
-
onAudioTrackSwitching(
|
455
|
+
private onAudioTrackSwitching(
|
449
456
|
event: Events.AUDIO_TRACK_SWITCHING,
|
450
457
|
data: AudioTrackSwitchingData,
|
451
458
|
) {
|
@@ -459,44 +466,41 @@ class AudioStreamController
|
|
459
466
|
this.removeUnbufferedFrags(fragCurrent.start);
|
460
467
|
}
|
461
468
|
this.resetLoadingState();
|
462
|
-
// destroy useless transmuxer when switching audio to main
|
463
|
-
if (!altAudio) {
|
464
|
-
this.resetTransmuxer();
|
465
|
-
} else {
|
466
|
-
// switching to audio track, start timer if not already started
|
467
|
-
this.setInterval(TICK_INTERVAL);
|
468
|
-
}
|
469
469
|
|
470
470
|
// should we switch tracks ?
|
471
471
|
if (altAudio) {
|
472
472
|
this.switchingTrack = data;
|
473
473
|
// main audio track are handled by stream-controller, just do something if switching to alt audio track
|
474
|
-
this.state = State.IDLE;
|
475
474
|
this.flushAudioIfNeeded(data);
|
475
|
+
if (this.state !== State.STOPPED) {
|
476
|
+
// switching to audio track, start timer if not already started
|
477
|
+
this.setInterval(TICK_INTERVAL);
|
478
|
+
this.state = State.IDLE;
|
479
|
+
this.tick();
|
480
|
+
}
|
476
481
|
} else {
|
482
|
+
// destroy useless transmuxer when switching audio to main
|
483
|
+
this.resetTransmuxer();
|
477
484
|
this.switchingTrack = null;
|
478
485
|
this.bufferedTrack = data;
|
479
|
-
this.
|
486
|
+
this.clearInterval();
|
480
487
|
}
|
481
|
-
this.tick();
|
482
488
|
}
|
483
489
|
|
484
|
-
onManifestLoading() {
|
485
|
-
|
486
|
-
this.startPosition = this.lastCurrentTime = 0;
|
490
|
+
protected onManifestLoading() {
|
491
|
+
super.onManifestLoading();
|
487
492
|
this.bufferFlushed = this.flushing = false;
|
488
|
-
this.
|
489
|
-
this.mainDetails =
|
493
|
+
this.mainDetails =
|
490
494
|
this.waitingData =
|
495
|
+
this.videoAnchor =
|
491
496
|
this.bufferedTrack =
|
492
497
|
this.cachedTrackLoadedData =
|
493
498
|
this.switchingTrack =
|
494
499
|
null;
|
495
|
-
this.
|
496
|
-
this.trackId = this.videoTrackCC = this.waitingVideoCC = -1;
|
500
|
+
this.trackId = -1;
|
497
501
|
}
|
498
502
|
|
499
|
-
onLevelLoaded(event: Events.LEVEL_LOADED, data: LevelLoadedData) {
|
503
|
+
private onLevelLoaded(event: Events.LEVEL_LOADED, data: LevelLoadedData) {
|
500
504
|
this.mainDetails = data.details;
|
501
505
|
if (this.cachedTrackLoadedData !== null) {
|
502
506
|
this.hls.trigger(Events.AUDIO_TRACK_LOADED, this.cachedTrackLoadedData);
|
@@ -504,7 +508,10 @@ class AudioStreamController
|
|
504
508
|
}
|
505
509
|
}
|
506
510
|
|
507
|
-
onAudioTrackLoaded(
|
511
|
+
private onAudioTrackLoaded(
|
512
|
+
event: Events.AUDIO_TRACK_LOADED,
|
513
|
+
data: TrackLoadedData,
|
514
|
+
) {
|
508
515
|
if (this.mainDetails == null) {
|
509
516
|
this.cachedTrackLoadedData = data;
|
510
517
|
return;
|
@@ -570,7 +577,8 @@ class AudioStreamController
|
|
570
577
|
}
|
571
578
|
|
572
579
|
_handleFragmentLoadProgress(data: FragLoadedData) {
|
573
|
-
const
|
580
|
+
const frag = data.frag as MediaFragment;
|
581
|
+
const { part, payload } = data;
|
574
582
|
const { config, trackId, levels } = this;
|
575
583
|
if (!levels) {
|
576
584
|
this.warn(
|
@@ -615,7 +623,7 @@ class AudioStreamController
|
|
615
623
|
const partial = partIndex !== -1;
|
616
624
|
const chunkMeta = new ChunkMetadata(
|
617
625
|
frag.level,
|
618
|
-
frag.sn
|
626
|
+
frag.sn,
|
619
627
|
frag.stats.chunkCount,
|
620
628
|
payload.byteLength,
|
621
629
|
partIndex,
|
@@ -644,7 +652,6 @@ class AudioStreamController
|
|
644
652
|
complete: false,
|
645
653
|
});
|
646
654
|
cache.push(new Uint8Array(payload));
|
647
|
-
this.waitingVideoCC = this.videoTrackCC;
|
648
655
|
this.state = State.WAITING_INIT_PTS;
|
649
656
|
}
|
650
657
|
}
|
@@ -657,32 +664,44 @@ class AudioStreamController
|
|
657
664
|
super._handleFragmentLoadComplete(fragLoadedData);
|
658
665
|
}
|
659
666
|
|
660
|
-
onBufferReset(/* event: Events.BUFFER_RESET */) {
|
667
|
+
private onBufferReset(/* event: Events.BUFFER_RESET */) {
|
661
668
|
// reset reference to sourcebuffers
|
662
|
-
this.mediaBuffer =
|
669
|
+
this.mediaBuffer = null;
|
663
670
|
this.loadedmetadata = false;
|
664
671
|
}
|
665
672
|
|
666
|
-
onBufferCreated(
|
673
|
+
private onBufferCreated(
|
674
|
+
event: Events.BUFFER_CREATED,
|
675
|
+
data: BufferCreatedData,
|
676
|
+
) {
|
667
677
|
const audioTrack = data.tracks.audio;
|
668
678
|
if (audioTrack) {
|
669
679
|
this.mediaBuffer = audioTrack.buffer || null;
|
670
680
|
}
|
671
|
-
|
672
|
-
|
681
|
+
}
|
682
|
+
|
683
|
+
private onFragLoading(event: Events.FRAG_LOADING, data: FragLoadingData) {
|
684
|
+
if (
|
685
|
+
data.frag.type === PlaylistLevelType.MAIN &&
|
686
|
+
data.frag.sn !== 'initSegment'
|
687
|
+
) {
|
688
|
+
this.mainFragLoading = data;
|
689
|
+
if (this.state === State.IDLE) {
|
690
|
+
this.tick();
|
691
|
+
}
|
673
692
|
}
|
674
693
|
}
|
675
694
|
|
676
|
-
onFragBuffered(event: Events.FRAG_BUFFERED, data: FragBufferedData) {
|
695
|
+
private onFragBuffered(event: Events.FRAG_BUFFERED, data: FragBufferedData) {
|
677
696
|
const { frag, part } = data;
|
678
697
|
if (frag.type !== PlaylistLevelType.AUDIO) {
|
679
698
|
if (!this.loadedmetadata && frag.type === PlaylistLevelType.MAIN) {
|
680
|
-
const
|
681
|
-
if (
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
699
|
+
const bufferedState = this.fragmentTracker.getState(frag);
|
700
|
+
if (
|
701
|
+
bufferedState === FragmentState.OK ||
|
702
|
+
bufferedState === FragmentState.PARTIAL
|
703
|
+
) {
|
704
|
+
this.loadedmetadata = true;
|
686
705
|
}
|
687
706
|
}
|
688
707
|
return;
|
@@ -702,7 +721,7 @@ class AudioStreamController
|
|
702
721
|
return;
|
703
722
|
}
|
704
723
|
if (frag.sn !== 'initSegment') {
|
705
|
-
this.fragPrevious = frag;
|
724
|
+
this.fragPrevious = frag as MediaFragment;
|
706
725
|
const track = this.switchingTrack;
|
707
726
|
if (track) {
|
708
727
|
this.bufferedTrack = track;
|
@@ -713,7 +732,7 @@ class AudioStreamController
|
|
713
732
|
this.fragBufferedComplete(frag, part);
|
714
733
|
}
|
715
734
|
|
716
|
-
|
735
|
+
protected onError(event: Events.ERROR, data: ErrorData) {
|
717
736
|
if (data.fatal) {
|
718
737
|
this.state = State.ERROR;
|
719
738
|
return;
|
@@ -887,12 +906,15 @@ class AudioStreamController
|
|
887
906
|
if (tracks.video) {
|
888
907
|
delete tracks.video;
|
889
908
|
}
|
909
|
+
if (tracks.audiovideo) {
|
910
|
+
delete tracks.audiovideo;
|
911
|
+
}
|
890
912
|
|
891
913
|
// include levelCodec in audio and video tracks
|
892
|
-
|
893
|
-
if (!track) {
|
914
|
+
if (!tracks.audio) {
|
894
915
|
return;
|
895
916
|
}
|
917
|
+
const track = tracks.audio;
|
896
918
|
|
897
919
|
track.id = 'audio';
|
898
920
|
|
@@ -904,7 +926,7 @@ class AudioStreamController
|
|
904
926
|
if (variantAudioCodecs && variantAudioCodecs.split(',').length === 1) {
|
905
927
|
track.levelCodec = variantAudioCodecs;
|
906
928
|
}
|
907
|
-
this.hls.trigger(Events.BUFFER_CODECS, tracks);
|
929
|
+
this.hls.trigger(Events.BUFFER_CODECS, tracks as BufferCodecsData);
|
908
930
|
const initSegment = track.initSegment;
|
909
931
|
if (initSegment?.byteLength) {
|
910
932
|
const segment: BufferAppendingData = {
|
@@ -928,7 +950,6 @@ class AudioStreamController
|
|
928
950
|
) {
|
929
951
|
// only load if fragment is not loaded or if in audio switch
|
930
952
|
const fragState = this.fragmentTracker.getState(frag);
|
931
|
-
this.fragCurrent = frag;
|
932
953
|
|
933
954
|
// we force a frag loading in audio switch as fragment tracker might not have evicted previous frags in case of quick audio switch
|
934
955
|
if (
|
@@ -951,7 +972,6 @@ class AudioStreamController
|
|
951
972
|
alignMediaPlaylistByPDT(track.details, mainDetails);
|
952
973
|
}
|
953
974
|
} else {
|
954
|
-
this.startFragRequested = true;
|
955
975
|
super.loadFragment(frag, track, targetBufferTime);
|
956
976
|
}
|
957
977
|
} else {
|
@@ -1,11 +1,16 @@
|
|
1
1
|
import type Hls from '../hls';
|
2
2
|
import type { NetworkComponentAPI } from '../types/component-api';
|
3
|
-
import {
|
3
|
+
import {
|
4
|
+
getSkipValue,
|
5
|
+
HlsSkip,
|
6
|
+
HlsUrlParameters,
|
7
|
+
type Level,
|
8
|
+
} from '../types/level';
|
4
9
|
import { computeReloadInterval, mergeDetails } from '../utils/level-helper';
|
5
|
-
import { ErrorData } from '../types/events';
|
10
|
+
import type { ErrorData } from '../types/events';
|
6
11
|
import { getRetryDelay, isTimeoutError } from '../utils/error-helper';
|
7
12
|
import { NetworkErrorAction } from './error-controller';
|
8
|
-
import {
|
13
|
+
import { Logger } from '../utils/logger';
|
9
14
|
import type { LevelDetails } from '../loader/level-details';
|
10
15
|
import type { MediaPlaylist } from '../types/media-playlist';
|
11
16
|
import type {
|
@@ -14,17 +19,17 @@ import type {
|
|
14
19
|
TrackLoadedData,
|
15
20
|
} from '../types/events';
|
16
21
|
|
17
|
-
export default class BasePlaylistController
|
22
|
+
export default class BasePlaylistController
|
23
|
+
extends Logger
|
24
|
+
implements NetworkComponentAPI
|
25
|
+
{
|
18
26
|
protected hls: Hls;
|
19
27
|
protected timer: number = -1;
|
20
28
|
protected requestScheduled: number = -1;
|
21
29
|
protected canLoad: boolean = false;
|
22
|
-
protected log: (msg: any) => void;
|
23
|
-
protected warn: (msg: any) => void;
|
24
30
|
|
25
31
|
constructor(hls: Hls, logPrefix: string) {
|
26
|
-
|
27
|
-
this.warn = logger.warn.bind(logger, `${logPrefix}:`);
|
32
|
+
super(logPrefix, hls.logger);
|
28
33
|
this.hls = hls;
|
29
34
|
}
|
30
35
|
|
@@ -66,7 +71,7 @@ export default class BasePlaylistController implements NetworkComponentAPI {
|
|
66
71
|
try {
|
67
72
|
uri = new self.URL(attr.URI, previous.url).href;
|
68
73
|
} catch (error) {
|
69
|
-
|
74
|
+
this.warn(
|
70
75
|
`Could not construct new URL for Rendition Report: ${error}`,
|
71
76
|
);
|
72
77
|
uri = attr.URI || '';
|
@@ -190,7 +195,19 @@ export default class BasePlaylistController implements NetworkComponentAPI {
|
|
190
195
|
details.targetduration * 1.5,
|
191
196
|
);
|
192
197
|
if (currentGoal > 0) {
|
193
|
-
if (
|
198
|
+
if (cdnAge > details.targetduration * 3) {
|
199
|
+
// Omit segment and part directives when the last response was more than 3 target durations ago,
|
200
|
+
this.log(
|
201
|
+
`Playlist last advanced ${lastAdvanced.toFixed(
|
202
|
+
2,
|
203
|
+
)}s ago. Omitting segment and part directives.`,
|
204
|
+
);
|
205
|
+
msn = undefined;
|
206
|
+
part = undefined;
|
207
|
+
} else if (
|
208
|
+
previousDetails?.tuneInGoal &&
|
209
|
+
cdnAge - details.partTarget > previousDetails.tuneInGoal
|
210
|
+
) {
|
194
211
|
// If we attempted to get the next or latest playlist update, but currentGoal increased,
|
195
212
|
// then we either can't catchup, or the "age" header cannot be trusted.
|
196
213
|
this.warn(
|