Skip to main content

hls_runtime/client/
output.rs

1//! Output events — [`Output`] out of [`crate::client::HlsClient::next_output`].
2
3use alloc::string::String;
4use alloc::sync::Arc;
5use alloc::vec::Vec;
6use broadcast_hls::MediaType;
7use transmux::TrackId;
8
9use transmux::DisplayInfo;
10use transmux::Sample;
11use transmux::TrackSpec;
12
13/// Policy for choosing a variant from a multivariant (master) playlist.
14#[derive(Debug, Clone, PartialEq, Default)]
15pub struct VariantPolicy {
16    /// Maximum allowed `BANDWIDTH` in bits per second.
17    pub max_bandwidth: Option<u32>,
18    /// Maximum allowed resolution height in pixels.
19    pub max_height: Option<u32>,
20    /// Allowed codec prefixes (e.g. `avc1`, `mp4a`): every comma-separated
21    /// `CODECS` entry must have one of these as a prefix.
22    pub supported_codecs: Option<Vec<String>>,
23}
24
25/// Information about one variant stream in a master playlist.
26#[derive(Debug, Clone, PartialEq)]
27pub struct VariantInfo {
28    /// Resolved absolute URL of the variant's media playlist.
29    pub url: String,
30    /// `BANDWIDTH` in bits per second.
31    pub bandwidth: u32,
32    /// `AVERAGE-BANDWIDTH`, if present.
33    pub average_bandwidth: Option<u32>,
34    /// `RESOLUTION` as `(width, height)`, if present.
35    pub resolution: Option<(u32, u32)>,
36    /// `CODECS` attribute, if present.
37    pub codecs: Option<String>,
38    /// `AUDIO` group id, if any.
39    pub audio_group: Option<String>,
40    /// `SUBTITLES` group id, if any.
41    pub subtitles_group: Option<String>,
42}
43
44/// Information about one `#EXT-X-MEDIA` rendition in a master playlist.
45#[derive(Debug, Clone, PartialEq)]
46pub struct RenditionInfo {
47    /// Rendition type.
48    pub media_type: MediaType,
49    /// `GROUP-ID`.
50    pub group_id: String,
51    /// `NAME`.
52    pub name: String,
53    /// `LANGUAGE`, if present.
54    pub language: Option<String>,
55    /// `DEFAULT` flag.
56    pub default: bool,
57    /// `AUTOSELECT` flag.
58    pub autoselect: bool,
59    /// Resolved absolute URI, or `None` when the rendition is carried inside
60    /// the variant's own segments (e.g. closed captions).
61    pub url: Option<String>,
62}
63
64/// The result of selecting a variant from a master playlist.
65#[derive(Debug, Clone, PartialEq)]
66pub struct MasterSelection {
67    /// Variants in playlist order.
68    pub variants: Vec<VariantInfo>,
69    /// Index of the chosen variant in [`Self::variants`].
70    pub chosen: usize,
71    /// Renditions exposed by the master playlist. Renditions with a `url` are
72    /// not fetched by this client; create a second `HlsClient::new(rendition.url)`
73    /// to play one.
74    pub renditions: Vec<RenditionInfo>,
75}
76
77/// One unit of decoded output, drained in order via
78/// [`crate::client::HlsClient::next_output`].
79///
80/// The ordering contract: exactly one [`Output::Init`] is emitted before any
81/// [`Output::Samples`] for a given track, and `Samples` are emitted in decode
82/// order with no gap and no repeat — a fetched Part's samples are emitted
83/// exactly once, whether it arrived as a standalone Part fetch or was
84/// coalesced into its parent's closed-segment view (see the crate docs'
85/// "Dedup" section).
86#[derive(Debug, Clone)]
87#[non_exhaustive]
88pub enum Output {
89    /// The Media Initialization Section bytes (`ftyp`+`moov`, RFC 8216bis
90    /// §4.4.4.5's `EXT-X-MAP` resource) — hand to the decoder once before any
91    /// `Samples`. Re-emitted only when the playlist's `EXT-X-MAP` changes
92    /// (e.g. across an `EXT-X-DISCONTINUITY`).
93    Init(Vec<u8>),
94    /// Newly available coded samples (access units) for one track, in decode
95    /// order, demuxed from a fetched Part or Segment resource via
96    /// `transmux::Fmp4Demux`.
97    Samples {
98        /// Track ID, matching the `moov`'s `trak.tkhd.track_ID` (ISO/IEC
99        /// 14496-12 §8.3.2) carried by the preceding [`Output::Init`].
100        track_id: TrackId,
101        /// The samples, in decode order.
102        samples: Vec<Sample>,
103    },
104    /// `EXT-X-DISCONTINUITY` (RFC 8216 §4.3.4.3): the encoding, timestamps,
105    /// tracks, or codec parameters may have changed between the previously
106    /// emitted samples and the ones that follow this marker. Forwarded
107    /// verbatim so the sink can flush/reset its decoder pipeline.
108    Discontinuity,
109    /// The playlist reached `#EXT-X-ENDLIST` (RFC 8216 §4.3.3.4) with no
110    /// further open segment or preload hint outstanding — playback is
111    /// complete, no more output will ever be produced.
112    EndOfStream,
113    /// The full current track set for the stream. Emitted before `Init` on the
114    /// classic TS-segment path and after the first `Init` on the fMP4 path.
115    Tracks(Vec<TrackSpec>),
116    /// A track's display state changed (SPS geometry etc.).
117    DisplayChanged {
118        /// Track ID.
119        track_id: TrackId,
120        /// PTS of the affected access unit, if known.
121        pts: Option<i64>,
122        /// The full new display state, shared with the following sample.
123        display: Arc<DisplayInfo>,
124    },
125    /// A PMT-listed elementary stream of a TS segment was left out of the track set, the
126    /// synthesized init and the samples because it cannot be carried in fragmented MP4
127    /// ([`transmux::CodecConfig::is_muxable_in_bmff`] is false: opaque data such as SCTE-35,
128    /// ID3 or private PES, or a subtitle without a sample entry). Emitted each time the demuxer
129    /// (re)announces such a track, so a consumer that reports it must deduplicate by
130    /// `track_id`. A TS-segment stream with no muxable track at all produces only these.
131    TrackDropped {
132        /// The dropped track's id.
133        track_id: TrackId,
134        /// The PMT `stream_type` (ISO/IEC 13818-1 Table 2-34) when the track is opaque data.
135        stream_type: Option<u8>,
136    },
137    /// A track of an fMP4 Media Initialization Section was left out of the
138    /// track set and the samples because the demuxer could not model it: an
139    /// encrypted sample entry (`encv`, `enca`, ... — this client decrypts only
140    /// AES-128 segments, never CENC/CBCS sample encryption) or an unrecognised
141    /// codec. Emitted once per track and init, so a consumer that reports it
142    /// deduplicates by `track_id` across inits. A stream whose every track is
143    /// skipped produces only these, followed by an empty [`Output::Tracks`].
144    TrackSkipped {
145        /// The skipped `trak`'s `tkhd.track_ID`, `None` when the demuxer could
146        /// not name it.
147        track_id: Option<TrackId>,
148        /// The track's sample-entry four-character code (`encv`, `enca`, ...).
149        fourcc: String,
150        /// The demuxer's reason.
151        reason: String,
152    },
153    /// The demuxer skipped one or more `moof` fragments of a Part/Segment
154    /// resource — unparsable, or addressing sample data outside the resource
155    /// (an absolute `tfhd` base-data-offset, which only holds when the
156    /// segment sits at that offset of the whole file; CMAF uses
157    /// default-base-is-moof). The samples of the other fragments are still
158    /// emitted; those of the skipped ones are lost.
159    FragmentDamaged {
160        /// The resource that held the skipped fragments.
161        id: crate::client::ResourceId,
162        /// How many fragments (or per-track fragment runs) were skipped.
163        skipped: usize,
164    },
165    /// A master (multivariant) playlist was parsed and a variant selected.
166    /// The caller may inspect [`MasterSelection`] and, for renditions with a
167    /// `url`, create a second client via `HlsClient::new(rendition.url)`.
168    Master(MasterSelection),
169}