ll-hls-runtime 0.1.1

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

ll-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 ll-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) — the sans-IO LL-HLS origin engine, moved out of multimux: the rolling in-RAM window (server::MediaStore), the blocking-reload + part-availability decision logic (resolve_playlist/resolve_resource, never blocking, never touching a clock), and the playlist renderers. multimux is now a thin tokio+axum adapter driving this engine; any other HTTP framework can adapt it the same way.

client::LlHlsClient 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 transmux::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 LlHlsClient 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 LlHlsClient 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, moved out of multimux (issue #663/#717 Stage 2) so any HTTP framework can adapt it:

  • server::MediaStore — the protocol-neutral rolling in-RAM window (init/segments/live parts/health/max-segment-duration). Wakeup is runtime-agnostic: progress_version() (a monotonic counter) + listen() (an event_listener::EventListener any executor — or none, via its blocking .wait() — can drive), not a tokio-specific channel.
  • server::MediaStore::resolve_playlist/resolve_resource — the Blocking Playlist Reload (RFC 8216bis §6.2.5.2) and part-availability decision logic as synchronous poll methods returning PlaylistOutcome/ ResourceOutcome — never blocking, never touching a clock. An async adapter (e.g. multimux) turns a WouldBlock outcome into an actual bounded wait via MediaStore::listen() plus its own tokio::time::timeout (or equivalent).
  • server::media_playlist_m3u8/master_playlist_m3u8 — the LL-HLS playlist renderers; master_playlist_m3u8 takes the media playlist's served filename as an explicit argument, so an adapter can serve it under any configured name.
  • server::CachePolicy (Immutable/NoCache) — the cache-control policy a resolved ResourceOutcome::Ready carries, for an adapter to apply as HTTP Cache-Control.

multimux is the reference adapter: a thin tokio+axum layer that calls resolve_playlist/resolve_resource and drives the one thing the sans-IO engine can't — the actual bounded .await on WouldBlock.

What's not here — explicit follow-ups

  • Multivariant Playlist rendition selectiontransmux::hls::MasterPlaylist::parse exists, but choosing a rendition/bitrate is a player-level policy this crate doesn't impose; LlHlsClient 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]
ll-hls-runtime = "0.1"

License

MIT OR Apache-2.0.