You’re watching a video on your phone. WiFi is strong, so it’s crisp 1080p. You walk to the kitchen. Signal weakens. The video buffers for 10 seconds. Terrible experience.

Adaptive bitrate streaming solves this. Instead of one video file, the server has the same video encoded at multiple quality levels. The client measures its bandwidth and switches quality between segments. Bandwidth drops? Next segment loads in 480p. Bandwidth recovers? Back to 1080p. No buffering, no interruption.

How HLS/DASH Works#

The video is split into small segments (2-10 seconds each). Each segment exists at multiple quality levels: 240p, 480p, 720p, 1080p. A manifest file lists all available segments and their URLs.

#EXTM3U
#EXT-X-STREAM-INF:BANDWIDTH=800000,RESOLUTION=640x360
/video/123/360p/playlist.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=2400000,RESOLUTION=1280x720
/video/123/720p/playlist.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=5000000,RESOLUTION=1920x1080
/video/123/1080p/playlist.m3u8

The client picks the highest quality that fits its current bandwidth. Every few segments, it re-estimates bandwidth and potentially switches.

graph TD C["Client"] --> M["Fetch Manifest"] M --> BW["Estimate Bandwidth"] BW --> D{Bandwidth Level?} D -->|"> 5 Mbps"| H["Request 1080p Segment"] D -->|"2-5 Mbps"| MED["Request 720p Segment"] D -->|"< 2 Mbps"| L["Request 360p Segment"] H --> P["Play Segment"] MED --> P L --> P P --> BW style C fill:#000000,stroke:#00ff00,stroke-width:2px,color:#fff style M fill:#000000,stroke:#00ff00,stroke-width:2px,color:#fff style BW fill:#000000,stroke:#00ff00,stroke-width:2px,color:#fff style D fill:#000000,stroke:#00ff00,stroke-width:2px,color:#fff style H fill:#000000,stroke:#00ff00,stroke-width:2px,color:#fff style MED fill:#000000,stroke:#00ff00,stroke-width:2px,color:#fff style L fill:#000000,stroke:#ff0000,stroke-width:2px,color:#fff style P fill:#000000,stroke:#00ff00,stroke-width:2px,color:#fff

Segment Duration Trade-offs#

Short segments (2s): switch quality quickly, adapt to network changes fast. But more HTTP requests, more overhead, and the client has a smaller buffer.

Long segments (10s): fewer requests, more efficient. But you’re stuck at the wrong quality for 10 seconds if bandwidth changes.

Most streaming services land on 4-6 second segments. Good balance between adaptability and efficiency.

Bandwidth Estimation#

The client measures how long the last segment took to download and computes throughput. Sounds simple, but it’s noisy. A single slow segment could cause unnecessary quality drops. Smart clients use a sliding window average and only switch quality when the trend is clear, not on a single measurement.

This is the same principle as backpressure: adapt to the capacity of the system in real time rather than assuming a fixed rate.

At Oracle, we had a similar adaptive behavior pattern, not for video but for config propagation. NSSF config updates needed to push to downstream nodes. Under normal conditions, we’d push full config snapshots. When network conditions degraded (high latency to certain nodes), the system needed to switch to delta-only updates: smaller payloads, less bandwidth. Initially we didn’t adapt. Slow links caused timeout cascades and retry storms. Adding bandwidth-aware payload sizing (full snapshot when healthy, deltas when degraded) cut retry messages by 50%. Same idea as adaptive bitrate: measure conditions, adjust the payload, keep things flowing.

What I’m Learning#

Adaptive bitrate streaming is really about closed-loop control. Measure, decide, act, repeat. The same pattern appears everywhere in distributed systems: circuit breakers adjust to failure rates, rate limiters adjust to load, backpressure adjusts to consumer capacity. The underlying idea is always the same: don’t assume conditions, measure them.

How does your system adapt its behavior when network conditions change?