hls.js 1.5.12 → 1.5.13-0.canary.10401
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 +4174 -2625
- package/dist/hls.js.d.ts +173 -108
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +2851 -1914
- 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 +2560 -1608
- 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 +3546 -1982
- 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 +141 -137
- 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 +215 -82
- 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 +73 -43
- 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 +36 -16
- 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,18 @@ class AudioStreamController
|
|
391
403
|
return;
|
392
404
|
}
|
393
405
|
|
394
|
-
// Buffer audio up to one target duration ahead of main buffer
|
395
|
-
const atBufferSyncLimit =
|
396
|
-
mainBufferInfo &&
|
397
|
-
frag.start > mainBufferInfo.end + trackDetails.targetduration;
|
398
406
|
if (
|
399
|
-
|
400
|
-
|
401
|
-
(!mainBufferInfo?.len && bufferInfo.len)
|
407
|
+
this.startFragRequested &&
|
408
|
+
(!trackDetails.live || targetBufferTime < this.hls.liveSyncPosition!)
|
402
409
|
) {
|
403
|
-
//
|
404
|
-
const
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
if (
|
412
|
-
(atBufferSyncLimit && !atGap) ||
|
413
|
-
(atGap && bufferInfo.nextStart && bufferInfo.nextStart < mainFrag.end)
|
414
|
-
) {
|
410
|
+
// Request audio segments up to one fragment ahead of main buffer
|
411
|
+
const mainFragLoading = this.mainFragLoading;
|
412
|
+
const mainTargetBufferEnd = mainFragLoading
|
413
|
+
? (mainFragLoading.part || mainFragLoading.frag).end
|
414
|
+
: null;
|
415
|
+
const atBufferSyncLimit =
|
416
|
+
mainTargetBufferEnd !== null && frag.start > mainTargetBufferEnd;
|
417
|
+
if (atBufferSyncLimit && !frag.endList) {
|
415
418
|
return;
|
416
419
|
}
|
417
420
|
}
|
@@ -419,24 +422,12 @@ class AudioStreamController
|
|
419
422
|
this.loadFragment(frag, levelInfo, targetBufferTime);
|
420
423
|
}
|
421
424
|
|
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;
|
425
|
+
protected onMediaDetaching() {
|
435
426
|
this.bufferFlushed = this.flushing = false;
|
436
427
|
super.onMediaDetaching();
|
437
428
|
}
|
438
429
|
|
439
|
-
onAudioTracksUpdated(
|
430
|
+
private onAudioTracksUpdated(
|
440
431
|
event: Events.AUDIO_TRACKS_UPDATED,
|
441
432
|
{ audioTracks }: AudioTracksUpdatedData,
|
442
433
|
) {
|
@@ -445,7 +436,7 @@ class AudioStreamController
|
|
445
436
|
this.levels = audioTracks.map((mediaPlaylist) => new Level(mediaPlaylist));
|
446
437
|
}
|
447
438
|
|
448
|
-
onAudioTrackSwitching(
|
439
|
+
private onAudioTrackSwitching(
|
449
440
|
event: Events.AUDIO_TRACK_SWITCHING,
|
450
441
|
data: AudioTrackSwitchingData,
|
451
442
|
) {
|
@@ -459,44 +450,41 @@ class AudioStreamController
|
|
459
450
|
this.removeUnbufferedFrags(fragCurrent.start);
|
460
451
|
}
|
461
452
|
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
453
|
|
470
454
|
// should we switch tracks ?
|
471
455
|
if (altAudio) {
|
472
456
|
this.switchingTrack = data;
|
473
457
|
// main audio track are handled by stream-controller, just do something if switching to alt audio track
|
474
|
-
this.state = State.IDLE;
|
475
458
|
this.flushAudioIfNeeded(data);
|
459
|
+
if (this.state !== State.STOPPED) {
|
460
|
+
// switching to audio track, start timer if not already started
|
461
|
+
this.setInterval(TICK_INTERVAL);
|
462
|
+
this.state = State.IDLE;
|
463
|
+
this.tick();
|
464
|
+
}
|
476
465
|
} else {
|
466
|
+
// destroy useless transmuxer when switching audio to main
|
467
|
+
this.resetTransmuxer();
|
477
468
|
this.switchingTrack = null;
|
478
469
|
this.bufferedTrack = data;
|
479
|
-
this.
|
470
|
+
this.clearInterval();
|
480
471
|
}
|
481
|
-
this.tick();
|
482
472
|
}
|
483
473
|
|
484
|
-
onManifestLoading() {
|
485
|
-
|
486
|
-
this.startPosition = this.lastCurrentTime = 0;
|
474
|
+
protected onManifestLoading() {
|
475
|
+
super.onManifestLoading();
|
487
476
|
this.bufferFlushed = this.flushing = false;
|
488
|
-
this.
|
489
|
-
this.mainDetails =
|
477
|
+
this.mainDetails =
|
490
478
|
this.waitingData =
|
479
|
+
this.videoAnchor =
|
491
480
|
this.bufferedTrack =
|
492
481
|
this.cachedTrackLoadedData =
|
493
482
|
this.switchingTrack =
|
494
483
|
null;
|
495
|
-
this.
|
496
|
-
this.trackId = this.videoTrackCC = this.waitingVideoCC = -1;
|
484
|
+
this.trackId = -1;
|
497
485
|
}
|
498
486
|
|
499
|
-
onLevelLoaded(event: Events.LEVEL_LOADED, data: LevelLoadedData) {
|
487
|
+
private onLevelLoaded(event: Events.LEVEL_LOADED, data: LevelLoadedData) {
|
500
488
|
this.mainDetails = data.details;
|
501
489
|
if (this.cachedTrackLoadedData !== null) {
|
502
490
|
this.hls.trigger(Events.AUDIO_TRACK_LOADED, this.cachedTrackLoadedData);
|
@@ -504,7 +492,10 @@ class AudioStreamController
|
|
504
492
|
}
|
505
493
|
}
|
506
494
|
|
507
|
-
onAudioTrackLoaded(
|
495
|
+
private onAudioTrackLoaded(
|
496
|
+
event: Events.AUDIO_TRACK_LOADED,
|
497
|
+
data: TrackLoadedData,
|
498
|
+
) {
|
508
499
|
if (this.mainDetails == null) {
|
509
500
|
this.cachedTrackLoadedData = data;
|
510
501
|
return;
|
@@ -570,7 +561,8 @@ class AudioStreamController
|
|
570
561
|
}
|
571
562
|
|
572
563
|
_handleFragmentLoadProgress(data: FragLoadedData) {
|
573
|
-
const
|
564
|
+
const frag = data.frag as MediaFragment;
|
565
|
+
const { part, payload } = data;
|
574
566
|
const { config, trackId, levels } = this;
|
575
567
|
if (!levels) {
|
576
568
|
this.warn(
|
@@ -615,7 +607,7 @@ class AudioStreamController
|
|
615
607
|
const partial = partIndex !== -1;
|
616
608
|
const chunkMeta = new ChunkMetadata(
|
617
609
|
frag.level,
|
618
|
-
frag.sn
|
610
|
+
frag.sn,
|
619
611
|
frag.stats.chunkCount,
|
620
612
|
payload.byteLength,
|
621
613
|
partIndex,
|
@@ -644,7 +636,6 @@ class AudioStreamController
|
|
644
636
|
complete: false,
|
645
637
|
});
|
646
638
|
cache.push(new Uint8Array(payload));
|
647
|
-
this.waitingVideoCC = this.videoTrackCC;
|
648
639
|
this.state = State.WAITING_INIT_PTS;
|
649
640
|
}
|
650
641
|
}
|
@@ -657,32 +648,44 @@ class AudioStreamController
|
|
657
648
|
super._handleFragmentLoadComplete(fragLoadedData);
|
658
649
|
}
|
659
650
|
|
660
|
-
onBufferReset(/* event: Events.BUFFER_RESET */) {
|
651
|
+
private onBufferReset(/* event: Events.BUFFER_RESET */) {
|
661
652
|
// reset reference to sourcebuffers
|
662
|
-
this.mediaBuffer =
|
653
|
+
this.mediaBuffer = null;
|
663
654
|
this.loadedmetadata = false;
|
664
655
|
}
|
665
656
|
|
666
|
-
onBufferCreated(
|
657
|
+
private onBufferCreated(
|
658
|
+
event: Events.BUFFER_CREATED,
|
659
|
+
data: BufferCreatedData,
|
660
|
+
) {
|
667
661
|
const audioTrack = data.tracks.audio;
|
668
662
|
if (audioTrack) {
|
669
663
|
this.mediaBuffer = audioTrack.buffer || null;
|
670
664
|
}
|
671
|
-
|
672
|
-
|
665
|
+
}
|
666
|
+
|
667
|
+
private onFragLoading(event: Events.FRAG_LOADING, data: FragLoadingData) {
|
668
|
+
if (
|
669
|
+
data.frag.type === PlaylistLevelType.MAIN &&
|
670
|
+
data.frag.sn !== 'initSegment'
|
671
|
+
) {
|
672
|
+
this.mainFragLoading = data;
|
673
|
+
if (this.state === State.IDLE) {
|
674
|
+
this.tick();
|
675
|
+
}
|
673
676
|
}
|
674
677
|
}
|
675
678
|
|
676
|
-
onFragBuffered(event: Events.FRAG_BUFFERED, data: FragBufferedData) {
|
679
|
+
private onFragBuffered(event: Events.FRAG_BUFFERED, data: FragBufferedData) {
|
677
680
|
const { frag, part } = data;
|
678
681
|
if (frag.type !== PlaylistLevelType.AUDIO) {
|
679
682
|
if (!this.loadedmetadata && frag.type === PlaylistLevelType.MAIN) {
|
680
|
-
const
|
681
|
-
if (
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
683
|
+
const bufferedState = this.fragmentTracker.getState(frag);
|
684
|
+
if (
|
685
|
+
bufferedState === FragmentState.OK ||
|
686
|
+
bufferedState === FragmentState.PARTIAL
|
687
|
+
) {
|
688
|
+
this.loadedmetadata = true;
|
686
689
|
}
|
687
690
|
}
|
688
691
|
return;
|
@@ -702,7 +705,7 @@ class AudioStreamController
|
|
702
705
|
return;
|
703
706
|
}
|
704
707
|
if (frag.sn !== 'initSegment') {
|
705
|
-
this.fragPrevious = frag;
|
708
|
+
this.fragPrevious = frag as MediaFragment;
|
706
709
|
const track = this.switchingTrack;
|
707
710
|
if (track) {
|
708
711
|
this.bufferedTrack = track;
|
@@ -713,7 +716,7 @@ class AudioStreamController
|
|
713
716
|
this.fragBufferedComplete(frag, part);
|
714
717
|
}
|
715
718
|
|
716
|
-
|
719
|
+
protected onError(event: Events.ERROR, data: ErrorData) {
|
717
720
|
if (data.fatal) {
|
718
721
|
this.state = State.ERROR;
|
719
722
|
return;
|
@@ -887,12 +890,15 @@ class AudioStreamController
|
|
887
890
|
if (tracks.video) {
|
888
891
|
delete tracks.video;
|
889
892
|
}
|
893
|
+
if (tracks.audiovideo) {
|
894
|
+
delete tracks.audiovideo;
|
895
|
+
}
|
890
896
|
|
891
897
|
// include levelCodec in audio and video tracks
|
892
|
-
|
893
|
-
if (!track) {
|
898
|
+
if (!tracks.audio) {
|
894
899
|
return;
|
895
900
|
}
|
901
|
+
const track = tracks.audio;
|
896
902
|
|
897
903
|
track.id = 'audio';
|
898
904
|
|
@@ -904,7 +910,7 @@ class AudioStreamController
|
|
904
910
|
if (variantAudioCodecs && variantAudioCodecs.split(',').length === 1) {
|
905
911
|
track.levelCodec = variantAudioCodecs;
|
906
912
|
}
|
907
|
-
this.hls.trigger(Events.BUFFER_CODECS, tracks);
|
913
|
+
this.hls.trigger(Events.BUFFER_CODECS, tracks as BufferCodecsData);
|
908
914
|
const initSegment = track.initSegment;
|
909
915
|
if (initSegment?.byteLength) {
|
910
916
|
const segment: BufferAppendingData = {
|
@@ -928,7 +934,6 @@ class AudioStreamController
|
|
928
934
|
) {
|
929
935
|
// only load if fragment is not loaded or if in audio switch
|
930
936
|
const fragState = this.fragmentTracker.getState(frag);
|
931
|
-
this.fragCurrent = frag;
|
932
937
|
|
933
938
|
// we force a frag loading in audio switch as fragment tracker might not have evicted previous frags in case of quick audio switch
|
934
939
|
if (
|
@@ -951,7 +956,6 @@ class AudioStreamController
|
|
951
956
|
alignMediaPlaylistByPDT(track.details, mainDetails);
|
952
957
|
}
|
953
958
|
} else {
|
954
|
-
this.startFragRequested = true;
|
955
959
|
super.loadFragment(frag, track, targetBufferTime);
|
956
960
|
}
|
957
961
|
} 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(
|