diff --git a/api-extractor/report/hls.js.api.md b/api-extractor/report/hls.js.api.md index 0c9acef2113..9744dc58ec2 100644 --- a/api-extractor/report/hls.js.api.md +++ b/api-extractor/report/hls.js.api.md @@ -8,10 +8,8 @@ // // @public (undocumented) export type ABRControllerConfig = { - abrEwmaFastLive: number; - abrEwmaSlowLive: number; - abrEwmaFastVoD: number; - abrEwmaSlowVoD: number; + abrEwmaFast: number; + abrEwmaSlow: number; abrEwmaDefaultEstimate: number; abrBandWidthFactor: number; abrBandWidthUpFactor: number; @@ -2110,16 +2108,16 @@ export interface UserdataSample { // Warnings were encountered during analysis: // -// src/config.ts:156:3 - (ae-forgotten-export) The symbol "AudioStreamController" needs to be exported by the entry point hls.d.ts -// src/config.ts:157:3 - (ae-forgotten-export) The symbol "AudioTrackController" needs to be exported by the entry point hls.d.ts -// src/config.ts:159:3 - (ae-forgotten-export) The symbol "SubtitleStreamController" needs to be exported by the entry point hls.d.ts -// src/config.ts:160:3 - (ae-forgotten-export) The symbol "SubtitleTrackController" needs to be exported by the entry point hls.d.ts -// src/config.ts:161:3 - (ae-forgotten-export) The symbol "TimelineController" needs to be exported by the entry point hls.d.ts -// src/config.ts:163:3 - (ae-forgotten-export) The symbol "EMEController" needs to be exported by the entry point hls.d.ts -// src/config.ts:165:3 - (ae-forgotten-export) The symbol "AbrController" needs to be exported by the entry point hls.d.ts -// src/config.ts:166:3 - (ae-forgotten-export) The symbol "BufferController" needs to be exported by the entry point hls.d.ts -// src/config.ts:167:3 - (ae-forgotten-export) The symbol "CapLevelController" needs to be exported by the entry point hls.d.ts -// src/config.ts:168:3 - (ae-forgotten-export) The symbol "FPSController" needs to be exported by the entry point hls.d.ts +// src/config.ts:154:3 - (ae-forgotten-export) The symbol "AudioStreamController" needs to be exported by the entry point hls.d.ts +// src/config.ts:155:3 - (ae-forgotten-export) The symbol "AudioTrackController" needs to be exported by the entry point hls.d.ts +// src/config.ts:157:3 - (ae-forgotten-export) The symbol "SubtitleStreamController" needs to be exported by the entry point hls.d.ts +// src/config.ts:158:3 - (ae-forgotten-export) The symbol "SubtitleTrackController" needs to be exported by the entry point hls.d.ts +// src/config.ts:159:3 - (ae-forgotten-export) The symbol "TimelineController" needs to be exported by the entry point hls.d.ts +// src/config.ts:161:3 - (ae-forgotten-export) The symbol "EMEController" needs to be exported by the entry point hls.d.ts +// src/config.ts:163:3 - (ae-forgotten-export) The symbol "AbrController" needs to be exported by the entry point hls.d.ts +// src/config.ts:164:3 - (ae-forgotten-export) The symbol "BufferController" needs to be exported by the entry point hls.d.ts +// src/config.ts:165:3 - (ae-forgotten-export) The symbol "CapLevelController" needs to be exported by the entry point hls.d.ts +// src/config.ts:166:3 - (ae-forgotten-export) The symbol "FPSController" needs to be exported by the entry point hls.d.ts // (No @packageDocumentation comment for this package) diff --git a/docs/API.md b/docs/API.md index 316dbf0306b..a096c0351ba 100644 --- a/docs/API.md +++ b/docs/API.md @@ -79,10 +79,8 @@ - [`stretchShortVideoTrack`](#stretchshortvideotrack) - [`maxAudioFramesDrift`](#maxaudioframesdrift) - [`forceKeyFrameOnDiscontinuity`](#forcekeyframeondiscontinuity) - - [`abrEwmaFastLive`](#abrewmafastlive) - - [`abrEwmaSlowLive`](#abrewmaslowlive) - - [`abrEwmaFastVoD`](#abrewmafastvod) - - [`abrEwmaSlowVoD`](#abrewmaslowvod) + - [`abrEwmaFast`](#abrewmafast) + - [`abrEwmaSlow`](#abrewmaslow) - [`abrEwmaDefaultEstimate`](#abrewmadefaultestimate) - [`abrBandWidthFactor`](#abrbandwidthfactor) - [`abrBandWidthUpFactor`](#abrbandwidthupfactor) @@ -375,10 +373,8 @@ var config = { stretchShortVideoTrack: false, maxAudioFramesDrift: 1, forceKeyFrameOnDiscontinuity: true, - abrEwmaFastLive: 3.0, - abrEwmaSlowLive: 9.0, - abrEwmaFastVoD: 3.0, - abrEwmaSlowVoD: 9.0, + abrEwmaFastLive: 0.5, + abrEwmaSlowLive: 1.5, abrEwmaDefaultEstimate: 500000, abrBandWidthFactor: 0.95, abrBandWidthUpFactor: 0.7, @@ -1056,45 +1052,23 @@ Setting this parameter to false can also generate decoding weirdness when switch parameter should be a boolean -### `abrEwmaFastLive` +### `abrEwmaFast` -(default: `3.0`) +(default: `0.5`) Fast bitrate Exponential moving average half-life, used to compute average bitrate for Live streams. -Half of the estimate is based on the last abrEwmaFastLive seconds of sample history. -Each of the sample is weighted by the fragment loading duration. +Half of the estimate is based on the last abrEwmaFast fragments or parts of sample history. parameter should be a float greater than 0 -### `abrEwmaSlowLive` +### `abrEwmaSlow` -(default: `9.0`) +(default: `1.5`) Slow bitrate Exponential moving average half-life, used to compute average bitrate for Live streams. -Half of the estimate is based on the last abrEwmaSlowLive seconds of sample history. -Each of the sample is weighted by the fragment loading duration. +Half of the estimate is based on the last abrEwmaSlow fragments or parts of sample history. -parameter should be a float greater than [abrEwmaFastLive](#abrewmafastlive) - -### `abrEwmaFastVoD` - -(default: `3.0`) - -Fast bitrate Exponential moving average half-life, used to compute average bitrate for VoD streams. -Half of the estimate is based on the last abrEwmaFastVoD seconds of sample history. -Each of the sample is weighted by the fragment loading duration. - -parameter should be a float greater than 0 - -### `abrEwmaSlowVoD` - -(default: `9.0`) - -Slow bitrate Exponential moving average half-life, used to compute average bitrate for VoD streams. -Half of the estimate is based on the last abrEwmaSlowVoD seconds of sample history. -Each of the sample is weighted by the fragment loading duration. - -parameter should be a float greater than [abrEwmaFastVoD](#abrewmafastvod) +parameter should be a float greater than [abrEwmaFast](#abrewmafast) ### `abrEwmaDefaultEstimate` diff --git a/src/config.ts b/src/config.ts index f164750e766..186c59816be 100644 --- a/src/config.ts +++ b/src/config.ts @@ -24,10 +24,8 @@ import type { } from './types/loader'; export type ABRControllerConfig = { - abrEwmaFastLive: number; - abrEwmaSlowLive: number; - abrEwmaFastVoD: number; - abrEwmaSlowVoD: number; + abrEwmaFast: number; + abrEwmaSlow: number; abrEwmaDefaultEstimate: number; abrBandWidthFactor: number; abrBandWidthUpFactor: number; @@ -242,10 +240,8 @@ export const hlsDefaultConfig: HlsConfig = { stretchShortVideoTrack: false, // used by mp4-remuxer maxAudioFramesDrift: 1, // used by mp4-remuxer forceKeyFrameOnDiscontinuity: true, // used by ts-demuxer - abrEwmaFastLive: 3, // used by abr-controller - abrEwmaSlowLive: 9, // used by abr-controller - abrEwmaFastVoD: 3, // used by abr-controller - abrEwmaSlowVoD: 9, // used by abr-controller + abrEwmaFast: 0.5, // used by abr-controller + abrEwmaSlow: 1.5, // used by abr-controller abrEwmaDefaultEstimate: 5e5, // 500 kbps // used by abr-controller abrBandWidthFactor: 0.95, // used by abr-controller abrBandWidthUpFactor: 0.7, // used by abr-controller diff --git a/src/controller/abr-controller.ts b/src/controller/abr-controller.ts index 07a2039da6a..233dee13aeb 100644 --- a/src/controller/abr-controller.ts +++ b/src/controller/abr-controller.ts @@ -14,7 +14,6 @@ import type { FragLoadedData, FragBufferedData, ErrorData, - LevelLoadedData, } from '../types/events'; import type { ComponentAPI } from '../types/component-api'; @@ -35,8 +34,8 @@ class AbrController implements ComponentAPI { const config = hls.config; this.bwEstimator = new EwmaBandWidthEstimator( - config.abrEwmaSlowVoD, - config.abrEwmaFastVoD, + config.abrEwmaSlow, + config.abrEwmaFast, config.abrEwmaDefaultEstimate ); @@ -48,7 +47,6 @@ class AbrController implements ComponentAPI { hls.on(Events.FRAG_LOADING, this.onFragLoading, this); hls.on(Events.FRAG_LOADED, this.onFragLoaded, this); hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this); - hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this); hls.on(Events.ERROR, this.onError, this); } @@ -57,7 +55,6 @@ class AbrController implements ComponentAPI { hls.off(Events.FRAG_LOADING, this.onFragLoading, this); hls.off(Events.FRAG_LOADED, this.onFragLoaded, this); hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this); - hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this); hls.off(Events.ERROR, this.onError, this); } @@ -80,15 +77,6 @@ class AbrController implements ComponentAPI { } } - protected onLevelLoaded(event: Events.LEVEL_LOADED, data: LevelLoadedData) { - const config = this.hls.config; - if (data.details.live) { - this.bwEstimator.update(config.abrEwmaSlowLive, config.abrEwmaFastLive); - } else { - this.bwEstimator.update(config.abrEwmaSlowVoD, config.abrEwmaFastVoD); - } - } - /* This method monitors the download rate of the current fragment, and will downswitch if that fragment will not load quickly enough to prevent underbuffering diff --git a/src/utils/ewma-bandwidth-estimator.ts b/src/utils/ewma-bandwidth-estimator.ts index 4904a80bea8..1aafdb01351 100644 --- a/src/utils/ewma-bandwidth-estimator.ts +++ b/src/utils/ewma-bandwidth-estimator.ts @@ -11,42 +11,29 @@ import EWMA from '../utils/ewma'; class EwmaBandWidthEstimator { private defaultEstimate_: number; private minWeight_: number; - private minDelayMs_: number; private slow_: EWMA; private fast_: EWMA; constructor(slow: number, fast: number, defaultEstimate: number) { this.defaultEstimate_ = defaultEstimate; - this.minWeight_ = 0.001; - this.minDelayMs_ = 50; + this.minWeight_ = 1; this.slow_ = new EWMA(slow); this.fast_ = new EWMA(fast); } - update(slow: number, fast: number) { - const { slow_, fast_ } = this; - if (this.slow_.halfLife !== slow) { - this.slow_ = new EWMA(slow, slow_.getEstimate(), slow_.getTotalWeight()); + sample(transferMs: number, numBytes: number) { + // limit speed to mitigate uncertainty from very fast transfers + if (numBytes) { + transferMs = Math.max(transferMs, 2); + // value is bandwidth in bits/s + const bandwidthInBps = (8000 * numBytes) / transferMs; + this.fast_.sample(bandwidthInBps); + this.slow_.sample(bandwidthInBps); } - if (this.fast_.halfLife !== fast) { - this.fast_ = new EWMA(fast, fast_.getEstimate(), fast_.getTotalWeight()); - } - } - - sample(durationMs: number, numBytes: number) { - durationMs = Math.max(durationMs, this.minDelayMs_); - const numBits = 8 * numBytes; - // weight is duration in seconds - const durationS = durationMs / 1000; - // value is bandwidth in bits/s - const bandwidthInBps = numBits / durationS; - this.fast_.sample(durationS, bandwidthInBps); - this.slow_.sample(durationS, bandwidthInBps); } canEstimate(): boolean { - const fast = this.fast_; - return fast && fast.getTotalWeight() >= this.minWeight_; + return this.fast_.getTotalWeight() >= this.minWeight_; } getEstimate(): number { diff --git a/src/utils/ewma.ts b/src/utils/ewma.ts index 8c9a0e61cd6..228e7e26f01 100644 --- a/src/utils/ewma.ts +++ b/src/utils/ewma.ts @@ -1,7 +1,6 @@ /* * compute an Exponential Weighted moving average * - https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average - * - heavily inspired from shaka-player */ class EWMA { @@ -19,10 +18,9 @@ class EWMA { this.totalWeight_ = weight; } - sample(weight: number, value: number) { - const adjAlpha = Math.pow(this.alpha_, weight); - this.estimate_ = value * (1 - adjAlpha) + adjAlpha * this.estimate_; - this.totalWeight_ += weight; + sample(value: number) { + this.estimate_ = value * (1 - this.alpha_) + this.alpha_ * this.estimate_; + this.totalWeight_++; } getTotalWeight(): number { diff --git a/tests/unit/controller/ewma-bandwidth-estimator.ts b/tests/unit/controller/ewma-bandwidth-estimator.ts index ae693ad2cd4..62091ed6f51 100644 --- a/tests/unit/controller/ewma-bandwidth-estimator.ts +++ b/tests/unit/controller/ewma-bandwidth-estimator.ts @@ -30,12 +30,12 @@ describe('EwmaBandWidthEstimator', function () { expect(bwEstimator.getEstimate()).to.equal(1000000); bwEstimator.sample(4000, 1000000); expect(bwEstimator.getEstimate()).to.closeTo( - 1396480.1544736226, + 1511550.3977404737, 0.000000001 ); bwEstimator.sample(1000, 1000000); expect(bwEstimator.getEstimate()).to.closeTo( - 2056826.9489827948, + 3775044.041335162, 0.000000001 ); }); @@ -47,45 +47,13 @@ describe('EwmaBandWidthEstimator', function () { expect(bwEstimator.getEstimate()).to.equal(1000000); bwEstimator.sample(4000, 1000000); expect(bwEstimator.getEstimate()).to.closeTo( - 1439580.319105247, + 1519244.5768252169, 0.000000001 ); bwEstimator.sample(1000, 1000000); expect(bwEstimator.getEstimate()).to.closeTo( - 2208342.324322311, + 3847839.2697109017, 0.000000001 ); }); - - it('returns correct value after updating slow and fast', function () { - const defaultEstimate = 5e5; - const bwEstimator = new EwmaBandWidthEstimator(9, 3, defaultEstimate); - expect(bwEstimator.getEstimate()).to.equal(defaultEstimate); - bwEstimator.sample(8000, 1000000); - expect(bwEstimator.getEstimate()).to.equal(1000000); - bwEstimator.sample(4000, 1000000); - expect(bwEstimator.getEstimate()).to.closeTo( - 1439580.319105247, - 0.000000001 - ); - bwEstimator.update(15, 4); - expect(bwEstimator.getEstimate()).to.closeTo( - 1878125.393685882, - 0.000000001 - ); - bwEstimator.sample(1000, 1000000); - expect(bwEstimator.getEstimate()).to.closeTo( - 2966543.443461984, - 0.000000001 - ); - }); - - it('returns correct value when updating before a sample', function () { - const defaultEstimate = 5e5; - const bwEstimator = new EwmaBandWidthEstimator(9, 3, defaultEstimate); - bwEstimator.update(15, 4); - expect(bwEstimator.getEstimate()).to.equal(defaultEstimate); - bwEstimator.sample(8000, 1000000); - expect(bwEstimator.getEstimate()).to.equal(1000000); - }); });