ll-hls-runtime 0.3.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]https://github.com/fishloa/rust-broadcast/issues/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::LlHlsOrigin`, 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::LlHlsClient` is a driveable, caller-driven state machine in the same
sans-IO shape as [`srt-runtime`](../srt-runtime) (issue #565): the core never
opens a socket or reads a clock. The caller drains `Action`s (what to fetch,
and how), performs the IO itself, and feeds the response back in; decoded
`Output`s (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-HINT`ed 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`](../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::LlHlsOrigin`** — 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.
- **`LlHlsOrigin::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 `LlHlsOrigin`/`ServedEgress` over a `media_plane::Trunk` — one
`LlHlsOrigin` 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 selection**`transmux::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.

```toml
[dependencies]
ll-hls-runtime = "0.1"
```

## License

MIT OR Apache-2.0.