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