Skip to content

ABR and Bandwidth

Three methods that expose throughput estimation and decode-capability probing.

bandwidth()

TypeScript
bandwidth(): number

Last-known throughput estimate in bits per second. Returns 0 until an active stream source reports a measurement (the player core itself does not drive estimation, the value comes from the active backend or a consumer-supplied estimator).

TypeScript
const bps = player.bandwidth();
const kbps = Math.round(bps / 1000);
console.log(`Estimated bandwidth: ${kbps} kbps`);

The value is updated by the active stream source whenever it completes a segment download. On networks where the backend does not report throughput (native <audio> or <video> without HLS), bandwidth() remains 0.

bandwidthEstimator()

TypeScript
bandwidthEstimator(): (() => number) | undefined
bandwidthEstimator(fn: () => number): void

Read or replace the bandwidth estimator function.

Reading returns the currently wired estimator (or undefined if none is set).

Writing installs a custom estimator. The active stream source calls the function on every ABR level decision. Install your own estimator when you have out-of-band signal (SignalR quality report, server-sent bitrate hint) that is more accurate than the default download-speed measurement.

TypeScript
player.bandwidthEstimator(() => {
return myQualitySignal.getRecommendedBitsPerSecond();
});

const estimator = player.bandwidthEstimator();
console.log(typeof estimator); // 'function'

The estimator is called synchronously on the ABR decision path, so keep it fast.

canPlay()

TypeScript
canPlay(profile: {
contentType: string;
width?: number;
height?: number;
bitrate?: number;
framerate?: number;
}): Promise<CanPlayResult>

Probes whether the browser can decode a specific media profile smoothly. Delegates to the platform adapter’s capabilities.canDecode bridge, which wraps the standard MediaCapabilities.decodingInfo() API.

TypeScript
const result = await player.canPlay({
contentType: 'video/mp4; codecs="avc1.64001f, mp4a.40.2"',
width: 1920,
height: 1080,
bitrate: 8_000_000, // 8 Mbps
framerate: 30,
});

if (result.supported && result.smooth) {
player.quality(targetIndex); // safe to lock this level
}

CanPlayResult

TypeScript
interface CanPlayResult {
supported: boolean; // browser can decode this codec/container
smooth: boolean; // browser can decode without dropped frames
powerEfficient: boolean; // browser can decode without excessive battery drain
}

supported: false means the codec is not supported, so do not select this quality level.

supported: true, smooth: false means the level is playable but the browser may drop frames under load, so use it as a last resort only.

powerEfficient: false is typically seen on mobile devices with high-bitrate streams that the CPU decodes in software rather than through the hardware decoder pipeline. It is informational, and smooth: true takes precedence for quality selection.

Using canPlay for quality filtering

Before rendering a quality picker, filter out levels the browser cannot handle:

TypeScript
const levels = player.qualityLevels();
const probes = await Promise.all(
levels.map((level) =>
player.canPlay({
contentType: 'application/vnd.apple.mpegurl',
width: level.width,
height: level.height,
bitrate: level.bitrate,
framerate: 30,
}),
),
);

const supportedLevels = levels.filter((_, index) => {
return probes[index].supported && probes[index].smooth;
});

For HLS streams, the video player’s HDR-aware ABR logic does this filtering automatically, using canPlay internally to exclude HDR levels on SDR displays. Calling canPlay manually is only needed when building a custom quality UI that must pre-filter the level list.

See also