Skip to main content

HlsClient

Struct HlsClient 

Source
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:

  1. HlsClient::new seeds the first Action::FetchPlaylist; drain it with HlsClient::poll and perform the GET.
  2. Feed the response back with HlsClient::on_playlist (playlist) or HlsClient::on_resource (init/part/segment bytes) — Action::FetchResource’s id correlates the two.
  3. Drain HlsClient::poll again for the next round of actions (a new reload, newly discoverable parts, a preload-hint prefetch, …) and HlsClient::next_output for newly available Outputs.

§Behaviour

  • Reload scheduling (issue #717 slice 2): once a playlist advertises EXT-X-SERVER-CONTROL/EXT-X-PART-INF and the origin’s CAN-BLOCK-RELOAD attribute is YES (broadcast_hls::LowLatencyConfig::can_block_reload is true — not merely broadcast_hls::MediaPlaylist::low_latency being Some, since an origin may carry parts/PART-INF while still advertising CAN-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 an Action::WaitMs hint derived from #EXT-X-TARGETDURATION. EXT-X-SKIP/CAN-SKIP-UNTIL Playlist 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 — see merge_delta internally.
  • Fetch pipeline (slice 3): the EXT-X-PRELOAD-HINTed part is fetched ahead of its own appearance as a numbered EXT-X-PART; BYTERANGE parts 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, or GAP=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::Init precedes any Output::Samples; parts/segments are demuxed via transmux::Fmp4Demux (by concatenating the cached init bytes with the fetched resource — this crate never re-implements ISOBMFF box parsing, only reuses transmux’s), so Output::Samples carries real access units, not opaque container bytes. #EXT-X-DISCONTINUITY on a segment surfaces as Output::Discontinuity immediately before that segment’s first samples. Known limitation: an in-progress (OpenSegment) segment carries no discontinuity flag of its own (only a closed MediaSegment does) — 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 .ts segments carrying their own PAT/PMT/PES, no separate init resource) routes each fetched Part/Segment through transmux::TsDemux instead, 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 recovered TrackSpecs synthesize the one Output::Init this crate’s contract requires (via transmux::build_init_segment) so downstream callers (e.g. multimux’s HlsPull, which recovers track specs from Output::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

Source

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).

Source

pub fn playlist_url(&self) -> &str

The Media Playlist URL this client is following.

Source

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.

Source

pub fn endlist_seen(&self) -> bool

Whether a playlist carrying EXT-X-ENDLIST has been processed (the stream is VOD or has finished).

Source

pub fn target_duration_secs(&self) -> Option<u32>

EXT-X-TARGETDURATION in seconds of the last full media playlist, None before one was processed.

Source

pub fn set_variant_policy(&mut self, policy: VariantPolicy)

Set the variant selection policy. Must be callable before the first on_playlist.

Source

pub fn select_variant(&mut self, index: usize) -> Result<()>

Switch to a different variant by index (stall switching).

Source

pub fn poll(&mut self) -> Option<Action>

Drain the next IO Action the caller must perform, if any.

Source

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.

Source

pub fn poll_timeout(&mut self, now: Instant) -> Option<Instant>

Available on crate feature 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.

Source

pub fn next_output(&mut self) -> Option<Output>

Drain the next Output event, if any.

Source

pub fn on_playlist(&mut self, bytes: &[u8]) -> Result<()>

Feed a freshly fetched Media Playlist response.

§Errors

Error::PlaylistNotUtf8 / Error::PlaylistParse on malformed input.

Source

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.

Source

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).

Source

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).

Source

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.

Source

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.

Source

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.

Source

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.

Source

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).

Trait Implementations§

Source§

impl Debug for HlsClient

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> DescriptorObject for T
where T: Debug + Any + Send + Sync,

Source§

fn as_any(&self) -> &(dyn Any + 'static)

Borrow as &dyn Any so the caller can downcast to the concrete type.
Source§

impl<T> ExtensionObject for T
where T: Debug + Any + Send + Sync,

Source§

fn as_any(&self) -> &(dyn Any + 'static)

Borrow as &dyn Any so the caller can downcast to the concrete type.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> TableObject for T
where T: Debug + Any + Send + Sync,

Source§

fn as_any(&self) -> &(dyn Any + 'static)

Borrow as &dyn Any so the caller can downcast to the concrete type.
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more