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.
Files changed (107) hide show
  1. package/README.md +3 -4
  2. package/dist/hls-demo.js +38 -41
  3. package/dist/hls-demo.js.map +1 -1
  4. package/dist/hls.js +2903 -4542
  5. package/dist/hls.js.d.ts +112 -186
  6. package/dist/hls.js.map +1 -1
  7. package/dist/hls.light.js +2284 -3295
  8. package/dist/hls.light.js.map +1 -1
  9. package/dist/hls.light.min.js +1 -1
  10. package/dist/hls.light.min.js.map +1 -1
  11. package/dist/hls.light.mjs +1804 -2817
  12. package/dist/hls.light.mjs.map +1 -1
  13. package/dist/hls.min.js +1 -1
  14. package/dist/hls.min.js.map +1 -1
  15. package/dist/hls.mjs +4652 -6293
  16. package/dist/hls.mjs.map +1 -1
  17. package/dist/hls.worker.js +1 -1
  18. package/dist/hls.worker.js.map +1 -1
  19. package/package.json +38 -38
  20. package/src/config.ts +2 -5
  21. package/src/controller/abr-controller.ts +25 -39
  22. package/src/controller/audio-stream-controller.ts +136 -156
  23. package/src/controller/audio-track-controller.ts +1 -1
  24. package/src/controller/base-playlist-controller.ts +10 -27
  25. package/src/controller/base-stream-controller.ts +107 -263
  26. package/src/controller/buffer-controller.ts +98 -252
  27. package/src/controller/buffer-operation-queue.ts +19 -16
  28. package/src/controller/cap-level-controller.ts +2 -3
  29. package/src/controller/cmcd-controller.ts +14 -51
  30. package/src/controller/content-steering-controller.ts +15 -29
  31. package/src/controller/eme-controller.ts +23 -10
  32. package/src/controller/error-controller.ts +22 -28
  33. package/src/controller/fps-controller.ts +3 -8
  34. package/src/controller/fragment-finders.ts +16 -44
  35. package/src/controller/fragment-tracker.ts +25 -58
  36. package/src/controller/gap-controller.ts +16 -43
  37. package/src/controller/id3-track-controller.ts +35 -45
  38. package/src/controller/latency-controller.ts +13 -18
  39. package/src/controller/level-controller.ts +19 -37
  40. package/src/controller/stream-controller.ts +83 -100
  41. package/src/controller/subtitle-stream-controller.ts +47 -35
  42. package/src/controller/subtitle-track-controller.ts +3 -5
  43. package/src/controller/timeline-controller.ts +22 -20
  44. package/src/crypt/aes-crypto.ts +2 -21
  45. package/src/crypt/decrypter.ts +16 -32
  46. package/src/crypt/fast-aes-key.ts +5 -28
  47. package/src/demux/audio/aacdemuxer.ts +5 -5
  48. package/src/demux/audio/ac3-demuxer.ts +4 -5
  49. package/src/demux/audio/adts.ts +4 -9
  50. package/src/demux/audio/base-audio-demuxer.ts +14 -16
  51. package/src/demux/audio/mp3demuxer.ts +3 -4
  52. package/src/demux/audio/mpegaudio.ts +1 -1
  53. package/src/demux/id3.ts +411 -0
  54. package/src/demux/inject-worker.ts +4 -38
  55. package/src/demux/mp4demuxer.ts +7 -7
  56. package/src/demux/sample-aes.ts +0 -2
  57. package/src/demux/transmuxer-interface.ts +83 -106
  58. package/src/demux/transmuxer-worker.ts +77 -111
  59. package/src/demux/transmuxer.ts +22 -46
  60. package/src/demux/tsdemuxer.ts +62 -122
  61. package/src/demux/video/avc-video-parser.ts +121 -210
  62. package/src/demux/video/base-video-parser.ts +2 -135
  63. package/src/demux/video/exp-golomb.ts +208 -0
  64. package/src/errors.ts +0 -2
  65. package/src/events.ts +1 -8
  66. package/src/exports-named.ts +1 -1
  67. package/src/hls.ts +48 -97
  68. package/src/loader/date-range.ts +5 -71
  69. package/src/loader/fragment-loader.ts +21 -23
  70. package/src/loader/fragment.ts +4 -8
  71. package/src/loader/key-loader.ts +1 -3
  72. package/src/loader/level-details.ts +6 -6
  73. package/src/loader/level-key.ts +9 -10
  74. package/src/loader/m3u8-parser.ts +144 -138
  75. package/src/loader/playlist-loader.ts +7 -5
  76. package/src/remux/mp4-generator.ts +1 -196
  77. package/src/remux/mp4-remuxer.ts +84 -55
  78. package/src/remux/passthrough-remuxer.ts +8 -23
  79. package/src/task-loop.ts +2 -5
  80. package/src/types/component-api.ts +1 -3
  81. package/src/types/demuxer.ts +0 -3
  82. package/src/types/events.ts +6 -19
  83. package/src/types/fragment-tracker.ts +2 -2
  84. package/src/types/general.ts +6 -0
  85. package/src/types/media-playlist.ts +1 -9
  86. package/src/types/remuxer.ts +1 -1
  87. package/src/utils/attr-list.ts +9 -96
  88. package/src/utils/buffer-helper.ts +31 -12
  89. package/src/utils/cea-608-parser.ts +3 -1
  90. package/src/utils/codecs.ts +5 -34
  91. package/src/utils/discontinuities.ts +47 -21
  92. package/src/utils/fetch-loader.ts +1 -1
  93. package/src/utils/hdr.ts +7 -4
  94. package/src/utils/imsc1-ttml-parser.ts +1 -1
  95. package/src/utils/keysystem-util.ts +6 -1
  96. package/src/utils/level-helper.ts +44 -71
  97. package/src/utils/logger.ts +23 -58
  98. package/src/utils/mp4-tools.ts +3 -5
  99. package/src/utils/rendition-helper.ts +74 -100
  100. package/src/utils/variable-substitution.ts +19 -0
  101. package/src/utils/webvtt-parser.ts +12 -2
  102. package/src/crypt/decrypter-aes-mode.ts +0 -4
  103. package/src/demux/video/hevc-video-parser.ts +0 -749
  104. package/src/utils/encryption-methods-util.ts +0 -21
  105. package/src/utils/hash.ts +0 -10
  106. package/src/utils/utf8-utils.ts +0 -18
  107. 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 { findFragWithCC, findNearestWithCC } from './fragment-finders';
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: MediaFragment;
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 videoAnchor: MediaFragment | null = null;
60
- private mainFragLoading: FragLoadingData | null = null;
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.registerListeners();
77
+ this._registerListeners();
83
78
  }
84
79
 
85
80
  protected onHandlerDestroying() {
86
- this.unregisterListeners();
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
- protected registerListeners() {
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
- protected unregisterListeners() {
106
+ private _unregisterListeners() {
110
107
  const { hls } = this;
111
- if (!hls) {
112
- return;
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 === PlaylistLevelType.MAIN) {
131
+ if (id === 'main') {
136
132
  const cc = frag.cc;
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;
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 (videoAnchor && videoAnchor.cc !== waitingData.frag.cc) {
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 ${videoAnchor.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 if (this.state !== State.STOPPED) {
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
- if (this.state !== State.STOPPED) {
277
- this.state = State.IDLE;
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 buffering is suspended
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
- // 3. if tracks or track not loaded and selected
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 = hls.maxBufferLength;
338
+ const maxBufLen = this.getMaxBufferLength(mainBufferInfo?.len);
354
339
 
355
340
  const fragments = trackDetails.fragments;
356
341
  const start = fragments[0].start;
357
- const loadPosition = this.getLoadPosition();
358
- let targetBufferTime = this.flushing ? loadPosition : bufferInfo.end;
342
+ let targetBufferTime = this.flushing
343
+ ? this.getLoadPosition()
344
+ : bufferInfo.end;
359
345
 
360
346
  if (switchingTrack && media) {
361
- const pos = loadPosition;
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
- // Request audio segments up to one fragment ahead of main stream-controller
407
- const mainFragLoading = this.mainFragLoading?.frag;
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
- 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!))
399
+ atBufferSyncLimit ||
400
+ // Or wait for main buffer after buffing some audio
401
+ (!mainBufferInfo?.len && bufferInfo.len)
416
402
  ) {
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
- }
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
- const atBufferSyncLimit = frag.start > mainFrag.end;
433
- if (atBufferSyncLimit) {
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 onMediaDetaching() {
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
- private onAudioTracksUpdated(
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
- private onAudioTrackSwitching(
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.clearInterval();
479
+ this.state = State.STOPPED;
487
480
  }
481
+ this.tick();
488
482
  }
489
483
 
490
- protected onManifestLoading() {
491
- super.onManifestLoading();
484
+ onManifestLoading() {
485
+ this.fragmentTracker.removeAllFragments();
486
+ this.startPosition = this.lastCurrentTime = 0;
492
487
  this.bufferFlushed = this.flushing = false;
493
- this.mainDetails =
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.trackId = -1;
495
+ this.startFragRequested = false;
496
+ this.trackId = this.videoTrackCC = this.waitingVideoCC = -1;
501
497
  }
502
498
 
503
- private onLevelLoaded(event: Events.LEVEL_LOADED, data: LevelLoadedData) {
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
- private onAudioTrackLoaded(
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.frag as MediaFragment;
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
- private onBufferReset(/* event: Events.BUFFER_RESET */) {
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
- private onBufferCreated(
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
- private onFragBuffered(event: Events.FRAG_BUFFERED, data: FragBufferedData) {
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 bufferedState = this.fragmentTracker.getState(frag);
700
- if (
701
- bufferedState === FragmentState.OK ||
702
- bufferedState === FragmentState.PARTIAL
703
- ) {
704
- this.loadedmetadata = true;
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 as MediaFragment;
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
- protected onError(event: Events.ERROR, data: ErrorData) {
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
- if (!tracks.audio) {
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 as BufferCodecsData);
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 {
@@ -33,7 +33,7 @@ class AudioTrackController extends BasePlaylistController {
33
33
  private selectDefaultTrack: boolean = true;
34
34
 
35
35
  constructor(hls: Hls) {
36
- super(hls, 'audio-track-controller');
36
+ super(hls, '[audio-track-controller]');
37
37
  this.registerListeners();
38
38
  }
39
39
 
@@ -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 type { ErrorData } from '../types/events';
5
+ import { ErrorData } from '../types/events';
11
6
  import { getRetryDelay, isTimeoutError } from '../utils/error-helper';
12
7
  import { NetworkErrorAction } from './error-controller';
13
- import { Logger } from '../utils/logger';
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
- super(logPrefix, hls.logger);
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
- this.warn(
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 (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
- ) {
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(