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}