hls-runtime 0.5.0

Sans-IO Low-Latency HLS (RFC 8216bis) client + server engines in one crate (blocking reload, part prefetch, rolling-window origin), with an optional tokio+reqwest IO adapter.
Documentation

hls-runtime

A sans-IO Low-Latency HLS (RFC 8216bis, HTTP Live Streaming 2nd Edition) client + server engine in one crate, mirroring rtsp-runtime's client+server split (renamed from ll-hls-client, never published — Stage 1 of the hls-runtime unification). The crate holds two halves:

  • client (issue #717, slices 2-4: reload scheduler, fetch pipeline, output adapter, plus slice 5's tokio feature for the async IO adapter) — a driveable LL-HLS playback client engine.
  • server (feature std; issue #663/#717 Stage 2, media-plane implementation plan step 4) — the sans-IO LL-HLS origin engine: server::HlsOrigin, a media_plane::egress::ServedEgress rendering playlists and resolving blocking-reload/part-availability requests (never blocking, never touching a clock) directly from a shared media_plane::Trunk — not a push-fed rolling-window store of its own. An async adapter (multimux, over tokio + axum) drives this engine the same way any HTTP framework can adapt it.

client::HlsClient is a driveable, caller-driven state machine in the same sans-IO shape as srt-runtime (issue #565): the core never opens a socket or reads a clock. The caller drains Actions (what to fetch, and how), performs the IO itself, and feeds the response back in; decoded Outputs (the init segment, then ordered access units) drain out in turn.

What's here

  • Reload scheduler — once a playlist advertises EXT-X-SERVER-CONTROL/ EXT-X-PART-INF (Low-Latency), every reload is a Blocking Playlist Reload (RFC 8216bis §6.2.5.2) naming the next not-yet-seen Partial Segment's _HLS_msn/_HLS_part — distinguishing a bare _HLS_msn (waits for a closed segment) from _HLS_part=0. Non-LL origins get a plain GET paced by a WaitMs hint derived from #EXT-X-TARGETDURATION. EXT-X-SKIP/ CAN-SKIP-UNTIL Playlist Delta Updates are requested once a full-playlist baseline exists and merged back into a full view.
  • Fetch pipeline — the EXT-X-PRELOAD-HINTed part is prefetched ahead of its own numbered EXT-X-PART appearance; BYTERANGE parts are supported, including the "omitted offset continues the previous sub-range" rule; the Media Initialization Section (EXT-X-MAP) is fetched once and reused.
  • Dedup / coalescing — once any of a segment's parts have been individually fetched, the segment is never re-fetched whole when it later closes; a playlist whose segments carry no parts at all (non-LL) falls back to whole-segment fetches. A part's samples are never double-counted against its parent segment's.
  • Output adapter — exactly one Output::Init precedes any Output::Samples; parts/segments are demuxed into real access units via transmux::Fmp4Demux (never opaque container bytes); EXT-X-DISCONTINUITY surfaces as Output::Discontinuity; resources arriving before the init segment are buffered and replayed once it arrives, so a caller's fetches may complete in any order.
  • Classic MPEG-TS-segment HLS (issue #760) — a playlist that never advertises an EXT-X-MAP (HLS v3, the dominant legacy/IPTV form: self-contained .ts segments, no init resource) routes each fetched Part/Segment through transmux::TsDemux instead, content-sniffed by the MPEG-TS sync byte once the playlist is known to carry no map. The first demuxed segment's recovered track specs synthesize the one Output::Init the contract above requires, so callers built against the fMP4 path need no TS-specific handling. The fMP4/CMAF + LL path is entirely unchanged.

Reuse, not re-description

The client module defines no playlist model of its own. Parsing is broadcast_hls::MediaPlaylist::parse (issue #717 slice 1 — the symmetric inverse of the LL-HLS origin's own to_m3u8() renderer, so origin and client share one wire model); demuxing a fetched CMAF part or segment into access units is transmux::Fmp4Demux (or, for classic MPEG-TS-segment HLS, issue #760's transmux::TsDemux). client holds only the client engine.

Zero IO in the core

No tokio/reqwest/socket dependency in HlsClient itself, ever. no_std

  • alloc (default std feature can be turned off) — verified by the --no-default-features gate. Drive it by hand — see tests/origin_loop.rs for a complete in-process example against a real transmux::ll_hls::LlHlsSegmenter origin (no real sockets: the origin's playlist/part/segment bytes are handed to the client exactly as a caller's HTTP fetch loop would).

The tokio feature (issue #717 slice 5)

Enabling the (non-default) tokio cargo feature adds client::tokio_client::TokioClient: a thin async shell (tokio + reqwest/rustls) driving HlsClient over real HTTP — blocking-reload/preload-hint query params, Range byte-ranges, per-request timeouts, and retry/backoff on transient failures. Authenticates via the shared broadcast-auth crate (TokioClientConfig::auth takes a broadcast_auth::Credentials — Basic/Digest/Bearer, with Digest computed end-to-end on a 401), the same model rtsp-runtime and multimux's HTTP input adapters use. tests/glass_to_glass.rs (gated on this feature) drives it against a real multimux-served LL-HLS origin over loopback HTTP, fed by a real-time-paced synthetic producer, and measures sub-second glass-to-glass latency — the epic's headline acceptance bar — plus asserts blocking-reload and preload-hint prefetch are actually exercised, and that a genuinely non-LL origin still plays via the full-segment fallback.

The server module (feature std)

The sans-IO LL-HLS origin engine, rendering from a shared media_plane::Trunk (media-plane implementation plan step 4) so any HTTP framework can adapt it:

  • server::HlsOrigin — implements media_plane::egress::ServedEgress. Owns exactly one media_plane::trunk::SegmentCursor (never one per request/peer) to keep a small synced window of currently-advertised closed segments, the lifetime-max segment duration, and the cumulative #EXT-X-DISCONTINUITY-SEQUENCE count — the one thing a &Trunk call alone cannot answer (there is no snapshot query over the segment log). Every other decision — live parts of the open segment, whether a segment has closed, and the just-closed-segment's-final-part-still-serves guarantee (multimux 0.2.1/0.2.2's hard-won bug fixes) — comes straight from the Trunk's own live-part log, with no cache at all.
  • HlsOrigin::resolve (the ServedEgress impl) — the Blocking Playlist Reload (RFC 8216bis §6.2.5.2) and part-availability decision logic as a synchronous poll method returning media_plane::egress::EgressResponse (Ready/Await/BadRequest/ NotFound) — never blocking, never touching a clock, and never Await-ing past the caller's own AwaitPolicy deadline. An async adapter turns an Await outcome into an actual bounded wait via media_plane::Trunk::listen() plus its own tokio::time::timeout (or equivalent).
  • server::master_playlist_m3u8 — the master-playlist renderer; takes the media playlist's served filename as an explicit argument, so an adapter can serve it under any configured name.
  • media_plane::egress::CachePolicy (Immutable/NoCache) — the cache-control policy a resolved EgressResponse::Ready carries, for an adapter to apply as HTTP Cache-Control.

multimux is the reference adapter, and as of its 0.5.0 it serves every route through HlsOrigin/ServedEgress over a media_plane::Trunk — one HlsOrigin per program, resolved per request. The push-fed MediaStore re-export it carried through 0.1.x is deleted: the Trunk is the single copy of the data, never a second cache of it.

What's not here — explicit follow-ups

  • Multivariant Playlist rendition selectionbroadcast_hls::MasterPlaylist::parse exists, but choosing a rendition/bitrate is a player-level policy this crate doesn't impose; HlsClient follows one Media Playlist URL.
  • Discontinuity signalling on a still-open segment — RFC 8216bis's OpenSegment (the in-progress, not-yet-closed segment) carries no discontinuity flag of its own; if every part of a segment was already delivered while it was open, a discontinuity revealed only once it closes is signalled late (after those parts' samples). A gap in the current wire model, not something this crate can fix locally.
[dependencies]
hls-runtime = "0.1"

License

MIT OR Apache-2.0.