hls.js 1.5.11-0.canary.10334 → 1.5.11

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 (88) 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 +2196 -3475
  5. package/dist/hls.js.d.ts +85 -108
  6. package/dist/hls.js.map +1 -1
  7. package/dist/hls.light.js +3138 -3785
  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 +1257 -1929
  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 +4185 -5487
  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 +36 -36
  20. package/src/config.ts +2 -3
  21. package/src/controller/abr-controller.ts +20 -24
  22. package/src/controller/audio-stream-controller.ts +74 -68
  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 +38 -160
  26. package/src/controller/buffer-controller.ts +92 -230
  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 +13 -7
  33. package/src/controller/fps-controller.ts +3 -8
  34. package/src/controller/fragment-tracker.ts +11 -15
  35. package/src/controller/gap-controller.ts +16 -43
  36. package/src/controller/id3-track-controller.ts +7 -7
  37. package/src/controller/latency-controller.ts +11 -9
  38. package/src/controller/level-controller.ts +19 -37
  39. package/src/controller/stream-controller.ts +32 -37
  40. package/src/controller/subtitle-stream-controller.ts +43 -28
  41. package/src/controller/subtitle-track-controller.ts +3 -5
  42. package/src/controller/timeline-controller.ts +21 -19
  43. package/src/crypt/aes-crypto.ts +2 -21
  44. package/src/crypt/decrypter.ts +16 -32
  45. package/src/crypt/fast-aes-key.ts +5 -28
  46. package/src/demux/audio/aacdemuxer.ts +2 -2
  47. package/src/demux/audio/ac3-demuxer.ts +3 -4
  48. package/src/demux/audio/adts.ts +4 -9
  49. package/src/demux/audio/base-audio-demuxer.ts +14 -16
  50. package/src/demux/audio/mp3demuxer.ts +3 -4
  51. package/src/demux/audio/mpegaudio.ts +1 -1
  52. package/src/demux/id3.ts +411 -0
  53. package/src/demux/mp4demuxer.ts +7 -7
  54. package/src/demux/sample-aes.ts +0 -2
  55. package/src/demux/transmuxer-interface.ts +12 -4
  56. package/src/demux/transmuxer-worker.ts +4 -4
  57. package/src/demux/transmuxer.ts +3 -16
  58. package/src/demux/tsdemuxer.ts +38 -75
  59. package/src/demux/video/avc-video-parser.ts +119 -208
  60. package/src/demux/video/base-video-parser.ts +18 -147
  61. package/src/demux/video/exp-golomb.ts +208 -0
  62. package/src/events.ts +1 -8
  63. package/src/exports-named.ts +1 -1
  64. package/src/hls.ts +38 -61
  65. package/src/loader/fragment-loader.ts +3 -10
  66. package/src/loader/key-loader.ts +1 -3
  67. package/src/loader/level-key.ts +9 -10
  68. package/src/loader/playlist-loader.ts +5 -4
  69. package/src/remux/mp4-generator.ts +1 -196
  70. package/src/remux/mp4-remuxer.ts +8 -24
  71. package/src/task-loop.ts +2 -5
  72. package/src/types/component-api.ts +1 -3
  73. package/src/types/demuxer.ts +0 -4
  74. package/src/types/events.ts +0 -4
  75. package/src/types/remuxer.ts +1 -1
  76. package/src/utils/buffer-helper.ts +31 -12
  77. package/src/utils/cea-608-parser.ts +3 -1
  78. package/src/utils/codecs.ts +5 -34
  79. package/src/utils/fetch-loader.ts +1 -1
  80. package/src/utils/imsc1-ttml-parser.ts +1 -1
  81. package/src/utils/keysystem-util.ts +6 -1
  82. package/src/utils/logger.ts +23 -58
  83. package/src/utils/mp4-tools.ts +3 -5
  84. package/src/utils/webvtt-parser.ts +1 -1
  85. package/src/crypt/decrypter-aes-mode.ts +0 -4
  86. package/src/demux/video/hevc-video-parser.ts +0 -749
  87. package/src/utils/encryption-methods-util.ts +0 -21
  88. package/src/utils/utf8-utils.ts +0 -18
package/package.json CHANGED
@@ -58,52 +58,52 @@
58
58
  "test:func:sauce": "SAUCE=1 UA=safari OS='OS X 10.15' BABEL_ENV=development mocha --require @babel/register tests/functional/auto/setup.js --timeout 40000 --exit",
59
59
  "type-check": "tsc --noEmit",
60
60
  "type-check:watch": "npm run type-check -- --watch",
61
- "prepare": "husky"
61
+ "prepare": "husky install"
62
62
  },
63
63
  "devDependencies": {
64
- "@babel/core": "7.24.6",
65
- "@babel/helper-module-imports": "7.24.6",
64
+ "@babel/core": "7.23.7",
65
+ "@babel/helper-module-imports": "7.22.15",
66
66
  "@babel/plugin-proposal-class-properties": "7.18.6",
67
67
  "@babel/plugin-proposal-object-rest-spread": "7.20.7",
68
68
  "@babel/plugin-proposal-optional-chaining": "7.21.0",
69
- "@babel/plugin-transform-object-assign": "7.24.6",
70
- "@babel/preset-env": "7.24.6",
71
- "@babel/preset-typescript": "7.24.6",
72
- "@babel/register": "7.24.6",
73
- "@microsoft/api-documenter": "7.25.2",
74
- "@microsoft/api-extractor": "7.46.2",
69
+ "@babel/plugin-transform-object-assign": "7.23.3",
70
+ "@babel/preset-env": "7.23.7",
71
+ "@babel/preset-typescript": "7.23.3",
72
+ "@babel/register": "7.23.7",
73
+ "@microsoft/api-documenter": "7.23.16",
74
+ "@microsoft/api-extractor": "7.39.1",
75
75
  "@rollup/plugin-alias": "5.1.0",
76
76
  "@rollup/plugin-babel": "6.0.4",
77
- "@rollup/plugin-commonjs": "25.0.8",
77
+ "@rollup/plugin-commonjs": "25.0.7",
78
78
  "@rollup/plugin-node-resolve": "15.2.3",
79
79
  "@rollup/plugin-replace": "5.0.5",
80
80
  "@rollup/plugin-terser": "0.4.4",
81
- "@rollup/plugin-typescript": "11.1.6",
82
- "@svta/common-media-library": "0.6.4",
83
- "@types/chai": "4.3.16",
81
+ "@rollup/plugin-typescript": "11.1.5",
82
+ "@svta/common-media-library": "0.6.1",
83
+ "@types/chai": "4.3.11",
84
84
  "@types/chart.js": "2.9.41",
85
85
  "@types/mocha": "10.0.6",
86
86
  "@types/sinon-chai": "3.2.12",
87
- "@typescript-eslint/eslint-plugin": "7.12.0",
88
- "@typescript-eslint/parser": "7.12.0",
87
+ "@typescript-eslint/eslint-plugin": "6.17.0",
88
+ "@typescript-eslint/parser": "6.17.0",
89
89
  "babel-loader": "9.1.3",
90
90
  "babel-plugin-transform-remove-console": "6.9.4",
91
- "chai": "4.4.1",
91
+ "chai": "4.3.10",
92
92
  "chart.js": "2.9.4",
93
- "chromedriver": "125.0.2",
93
+ "chromedriver": "120.0.1",
94
94
  "doctoc": "2.2.1",
95
- "es-check": "7.2.1",
96
- "eslint": "8.57.0",
95
+ "es-check": "7.1.1",
96
+ "eslint": "8.56.0",
97
97
  "eslint-config-prettier": "9.1.0",
98
98
  "eslint-plugin-import": "2.29.1",
99
- "eslint-plugin-mocha": "10.4.3",
100
- "eslint-plugin-n": "17.7.0",
101
- "eslint-plugin-promise": "6.2.0",
99
+ "eslint-plugin-mocha": "10.2.0",
100
+ "eslint-plugin-node": "11.1.0",
101
+ "eslint-plugin-promise": "6.1.1",
102
102
  "eventemitter3": "5.0.1",
103
103
  "http-server": "14.1.1",
104
- "husky": "9.0.11",
104
+ "husky": "8.0.3",
105
105
  "jsonpack": "1.1.5",
106
- "karma": "6.4.3",
106
+ "karma": "6.4.2",
107
107
  "karma-chrome-launcher": "3.2.0",
108
108
  "karma-coverage": "2.2.1",
109
109
  "karma-mocha": "2.0.1",
@@ -111,24 +111,24 @@
111
111
  "karma-rollup-preprocessor": "github:jlmakes/karma-rollup-preprocessor#7a7268d91149307b3cf2888ee4e65ccd079955a3",
112
112
  "karma-sinon-chai": "2.0.2",
113
113
  "karma-sourcemap-loader": "0.4.0",
114
- "lint-staged": "15.2.5",
114
+ "lint-staged": "15.2.0",
115
115
  "markdown-styles": "3.2.0",
116
- "micromatch": "4.0.7",
117
- "mocha": "10.4.0",
116
+ "micromatch": "4.0.5",
117
+ "mocha": "10.2.0",
118
118
  "node-fetch": "3.3.2",
119
- "npm-run-all2": "6.2.0",
120
- "prettier": "3.2.5",
119
+ "npm-run-all": "4.1.5",
120
+ "prettier": "3.1.1",
121
121
  "promise-polyfill": "8.3.0",
122
- "rollup": "4.18.0",
122
+ "rollup": "4.9.4",
123
123
  "rollup-plugin-istanbul": "5.0.0",
124
124
  "sauce-connect-launcher": "1.3.2",
125
- "selenium-webdriver": "4.21.0",
126
- "semver": "7.6.2",
127
- "sinon": "18.0.0",
125
+ "selenium-webdriver": "4.16.0",
126
+ "semver": "7.5.4",
127
+ "sinon": "17.0.1",
128
128
  "sinon-chai": "3.7.0",
129
- "typescript": "5.4.5",
129
+ "typescript": "5.3.3",
130
130
  "url-toolkit": "2.2.5",
131
- "wrangler": "3.57.1"
131
+ "wrangler": "3.22.4"
132
132
  },
133
- "version": "1.5.11-0.canary.10334"
133
+ "version": "1.5.11"
134
134
  }
package/src/config.ts CHANGED
@@ -17,10 +17,10 @@ import XhrLoader from './utils/xhr-loader';
17
17
  import FetchLoader, { fetchSupported } from './utils/fetch-loader';
18
18
  import Cues from './utils/cues';
19
19
  import { requestMediaKeySystemAccess } from './utils/mediakeys-helper';
20
+ import { ILogger, logger } from './utils/logger';
20
21
 
21
22
  import type Hls from './hls';
22
23
  import type { CuesInterface } from './utils/cues';
23
- import type { ILogger } from './utils/logger';
24
24
  import type { MediaKeyFunc, KeySystems } from './utils/mediakeys-helper';
25
25
  import type {
26
26
  FragmentLoaderContext,
@@ -558,7 +558,6 @@ function timelineConfig(): TimelineControllerConfig {
558
558
  export function mergeConfig(
559
559
  defaultConfig: HlsConfig,
560
560
  userConfig: Partial<HlsConfig>,
561
- logger: ILogger,
562
561
  ): HlsConfig {
563
562
  if (
564
563
  (userConfig.liveSyncDurationCount ||
@@ -665,7 +664,7 @@ function deepCpy(obj: any): any {
665
664
  /**
666
665
  * @ignore
667
666
  */
668
- export function enableStreamingMode(config: HlsConfig, logger: ILogger) {
667
+ export function enableStreamingMode(config) {
669
668
  const currentLoader = config.loader;
670
669
  if (currentLoader !== FetchLoader && currentLoader !== XhrLoader) {
671
670
  // If a developer has configured their own loader, respect that choice
@@ -2,7 +2,7 @@ import EwmaBandWidthEstimator from '../utils/ewma-bandwidth-estimator';
2
2
  import { Events } from '../events';
3
3
  import { ErrorDetails } from '../errors';
4
4
  import { PlaylistLevelType } from '../types/loader';
5
- import { Logger } from '../utils/logger';
5
+ import { logger } from '../utils/logger';
6
6
  import {
7
7
  SUPPORTED_INFO_DEFAULT,
8
8
  getMediaDecodingInfoPromise,
@@ -31,7 +31,7 @@ import type {
31
31
  } from '../types/events';
32
32
  import type { AbrComponentAPI } from '../types/component-api';
33
33
 
34
- class AbrController extends Logger implements AbrComponentAPI {
34
+ class AbrController implements AbrComponentAPI {
35
35
  protected hls: Hls;
36
36
  private lastLevelLoadSec: number = 0;
37
37
  private lastLoadedFragLevel: number = -1;
@@ -48,7 +48,6 @@ class AbrController extends Logger implements AbrComponentAPI {
48
48
  public bwEstimator: EwmaBandWidthEstimator;
49
49
 
50
50
  constructor(hls: Hls) {
51
- super('abr', hls.logger);
52
51
  this.hls = hls;
53
52
  this.bwEstimator = this.initEstimator();
54
53
  this.registerListeners();
@@ -56,7 +55,7 @@ class AbrController extends Logger implements AbrComponentAPI {
56
55
 
57
56
  public resetEstimator(abrEwmaDefaultEstimate?: number) {
58
57
  if (abrEwmaDefaultEstimate) {
59
- this.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
58
+ logger.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
60
59
  this.hls.config.abrEwmaDefaultEstimate = abrEwmaDefaultEstimate;
61
60
  }
62
61
  this.firstSelection = -1;
@@ -356,7 +355,7 @@ class AbrController extends Logger implements AbrComponentAPI {
356
355
  }
357
356
 
358
357
  this.clearTimer();
359
- this.warn(`Fragment ${frag.sn}${
358
+ logger.warn(`[abr] Fragment ${frag.sn}${
360
359
  part ? ' part ' + part.index : ''
361
360
  } of level ${frag.level} is loading too slowly;
362
361
  Time to underbuffer: ${bufferStarvationDelay.toFixed(3)} s
@@ -480,8 +479,8 @@ class AbrController extends Logger implements AbrComponentAPI {
480
479
  }
481
480
  const firstLevel = this.hls.firstLevel;
482
481
  const clamped = Math.min(Math.max(firstLevel, minAutoLevel), maxAutoLevel);
483
- this.warn(
484
- `Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`,
482
+ logger.warn(
483
+ `[abr] Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`,
485
484
  );
486
485
  return clamped;
487
486
  }
@@ -539,9 +538,6 @@ class AbrController extends Logger implements AbrComponentAPI {
539
538
 
540
539
  private getNextABRAutoLevel(): number {
541
540
  const { fragCurrent, partCurrent, hls } = this;
542
- if (hls.levels.length <= 1) {
543
- return hls.loadLevel;
544
- }
545
541
  const { maxAutoLevel, config, minAutoLevel } = hls;
546
542
  const currentFragDuration = partCurrent
547
543
  ? partCurrent.duration
@@ -588,8 +584,8 @@ class AbrController extends Logger implements AbrComponentAPI {
588
584
  ? Math.min(currentFragDuration, config.maxLoadingDelay)
589
585
  : config.maxLoadingDelay;
590
586
  maxStarvationDelay = maxLoadingDelay - bitrateTestDelay;
591
- this.info(
592
- `bitrate test took ${Math.round(
587
+ logger.info(
588
+ `[abr] bitrate test took ${Math.round(
593
589
  1000 * bitrateTestDelay,
594
590
  )}ms, set first fragment max fetchDuration to ${Math.round(
595
591
  1000 * maxStarvationDelay,
@@ -608,8 +604,8 @@ class AbrController extends Logger implements AbrComponentAPI {
608
604
  bwFactor,
609
605
  bwUpFactor,
610
606
  );
611
- this.info(
612
- `${
607
+ logger.info(
608
+ `[abr] ${
613
609
  bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'
614
610
  }, optimal quality level ${bestLevel}`,
615
611
  );
@@ -702,7 +698,7 @@ class AbrController extends Logger implements AbrComponentAPI {
702
698
  : videoRanges[0];
703
699
  currentFrameRate = minFramerate;
704
700
  currentBw = Math.max(currentBw, minBitrate);
705
- this.log(`picked start tier ${JSON.stringify(startTier)}`);
701
+ logger.log(`[abr] picked start tier ${JSON.stringify(startTier)}`);
706
702
  } else {
707
703
  currentCodecSet = level?.codecSet;
708
704
  currentVideoRange = level?.videoRange;
@@ -755,19 +751,19 @@ class AbrController extends Logger implements AbrComponentAPI {
755
751
  const levels = this.hls.levels;
756
752
  const index = levels.indexOf(levelInfo);
757
753
  if (decodingInfo.error) {
758
- this.warn(
759
- `MediaCapabilities decodingInfo error: "${
754
+ logger.warn(
755
+ `[abr] MediaCapabilities decodingInfo error: "${
760
756
  decodingInfo.error
761
757
  }" for level ${index} ${JSON.stringify(decodingInfo)}`,
762
758
  );
763
759
  } else if (!decodingInfo.supported) {
764
- this.warn(
765
- `Unsupported MediaCapabilities decodingInfo result for level ${index} ${JSON.stringify(
760
+ logger.warn(
761
+ `[abr] Unsupported MediaCapabilities decodingInfo result for level ${index} ${JSON.stringify(
766
762
  decodingInfo,
767
763
  )}`,
768
764
  );
769
765
  if (index > -1 && levels.length > 1) {
770
- this.log(`Removing unsupported level ${index}`);
766
+ logger.log(`[abr] Removing unsupported level ${index}`);
771
767
  this.hls.removeLevel(index);
772
768
  }
773
769
  }
@@ -846,8 +842,8 @@ class AbrController extends Logger implements AbrComponentAPI {
846
842
  (forcedAutoLevel === -1 || forcedAutoLevel !== loadLevel)
847
843
  ) {
848
844
  if (levelsSkipped.length) {
849
- this.trace(
850
- `Skipped level(s) ${levelsSkipped.join(
845
+ logger.trace(
846
+ `[abr] Skipped level(s) ${levelsSkipped.join(
851
847
  ',',
852
848
  )} of ${maxAutoLevel} max with CODECS and VIDEO-RANGE:"${
853
849
  levels[levelsSkipped[0]].codecs
@@ -856,8 +852,8 @@ class AbrController extends Logger implements AbrComponentAPI {
856
852
  }" ${currentVideoRange}`,
857
853
  );
858
854
  }
859
- this.info(
860
- `switch candidate:${selectionBaseLevel}->${i} adjustedbw(${Math.round(
855
+ logger.info(
856
+ `[abr] switch candidate:${selectionBaseLevel}->${i} adjustedbw(${Math.round(
861
857
  adjustedbw,
862
858
  )})-bitrate=${Math.round(
863
859
  adjustedbw - bitrate,
@@ -71,27 +71,30 @@ class AudioStreamController
71
71
  hls,
72
72
  fragmentTracker,
73
73
  keyLoader,
74
- 'audio-stream-controller',
74
+ '[audio-stream-controller]',
75
75
  PlaylistLevelType.AUDIO,
76
76
  );
77
- this.registerListeners();
77
+ this._registerListeners();
78
78
  }
79
79
 
80
80
  protected onHandlerDestroying() {
81
- this.unregisterListeners();
81
+ this._unregisterListeners();
82
82
  super.onHandlerDestroying();
83
83
  this.mainDetails = null;
84
84
  this.bufferedTrack = null;
85
85
  this.switchingTrack = null;
86
86
  }
87
87
 
88
- protected registerListeners() {
89
- super.registerListeners();
88
+ private _registerListeners() {
90
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);
91
93
  hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
92
94
  hls.on(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
93
95
  hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
94
96
  hls.on(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
97
+ hls.on(Events.ERROR, this.onError, this);
95
98
  hls.on(Events.BUFFER_RESET, this.onBufferReset, this);
96
99
  hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
97
100
  hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
@@ -100,16 +103,16 @@ class AudioStreamController
100
103
  hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
101
104
  }
102
105
 
103
- protected unregisterListeners() {
106
+ private _unregisterListeners() {
104
107
  const { hls } = this;
105
- if (!hls) {
106
- return;
107
- }
108
- 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);
109
111
  hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
110
112
  hls.off(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
111
113
  hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
112
114
  hls.off(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
115
+ hls.off(Events.ERROR, this.onError, this);
113
116
  hls.off(Events.BUFFER_RESET, this.onBufferReset, this);
114
117
  hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
115
118
  hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
@@ -255,9 +258,7 @@ class AudioStreamController
255
258
  this.fragmentTracker.removeFragment(waitingData.frag);
256
259
  this.waitingData = null;
257
260
  this.waitingVideoCC = -1;
258
- if (this.state !== State.STOPPED) {
259
- this.state = State.IDLE;
260
- }
261
+ this.state = State.IDLE;
261
262
  }
262
263
  }
263
264
 
@@ -280,14 +281,12 @@ class AudioStreamController
280
281
  const { hls, levels, media, trackId } = this;
281
282
  const config = hls.config;
282
283
 
283
- // 1. if buffering is suspended
284
- // 2. if video not attached AND
284
+ // 1. if video not attached AND
285
285
  // start fragment already requested OR start frag prefetch not enabled
286
- // 3. if tracks or track not loaded and selected
286
+ // 2. if tracks or track not loaded and selected
287
287
  // then exit loop
288
288
  // => if media not attached but start frag prefetch is enabled and start frag not requested yet, we will not exit loop
289
289
  if (
290
- !this.buffering ||
291
290
  (!media && (this.startFragRequested || !config.startFragPrefetch)) ||
292
291
  !levels?.[trackId]
293
292
  ) {
@@ -331,8 +330,12 @@ class AudioStreamController
331
330
  return;
332
331
  }
333
332
 
333
+ const mainBufferInfo = this.getFwdBufferInfo(
334
+ this.videoBuffer ? this.videoBuffer : this.media,
335
+ PlaylistLevelType.MAIN,
336
+ );
334
337
  const bufferLen = bufferInfo.len;
335
- const maxBufLen = hls.maxBufferLength;
338
+ const maxBufLen = this.getMaxBufferLength(mainBufferInfo?.len);
336
339
 
337
340
  const fragments = trackDetails.fragments;
338
341
  const start = fragments[0].start;
@@ -388,44 +391,52 @@ class AudioStreamController
388
391
  return;
389
392
  }
390
393
 
391
- if (!trackDetails.live || targetBufferTime < this.hls.liveSyncPosition!) {
392
- // Request audio segments up to one fragment ahead of main buffer
393
- const mainBufferInfo = this.getFwdBufferInfo(
394
- this.videoBuffer ? this.videoBuffer : this.media,
395
- PlaylistLevelType.MAIN,
396
- );
397
- const atBufferSyncLimit =
398
- !!mainBufferInfo && frag.start > mainBufferInfo.end + frag.duration;
399
- if (atBufferSyncLimit) {
400
- // Check fragment-tracker for main fragments since GAP segments do not show up in bufferInfo
401
- const mainFrag = this.fragmentTracker.getFragAtPos(
402
- frag.start,
403
- PlaylistLevelType.MAIN,
404
- );
405
- if (mainFrag === null) {
406
- return;
407
- }
408
- // Bridge gaps in main buffer (also prevents loop loading at gaps)
409
- atGap ||= !!mainFrag.gap || mainBufferInfo.len === 0;
410
- if (
411
- !atGap ||
412
- (bufferInfo.nextStart && bufferInfo.nextStart < mainFrag.end)
413
- ) {
414
- return;
415
- }
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
+ if (
399
+ atBufferSyncLimit ||
400
+ // Or wait for main buffer after buffing some audio
401
+ (!mainBufferInfo?.len && bufferInfo.len)
402
+ ) {
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
+ ) {
415
+ return;
416
416
  }
417
417
  }
418
418
 
419
419
  this.loadFragment(frag, levelInfo, targetBufferTime);
420
420
  }
421
421
 
422
- 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() {
423
434
  this.videoBuffer = null;
424
435
  this.bufferFlushed = this.flushing = false;
425
436
  super.onMediaDetaching();
426
437
  }
427
438
 
428
- private onAudioTracksUpdated(
439
+ onAudioTracksUpdated(
429
440
  event: Events.AUDIO_TRACKS_UPDATED,
430
441
  { audioTracks }: AudioTracksUpdatedData,
431
442
  ) {
@@ -434,7 +445,7 @@ class AudioStreamController
434
445
  this.levels = audioTracks.map((mediaPlaylist) => new Level(mediaPlaylist));
435
446
  }
436
447
 
437
- private onAudioTrackSwitching(
448
+ onAudioTrackSwitching(
438
449
  event: Events.AUDIO_TRACK_SWITCHING,
439
450
  data: AudioTrackSwitchingData,
440
451
  ) {
@@ -448,28 +459,29 @@ class AudioStreamController
448
459
  this.removeUnbufferedFrags(fragCurrent.start);
449
460
  }
450
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
+ }
451
469
 
452
470
  // should we switch tracks ?
453
471
  if (altAudio) {
454
472
  this.switchingTrack = data;
455
473
  // main audio track are handled by stream-controller, just do something if switching to alt audio track
474
+ this.state = State.IDLE;
456
475
  this.flushAudioIfNeeded(data);
457
- if (this.state !== State.STOPPED) {
458
- // switching to audio track, start timer if not already started
459
- this.setInterval(TICK_INTERVAL);
460
- this.state = State.IDLE;
461
- this.tick();
462
- }
463
476
  } else {
464
- // destroy useless transmuxer when switching audio to main
465
- this.resetTransmuxer();
466
477
  this.switchingTrack = null;
467
478
  this.bufferedTrack = data;
468
- this.clearInterval();
479
+ this.state = State.STOPPED;
469
480
  }
481
+ this.tick();
470
482
  }
471
483
 
472
- protected onManifestLoading() {
484
+ onManifestLoading() {
473
485
  this.fragmentTracker.removeAllFragments();
474
486
  this.startPosition = this.lastCurrentTime = 0;
475
487
  this.bufferFlushed = this.flushing = false;
@@ -484,7 +496,7 @@ class AudioStreamController
484
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;
@@ -648,16 +657,13 @@ 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
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;
@@ -667,7 +673,7 @@ class AudioStreamController
667
673
  }
668
674
  }
669
675
 
670
- private onFragBuffered(event: Events.FRAG_BUFFERED, data: FragBufferedData) {
676
+ onFragBuffered(event: Events.FRAG_BUFFERED, data: FragBufferedData) {
671
677
  const { frag, part } = data;
672
678
  if (frag.type !== PlaylistLevelType.AUDIO) {
673
679
  if (!this.loadedmetadata && frag.type === PlaylistLevelType.MAIN) {
@@ -707,7 +713,7 @@ class AudioStreamController
707
713
  this.fragBufferedComplete(frag, part);
708
714
  }
709
715
 
710
- protected onError(event: Events.ERROR, data: ErrorData) {
716
+ private onError(event: Events.ERROR, data: ErrorData) {
711
717
  if (data.fatal) {
712
718
  this.state = State.ERROR;
713
719
  return;
@@ -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(