Skip to main content

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}