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.
Files changed (103) hide show
  1. package/README.md +4 -3
  2. package/dist/hls-demo.js +41 -38
  3. package/dist/hls-demo.js.map +1 -1
  4. package/dist/hls.js +4174 -2625
  5. package/dist/hls.js.d.ts +173 -108
  6. package/dist/hls.js.map +1 -1
  7. package/dist/hls.light.js +2851 -1914
  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 +2560 -1608
  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 +3546 -1982
  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 +5 -2
  21. package/src/controller/abr-controller.ts +39 -25
  22. package/src/controller/audio-stream-controller.ts +141 -137
  23. package/src/controller/audio-track-controller.ts +1 -1
  24. package/src/controller/base-playlist-controller.ts +27 -10
  25. package/src/controller/base-stream-controller.ts +215 -82
  26. package/src/controller/buffer-controller.ts +250 -97
  27. package/src/controller/buffer-operation-queue.ts +16 -19
  28. package/src/controller/cap-level-controller.ts +3 -2
  29. package/src/controller/cmcd-controller.ts +51 -14
  30. package/src/controller/content-steering-controller.ts +29 -15
  31. package/src/controller/eme-controller.ts +10 -23
  32. package/src/controller/error-controller.ts +28 -22
  33. package/src/controller/fps-controller.ts +8 -3
  34. package/src/controller/fragment-finders.ts +44 -16
  35. package/src/controller/fragment-tracker.ts +58 -25
  36. package/src/controller/gap-controller.ts +43 -16
  37. package/src/controller/id3-track-controller.ts +45 -35
  38. package/src/controller/latency-controller.ts +18 -13
  39. package/src/controller/level-controller.ts +37 -19
  40. package/src/controller/stream-controller.ts +100 -83
  41. package/src/controller/subtitle-stream-controller.ts +35 -47
  42. package/src/controller/subtitle-track-controller.ts +5 -3
  43. package/src/controller/timeline-controller.ts +20 -22
  44. package/src/crypt/aes-crypto.ts +21 -2
  45. package/src/crypt/decrypter-aes-mode.ts +4 -0
  46. package/src/crypt/decrypter.ts +32 -16
  47. package/src/crypt/fast-aes-key.ts +28 -5
  48. package/src/demux/audio/aacdemuxer.ts +2 -2
  49. package/src/demux/audio/ac3-demuxer.ts +4 -3
  50. package/src/demux/audio/adts.ts +9 -4
  51. package/src/demux/audio/base-audio-demuxer.ts +16 -14
  52. package/src/demux/audio/mp3demuxer.ts +4 -3
  53. package/src/demux/audio/mpegaudio.ts +1 -1
  54. package/src/demux/mp4demuxer.ts +7 -7
  55. package/src/demux/sample-aes.ts +2 -0
  56. package/src/demux/transmuxer-interface.ts +8 -16
  57. package/src/demux/transmuxer-worker.ts +4 -4
  58. package/src/demux/transmuxer.ts +16 -3
  59. package/src/demux/tsdemuxer.ts +75 -38
  60. package/src/demux/video/avc-video-parser.ts +210 -121
  61. package/src/demux/video/base-video-parser.ts +135 -2
  62. package/src/demux/video/exp-golomb.ts +0 -208
  63. package/src/demux/video/hevc-video-parser.ts +749 -0
  64. package/src/events.ts +8 -1
  65. package/src/exports-named.ts +1 -1
  66. package/src/hls.ts +73 -43
  67. package/src/loader/date-range.ts +71 -5
  68. package/src/loader/fragment-loader.ts +23 -21
  69. package/src/loader/fragment.ts +8 -4
  70. package/src/loader/key-loader.ts +3 -1
  71. package/src/loader/level-details.ts +6 -6
  72. package/src/loader/level-key.ts +10 -9
  73. package/src/loader/m3u8-parser.ts +138 -144
  74. package/src/loader/playlist-loader.ts +5 -7
  75. package/src/remux/mp4-generator.ts +196 -1
  76. package/src/remux/mp4-remuxer.ts +36 -16
  77. package/src/remux/passthrough-remuxer.ts +1 -1
  78. package/src/task-loop.ts +5 -2
  79. package/src/types/component-api.ts +3 -1
  80. package/src/types/demuxer.ts +3 -0
  81. package/src/types/events.ts +19 -6
  82. package/src/types/fragment-tracker.ts +2 -2
  83. package/src/types/media-playlist.ts +9 -1
  84. package/src/types/remuxer.ts +1 -1
  85. package/src/utils/attr-list.ts +96 -9
  86. package/src/utils/buffer-helper.ts +12 -31
  87. package/src/utils/cea-608-parser.ts +1 -3
  88. package/src/utils/codecs.ts +34 -5
  89. package/src/utils/encryption-methods-util.ts +21 -0
  90. package/src/utils/fetch-loader.ts +1 -1
  91. package/src/utils/hash.ts +10 -0
  92. package/src/utils/hdr.ts +4 -7
  93. package/src/utils/imsc1-ttml-parser.ts +1 -1
  94. package/src/utils/keysystem-util.ts +1 -6
  95. package/src/utils/level-helper.ts +71 -44
  96. package/src/utils/logger.ts +58 -23
  97. package/src/utils/mp4-tools.ts +5 -3
  98. package/src/utils/rendition-helper.ts +100 -74
  99. package/src/utils/utf8-utils.ts +18 -0
  100. package/src/utils/variable-substitution.ts +0 -19
  101. package/src/utils/webvtt-parser.ts +2 -12
  102. package/src/demux/id3.ts +0 -411
  103. 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 { Fragment, ElementaryStreamTypes, Part } from '../loader/fragment';
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 { fragmentWithinToleranceTest } from './fragment-finders';
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: Fragment;
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 videoBuffer: Bufferable | null = null;
54
- private videoTrackCC: number = -1;
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
- '[audio-stream-controller]',
79
+ 'audio-stream-controller',
75
80
  PlaylistLevelType.AUDIO,
76
81
  );
77
- this._registerListeners();
82
+ this.registerListeners();
78
83
  }
79
84
 
80
85
  protected onHandlerDestroying() {
81
- this._unregisterListeners();
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
- private _registerListeners() {
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
- private _unregisterListeners() {
109
+ protected unregisterListeners() {
107
110
  const { hls } = this;
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);
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 === 'main') {
135
+ if (id === PlaylistLevelType.MAIN) {
132
136
  const cc = frag.cc;
133
- this.initPTS[frag.cc] = { baseTime: initPTS, timescale };
134
- this.log(`InitPTS for cc: ${cc} found from main: ${initPTS}`);
135
- this.videoTrackCC = 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;
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 (this.videoTrackCC !== this.waitingVideoCC) {
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 ${this.videoTrackCC}`,
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.waitingVideoCC = -1;
261
- this.state = State.IDLE;
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 video not attached AND
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
- // 2. if tracks or track not loaded and selected
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 = this.getMaxBufferLength(mainBufferInfo?.len);
353
+ const maxBufLen = hls.maxBufferLength;
339
354
 
340
355
  const fragments = trackDetails.fragments;
341
356
  const start = fragments[0].start;
342
- let targetBufferTime = this.flushing
343
- ? this.getLoadPosition()
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 = this.getLoadPosition();
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
- atBufferSyncLimit ||
400
- // Or wait for main buffer after buffing some audio
401
- (!mainBufferInfo?.len && bufferInfo.len)
407
+ this.startFragRequested &&
408
+ (!trackDetails.live || targetBufferTime < this.hls.liveSyncPosition!)
402
409
  ) {
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
- ) {
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 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;
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.state = State.STOPPED;
470
+ this.clearInterval();
480
471
  }
481
- this.tick();
482
472
  }
483
473
 
484
- onManifestLoading() {
485
- this.fragmentTracker.removeAllFragments();
486
- this.startPosition = this.lastCurrentTime = 0;
474
+ protected onManifestLoading() {
475
+ super.onManifestLoading();
487
476
  this.bufferFlushed = this.flushing = false;
488
- this.levels =
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.startFragRequested = false;
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(event: Events.AUDIO_TRACK_LOADED, data: TrackLoadedData) {
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 { frag, part, payload } = data;
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 as number,
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 = this.videoBuffer = null;
653
+ this.mediaBuffer = null;
663
654
  this.loadedmetadata = false;
664
655
  }
665
656
 
666
- onBufferCreated(event: Events.BUFFER_CREATED, data: BufferCreatedData) {
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
- if (data.tracks.video) {
672
- this.videoBuffer = data.tracks.video.buffer || null;
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 bufferable = this.videoBuffer || this.media;
681
- if (bufferable) {
682
- const bufferedTimeRanges = BufferHelper.getBuffered(bufferable);
683
- if (bufferedTimeRanges.length) {
684
- this.loadedmetadata = true;
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
- private onError(event: Events.ERROR, data: ErrorData) {
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
- const track = tracks.audio;
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 {
@@ -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,11 +1,16 @@
1
1
  import type Hls from '../hls';
2
2
  import type { NetworkComponentAPI } from '../types/component-api';
3
- import { getSkipValue, HlsSkip, HlsUrlParameters, Level } from '../types/level';
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 { logger } from '../utils/logger';
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 implements NetworkComponentAPI {
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
- this.log = logger.log.bind(logger, `${logPrefix}:`);
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
- logger.warn(
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 (previousDetails && currentGoal > previousDetails.tuneInGoal) {
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(