diff --git a/api-extractor/report/hls.js.api.md b/api-extractor/report/hls.js.api.md
index 65cdb1e3498..254e7312b53 100644
--- a/api-extractor/report/hls.js.api.md
+++ b/api-extractor/report/hls.js.api.md
@@ -22,7 +22,7 @@ export interface AbrComponentAPI extends ComponentAPI {
 export class AbrController implements AbrComponentAPI {
     constructor(hls: Hls);
     // (undocumented)
-    readonly bwEstimator: EwmaBandWidthEstimator;
+    bwEstimator: EwmaBandWidthEstimator;
     // (undocumented)
     clearTimer(): void;
     // (undocumented)
@@ -45,6 +45,8 @@ export class AbrController implements AbrComponentAPI {
     // (undocumented)
     protected registerListeners(): void;
     // (undocumented)
+    resetEstimator(abrEwmaDefaultEstimate?: number): void;
+    // (undocumented)
     protected unregisterListeners(): void;
 }
 
@@ -57,6 +59,7 @@ export type ABRControllerConfig = {
     abrEwmaFastVoD: number;
     abrEwmaSlowVoD: number;
     abrEwmaDefaultEstimate: number;
+    abrEwmaDefaultEstimateMax: number;
     abrBandWidthFactor: number;
     abrBandWidthUpFactor: number;
     abrMaxWithRealBitrate: boolean;
@@ -1523,6 +1526,7 @@ class Hls implements HlsEventEmitter {
     set autoLevelCapping(newLevel: number);
     get autoLevelEnabled(): boolean;
     get bandwidthEstimate(): number;
+    set bandwidthEstimate(abrEwmaDefaultEstimate: number);
     get capLevelToPlayerSize(): boolean;
     // Warning: (ae-setter-with-docs) The doc comment for the property "capLevelToPlayerSize" must appear on the getter, not the setter.
     set capLevelToPlayerSize(shouldStartCapping: boolean);
@@ -1982,6 +1986,8 @@ export class Level {
     // (undocumented)
     audioGroupIds?: (string | undefined)[];
     // (undocumented)
+    get averageBitrate(): number;
+    // (undocumented)
     readonly bitrate: number;
     // (undocumented)
     readonly codecSet: string;
diff --git a/docs/API.md b/docs/API.md
index ff869ce4ef7..5982944d1d8 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -104,6 +104,7 @@ See [API Reference](https://hlsjs-dev.video-dev.org/api-docs/) for a complete li
   - [`abrEwmaFastVoD`](#abrewmafastvod)
   - [`abrEwmaSlowVoD`](#abrewmaslowvod)
   - [`abrEwmaDefaultEstimate`](#abrewmadefaultestimate)
+  - [`abrEwmaDefaultEstimateMax`](#abrewmadefaultestimatemax)
   - [`abrBandWidthFactor`](#abrbandwidthfactor)
   - [`abrBandWidthUpFactor`](#abrbandwidthupfactor)
   - [`abrMaxWithRealBitrate`](#abrmaxwithrealbitrate)
@@ -410,6 +411,7 @@ var config = {
   abrEwmaFastVoD: 3.0,
   abrEwmaSlowVoD: 9.0,
   abrEwmaDefaultEstimate: 500000,
+  abrEwmaDefaultEstimateMax: 5000000,
   abrBandWidthFactor: 0.95,
   abrBandWidthUpFactor: 0.7,
   abrMaxWithRealBitrate: false,
@@ -1342,6 +1344,12 @@ parameter should be a float greater than [abrEwmaFastVoD](#abrewmafastvod)
 
 Default bandwidth estimate in bits/s prior to collecting fragment bandwidth samples.
 
+### `abrEwmaDefaultEstimateMax`
+
+(default: `5000000`)
+
+Limits value of updated bandwidth estimate taken from first variant found in multivariant playlist on start.
+
 ### `abrBandWidthFactor`
 
 (default: `0.95`)
@@ -1622,6 +1630,8 @@ Default value is set via [`capLevelToPlayerSize`](#capleveltoplayersize) in conf
 
 get: Returns the current bandwidth estimate in bits/s, if available. Otherwise, `NaN` is returned.
 
+set: Reset `EwmaBandWidthEstimator` using the value set as the new default estimate. This will update the value of `config.abrEwmaDefaultEstimate`.
+
 ### `hls.removeLevel(levelIndex, urlId)`
 
 Remove a loaded level from the list of levels, or a url from a level's list of redundant urls.
diff --git a/src/config.ts b/src/config.ts
index e7a58a1d470..2404b47fa69 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -38,6 +38,7 @@ export type ABRControllerConfig = {
    * Default bandwidth estimate in bits/s prior to collecting fragment bandwidth samples
    */
   abrEwmaDefaultEstimate: number;
+  abrEwmaDefaultEstimateMax: number;
   abrBandWidthFactor: number;
   abrBandWidthUpFactor: number;
   abrMaxWithRealBitrate: boolean;
@@ -361,6 +362,7 @@ export const hlsDefaultConfig: HlsConfig = {
   abrEwmaFastVoD: 3, // used by abr-controller
   abrEwmaSlowVoD: 9, // used by abr-controller
   abrEwmaDefaultEstimate: 5e5, // 500 kbps  // used by abr-controller
+  abrEwmaDefaultEstimateMax: 5e6, // 5 mbps
   abrBandWidthFactor: 0.95, // used by abr-controller
   abrBandWidthUpFactor: 0.7, // used by abr-controller
   abrMaxWithRealBitrate: false, // used by abr-controller
diff --git a/src/controller/abr-controller.ts b/src/controller/abr-controller.ts
index 8b95ea018af..2f663839f13 100644
--- a/src/controller/abr-controller.ts
+++ b/src/controller/abr-controller.ts
@@ -26,19 +26,28 @@ class AbrController implements AbrComponentAPI {
   private partCurrent: Part | null = null;
   private bitrateTestDelay: number = 0;
 
-  public readonly bwEstimator: EwmaBandWidthEstimator;
+  public bwEstimator: EwmaBandWidthEstimator;
 
   constructor(hls: Hls) {
     this.hls = hls;
+    this.bwEstimator = this.initEstimator();
+    this.registerListeners();
+  }
 
-    const config = hls.config;
-    this.bwEstimator = new EwmaBandWidthEstimator(
+  public resetEstimator(abrEwmaDefaultEstimate?: number) {
+    if (abrEwmaDefaultEstimate) {
+      logger.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
+      this.hls.config.abrEwmaDefaultEstimate = abrEwmaDefaultEstimate;
+    }
+    this.bwEstimator = this.initEstimator();
+  }
+  private initEstimator(): EwmaBandWidthEstimator {
+    const config = this.hls.config;
+    return new EwmaBandWidthEstimator(
       config.abrEwmaSlowVoD,
       config.abrEwmaFastVoD,
       config.abrEwmaDefaultEstimate
     );
-
-    this.registerListeners();
   }
 
   protected registerListeners() {
@@ -90,7 +99,7 @@ class AbrController implements AbrComponentAPI {
     bandwidth: number,
     fragSizeBits: number,
     isSwitch: boolean
-  ) {
+  ): number {
     const fragLoadSec = timeToFirstByteSec + fragSizeBits / bandwidth;
     const playlistLoadSec = isSwitch ? this.lastLevelLoadSec : 0;
     return fragLoadSec + playlistLoadSec;
@@ -173,7 +182,7 @@ class AbrController implements AbrComponentAPI {
       ? stats.loading.first - stats.loading.start
       : -1;
     const loadedFirstByte = stats.loaded && ttfb > -1;
-    const bwEstimate: number = this.bwEstimator.getEstimate();
+    const bwEstimate: number = this.getBwEstimate();
     const { levels, minAutoLevel } = hls;
     const level = levels[frag.level];
     const expectedLen =
@@ -328,7 +337,7 @@ class AbrController implements AbrComponentAPI {
         this.bwEstimator.getEstimateTTFB()
       );
     this.bwEstimator.sample(processingMs, stats.loaded);
-    stats.bwEstimate = this.bwEstimator.getEstimate();
+    stats.bwEstimate = this.getBwEstimate();
     if (frag.bitrateTest) {
       this.bitrateTestDelay = processingMs / 1000;
     } else {
@@ -346,7 +355,7 @@ class AbrController implements AbrComponentAPI {
   }
 
   // return next auto level
-  get nextAutoLevel() {
+  get nextAutoLevel(): number {
     const forcedAutoLevel = this._nextAutoLevel;
     const bwEstimator = this.bwEstimator;
     // in case next auto level has been forced, and bw not available or not reliable, return forced value
@@ -387,9 +396,7 @@ class AbrController implements AbrComponentAPI {
     // if we're playing back at the normal rate.
     const playbackRate =
       media && media.playbackRate !== 0 ? Math.abs(media.playbackRate) : 1.0;
-    const avgbw = this.bwEstimator
-      ? this.bwEstimator.getEstimate()
-      : config.abrEwmaDefaultEstimate;
+    const avgbw = this.getBwEstimate();
     // bufferStarvationDelay is the wall-clock time left until the playback buffer is exhausted.
     const bufferInfo = hls.mainForwardBufferInfo;
     const bufferStarvationDelay =
@@ -455,6 +462,12 @@ class AbrController implements AbrComponentAPI {
     return Math.max(bestLevel, 0);
   }
 
+  private getBwEstimate(): number {
+    return this.bwEstimator
+      ? this.bwEstimator.getEstimate()
+      : this.hls.config.abrEwmaDefaultEstimate;
+  }
+
   private findBestLevel(
     currentBw: number,
     minAutoLevel: number,
@@ -543,7 +556,7 @@ class AbrController implements AbrComponentAPI {
         // fragment fetchDuration unknown OR live stream OR fragment fetchDuration less than max allowed fetch duration, then this level matches
         // we don't account for max Fetch Duration for live streams, this is to avoid switching down when near the edge of live sliding window ...
         // special case to support startLevel = -1 (bitrateTest) on live streams : in that case we should not exit loop so that findBestLevel will return -1
-        (fetchDuration === 0 ||
+        (fetchDuration <= ttfbEstimateSec ||
           !Number.isFinite(fetchDuration) ||
           (live && !this.bitrateTestDelay) ||
           fetchDuration < maxFetchDuration)
@@ -556,7 +569,7 @@ class AbrController implements AbrComponentAPI {
     return -1;
   }
 
-  set nextAutoLevel(nextLevel) {
+  set nextAutoLevel(nextLevel: number) {
     this._nextAutoLevel = nextLevel;
   }
 }
diff --git a/src/controller/content-steering-controller.ts b/src/controller/content-steering-controller.ts
index 6a547e73777..6dc8d6f14dc 100644
--- a/src/controller/content-steering-controller.ts
+++ b/src/controller/content-steering-controller.ts
@@ -1,7 +1,6 @@
 import { Events } from '../events';
-import { Level } from '../types/level';
+import { Level, addGroupId } from '../types/level';
 import { AttrList } from '../utils/attr-list';
-import { addGroupId } from './level-controller';
 import { ErrorActionFlags, NetworkErrorAction } from './error-controller';
 import { logger } from '../utils/logger';
 import type Hls from '../hls';
diff --git a/src/controller/level-controller.ts b/src/controller/level-controller.ts
index 0bb244575f3..a100951b5e9 100644
--- a/src/controller/level-controller.ts
+++ b/src/controller/level-controller.ts
@@ -13,7 +13,7 @@ import {
   LevelsUpdatedData,
   ManifestLoadingData,
 } from '../types/events';
-import { Level } from '../types/level';
+import { Level, addGroupId } from '../types/level';
 import { Events } from '../events';
 import { ErrorTypes, ErrorDetails } from '../errors';
 import {
@@ -22,10 +22,11 @@ import {
 } from '../utils/codecs';
 import BasePlaylistController from './base-playlist-controller';
 import { PlaylistContextType, PlaylistLevelType } from '../types/loader';
+import ContentSteeringController from './content-steering-controller';
+import { hlsDefaultConfig } from '../config';
 import type Hls from '../hls';
 import type { HlsUrlParameters, LevelParsed } from '../types/level';
 import type { MediaPlaylist } from '../types/media-playlist';
-import ContentSteeringController from './content-steering-controller';
 
 let chromeOrFirefox: boolean;
 
@@ -279,9 +280,24 @@ export default class LevelController extends BasePlaylistController {
     for (let i = 0; i < levels.length; i++) {
       if (levels[i] === firstLevelInPlaylist) {
         this._firstLevel = i;
+        const firstLevelBitrate = firstLevelInPlaylist.bitrate;
+        const bandwidthEstimate = this.hls.bandwidthEstimate;
         this.log(
-          `manifest loaded, ${levels.length} level(s) found, first bitrate: ${firstLevelInPlaylist.bitrate}`
+          `manifest loaded, ${levels.length} level(s) found, first bitrate: ${firstLevelBitrate}`
         );
+        // Update default bwe to first variant bitrate as long it has not been configured or set
+        if (this.hls.userConfig?.abrEwmaDefaultEstimate === undefined) {
+          const startingBwEstimate = Math.min(
+            firstLevelBitrate,
+            this.hls.config.abrEwmaDefaultEstimateMax
+          );
+          if (
+            startingBwEstimate > bandwidthEstimate &&
+            bandwidthEstimate === hlsDefaultConfig.abrEwmaDefaultEstimate
+          ) {
+            this.hls.bandwidthEstimate = startingBwEstimate;
+          }
+        }
         break;
       }
     }
@@ -380,6 +396,8 @@ export default class LevelController extends BasePlaylistController {
     delete levelSwitchingData._attrs;
     // @ts-ignore
     delete levelSwitchingData._urlId;
+    // @ts-ignore
+    delete levelSwitchingData._avgBitrate;
     this.hls.trigger(Events.LEVEL_SWITCHING, levelSwitchingData);
     // check if we need to load playlist for this level
     const levelDetails = level.details;
@@ -617,27 +635,6 @@ export default class LevelController extends BasePlaylistController {
   }
 }
 
-export function addGroupId(
-  level: Level,
-  type: string,
-  id: string | undefined
-): void {
-  if (!id) {
-    return;
-  }
-  if (type === 'audio') {
-    if (!level.audioGroupIds) {
-      level.audioGroupIds = [];
-    }
-    level.audioGroupIds[level.url.length - 1] = id;
-  } else if (type === 'text') {
-    if (!level.textGroupIds) {
-      level.textGroupIds = [];
-    }
-    level.textGroupIds[level.url.length - 1] = id;
-  }
-}
-
 function assignTrackIdsByGroup(tracks: MediaPlaylist[]): void {
   const groups = {};
   tracks.forEach((track) => {
diff --git a/src/hls.ts b/src/hls.ts
index 859d65cf5b9..81584672a4d 100644
--- a/src/hls.ts
+++ b/src/hls.ts
@@ -611,6 +611,10 @@ export default class Hls implements HlsEventEmitter {
     return bwEstimator.getEstimate();
   }
 
+  set bandwidthEstimate(abrEwmaDefaultEstimate: number) {
+    this.abrController.resetEstimator(abrEwmaDefaultEstimate);
+  }
+
   /**
    * get time to first byte estimate
    * @type {number}
diff --git a/src/loader/m3u8-parser.ts b/src/loader/m3u8-parser.ts
index 2ecf9368d66..edd072b5afe 100644
--- a/src/loader/m3u8-parser.ts
+++ b/src/loader/m3u8-parser.ts
@@ -152,8 +152,8 @@ export default class M3U8Parser {
         const level: LevelParsed = {
           attrs,
           bitrate:
-            attrs.decimalInteger('AVERAGE-BANDWIDTH') ||
-            attrs.decimalInteger('BANDWIDTH'),
+            attrs.decimalInteger('BANDWIDTH') ||
+            attrs.decimalInteger('AVERAGE-BANDWIDTH'),
           name: attrs.NAME,
           url: M3U8Parser.resolve(uri, baseurl),
         };
diff --git a/src/types/level.ts b/src/types/level.ts
index 7abe3c2f4cd..3fccd8265bd 100644
--- a/src/types/level.ts
+++ b/src/types/level.ts
@@ -105,6 +105,7 @@ export class Level {
   public textGroupIds?: (string | undefined)[];
   public url: string[];
   private _urlId: number = 0;
+  private _avgBitrate: number = 0;
 
   constructor(data: LevelParsed) {
     this.url = [data.url];
@@ -117,6 +118,7 @@ export class Level {
     this.name = data.name;
     this.width = data.width || 0;
     this.height = data.height || 0;
+    this._avgBitrate = this.attrs.decimalInteger('AVERAGE-BANDWIDTH');
     this.audioCodec = data.audioCodec;
     this.videoCodec = data.videoCodec;
     this.unknownCodecs = data.unknownCodecs;
@@ -130,6 +132,10 @@ export class Level {
     return Math.max(this.realBitrate, this.bitrate);
   }
 
+  get averageBitrate(): number {
+    return this._avgBitrate || this.realBitrate || this.bitrate;
+  }
+
   get attrs(): LevelAttributes {
     return this._attrs[this._urlId];
   }
@@ -169,3 +175,24 @@ export class Level {
     this._attrs.push(data.attrs);
   }
 }
+
+export function addGroupId(
+  level: Level,
+  type: string,
+  id: string | undefined
+): void {
+  if (!id) {
+    return;
+  }
+  if (type === 'audio') {
+    if (!level.audioGroupIds) {
+      level.audioGroupIds = [];
+    }
+    level.audioGroupIds[level.url.length - 1] = id;
+  } else if (type === 'text') {
+    if (!level.textGroupIds) {
+      level.textGroupIds = [];
+    }
+    level.textGroupIds[level.url.length - 1] = id;
+  }
+}
diff --git a/tests/unit/controller/abr-controller.js b/tests/unit/controller/abr-controller.ts
similarity index 64%
rename from tests/unit/controller/abr-controller.js
rename to tests/unit/controller/abr-controller.ts
index ab555430485..2c0f4d83256 100644
--- a/tests/unit/controller/abr-controller.js
+++ b/tests/unit/controller/abr-controller.ts
@@ -2,10 +2,25 @@ import AbrController from '../../../src/controller/abr-controller';
 import EwmaBandWidthEstimator from '../../../src/utils/ewma-bandwidth-estimator';
 import Hls from '../../../src/hls';
 
+import chai from 'chai';
+import sinonChai from 'sinon-chai';
+
+chai.use(sinonChai);
+const expect = chai.expect;
+
 describe('AbrController', function () {
+  it('can be reset with new BWE', function () {
+    const hls = new Hls({ maxStarvationDelay: 4 });
+    const abrController = new AbrController(hls);
+    abrController.bwEstimator = new EwmaBandWidthEstimator(15, 4, 5e5, 100);
+    expect(abrController.bwEstimator.getEstimate()).to.equal(5e5);
+    abrController.resetEstimator(5e6);
+    expect(abrController.bwEstimator.getEstimate()).to.equal(5e6);
+  });
+
   it('should return correct next auto level', function () {
     const hls = new Hls({ maxStarvationDelay: 4 });
-    hls.levelController._levels = [
+    (hls as any).levelController._levels = [
       {
         bitrate: 105000,
         name: '144',
@@ -38,7 +53,7 @@ describe('AbrController', function () {
       },
     ];
     const abrController = new AbrController(hls);
-    abrController.bwEstimator = new EwmaBandWidthEstimator(hls, 15, 4, 5e5);
+    abrController.bwEstimator = new EwmaBandWidthEstimator(15, 4, 5e5, 100);
     expect(abrController.nextAutoLevel).to.equal(0);
   });
 });
diff --git a/tests/unit/controller/subtitle-stream-controller.js b/tests/unit/controller/subtitle-stream-controller.js
index f12eb8b267d..11cd0014a74 100644
--- a/tests/unit/controller/subtitle-stream-controller.js
+++ b/tests/unit/controller/subtitle-stream-controller.js
@@ -7,6 +7,7 @@ import { Fragment } from '../../../src/loader/fragment';
 import { PlaylistLevelType } from '../../../src/types/loader';
 import KeyLoader from '../../../src/loader/key-loader';
 import { SubtitleStreamController } from '../../../src/controller/subtitle-stream-controller';
+import { AttrList } from '../../../src/utils/attr-list';
 
 const mediaMock = {
   currentTime: 0,
@@ -18,11 +19,11 @@ const tracksMock = [
   {
     id: 0,
     details: { url: '', fragments: [] },
-    attrs: {},
+    attrs: new AttrList(),
   },
   {
     id: 1,
-    attrs: {},
+    attrs: new AttrList(),
   },
 ];