pub struct HlsClient { /* private fields */ }Expand description
A driveable, sans-IO Low-Latency HLS (RFC 8216bis) playback client.
HlsClient never touches a socket or a clock. The caller drives it:
HlsClient::newseeds the firstAction::FetchPlaylist; drain it withHlsClient::polland perform the GET.- Feed the response back with
HlsClient::on_playlist(playlist) orHlsClient::on_resource(init/part/segment bytes) —Action::FetchResource’sidcorrelates the two. - Drain
HlsClient::pollagain for the next round of actions (a new reload, newly discoverable parts, a preload-hint prefetch, …) andHlsClient::next_outputfor newly availableOutputs.
§Behaviour
- Reload scheduling (issue #717 slice 2): once a playlist advertises
EXT-X-SERVER-CONTROL/EXT-X-PART-INFand the origin’sCAN-BLOCK-RELOADattribute isYES(broadcast_hls::LowLatencyConfig::can_block_reloadistrue— not merelybroadcast_hls::MediaPlaylist::low_latencybeingSome, since an origin may carry parts/PART-INF while still advertisingCAN-BLOCK-RELOAD=NO), 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. Otherwise reloads are plain GETs paced by anAction::WaitMshint derived from#EXT-X-TARGETDURATION.EXT-X-SKIP/CAN-SKIP-UNTILPlaylist Delta Updates (RFC 8216bis §4.4.5.2) are requested once a full-playlist baseline exists, and merged back into a full view before further processing — seemerge_deltainternally. - Fetch pipeline (slice 3): the
EXT-X-PRELOAD-HINTed part is fetched ahead of its own appearance as a numberedEXT-X-PART;BYTERANGEparts are supported, including the RFC 8216bis §4.4.4.9 “omitted offset means immediately after the previous sub-range of the same resource” rule (tracked per resource URL). Each Media Initialization Section (EXT-X-MAP) is fetched once, one at a time, and used for exactly the segments of its own map-run (RFC 8216 §4.4.4.5: a map applies to every following segment until the next map). - Dedup / coalescing: once any of a segment’s parts have been
individually fetched, that segment is never re-fetched whole — when it
later closes (
#EXTINF+URI), the client only fetches whichever of its parts (if any) are still missing, and marks the segment “delivered” once every part is accounted for (fetched, orGAP=YES). A playlist whose segments carry no parts at all (a non-LL origin) falls back to fetching the whole segment resource — the two paths never overlap for a single segment, so a part’s samples are never double-counted against its parent’s. - Output adapter (slice 4): exactly one
Output::Initprecedes anyOutput::Samples; parts/segments are demuxed viatransmux::Fmp4Demux(by concatenating the cached init bytes with the fetched resource — this crate never re-implements ISOBMFF box parsing, only reuses transmux’s), soOutput::Samplescarries real access units, not opaque container bytes.#EXT-X-DISCONTINUITYon a segment surfaces asOutput::Discontinuityimmediately before that segment’s first samples. Known limitation: an in-progress (OpenSegment) segment carries no discontinuity flag of its own (only a closedMediaSegmentdoes) — if every part of a segment was already delivered while it was still open, a discontinuity revealed only once it closes is signalled late (after those parts’ samples, not before). This is a gap in the current wire model (broadcast_hls::OpenSegment), not something this crate can fix locally. - 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.tssegments carrying their own PAT/PMT/PES, no separate init resource) routes each fetched Part/Segment throughtransmux::TsDemuxinstead, content-sniffed by the MPEG-TS sync byte rather than blocked on an init fetch that will never come. The first successfully demuxed segment’s recoveredTrackSpecs synthesize the oneOutput::Initthis crate’s contract requires (viatransmux::build_init_segment) so downstream callers (e.g.multimux’sHlsPull, which recovers track specs fromOutput::Init) need no TS-specific handling of their own. The fMP4/CMAF plus LL (parts/preload-hint) path above is entirely unchanged; the two never overlap for a single playlist.
Implementations§
Source§impl HlsClient
impl HlsClient
Sourcepub fn new(playlist_url: impl Into<String>) -> Self
pub fn new(playlist_url: impl Into<String>) -> Self
Create a new client for the Media Playlist at playlist_url, seeding
the first Action::FetchPlaylist (a plain, non-blocking GET — the
client does not yet know whether the origin supports blocking reload).
Sourcepub fn playlist_url(&self) -> &str
pub fn playlist_url(&self) -> &str
The Media Playlist URL this client is following.
Sourcepub fn set_playlist_base(&mut self, base: impl Into<String>)
pub fn set_playlist_base(&mut self, base: impl Into<String>)
Set the URL relative playlist URIs resolve against: the final URL of the next playlist
response after redirects (RFC 3986 section 5.1.3). Reloads keep requesting
HlsClient::playlist_url; the base is cleared when the client switches to another
playlist URL.
Sourcepub fn endlist_seen(&self) -> bool
pub fn endlist_seen(&self) -> bool
Whether a playlist carrying EXT-X-ENDLIST has been processed (the stream is VOD or
has finished).
Sourcepub fn target_duration_secs(&self) -> Option<u32>
pub fn target_duration_secs(&self) -> Option<u32>
EXT-X-TARGETDURATION in seconds of the last full media playlist, None before one
was processed.
Sourcepub fn set_variant_policy(&mut self, policy: VariantPolicy)
pub fn set_variant_policy(&mut self, policy: VariantPolicy)
Set the variant selection policy. Must be callable before the first
on_playlist.
Sourcepub fn select_variant(&mut self, index: usize) -> Result<()>
pub fn select_variant(&mut self, index: usize) -> Result<()>
Switch to a different variant by index (stall switching).
Sourcepub fn poll(&mut self) -> Option<Action>
pub fn poll(&mut self) -> Option<Action>
Drain the next IO Action the caller must perform, if any.
Sourcepub fn next_wait(&self) -> Option<Duration>
pub fn next_wait(&self) -> Option<Duration>
The WaitMs hint at the front of the action queue, as a Duration;
None when the next queued action is a fetch (or nothing is queued). A
wait is queued BEHIND the fetches on_playlist queued with it, so it is
reported only once those have been drained with Self::poll.
Peeking never consumes it, and the core never reads a clock.
Sourcepub fn poll_timeout(&mut self, now: Instant) -> Option<Instant>
Available on crate feature std only.
pub fn poll_timeout(&mut self, now: Instant) -> Option<Instant>
std only.The absolute deadline of the queued WaitMs, for callers that schedule
on std::time::Instant: None unless a wait is at the front of the
queue (see Self::next_wait).
The deadline is anchored the first time this is called for a given wait
(now + the wait) and every later call returns that SAME instant, until
the wait is drained with Self::poll. Re-querying after unrelated
wake-ups therefore never re-arms the timer. The core has no clock of its
own, so “when the wait was scheduled” is the caller’s now at the first
query: query as soon as the wait reaches the front.
Sourcepub fn next_output(&mut self) -> Option<Output>
pub fn next_output(&mut self) -> Option<Output>
Drain the next Output event, if any.
Sourcepub fn on_playlist(&mut self, bytes: &[u8]) -> Result<()>
pub fn on_playlist(&mut self, bytes: &[u8]) -> Result<()>
Feed a freshly fetched Media Playlist response.
§Errors
Error::PlaylistNotUtf8 / Error::PlaylistParse on malformed
input.
Sourcepub fn ensure_playlist_reload(&mut self) -> bool
pub fn ensure_playlist_reload(&mut self) -> bool
Queue a plain playlist reload unless one is already queued. For a driver that has just dropped a response the core rejected before it could schedule the next reload itself.
Sourcepub fn retry_init(&mut self) -> bool
pub fn retry_init(&mut self) -> bool
Request a Media Initialization Section again after a failed fetch
(Self::on_error with ResourceId::Init). A live playlist’s next
reload does this by itself; a VOD playlist (EXT-X-ENDLIST) has no
further reload, so its driver calls this. false when there is
nothing to retry (an init is in flight, or none failed).
Sourcepub fn retry_resource(&mut self, id: ResourceId) -> bool
pub fn retry_resource(&mut self, id: ResourceId) -> bool
Request a Part/Segment again after a failed fetch (Self::on_error
or an Error from Self::on_resource) without a playlist reload:
a VOD playlist (EXT-X-ENDLIST) has no further reload to do it. false
when there is nothing to retry (the fetch is in flight, the resource
was accepted, or it left the window).
Sourcepub fn skip_resource(&mut self, id: ResourceId)
pub fn skip_resource(&mut self, id: ResourceId)
Give up on a Part/Segment that cannot be fetched and carry on with the
next one: it is never requested again, the timeline restarts (held-back
final access units of the old one are flushed first) and
Output::Discontinuity follows, so the consumer re-syncs across the
gap. No-op for ResourceId::Init, which cannot be skipped.
Sourcepub fn on_resource(&mut self, id: ResourceId, bytes: &[u8]) -> Result<()>
pub fn on_resource(&mut self, id: ResourceId, bytes: &[u8]) -> Result<()>
Feed the bytes fetched for a previously requested ResourceId
(init/part/segment). Part/Segment resources delivered before the
init segment are buffered internally and demuxed once the init
arrives — the caller’s fetches may complete in any order.
§Errors
Error::UnrequestedResource if id was never requested (the
requested bookkeeping — or, for Init, init_uri — has no record
of it): a caller/driver bug, or a stale/duplicate delivery after the
client already moved past this id.
Error::Demux if transmux::Fmp4Demux rejects the concatenation of
the cached init + bytes.
Sourcepub fn on_key(&mut self, id: KeyId, bytes: &[u8]) -> Result<()>
pub fn on_key(&mut self, id: KeyId, bytes: &[u8]) -> Result<()>
Feed the key file fetched for an Action::FetchKey. Encrypted
segments that arrived earlier are decrypted and demuxed in Media
Sequence Number order.
§Errors
Error::UnrequestedKey for an unknown id or a key already held;
Error::BadKeyLength unless bytes is exactly 16 octets;
Error::DecryptFailed if a waiting segment does not decrypt.
Sourcepub fn on_key_error(&mut self, id: KeyId)
pub fn on_key_error(&mut self, id: KeyId)
Report that fetching key id failed. The next Self::on_playlist
requests it again; segments already waiting on it keep waiting.
Sourcepub fn on_error(&mut self, id: Option<ResourceId>)
pub fn on_error(&mut self, id: Option<ResourceId>)
Report that a previously requested ResourceId (or the playlist
itself, via None) failed. Clears the id’s “requested” bookkeeping
so the next Self::on_playlist call naturally re-requests it (no
automatic retry timer — the caller drives retry cadence).