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