Skip to content

Function: createBolaSelector() ​

ts
function createBolaSelector(options?): BolaSelector;

Defined in: createBolaSelector.ts:94

Beta

Creates a bitrate selector that implements BOLA, the Buffer Occupancy based Lyapunov Algorithm.

BOLA scores each rung with the effective buffer. The effective buffer is the real buffer level plus a placeholder buffer. A fuller buffer allows a higher rung. The selector has two states:

  • Startup. The selector picks the highest rung whose download fits in one segment at throughput * safetyFactor. The download time counts the mean time to first byte of the last four finished samples. Without segmentDuration, the pick is the highest rung at or below throughput * safetyFactor / playbackRate. The selector sets the placeholder so that the effective buffer is at least the threshold of that rung. It moves to the steady state in the first call after a sample, when the buffer holds one segment. That call still picks from the throughput.
  • Steady. The selector picks the rung for the effective buffer and applies two guards. The oscillation guard limits an upward switch. Suppose the buffer picks a rung above the current rung. Suppose that rung is also above the rung whose download fits in one segment at throughput * safetyFactor. Then the guard keeps the higher of the current rung and that sustainable rung. The starvation guard lowers the pick when the download of the next segment does not fit in the real buffer.

A call returns a delay when the effective buffer is above the maximum buffer of the chosen rung. The placeholder absorbs part of the excess first, and the remainder is the delay. The top rung never has a delay. The player waits and calls select again. When the buffer drained during the wait, the second call returns a delay of 0. A call in which the starvation guard lowers the pick returns no delay. In the steady state, a bufferLevel of 0.3 seconds or less counts as a stall and clears the placeholder. When the ladder or the buffer target changes, the selector converts the placeholder to keep the pick equivalent. A placeholder cannot be negative, so the conversion has a limit. The README states it. After an aborted download, the next lower rung replaces the aborted rung. The replacement becomes the current rung, and its threshold caps the effective buffer.

The starvation guard runs when the input has a positive segmentDuration and the throughput is known. It waits until two samples have finished since the last reset. It keeps the highest rung, at or below the pick, that downloads within bufferLevel / playbackRate. It assumes throughput * starvationSafetyFactor for that download. It adds the mean time to first byte of the last four finished samples to that download. Rung 0 always passes. A call without segmentDuration skips the starvation guard. It also skips the bound on the startup pick and on the oscillation guard.

The selector reads timing, bandwidth, duration, bufferLevel, isReplacement, and aborted from a sample. A sample without a positive finite duration keeps the selector in the startup state. The selector needs the throughput estimate at startup and in both guards. It reads segmentDuration and playbackRate at startup, in the oscillation guard, and in the starvation guard. The BOLA buffer rules ignore playbackRate.

BOLA suits a large buffer. In the dash.js follow-up paper, a throughput rule oscillates less than the original BOLA with a buffer of 10 seconds. The original BOLA has no placeholder buffer and no starvation guard. A small bufferTarget needs care.

Parameters ​

options? ​

BolaSelectorOptions

The safety factor and the starvation safety factor

Returns ​

BolaSelector

The selector

Example ​

ts
const selector = createBolaSelector()
const ladder = [
	{ bandwidth: 300_000 },
	{ bandwidth: 750_000 },
	{ bandwidth: 1_500_000 },
	{ bandwidth: 3_000_000 },
	{ bandwidth: 6_000_000 },
]

// First request. No sample exists and the estimate is unknown, so the selector picks rung 0.
const first = selector.select({ ladder, bufferLevel: 0, throughput: NaN, now: 0 })

// The player downloads a 4 second segment of rung 0 (150,000 bytes) in 400 ms, at 3 Mbps.
const timing = { startTime: 0, encodedBodySize: 150_000, duration: 400 }
selector.sample({
	timing,
	bandwidth: ladder[first.index].bandwidth,
	duration: 4,
	bufferLevel: 0,
})

// Second request. The estimate is 3 Mbps, so the selector picks rung 2 (1.5 Mbps).
const second = selector.select({ ladder, bufferLevel: 4, throughput: 3_000_000, now: 400 })

Throws ​

RangeError when safetyFactor or starvationSafetyFactor is not a finite number above 0 and at most 1

See ​

A Streaming Video Technology Alliance project.