dash_runtime/client/error.rs
1//! Failure vocabulary: what went wrong with a fetch ([`Failure`]), how the
2//! core classifies it ([`FailureClass`]) and the errors the `on_*` calls
3//! return ([`Error`]).
4
5use broadcast_common::impl_spec_display;
6use thiserror::Error;
7
8use super::action::{ResourceId, UtcId};
9use crate::error::MpdError;
10
11/// How a fetch failed, as the caller observed it.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13#[non_exhaustive]
14pub enum Failure {
15 /// The server answered with this HTTP status (an error status, or a
16 /// success status the caller could not use).
17 Status(u16),
18 /// Connection refused, reset, or closed before the response completed.
19 Transport,
20 /// No (complete) response within the caller's deadline.
21 Timeout,
22 /// The body exceeded the caller's size cap.
23 TooLarge,
24}
25
26/// The cause the core attributes to a counted failure.
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28#[non_exhaustive]
29pub enum FailureKind {
30 /// 404 or 410: the resource is not there.
31 Missing,
32 /// 425 (RFC 8470 Too Early).
33 TooEarly,
34 /// Any other error status.
35 Server,
36 /// Transport error or timeout.
37 Transport,
38 /// A body arrived that is not usable (not media, wrong length, rejected
39 /// by the demuxer, oversize).
40 Invalid,
41}
42
43impl FailureKind {
44 /// Stable snake_case label.
45 #[must_use]
46 pub fn name(&self) -> &'static str {
47 match self {
48 Self::Missing => "missing",
49 Self::TooEarly => "too_early",
50 Self::Server => "server",
51 Self::Transport => "transport",
52 Self::Invalid => "invalid",
53 }
54 }
55}
56impl_spec_display!(FailureKind);
57
58/// What the caller does after a failure.
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60#[non_exhaustive]
61pub enum FailureClass {
62 /// Back off (the adapter owns the arithmetic, `Retry-After` included) and
63 /// call [`super::DashClient::retry`]. `attempt` is the 1-based count of
64 /// consecutive failures of this resource.
65 Retry {
66 /// Consecutive failures including this one.
67 attempt: u32,
68 /// Why.
69 kind: FailureKind,
70 },
71 /// The failure count reached the configured cap: the presentation has
72 /// ended with an [`super::Output::Fatal`].
73 GiveUp,
74 /// A live segment requested before it exists (a `425`, or a `404`/`410`
75 /// inside the early-grace window): not counted. The client asks for it
76 /// again from [`super::DashClient::poll`] at `retry_at_us`; the caller
77 /// does nothing.
78 NotYetAvailable {
79 /// Monotonic microseconds at which the segment is requested again.
80 retry_at_us: u64,
81 },
82 /// A live segment that left the time-shift window before it was fetched:
83 /// not counted and not retried. The client continues at the oldest
84 /// segment still available and marks the gap with a `Discontinuity`.
85 Expired,
86 /// A live segment given up on after the configured number of consecutive
87 /// failures: [`super::Output::SegmentLost`] and a `Discontinuity` follow.
88 SegmentSkipped,
89 /// The resource is the missing tail of a Period (DESIGN section 5.5 and
90 /// the multi-period decision in the crate README): nothing to retry,
91 /// nothing counted, playback continues.
92 EndTail,
93}
94
95/// Why a delivered body was rejected.
96#[derive(Debug, Clone, Copy, PartialEq, Eq)]
97#[non_exhaustive]
98pub enum Rejection {
99 /// Not a media fragment (`moof` then `mdat`): an error page, a truncated
100 /// transfer or garbage.
101 NotMedia,
102 /// An init without a complete `moov`.
103 InitUndecodable,
104 /// The demuxer refused the init and segment.
105 DemuxFailed,
106 /// The `sidx` bytes are not a usable index.
107 BadIndex,
108 /// A ranged response whose length differs from the requested range.
109 WrongLength,
110 /// Larger than `DashConfig::max_segment_bytes`.
111 TooLarge,
112 /// A progressive MP4 served as a media segment.
113 NonFragmentedMp4,
114}
115
116impl Rejection {
117 /// Stable snake_case label.
118 #[must_use]
119 pub fn name(&self) -> &'static str {
120 match self {
121 Self::NotMedia => "not_media",
122 Self::InitUndecodable => "init_undecodable",
123 Self::DemuxFailed => "demux_failed",
124 Self::BadIndex => "bad_index",
125 Self::WrongLength => "wrong_length",
126 Self::TooLarge => "too_large",
127 Self::NonFragmentedMp4 => "non_fragmented_mp4",
128 }
129 }
130}
131impl_spec_display!(Rejection);
132
133/// Why the presentation cannot continue.
134#[derive(Debug, Clone, PartialEq, Eq)]
135#[non_exhaustive]
136pub enum FatalKind {
137 /// A dynamic MPD and no UTC source at all: no `UTCTiming` could be read,
138 /// the host never supplied a wall clock and the MPD response carried no
139 /// usable `Date`. Segment availability cannot be decided (DESIGN 3.4).
140 ClockUnavailable,
141 /// The MPD has nothing a client can play.
142 MpdInvalid {
143 /// What is missing.
144 reason: &'static str,
145 },
146 /// A feature of the MPD that this client deliberately does not implement,
147 /// with nothing playable left ([`crate::error::Unsupported`]).
148 Unsupported(crate::error::Unsupported),
149 /// A URL derived from the MPD was refused.
150 UnsupportedAddressing {
151 /// Which rule (`cross_origin`, `unusable_url`).
152 reason: &'static str,
153 },
154 /// A segment failed the configured number of times in a row.
155 SegmentFetchFailed {
156 /// The segment.
157 id: ResourceId,
158 /// Status of the last failure, when it was an HTTP status.
159 status: Option<u16>,
160 },
161 /// An init or index failed the configured number of times in a row.
162 InitFetchFailed {
163 /// The resource.
164 id: ResourceId,
165 /// Status of the last failure, when it was an HTTP status.
166 status: Option<u16>,
167 },
168 /// A progressive MP4 was served as a media segment of a fragmented
169 /// presentation.
170 NonFragmentedMp4 {
171 /// The segment.
172 id: ResourceId,
173 },
174}
175
176impl FatalKind {
177 /// Stable snake_case label.
178 #[must_use]
179 pub fn name(&self) -> &'static str {
180 match self {
181 Self::ClockUnavailable => "clock_unavailable",
182 Self::MpdInvalid { .. } => "mpd_invalid",
183 Self::Unsupported(u) => u.name(),
184 Self::UnsupportedAddressing { .. } => "unsupported_addressing",
185 Self::SegmentFetchFailed { .. } => "segment_fetch_failed",
186 Self::InitFetchFailed { .. } => "init_fetch_failed",
187 Self::NonFragmentedMp4 { .. } => "non_fragmented_mp4",
188 }
189 }
190}
191impl_spec_display!(FatalKind);
192
193/// Errors returned by the client's `on_*` and `select_*` calls.
194#[derive(Debug, Clone, PartialEq, Eq, Error)]
195#[non_exhaustive]
196pub enum Error {
197 /// A resource was delivered for an id the client did not request, or one
198 /// it no longer waits for.
199 #[error("resource {id:?} was not requested")]
200 UnrequestedResource {
201 /// The id.
202 id: ResourceId,
203 },
204 /// A second delivery of a resource already accepted.
205 #[error("resource {id:?} was delivered twice")]
206 DuplicateResource {
207 /// The id.
208 id: ResourceId,
209 },
210 /// The MPD was not requested, or already accepted.
211 #[error("no MPD is awaited")]
212 MpdNotAwaited,
213 /// The MPD bytes were rejected; request it again with
214 /// [`super::DashClient::retry_mpd`].
215 #[error("MPD rejected: {0}")]
216 Mpd(MpdError),
217 /// A `UTCTiming` answer for a request the client does not wait for
218 /// (answered twice, abandoned after its timeout, or never made).
219 #[error("UTC request {id:?} was not requested")]
220 UnrequestedUtc {
221 /// The id.
222 id: UtcId,
223 },
224 /// A body was rejected and counted as a failure; `class` says what to do.
225 #[error("resource {id:?} rejected ({reason}): {class:?}")]
226 Rejected {
227 /// The resource.
228 id: ResourceId,
229 /// Why.
230 reason: Rejection,
231 /// What the caller does next.
232 class: FailureClass,
233 },
234 /// The presentation has ended or failed; everything is ignored.
235 #[error("the client has stopped")]
236 Stopped,
237 /// [`super::DashClient::select_representation`] with an index outside the
238 /// current variant list.
239 #[error("no such variant")]
240 NoSuchVariant,
241 /// [`super::DashClient::select_audio`] with an index outside the current
242 /// audio list.
243 #[error("no such audio set")]
244 NoSuchAudio,
245 /// A switch is only possible while the Period being played is the one
246 /// being fetched.
247 #[error("the Representation cannot be switched now")]
248 NotSwitchable,
249}
250
251/// Result alias.
252pub type Result<T> = core::result::Result<T, Error>;