Same symptom, five root causes.
A bug report lands in your queue: "the video is playing at low quality." One sentence, and it is almost useless, because at least five completely different faults produce that exact blur. Before it is a technical problem, it is an evidence-routing problem.
~2200 words; about a 9 minute read
Why this is your problem.
Five faults produce the same blurry picture. The manifest was trimmed and the high rungs were never offered. The network genuinely cannot sustain the top rung. The CDN misses cache on every segment. The player is hard-capped to a low resolution by its own config. Or the player's ABR is mistuned and refuses to climb even though it could.
Same picture. Five different fixes, owned by four different teams: encoding, networking, CDN, client. Pick the wrong one and you do not lose minutes. You lose days.
When a ticket lands as "the video is blurry," nobody knows which team owns it, so it bounces. Encoding checks the ladder, "looks fine." Networking checks the link, "healthy." CDN checks cache, "hot." Client checks the player, "rendering correctly." One misrouted ticket does not cost five minutes of handoff. It costs days of limbo with no owner. The interesting part: once it reaches the right person, the diagnosis often takes minutes. The cost is not the fix. It is the routing.
The setup.
The whole argument falls apart if the numbers are massaged, so here is exactly how it was proven.
A controlled rig
One real HLS ladder (240p / 480p / 720p in AVC, 1080p in HEVC). An nginx server that serves that ladder under several profiles: full manifest, a manifest trimmed to 480p, a byte-rate-throttled path, and a path that stamps fake cache-MISS headers. Plus a demo Android app that can mistune its own ExoPlayer on command.
Each fault is a known, injected condition. The right answer is known going in. The only question is how fast each approach gets to it. Every metric below is a real, unedited capture. The faults are staged. The measurements are not.
Same picture, opposite causes.
Two cases that look identical on screen and have nothing in common underneath.

Case A, the manifest caps out
The Tracks tab lists the runtime ladder, every rung the player actually knows about: 426x240 and 854x480, and nothing above. Now look at the throughput: 39 MB/s. The link is plainly fast. The player cannot pick a higher rung because the ladder does not offer one. The cap is in the manifest, not the network. This routes to encoding and packaging.

Case B, the network chokes
Same blurry video. But now the ladder is full: 240p, 480p, 720p all present and selectable. Yet the latest segment took 2031 ms at 927.5 KB/s. The link is saturated. The player is correctly sitting low because the network cannot feed the top rung. A curl confirms it: 2.03 s throttled versus 20 ms unthrottled, roughly 100x slower. The rungs exist. The link cannot reach them. This routes to networking.
Two screenshots, opposite root causes, identical picture on the device. Could an engineer read this from logs? Mostly, yes. ExoPlayer ships EventLogger, which dumps the runtime ladder. But it gives you the ladder, not the per-segment timing, so you still need a second tool for the "is the link fast" half of every diagnosis. And in most QA and support workflows that stack is not self-service: it needs source access, a debug build, a wired device, adb, and the fluency to read a logcat firehose. For a tester the raw arm is not slower, it is absent.
Five faults, five teams.
All five cases point to the same gap: QA has no on-device visibility into the streaming state. They can describe what they see. They cannot see why.
Five faults, five teams, and every single one: QA cannot self-route. Not because testers lack skill, but because the data is not exposed on-device. And cdn_miss hurts most, because even an engineer with every tool has to first suspect the CDN before they set up a proxy. Nothing prompts that hypothesis.
This is QA's real job: not to solve the bug, but to route it correctly on day one. A two-minute "the ladder caps at 480p" saves three days of bouncing. But they cannot route what they cannot see.
Where the tool falls short.
A diagnostics tool you cannot trust to admit what it cannot see is not a diagnostics tool. So, honestly:
The overlay is not uniquely capable
EventLogger's track dump exposes the same per-rung detail the overlay colors. The overlay's contribution is making it visual and on-device, not making it visible at all. Better to say that plainly than oversell it.
One gap runs deeper
The 1080p rung is greyed in two different cases, but for different reasons: on this device 1080p is HEVC the hardware cannot decode. Same grey dot, two meanings. EventLogger prints a supported flag per rung, so it can name that. The overlay cannot yet, because its variant data does not carry the flag. Surfacing isSupported and the live TrackSelectionParameters are the two directions worth exploring. Neither is shipped. Naming them is the point.
"The video is blurry" is not a technical problem. It is a triage problem, an observability gap, and it is solvable. For engineers the win is modest: the overlay saves the setup tax and puts the ladder, the throughput, and the cache status side by side, on-device. For QA and triage the swing is much larger, from "none" to "enough to route the ticket correctly on the first try."
StreamProbe is a debug-only diagnostics SDK for Media3/ExoPlayer (Android) and AVFoundation (iOS). It is never enabled in release builds.
❦
Originally published on Medium.