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