latencyprobe

How does your live origin answer a low-latency HLS player?

A playlist can declare CAN-BLOCK-RELOAD=YES while the CDN in front of it caches the playlist without its query string — and every player falls back to polling, seconds behind live. latencyprobe acts as the player, times the answers, and checks each against the HLS spec.

go install github.com/Allan-Nava/latencyprobe/cmd/latencyprobe@latest
latencyprobe logo: a stopwatch over a row of parts, the next one dashed
$ latencyprobe https://cdn.example/live/master.m3u8

🔴 BAD    server-control    PART-HOLD-BACK=0.8s is below twice PART-TARGET (1s)  [§4.4.3.8]
🔴 BAD    delta             _HLS_skip=YES returned a full playlist (16s listed, Skip Boundary 12s)  [§6.2.5.1]
🔴 BAD    blocking          12 of 12 blocking reloads came back without the requested part  [§6.2.5.2]
                           ↳ 12 of those responses carried an Age header: a cache is answering, most likely
                             keyed without the query string, so _HLS_msn and _HLS_part never reach the origin
🟢 OK     bad-request       _HLS_part without _HLS_msn is refused with 400  [§6.2.5.2]

6 findings: 3 OK, 0 WARN, 3 BAD, 0 ERROR — 19 requests in 6.2s

From llhls-origin, the local test origin in the repository, with the cache fault switched on — no public low-latency HLS stream was reachable to show instead.

A healthy origin, measured

🟢 OK     blocking   21 blocking reloads, each answered with the requested part; wait p50 499ms, p95 504ms
🟢 OK     cadence    a new part every 500ms (median), PART-TARGET 0.5s — in step with PART-TARGET
🟢 OK     edge-age   a part reaches a blocked client 302ms after its media ends (p50; p95 305ms)
🟢 OK     latency    a player starting at the recommended hold-back plays 1.80s behind the media's wall clock

In GitHub Actions

Gate a deploy, or watch an origin on a schedule. The report lands in the job summary as a table; exit-on: bad fails the job when the origin breaks a MUST.

- uses: Allan-Nava/latencyprobe@v0.2.0
  with:
    url: https://cdn.example/live/master.m3u8
    duration: 60s
    exit-on: bad

A composite action: it installs the release binary for the runner, verified against checksums.txt — Linux and macOS runners, no image to pull. Inputs and outputs are in the README.

What it checks

Every finding names the requirement it comes from in draft-pantos-hls-rfc8216bis-22. A MUST the server breaks is BAD; a SHOULD, or the Appendix B.1 low-latency server profile, is WARN.

CheckWhat latencyprobe doesRule
blockingFollows the live edge with _HLS_msn / _HLS_part requests, as a player does. A response without the requested part means the origin is not blocking — or a cache ignores the query string, which the Age header gives away.§6.2.5.2
delta_HLS_skip=YES must return a delta update past the Skip Boundary, skip only beyond it, and line SKIPPED-SEGMENTS up with the full playlist.§6.2.5.1, §4.4.5.2
rendition-reportEach report against the rendition it describes, fetched straight after: not ahead of it, not a segment behind; one per other rendition.§4.4.5.4, B.1
bad-request_HLS_part without _HLS_msn must get 400; an _HLS_msn far past the edge should get an immediate 400, not a hang.§6.2.5.2
server-controlHOLD-BACK, PART-HOLD-BACK and CAN-SKIP-UNTIL against the minimums the spec states.§4.4.3.8
cadenceHow often a new part actually arrives, against PART-TARGET.
edge-ageHow long after its media ends, by EXT-X-PROGRAM-DATE-TIME, a part reaches a blocked client.
latencyWhat a player at the recommended hold-back actually plays behind wall clock.

Limits, stated

Clocks

edge-age compares the packager's clock with yours. latencyprobe warns when the server's Date is more than 1.5 s off; inside that, run it from an NTP-synced machine.

One vantage point

It measures the path from where it runs. A CDN edge near you can behave differently from one elsewhere — run it from where your viewers are.

Playlists, not media

Whether the parts contain what the playlist says is a different question. segcheck downloads the segments and parts and answers it.

Try it without an origin

$ go run ./cmd/llhls-origin --stale-cache --age     # a CDN keyed without the query string
$ go run ./cmd/llhls-origin --lag 300ms             # each part listed 300 ms late
$ latencyprobe http://127.0.0.1:<port>/master.m3u8

The same origin runs in the tests, where each fault is switched on and the finding it should cause is asserted.