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.
Files changed (103) 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 +2704 -4236
  5. package/dist/hls.js.d.ts +110 -179
  6. package/dist/hls.js.map +1 -1
  7. package/dist/hls.light.js +1994 -2914
  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 +3458 -4388
  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 +4506 -6048
  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 +137 -141
  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 +91 -235
  26. package/src/controller/buffer-controller.ts +97 -250
  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 +2 -2
  48. package/src/demux/audio/ac3-demuxer.ts +3 -4
  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/mp4demuxer.ts +7 -7
  55. package/src/demux/sample-aes.ts +0 -2
  56. package/src/demux/transmuxer-interface.ts +16 -8
  57. package/src/demux/transmuxer-worker.ts +4 -4
  58. package/src/demux/transmuxer.ts +3 -16
  59. package/src/demux/tsdemuxer.ts +38 -75
  60. package/src/demux/video/avc-video-parser.ts +121 -210
  61. package/src/demux/video/base-video-parser.ts +2 -135
  62. package/src/demux/video/exp-golomb.ts +208 -0
  63. package/src/events.ts +1 -8
  64. package/src/exports-named.ts +1 -1
  65. package/src/hls.ts +47 -84
  66. package/src/loader/date-range.ts +5 -71
  67. package/src/loader/fragment-loader.ts +21 -23
  68. package/src/loader/fragment.ts +4 -8
  69. package/src/loader/key-loader.ts +1 -3
  70. package/src/loader/level-details.ts +6 -6
  71. package/src/loader/level-key.ts +9 -10
  72. package/src/loader/m3u8-parser.ts +144 -138
  73. package/src/loader/playlist-loader.ts +7 -5
  74. package/src/remux/mp4-generator.ts +1 -196
  75. package/src/remux/mp4-remuxer.ts +62 -32
  76. package/src/remux/passthrough-remuxer.ts +1 -1
  77. package/src/task-loop.ts +2 -5
  78. package/src/types/component-api.ts +1 -3
  79. package/src/types/demuxer.ts +0 -3
  80. package/src/types/events.ts +6 -19
  81. package/src/types/fragment-tracker.ts +2 -2
  82. package/src/types/general.ts +6 -0
  83. package/src/types/media-playlist.ts +1 -9
  84. package/src/types/remuxer.ts +1 -1
  85. package/src/utils/attr-list.ts +9 -96
  86. package/src/utils/buffer-helper.ts +31 -12
  87. package/src/utils/cea-608-parser.ts +3 -1
  88. package/src/utils/codecs.ts +5 -34
  89. package/src/utils/fetch-loader.ts +1 -1
  90. package/src/utils/hdr.ts +7 -4
  91. package/src/utils/imsc1-ttml-parser.ts +1 -1
  92. package/src/utils/keysystem-util.ts +6 -1
  93. package/src/utils/level-helper.ts +44 -71
  94. package/src/utils/logger.ts +23 -58
  95. package/src/utils/mp4-tools.ts +3 -5
  96. package/src/utils/rendition-helper.ts +74 -100
  97. package/src/utils/variable-substitution.ts +19 -0
  98. package/src/utils/webvtt-parser.ts +12 -2
  99. package/src/crypt/decrypter-aes-mode.ts +0 -4
  100. package/src/demux/video/hevc-video-parser.ts +0 -749
  101. package/src/utils/encryption-methods-util.ts +0 -21
  102. package/src/utils/hash.ts +0 -10
  103. 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 { 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,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
- this.startFragRequested &&
408
- (!trackDetails.live || targetBufferTime < this.hls.liveSyncPosition!)
399
+ atBufferSyncLimit ||
400
+ // Or wait for main buffer after buffing some audio
401
+ (!mainBufferInfo?.len && bufferInfo.len)
409
402
  ) {
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) {
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 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;
426
435
  this.bufferFlushed = this.flushing = false;
427
436
  super.onMediaDetaching();
428
437
  }
429
438
 
430
- private onAudioTracksUpdated(
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
- private onAudioTrackSwitching(
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.clearInterval();
479
+ this.state = State.STOPPED;
471
480
  }
481
+ this.tick();
472
482
  }
473
483
 
474
- protected onManifestLoading() {
475
- super.onManifestLoading();
484
+ onManifestLoading() {
485
+ this.fragmentTracker.removeAllFragments();
486
+ this.startPosition = this.lastCurrentTime = 0;
476
487
  this.bufferFlushed = this.flushing = false;
477
- this.mainDetails =
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.trackId = -1;
495
+ this.startFragRequested = false;
496
+ this.trackId = this.videoTrackCC = this.waitingVideoCC = -1;
485
497
  }
486
498
 
487
- private onLevelLoaded(event: Events.LEVEL_LOADED, data: LevelLoadedData) {
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
- private onAudioTrackLoaded(
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.frag as MediaFragment;
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
- private onBufferReset(/* event: Events.BUFFER_RESET */) {
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
- private onBufferCreated(
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
- private onFragBuffered(event: Events.FRAG_BUFFERED, data: FragBufferedData) {
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 bufferedState = this.fragmentTracker.getState(frag);
684
- if (
685
- bufferedState === FragmentState.OK ||
686
- bufferedState === FragmentState.PARTIAL
687
- ) {
688
- 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
+ }
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 as MediaFragment;
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
- protected onError(event: Events.ERROR, data: ErrorData) {
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
- if (!tracks.audio) {
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 as BufferCodecsData);
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 {
@@ -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(