dash_runtime/client/output.rs
1//! What the client hands back: [`Output`], drained with
2//! [`super::DashClient::next_output`].
3
4use broadcast_common::impl_spec_display;
5use transmux::{Sample, TrackId, TrackSpec};
6
7use super::action::ResourceId;
8use super::error::FatalKind;
9use crate::error::Unsupported;
10use crate::selection::{DropReason, RepRef};
11use crate::timing::clock::ClockSource;
12
13/// One Representation of a selectable video AdaptationSet, in the shape a
14/// host's variant list wants (DESIGN D11).
15#[derive(Debug, Clone, PartialEq)]
16#[non_exhaustive]
17pub struct VariantInfo {
18 /// Where it is in the MPD.
19 pub rep: RepRef,
20 /// Absolute URL of its Initialization Segment, when it has one.
21 pub init_url: Option<String>,
22 /// `@bandwidth`, bits per second.
23 pub bandwidth: u64,
24 /// `(width, height)`.
25 pub resolution: Option<(u32, u32)>,
26 /// `@codecs`.
27 pub codecs: Option<String>,
28 /// The `@group` (else the `@id`) of the audio AdaptationSet it pairs
29 /// with, when the Period has audio.
30 pub audio_group: Option<String>,
31}
32
33/// One selectable audio AdaptationSet.
34#[derive(Debug, Clone, PartialEq)]
35#[non_exhaustive]
36pub struct AudioRendition {
37 /// AdaptationSet index in the Period.
38 pub set: usize,
39 /// `@group`, else `@id`.
40 pub group: Option<String>,
41 /// First `Label`, else `@lang`, else the first Representation's `@id`.
42 pub name: String,
43 /// `@lang`.
44 pub language: Option<String>,
45 /// Role `main` (DASH has no DEFAULT flag).
46 pub default: bool,
47 /// Always `true`: DASH has no AUTOSELECT flag, every set may be chosen.
48 pub autoselect: bool,
49 /// Absolute init URL of the chosen (else first) Representation.
50 pub init_url: Option<String>,
51 /// The selectable Representations.
52 pub reps: Vec<RepRef>,
53}
54
55/// The selection made for one Period.
56#[derive(Debug, Clone, PartialEq)]
57#[non_exhaustive]
58pub struct ManifestSelection {
59 /// Period index in the MPD.
60 pub period: usize,
61 /// Every selectable video Representation, MPD order.
62 pub variants: Vec<VariantInfo>,
63 /// Index into `variants` of the one playing.
64 pub chosen: Option<usize>,
65 /// Selectable audio sets.
66 pub audio: Vec<AudioRendition>,
67 /// Index into `audio` of the one playing.
68 pub chosen_audio: Option<usize>,
69}
70
71/// Why a set or Representation is not played.
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73#[non_exhaustive]
74pub enum DropCause {
75 /// Selection refused it (text, TS, WebM, trick mode, encryption, ...).
76 Selection(DropReason),
77 /// No `Initialization`: self-initializing segments are not supported.
78 NoInitialization,
79 /// A static Representation whose segment count cannot be known.
80 UnboundedPlan,
81 /// The segment information cannot be addressed (`name` of the error).
82 Addressing(&'static str),
83 /// A URL could not be built.
84 UnusableUrl,
85 /// A URL left the MPD origin while `cross_origin_urls` is off.
86 CrossOrigin,
87 /// A URL used a scheme this client does not fetch.
88 Unsupported(Unsupported),
89}
90
91impl DropCause {
92 /// Stable snake_case label.
93 #[must_use]
94 pub fn name(&self) -> &'static str {
95 match self {
96 Self::Selection(r) => r.name(),
97 Self::NoInitialization => "no_initialization",
98 Self::UnboundedPlan => "unbounded_plan",
99 Self::Addressing(n) => n,
100 Self::UnusableUrl => "unusable_url",
101 Self::CrossOrigin => "cross_origin",
102 Self::Unsupported(u) => u.name(),
103 }
104 }
105}
106impl_spec_display!(DropCause);
107
108/// A non-fatal, once-per-session observation.
109#[derive(Debug, Clone, Copy, PartialEq, Eq)]
110#[non_exhaustive]
111pub enum Notice {
112 /// A `ContentProtection` element: encrypted content is not decrypted.
113 UnsupportedEncryption,
114 /// `EventStream` / `InbandEventStream` present and not surfaced.
115 EventStreamIgnored,
116 /// `Preselection` present and not used.
117 PreselectionIgnored,
118 /// A remote element (`xlink:href`) was skipped.
119 XlinkSkipped,
120 /// `PatchLocation` present; patches are not used.
121 PatchIgnored,
122 /// A low-latency MPD (`availabilityTimeOffset` or
123 /// `availabilityTimeComplete="false"`): played at segment granularity.
124 LowLatencyIgnored,
125 /// The client requested segments too slowly: the segments it wanted left
126 /// the time-shift window and it continued at the oldest one still there.
127 FellBehind,
128 /// A `UTCTiming` element this client cannot act on (`ntp`, `sntp`, an
129 /// unknown scheme) was skipped.
130 UtcSchemeUnsupported,
131 /// A `UTCTiming` URL left the MPD origin while `cross_origin_urls` is
132 /// off and was skipped.
133 UtcCrossOrigin,
134}
135
136impl Notice {
137 /// Stable snake_case label.
138 #[must_use]
139 pub fn name(&self) -> &'static str {
140 match self {
141 Self::UnsupportedEncryption => "unsupported_encryption",
142 Self::EventStreamIgnored => "event_stream_ignored",
143 Self::PreselectionIgnored => "preselection_ignored",
144 Self::XlinkSkipped => "xlink_skipped",
145 Self::PatchIgnored => "patch_ignored",
146 Self::LowLatencyIgnored => "low_latency_ignored",
147 Self::FellBehind => "fell_behind",
148 Self::UtcSchemeUnsupported => "utc_scheme_unsupported",
149 Self::UtcCrossOrigin => "utc_cross_origin",
150 }
151 }
152}
153impl_spec_display!(Notice);
154
155/// One unit of output.
156///
157/// Per Period the order is: `Notice`/`TrackDropped` for what was left out,
158/// `Manifest`, `PeriodChanged`, then (when the Period does not continue the
159/// previous one) `Discontinuity`, `Init` per changed init, `Tracks` when the
160/// track set changed, and the `Samples`. `Samples` of one track are in decode
161/// order with no gap and no repeat; their timestamps are the container's
162/// (`tfdt`) timeline, not a Period-rebased one.
163#[derive(Debug, Clone)]
164#[non_exhaustive]
165pub enum Output {
166 /// The selection made for a Period.
167 Manifest(ManifestSelection),
168 /// A Period starts playing.
169 PeriodChanged {
170 /// Period index in the MPD.
171 index: usize,
172 /// `Period@id`.
173 id: Option<String>,
174 /// PeriodStart, microseconds from the presentation start.
175 start_us: i128,
176 /// `true` only for a boundary signalled period-continuous
177 /// (`urn:mpeg:dash:period-continuity:2015`, 23009-1 5.3.2.4) whose
178 /// init is unchanged: nothing is re-announced and no `Discontinuity`
179 /// is emitted. Always `false` for the first Period.
180 continuous: bool,
181 },
182 /// The Initialization Segment bytes (`ftyp` + `moov`) a decoder takes
183 /// before the samples that follow.
184 Init(Vec<u8>),
185 /// The full track set; session track ids are 1-based in selection order
186 /// (video, then audio) and survive a Representation switch.
187 Tracks(Vec<TrackSpec>),
188 /// Access units of one track, in decode order.
189 Samples {
190 /// Session track id.
191 track_id: TrackId,
192 /// The samples.
193 samples: Vec<Sample>,
194 },
195 /// The timeline may jump: a Period that does not continue the previous
196 /// one, a skipped segment, or a Representation switch that cannot be
197 /// aligned.
198 Discontinuity,
199 /// A set or Representation is not played.
200 TrackDropped {
201 /// Period index.
202 period: usize,
203 /// AdaptationSet index.
204 set: usize,
205 /// Representation index when only it was dropped.
206 rep: Option<usize>,
207 /// Why.
208 cause: DropCause,
209 },
210 /// A track of an init that the demuxer could not model (an encrypted or
211 /// unknown sample entry); once per track and init.
212 TrackSkipped {
213 /// Session track id, when it maps to one.
214 track_id: Option<TrackId>,
215 /// The sample-entry four-character code.
216 fourcc: String,
217 /// The demuxer's reason.
218 reason: String,
219 },
220 /// The demuxer skipped fragments of a segment; the others were emitted.
221 FragmentDamaged {
222 /// The segment.
223 id: ResourceId,
224 /// How many fragments were skipped.
225 skipped: usize,
226 },
227 /// A non-fatal observation.
228 Notice(Notice),
229 /// The UTC clock has its first anchor, or its source changed (23009-1
230 /// 5.8.5.7; DESIGN 3.4). Not emitted for every refinement.
231 ClockSynced {
232 /// Where the anchor came from.
233 source: ClockSource,
234 /// Half-width of its error bar, microseconds.
235 uncertainty_us: u64,
236 },
237 /// A segment of a live presentation was given up on after the configured
238 /// number of failures; a `Discontinuity` marks the gap. (A static
239 /// presentation ends with a fatal error instead.)
240 SegmentLost {
241 /// The segment.
242 id: ResourceId,
243 /// Status of the last failure, when it was an HTTP status.
244 status: Option<u16>,
245 },
246 /// The presentation cannot continue; nothing follows.
247 Fatal(FatalKind),
248 /// Every selected segment was delivered.
249 EndOfStream,
250}