A real player (ExoPlayer, AVPlayer, hls.js) downloads video in short segments (here, 4 s each) and must pick a bitrate before each segment starts, using only what it knows so far: current buffer occupancy and recently observed throughput.
Buffer-based (BBA-style) rule: map the buffer level directly to a target rate between a reservoir and a cushion:
if B ≤ reservoir: rate = min
if B ≥ cushion: rate = max
else: rate = min + (max-min)·(B-reservoir)/(cushion-reservoir)
A shrinking buffer is itself the congestion signal — no throughput measurement needed.
Throughput-based rule: keep an exponentially-weighted moving average of measured segment download rates, then pick the highest bitrate that stays under it with a safety margin:
T̂ ← w·T̂ + (1-w)·T_measured (w = 0.5)
target = margin · T̂
pick highest level with bitrate ≤ target
Download time for a segment is size/throughput: Δt = (bitrate·segDur) / throughput. If the buffer empties before the next segment finishes, playback stalls (a rebuffer event) until at least one full segment (4 s) is buffered again.
- Base bandwidth / jitter — sets the simulated network capacity and how much it fluctuates second to second.
- Network drop — cuts bandwidth to ~15% for 5 seconds, the classic case that separates the two algorithms.
- Buffer-based vs Throughput-based — switch the controller live and watch how each reacts to the same network trace.
- Buffer cushion target — how much buffer the buffer-based controller needs before it's willing to play the top bitrate.
- ABR aggressiveness — the throughput-based controller's safety margin: higher uses more of the estimated bandwidth (higher quality, more stall risk), lower is more conservative.