Module 19 · Lesson 6 of 13Free
Adaptive bitrate streaming: quality that adapts to the network
- Level
- Advanced
- Topic
- Media uploads, streaming and file sync
- Reading time
- 19 min
When you finish this page you can
- Trace how a player changes video quality without changing its position in the video.
- Calculate whether a segment will arrive before playback runs out of buffered media.
- Implement a small bitrate-selection policy and explain its limits.
- Distinguish network problems from packaging, delivery and decoding failures.
Builds on 2 lessons
Start with lesson 519 minVideo lesson · coming soon
A customer watches a product demonstration on . The video needs 5 megabits each second, but her connection drops to 2 megabits per second. The picture freezes before she sees how the product works.
Kartly already has several encoded versions, prepared in Video transcoding. How can the player change versions without restarting the video?
Change quality, not playback position
Imagine each version cut into matching time intervals. The player can fetch the opening interval in low quality, then fetch the next interval in higher quality.
These intervals are . For this lesson’s design, each contains four seconds of video and audio.
The player has downloaded eight seconds at high quality. The connection slows down. Must it discard those seconds before requesting lower quality?Show answer
Diagram not drawn yet: abr-matching-time-intervals.svg
Two rows show the same video divided into intervals of zero to four, four to eight, and eight to twelve seconds. A selected path uses high quality for the first interval and low quality for the remaining intervals. Playback time continues without a gap.
, or ABR, means choosing the encoded bitrate as playback conditions change. Bitrate measures encoded data per second, not picture dimensions.
A holds downloaded media that the viewer has not watched yet. Measure it in playable seconds. A buffer containing eight seconds buys time during a slow download.
For the running example, assume a one-minute product video and this :
| Version | Picture size | Illustrative total bitrate |
|---|---|---|
| Low | 640 × 360 | 0.6 Mbps |
| Medium | 854 × 480 | 1.2 Mbps |
| High | 1280 × 720 | 2.5 Mbps |
| Highest | 1920 × 1080 | 5 Mbps |
These are lesson-specific assumptions, not Kartly-wide traffic facts or recommended encoding settings. Rates include audio and container data. Calculations initially assume constant-sized segments and ignore request overhead.
The player has useful choices, but it still needs to know which choice will arrive in time.
A download spends the buffer
Consider the opening customer. She has eight seconds buffered when drops.
Throughput is the rate at which media bytes actually arrive. A connection’s advertised speed is not a measurement of this download.
Find the next segment’s size
5 megabits/second × 4 seconds = 20 megabits
The highest-quality segment contains four seconds of playback, regardless of how long downloading takes.
Find its download time
20 megabits ÷ 2 megabits/second = 10 seconds
Playback can continue for only eight seconds. Under this model, the viewer then waits two seconds.
Calculate the buffer afterwards
max(0, 8 seconds − 10 seconds) + 4 seconds = 4 seconds
The completed segment restores four seconds of playable media. This assumes the whole segment must arrive before it becomes playable.
Try the medium version instead
1.2 megabits/second × 4 seconds ÷ 2 megabits/second = 2.4 seconds
The buffer becomes 8 − 2.4 + 4 = 9.6 seconds. Lower quality lets the player rebuild its protection against another slowdown.
A tempting rule is “choose a bitrate just below measured throughput.” It leaves little room for the next request to be slower.
In this example, the network must deliver each four-second segment in less than four seconds to grow the buffer. Request delays count against that deadline too.
For a completed download, the teaching model is:
- Download time = segment size ÷ throughput.
- Stall time = max(0, download time − starting buffer).
- New buffer = max(0, starting buffer − download time) + segment duration.
These equations assume continuous normal-speed playback and one segment download at a time. They do not model partial-segment playback or decoder delays.
A larger buffer can hide a temporary deficit. It cannot sustain a bitrate above throughput forever. That leaves a product decision: who should choose quality, and how often?
Let the player choose downloads
The simplest implementation offers one downloadable video. That is reasonable when the file is small and occasional pauses are acceptable.
Kartly’s product demonstrations need to remain useful as customers move between Wi-Fi and mobile data.
How should Kartly offer quality choices?
Deliver one fixed-quality file.
There is only one object to publish and cache. A high rate repeatedly drains the buffer on this customer’s connection; a low rate wastes picture quality on faster connections.
Offer whole files with a manual quality selector.
Customers can select a version themselves. This suits deliberate downloads, but asks viewers to diagnose changing network conditions during playback.
Offer aligned segments and let the player adapt.
The player selects the next segment using its own download measurements and buffer. All viewers can still request the same stored segment objects.
Why this choice
The player knows how much playable media remains on this device. A server handling an ordinary object request does not know that local state.
Segmentation gives the player repeated opportunities to change course without downloading an entire replacement file. The cost is additional packaging, requests and playback logic.
Keep fixed files for downloads or very short clips when this complexity is not justified. Keep a manual quality cap for viewers who want to limit data use.
Here is the delivery design chosen for this lesson:
Diagram not drawn yet: abr-static-delivery-path.svg
A packager writes versioned manifests and aligned segments into object storage. A player requests them through a CDN, which fetches missing objects from storage. Inside the player, a selector chooses downloads, a buffer holds playable media, and a decoder consumes it.
| Component | Responsibility | Talks to | State it keeps | Failure and response |
|---|---|---|---|---|
| Packager | Produce matching segments and playlists | Storage | Unpublished output | Validation failure keeps the new version unpublished |
| Hold published media | Packager, | Versioned objects | Fetch errors appear in delivery logs; restore access or the missing object | |
| CDN | Serve shared objects near viewers | Player, storage | Cached objects | Request failures trigger bounded retries; sustained errors need delivery recovery |
| Player | Choose, fetch and play media | CDN, decoder | Measurements and buffer | Depletion pauses playback; successful downloads allow resumption |
The publication path is:
- The packager writes every segment under a new version prefix.
- It validates their timelines and .
- It writes the playlists.
- Kartly exposes the new entry playlist only after validation succeeds.
The playback path is:
- The player fetches the entry playlist.
- It selects a supported version and fetches that version’s segment list.
- It requests the next segment through the CDN.
- On completion, it records bytes and elapsed time.
- It adds usable media to the buffer and chooses again.
A CDN miss adds an origin fetch, as covered in CDNs. It does not require the origin to make the quality decision.
This separates delivery from adaptation. The remaining contract is more demanding: every advertised version must describe the same timeline.
Matching segments make switching possible
A compressed picture can depend on earlier pictures. Starting a segment at an arbitrary frame can leave the decoder missing those dependencies.
For this design, make each video segment start at an independently decodable boundary. Apple’s HLS authoring specification requires video segments to start with an IDR frame, a decoder reset point.2
Align those boundaries across versions. Apple also recommends matching segment boundaries across audio and video variants for interoperability.2
In Kartly’s package, the segment covering seconds eight through twelve exists in every version. Switching means selecting that interval at another bitrate.
A describes the available media and how to find it. In HLS, a multivariant playlist points to media playlists, which list segments.1
For example, this entry playlist advertises two versions. Their paths belong to one immutable package:
HTTP/1.1 200 OK
Content-Type: application/vnd.apple.mpegurl
#EXTM3U
#EXT-X-INDEPENDENT-SEGMENTS
#EXT-X-STREAM-INF:BANDWIDTH=600000,AVERAGE-BANDWIDTH=600000,RESOLUTION=640x360
low/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1200000,AVERAGE-BANDWIDTH=1200000,RESOLUTION=854x480
medium/index.m3u8This is a minimal protocol illustration, not a complete device-certification template. The other ladder entries are omitted.
BANDWIDTH describes peak segment bitrate, including playable combinations. It is not the connection speed the player just observed.1
Here is the beginning of medium/index.m3u8, using segments that contain both audio and video:
HTTP/1.1 200 OK
Content-Type: application/vnd.apple.mpegurl
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:4
#EXT-X-PLAYLIST-TYPE:VOD
#EXTINF:4.000,
seg-000.ts
#EXTINF:4.000,
seg-001.ts
#EXTINF:4.000,
seg-002.tsThe complete playlist continues through the minute and ends with EXT-X-ENDLIST. EXTINF gives a segment’s duration.1
Our filenames match across versions for convenience. Players must use timeline information, not assume equal sequence numbers mean equal playback positions.1
Four-second segments are an illustrative choice. Shorter segments provide more frequent decisions but require more requests. Longer segments reduce request frequency but commit the player to larger downloads.
Packaging creates safe switching opportunities. It does not tell the player when taking one is wise.
Drop quickly, climb with evidence
Suppose the latest download suggests ample throughput, but only two seconds remain buffered. Should the player increase quality?
That measurement describes a completed request. It does not promise the next request will be equally fast.
Which signals should control the next quality choice?
Use measured throughput alone.
Recent downloads estimate an affordable rate. A brief fast sample can still trigger an increase just before conditions worsen.
Use buffer level alone.
A full buffer permits higher quality, and a shrinking buffer forces lower quality. Starting with an empty buffer provides little information about network capacity.
Combine throughput estimates with buffer protection.
Throughput limits the candidate rate. Buffer level and repeated supporting measurements control whether an increase is allowed.
Why this choice
Kartly wants to avoid interruptions without holding every viewer at the lowest rate. A combined policy can react to reduced capacity immediately, then require evidence before increasing quality.
The tradeoff is slower recovery to sharper pictures. For a constrained data mode, Kartly would instead apply a lower quality cap regardless of spare throughput.
These are policy families, not one universal algorithm. The dash.js documentation describes throughput-based and buffer-based rules, with dynamic selection between them.3
For an educational implementation, use these explicit rules:
- Start at the lowest supported version.
- Reserve one quarter of estimated throughput as headroom.
- If the buffer falls below six seconds, request at least one rung lower when possible.
- Allow an increase only above twelve buffered seconds.
- Require three consecutive samples supporting the next rung.
- Increase by at most one rung per decision; allow immediate larger decreases.
Those thresholds are this lesson’s policy assumptions. They are not HLS requirements or recommended production defaults.
The player owns the following selector. Its input estimate is already conservative and includes all media traffic in this simplified combined-audio/video design.
from math import isfinite
RATES: tuple[float, ...] = (0.6, 1.2, 2.5, 5.0)
def choose_quality(
estimate_mbps: float,
buffer_s: float,
current: int,
stable_samples: int,
) -> int:
if (
not isfinite(estimate_mbps) or estimate_mbps < 0
or not isfinite(buffer_s) or buffer_s < 0
or not 0 <= current < len(RATES) or stable_samples < 0
):
raise ValueError("Invalid selector input")
budget = 0.75 * estimate_mbps
target = 0
for index, rate in enumerate(RATES):
if rate <= budget:
target = index
if buffer_s < 6:
target = min(target, max(0, current - 1))
if target <= current:
return target
if buffer_s < 12 or stable_samples < 3:
return current
return min(target, current + 1)const RATES: readonly number[] = [0.6, 1.2, 2.5, 5.0];
function chooseQuality(
estimateMbps: number,
bufferS: number,
current: number,
stableSamples: number,
): number {
if (
!Number.isFinite(estimateMbps) || estimateMbps < 0 ||
!Number.isFinite(bufferS) || bufferS < 0 ||
!Number.isInteger(current) || current < 0 ||
current >= RATES.length ||
!Number.isInteger(stableSamples) || stableSamples < 0
) {
throw new Error("Invalid selector input");
}
const budget = 0.75 * estimateMbps;
let target = 0;
RATES.forEach((rate, index) => {
if (rate <= budget) target = index;
});
if (bufferS < 6) {
target = Math.min(target, Math.max(0, current - 1));
}
if (target <= current) return target;
if (bufferS < 12 || stableSamples < 3) return current;
return Math.min(target, current + 1);
}Before calling this function, exclude unsupported versions. For this example, assume the device supports the entire ladder.
The surrounding player counts consecutive completed downloads whose estimates support the next rung. Reset that count after an error, an unsupported increase, or a quality change.
At an estimated 2 Mbps, the budget is 1.5 Mbps. The selector chooses medium quality from the highest rung. With sixteen seconds buffered, that lower rate can rebuild protection without waiting for several more samples.
At an estimated 8 Mbps, medium quality does not jump straight to highest. It first needs enough buffer and supporting samples, then moves to high.
If even the lowest rate exceeds capacity, the function still selects it. That means “smallest available request,” not “playback is safe.”
The code also cannot cancel a slow request already in progress. Selection and request scheduling are separate responsibilities.
A healthy server can still stall
For this lesson’s production design, record playback outcomes alongside delivery errors.
| What the viewer sees | Evidence to collect | Recovery |
|---|---|---|
| Frequent pauses | Buffer before requests, bytes, elapsed time | Reduce quality; investigate sustained capacity deficits |
| Failure at one playback position | Object path, status code, package version | Repair or withdraw a broken package |
| Sharp picture with jerky motion | Dropped frames, device and codec | Choose a less demanding supported version |
| Endless loading after a fetch error | Retry count and total waiting time | Bound retries, then show a recoverable error |
Do not treat a missing object as low bandwidth. Requesting that missing segment repeatedly at another bitrate only helps if that alternative object actually exists.
For , budget retries against the remaining buffer. A retry that takes longer than the media remaining will not prevent a pause.
If an in-flight high-quality request is clearly too slow, cancellation may help. Compare estimated remaining time with the cost of restarting at lower quality. Restarting discards work already downloaded, so cancellation is not automatically faster.
Keep published segment paths immutable. Otherwise, two concurrent publication jobs could leave cached playlists referring to a mixture of package versions.
More viewers multiply delivery work
As a lesson-specific estimate, suppose 10,000 viewers each download one combined segment every four seconds. That averages 2,500 segment requests per second during steady playback.
The CDN absorbs much of that shared-object traffic. It does not remove every viewer’s last-mile limitation. Adding origin capacity cannot fix the opening customer’s 2 Mbps connection.
Avoid inserting an application-server call before every segment. That would add another dependency to each download deadline without improving the player’s local measurements.
Where this shows up
- Design: a video platform like YouTube connects packaging, publication and playback at platform scale.
- Design: a photo-sharing app like Instagram can apply adaptive delivery when its media feed includes video.
The player can now find and fetch several versions; Video security asks who should be allowed to fetch them.
Common mistakes
- Using resolution as a capacity estimate. Use encoded byte sizes and durations. Two videos with the same dimensions need not have the same bitrate.
- Counting idle time as download time. If the player deliberately waits between requests, exclude that pause from its transfer measurement.
- Smoothing away every slowdown. A long averaging window can retain yesterday’s useful rate after conditions change. Test abrupt drops, not only stable connections.
- Buffering without a limit. A viewer who leaves early never watches those extra bytes. Set a bounded buffer target rather than downloading indefinitely.
- Blaming every pause on throughput. Delivery errors, invalid timelines and decoding failures need different recovery paths.
Check your understanding
A four-second segment contains 12 megabits. Throughput is 2 Mbps, and five seconds remain buffered. Under our whole-segment model, does playback pause?Show answer
The selector is at medium quality with sixteen seconds buffered. Its estimate rises to 8 Mbps, but only one sample supports an increase. What happens?Show answer
A package has matching filenames across versions, but one version’s segment starts half a second later. Is switching safe?Show answer
A viewer’s sustainable throughput falls below the lowest available bitrate. Can a larger buffer guarantee uninterrupted playback for an arbitrarily long video?Show answer
Summary
- The player changes versions by requesting the next matching time interval at another bitrate.
- Compare download time with buffered playback time; sustainable delivery must keep up with consumption.
- Aligned boundaries, usable decoder entry points and accurate manifests make switching possible.
- Throughput suggests an affordable rate; buffer protection and repeated evidence make increases more cautious.
- Adaptation cannot repair missing media, unsupported decoding or a connection slower than the lowest available rate.