hls_runtime/client/mod.rs
1//! LL-HLS playback client engine — a sans-IO Low-Latency HLS (RFC 8216bis)
2//! client (issue #717, slices 2-4; formerly the standalone `ll-hls-client`
3//! crate, folded in here as `hls-runtime`'s `client` module — see
4//! `docs/superpowers/specs/2026-07-18-multimux-hub-design.md`).
5//!
6//! [`HlsClient`] is a driveable, caller-driven state machine — the same
7//! sans-IO shape as `srt-runtime` (issue #565): the core never opens a socket
8//! or reads a clock. The caller drains [`Action`]s (what to fetch, and how),
9//! performs the IO itself, and feeds the response back in via
10//! [`HlsClient::on_playlist`] / [`HlsClient::on_resource`] /
11//! [`HlsClient::on_error`]; decoded [`Output`]s (the init segment, then
12//! ordered access units) drain out via [`HlsClient::next_output`].
13//!
14//! # Reuse, not re-description
15//!
16//! This module defines **no playlist model of its own**. Parsing is
17//! [`broadcast_hls::MediaPlaylist::parse`] (issue #717 slice 1 — the
18//! symmetric inverse of the LL-HLS origin's own `to_m3u8()` renderer, so
19//! origin and client share one wire model); demuxing a fetched CMAF part or
20//! segment into access units is [`transmux::Fmp4Demux`]. `client` holds only
21//! the client **engine**: the reload scheduler, the fetch pipeline
22//! (prefetch/byte-range/dedup), and the output ordering — see
23//! [`HlsClient`]'s docs for the full behaviour.
24//!
25//! # Zero IO in the core
26//!
27//! No `tokio`/`reqwest`/socket dependency in [`HlsClient`] itself, ever —
28//! it is driveable by hand (see the crate's `tests/origin_loop.rs` for a
29//! complete in-process example against a real
30//! `transmux::ll_hls::LlHlsSegmenter` origin) with zero IO dependencies at
31//! all (verified by the `--no-default-features` gate).
32//!
33//! # The `tokio` feature (issue #717 slice 5)
34//!
35//! Enabling the `tokio` cargo feature (NOT default) adds
36//! [`tokio_client::TokioClient`], a thin async shell (tokio + reqwest/rustls)
37//! that drives [`HlsClient`] over real HTTP — blocking-reload/preload-hint
38//! query params, typed byte-range `Range` headers (`headers::Range`),
39//! per-request timeouts, `backon` retry/backoff with jitter on transient
40//! failures, and a `CancellationToken` for shutdown (no polling sleeps; the
41//! core exposes [`HlsClient::next_wait`]/[`HlsClient::poll_timeout`] for
42//! callers that schedule themselves). See [`tokio_client`]'s module docs
43//! for the full behaviour and its `tests/glass_to_glass.rs` for a
44//! loopback-HTTP, sub-second glass-to-glass proof against a real
45//! `multimux`-served LL-HLS origin. This feature is entirely additive — the
46//! sans-IO core above is completely unaffected by it either way.
47//!
48//! # Example
49//!
50//! ```
51//! use hls_runtime::client::{Action, HlsClient};
52//!
53//! let mut client = HlsClient::new("http://origin/live/stream.m3u8");
54//! // The caller performs this GET; here we just inspect the first action.
55//! match client.poll() {
56//! Some(Action::FetchPlaylist { url, blocking, .. }) => {
57//! assert_eq!(url, "http://origin/live/stream.m3u8");
58//! assert!(blocking.is_none()); // nothing fetched yet, so no LL info.
59//! }
60//! other => panic!("unexpected first action: {other:?}"),
61//! }
62//! ```
63
64mod action;
65mod engine;
66mod error;
67mod output;
68#[cfg(feature = "tokio")]
69pub mod tokio_client;
70mod url;
71
72pub use action::{Action, BlockingReload, KeyId, ResourceId};
73pub use engine::HlsClient;
74pub use error::{Error, Result};
75pub use output::{MasterSelection, Output, RenditionInfo, VariantInfo, VariantPolicy};
76#[cfg(feature = "tokio")]
77pub use tokio_client::{TokioClient, TokioClientConfig, TokioClientStats, TokioError};