mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
96 lines
3.7 KiB
JavaScript
96 lines
3.7 KiB
JavaScript
import { applyZeroFactor, calculateEwma } from "./ewma.js";
|
|
//#region src/network/bandwidth-estimator.ts
|
|
/**
|
|
* Dual EWMA Bandwidth Estimator
|
|
*
|
|
* Estimates available bandwidth using two EWMA calculations with different
|
|
* half-lives, taking the minimum of both. This approach (from Shaka Player):
|
|
*
|
|
* - **Fast EWMA** (2s half-life): Reacts quickly to bandwidth drops
|
|
* - **Slow EWMA** (5s half-life): Provides stability during fluctuations
|
|
* - **min(fast, slow)**: Adapts down quickly, up slowly
|
|
*
|
|
* This naturally provides asymmetric behavior needed for good QoE:
|
|
* avoiding stalls (quick downgrade) while preventing oscillation (slow upgrade).
|
|
*/
|
|
/**
|
|
* Default bandwidth estimator configuration.
|
|
*
|
|
* Values match Shaka Player defaults based on experimentation.
|
|
*/
|
|
const DEFAULT_BANDWIDTH_CONFIG = {
|
|
fastHalfLife: 2,
|
|
slowHalfLife: 5,
|
|
minTotalBytes: 128e3,
|
|
minBytes: 16e3,
|
|
minDuration: 5
|
|
};
|
|
/**
|
|
* Add a bandwidth sample from a segment download.
|
|
*
|
|
* Samples are filtered based on:
|
|
* - Minimum bytes (filters TTFB-dominated small segments)
|
|
* - Minimum duration (filters cached responses)
|
|
*
|
|
* Valid samples update both fast and slow EWMA estimates.
|
|
*
|
|
* @param state - Current estimator state
|
|
* @param durationMs - Download duration in milliseconds
|
|
* @param numBytes - Number of bytes downloaded
|
|
* @param config - Optional estimator configuration (uses defaults if not provided)
|
|
* @returns New estimator state with sample incorporated (or unchanged if filtered)
|
|
*
|
|
* @example
|
|
* let state = { fastEstimate: 0, fastTotalWeight: 0, ... };
|
|
* // Sample: 1MB in 1 second
|
|
* state = sampleBandwidth(state, 1000, 1_000_000);
|
|
*/
|
|
function sampleBandwidth(state, durationMs, numBytes, config = DEFAULT_BANDWIDTH_CONFIG) {
|
|
const updatedBytesSampled = state.bytesSampled + numBytes;
|
|
if (numBytes < config.minBytes) return {
|
|
...state,
|
|
bytesSampled: updatedBytesSampled
|
|
};
|
|
if (durationMs < config.minDuration) return {
|
|
...state,
|
|
bytesSampled: updatedBytesSampled
|
|
};
|
|
const bandwidth = 8e3 * numBytes / durationMs;
|
|
const weight = durationMs / 1e3;
|
|
return {
|
|
fastEstimate: calculateEwma(state.fastEstimate, bandwidth, weight, config.fastHalfLife),
|
|
fastTotalWeight: state.fastTotalWeight + weight,
|
|
slowEstimate: calculateEwma(state.slowEstimate, bandwidth, weight, config.slowHalfLife),
|
|
slowTotalWeight: state.slowTotalWeight + weight,
|
|
bytesSampled: updatedBytesSampled
|
|
};
|
|
}
|
|
/**
|
|
* Get the current bandwidth estimate.
|
|
*
|
|
* Returns the **minimum** of the fast and slow EWMA estimates.
|
|
* This provides the key asymmetric behavior:
|
|
* - When bandwidth drops, fast EWMA reacts first and dominates (quick adaptation)
|
|
* - When bandwidth rises, slow EWMA lags behind and dominates (slow adaptation)
|
|
*
|
|
* Uses default estimate until enough data has been sampled — and when no
|
|
* estimator state exists at all (`state === undefined`).
|
|
*
|
|
* @param state - Current estimator state, or `undefined` before any samples have been collected
|
|
* @param defaultEstimate - Fallback estimate before sufficient samples (bps)
|
|
* @param config - Optional estimator configuration (uses defaults if not provided)
|
|
* @returns Bandwidth estimate in bits per second
|
|
*
|
|
* @example
|
|
* const estimate = getBandwidthEstimate(state, 5_000_000); // 5 Mbps default
|
|
*/
|
|
function getBandwidthEstimate(state, defaultEstimate, config = DEFAULT_BANDWIDTH_CONFIG) {
|
|
if (!state || state.bytesSampled < config.minTotalBytes) return defaultEstimate;
|
|
const fastEstimate = applyZeroFactor(state.fastEstimate, state.fastTotalWeight, config.fastHalfLife);
|
|
const slowEstimate = applyZeroFactor(state.slowEstimate, state.slowTotalWeight, config.slowHalfLife);
|
|
return Math.min(fastEstimate, slowEstimate);
|
|
}
|
|
//#endregion
|
|
export { DEFAULT_BANDWIDTH_CONFIG, getBandwidthEstimate, sampleBandwidth };
|
|
|
|
//# sourceMappingURL=bandwidth-estimator.js.map
|