Expand description
§hls-runtime
Sans-IO Low-Latency HLS (RFC 8216bis) client and origin engines in one crate: a driveable playback client, and an origin that renders playlists and resolves blocking-reload requests straight from a media_plane::Trunk. An optional tokio + reqwest adapter provides real HTTP for the client.
Formerly published as ll-hls-runtime (last release 0.3.1 under that name); search for either name. (The earlier in-repo name ll-hls-client was never published.)
[dependencies]
hls-runtime = "0.11"§What it does / what it does not
Does (client): client::HlsClient is a caller-driven state machine. It tells you what to fetch (Action::FetchPlaylist, FetchResource, FetchKey, WaitMs), you do the IO, you feed the bytes back, and decoded Outputs (one Init, then ordered Samples, plus Discontinuity and EndOfStream) come out. It schedules Blocking Playlist Reloads (_HLS_msn/_HLS_part), requests and merges Playlist Delta Updates (EXT-X-SKIP), prefetches the EXT-X-PRELOAD-HINT part, honours BYTERANGE parts, avoids re-fetching a segment whose parts it already has, demuxes fMP4/CMAF with transmux::Fmp4Demux, and also plays classic MPEG-TS-segment HLS (HLS v3, no EXT-X-MAP) through transmux::TsDemux.
Does (origin): server::HlsOrigin implements media_plane::egress::ServedEgress. It owns one segment cursor, renders media and master playlists (LL-HLS parts, preload hint, EXT-X-DATERANGE from the trunk’s SCTE-35 events, discontinuity sequence), names resources per origin instance so a name maps to one set of bytes for ever, and resolves blocking-reload and part-availability decisions as a synchronous poll returning Ready, Await, BadRequest or NotFound.
Does not: open sockets or read a clock in the core (an async adapter such as multimux turns an Await into a bounded wait), define its own playlist model (that is broadcast-hls), choose a rendition from a multivariant playlist (the client follows one media playlist URL), or write playlist syntax itself. The origin advertises CAN-SKIP-UNTIL and answers _HLS_skip with an EXT-X-SKIP delta update, and emits EXT-X-RENDITION-REPORT for its rendition siblings.
§Standards supported
| Standard | Edition | Scope | Status | Notes |
|---|---|---|---|---|
IETF HLS 2nd edition, draft-pantos-hls-rfc8216bis | Internet-Draft of 1 May 2026, “HTTP Live Streaming 2nd Edition” (vendored specs/ietf_draft_pantos_hls_rfc8216bis.txt) | §6.2.5.2 Blocking Playlist Reload, Partial Segments, preload hint, Playlist Delta Updates (client), EXT-X-DATERANGE (origin), discontinuities | Partial | Origin has no delta updates or rendition reports; client has no ABR |
| IETF RFC 8216 HLS | RFC 8216 (vendored specs/ietf_rfc8216_hls.txt) | Classic TS-segment HLS playback (HLS v3 form) | Partial | Client only |
| Apple HLS timed metadata | Vendored specs/apple_hls_timed_metadata.html | SCTE-35 cues rendered as EXT-X-DATERANGE via timed-metadata | Partial | |
| IETF RFC 3986 reference resolution, RFC 3339 dates | RFC 3986, RFC 3339 | Relative URL joins (url), program-date-time (jiff) | Full | Not hand-rolled |
| IETF RFC 7617 / 7616 / 6750 | not vendored | TokioClient authentication through broadcast-auth; credentials go only to the playlist’s origin | Full, behind tokio |
§Features
| Feature | Default | Description |
|---|---|---|
std | yes | Enables server (needs media_plane::Trunk, which is std), plus bytes, url/std, jiff/std. Without it the client core is #![no_std] + alloc. |
serde | no | Serialize on the public action, output and id types. |
tokio | no | client::tokio_client::TokioClient: async shell over tokio and reqwest (rustls). Blocking-reload query parameters, typed Range, per-request and connect timeouts, backon retry with jitter (no retry of a 4xx other than 408 and 429), a CancellationToken, and Basic, Bearer or Digest through broadcast-auth. Implies std. |
§Quick start
Drive the client over real ffmpeg fMP4 HLS output, with the playlist text and the segment files handed over by hand (no HTTP):
use hls_runtime::client::{Action, HlsClient, Output};
let playlist = "#EXTM3U\n#EXT-X-VERSION:7\n#EXT-X-TARGETDURATION:1\n#EXT-X-MEDIA-SEQUENCE:0\n\
#EXT-X-PLAYLIST-TYPE:VOD\n#EXT-X-MAP:URI=\"init.mp4\"\n\
#EXTINF:1.000000,\nindex0.m4s\n#EXTINF:1.000000,\nindex1.m4s\n#EXTINF:1.000000,\nindex2.m4s\n\
#EXT-X-ENDLIST\n";
let mut client = HlsClient::new("http://origin/live/index.m3u8");
// The first action is always a plain playlist GET.
assert!(matches!(client.poll(), Some(Action::FetchPlaylist { .. })));
client.on_playlist(playlist.as_bytes()).unwrap();
let (mut init, mut samples) = (0, 0);
loop {
while let Some(action) = client.poll() {
if let Action::FetchResource { id, url, .. } = action {
let file = url.rsplit('/').next().unwrap();
let bytes = test_fixtures::read(&format!("hls/cmaf-fmp4/{file}"));
client.on_resource(id, &bytes).unwrap();
}
}
let mut progressed = false;
while let Some(out) = client.next_output() {
progressed = true;
match out {
Output::Init(_) => init += 1,
Output::Samples { samples: s, .. } => samples += s.len(),
_ => {}
}
}
if !progressed {
break;
}
}
assert_eq!(init, 1, "exactly one init precedes the samples");
assert!(samples > 0, "real H.264 and AAC access units came out");For the origin, examples/origin_playlist.rs builds a Trunk, publishes parts and segments, and renders a playlist through HlsOrigin::resolve; examples/client_stepping.rs steps the client against that origin’s output. Run them with cargo run -p hls-runtime --example <name>. tests/origin_loop.rs drives the client against a real transmux::ll_hls::LlHlsSegmenter in process.
§What is implemented
client:HlsClient(new,poll,on_playlist,on_resource,on_error,on_key,on_key_error,next_output,poll_timeout),Action,Output,ResourceId,KeyId, error type, relative URL resolution.- AES-128 segments (RFC 8216 §4.3.2.4, §5):
METHOD=AES-128with theidentityKEYFORMATis decrypted before demuxing. The key is requested once per distinct URI (Action::FetchKey, answered withon_key, which requires exactly 16 octets), segments wait for it, and the IV is theIVattribute or the Media Sequence Number as a 16-octet big-endian number. A bad key, padding or length isError::DecryptFailed.SAMPLE-AES,AES-256-GCM, an encryptedEXT-X-MAP,EXT-X-I-FRAMES-ONLYwith encryption and encrypted partial segments areError::UnsupportedEncryption, never passed through as plaintext. server(featurestd):HlsOriginand its builder (target_duration_secs,window_segments,low_latency),HlsRequest,BlockingQuery,HlsBody,ClosedSegment,master_playlist(measured peakBANDWIDTH, the init segment’sCODECS, audio rendition groups viaaudio_renditions/closed_captions_none, split-audio groups viaaudio_sibling_origins(per-groupCODECS, video+audioBANDWIDTH, PMT-derivedLANGUAGE/NAME/CHARACTERISTICS), typedHlsMasterError),master_playlist_m3u8(context-free renderer),set_init,set_track_specs(TS origins),DEFAULT_TRACK_ID.- Typed identifiers:
server::SegmentSeqandserver::PartIndexre-exportmedia_plane::ids,server::Generationtypes the init-segment generation, and tracks aretransmux::TrackId, so a swapped segment, part or generation argument is a compile error. Wire names (seg-1-5-7.m4s,init-1-5-2.mp4) are unchanged. - Cache policy: each resolved resource carries a
media_plane::egress::CachePolicy(Immutablefor instance-named parts, segments and generation-named inits;NoCachefor the bare current init and playlists). - Poison-tolerant locks in the origin, and bounded behaviour on abusive requests (
tests/origin_hardening.rs). - HDR and subtitle signalling: multivariant
SUPPLEMENTAL-CODECSandVIDEO-RANGEfor HDR10, HLG, Dolby Vision (profiles 5, 8.1, 8.4) and HDR10+ (/cdm4);Container::WebVttrenditions served as.vttsegments, with Partial Segments on a low-latency origin;HlsOrigin::set_part_target_msfor renditions whose frame grid does not divide the part target. - Caption signalling:
HlsOrigin::set_declared_closed_caption_languagesandHlsOrigin::set_detected_closed_captionsrenderCLOSED-CAPTIONS(orCLOSED-CAPTIONS=NONE) on every variant from the ATSCcaption_service_descriptor(issue #1428). - Blocking reloads:
HlsOrigin::published_target_duration()exposes the frozenEXT-X-TARGETDURATIONso an adapter can bound a blocking reload by three target durations (issue #1651). - Variant selection:
client::choose_variant(&[VariantInfo], &VariantPolicy)is public for adaptive sources (issue #1460).
§Design notes
Sans-IO (no clock read, no socket, in the core), #![forbid(unsafe_code)], no_std + alloc client, one wire model (playlists are broadcast_hls::MediaPlaylist, not re-described), the Trunk is the single copy of the data (the old push-fed MediaStore was deleted), #[non_exhaustive] on Action, Output and errors, golden client transcripts and a no-hand-roll guard. Known gap: a discontinuity revealed only when a still-open segment closes is signalled late, because OpenSegment carries no discontinuity flag in the wire model.
§Related crates
Depends on broadcast-hls (playlist syntax), transmux (demux, TrackId), media-plane (Trunk, ServedEgress), timed-metadata (EXT-X-DATERANGE) and, for tokio, broadcast-auth. multimux is the reference adapter: it serves every LL-HLS route through one HlsOrigin per program over axum and tokio.
§MSRV and licence
MSRV 1.95.0. Licensed under MIT OR Apache-2.0.
Re-exports§
pub use server::bcp47_language;std
Modules§
- client
- LL-HLS playback client engine — a sans-IO Low-Latency HLS (RFC 8216bis)
client (issue #717, slices 2-4; formerly the standalone
ll-hls-clientcrate, folded in here ashls-runtime’sclientmodule — seedocs/superpowers/specs/2026-07-18-multimux-hub-design.md). - server
std - LL-HLS origin engine (issue #663/#717 Stage 2; plan step 4): the
blocking-reload + part-availability decision logic and playlist
rendering, driven directly from a shared
media_plane::Trunk— not a push-fed rolling-window store of its own.
Constants§
- SPEC
- The RFC this crate’s client behaviour implements against.