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 URL derived from the MPD was refused.
147 UnsupportedAddressing {
148 /// Which rule (`cross_origin`, `unusable_url`).
149 reason: &'static str,
150 },
151 /// A segment failed the configured number of times in a row.
152 SegmentFetchFailed {
153 /// The segment.
154 id: ResourceId,
155 /// Status of the last failure, when it was an HTTP status.
156 status: Option<u16>,
157 },
158 /// An init or index failed the configured number of times in a row.
159 InitFetchFailed {
160 /// The resource.
161 id: ResourceId,
162 /// Status of the last failure, when it was an HTTP status.
163 status: Option<u16>,
164 },
165 /// A progressive MP4 was served as a media segment of a fragmented
166 /// presentation.
167 NonFragmentedMp4 {
168 /// The segment.
169 id: ResourceId,
170 },
171}
172
173impl FatalKind {
174 /// Stable snake_case label.
175 #[must_use]
176 pub fn name(&self) -> &'static str {
177 match self {
178 Self::ClockUnavailable => "clock_unavailable",
179 Self::MpdInvalid { .. } => "mpd_invalid",
180 Self::UnsupportedAddressing { .. } => "unsupported_addressing",
181 Self::SegmentFetchFailed { .. } => "segment_fetch_failed",
182 Self::InitFetchFailed { .. } => "init_fetch_failed",
183 Self::NonFragmentedMp4 { .. } => "non_fragmented_mp4",
184 }
185 }
186}
187impl_spec_display!(FatalKind);
188
189/// Errors returned by the client's `on_*` and `select_*` calls.
190#[derive(Debug, Clone, PartialEq, Eq, Error)]
191#[non_exhaustive]
192pub enum Error {
193 /// A resource was delivered for an id the client did not request, or one
194 /// it no longer waits for.
195 #[error("resource {id:?} was not requested")]
196 UnrequestedResource {
197 /// The id.
198 id: ResourceId,
199 },
200 /// A second delivery of a resource already accepted.
201 #[error("resource {id:?} was delivered twice")]
202 DuplicateResource {
203 /// The id.
204 id: ResourceId,
205 },
206 /// The MPD was not requested, or already accepted.
207 #[error("no MPD is awaited")]
208 MpdNotAwaited,
209 /// The MPD bytes were rejected; request it again with
210 /// [`super::DashClient::retry_mpd`].
211 #[error("MPD rejected: {0}")]
212 Mpd(MpdError),
213 /// A `UTCTiming` answer for a request the client does not wait for
214 /// (answered twice, abandoned after its timeout, or never made).
215 #[error("UTC request {id:?} was not requested")]
216 UnrequestedUtc {
217 /// The id.
218 id: UtcId,
219 },
220 /// A body was rejected and counted as a failure; `class` says what to do.
221 #[error("resource {id:?} rejected ({reason}): {class:?}")]
222 Rejected {
223 /// The resource.
224 id: ResourceId,
225 /// Why.
226 reason: Rejection,
227 /// What the caller does next.
228 class: FailureClass,
229 },
230 /// The presentation has ended or failed; everything is ignored.
231 #[error("the client has stopped")]
232 Stopped,
233 /// [`super::DashClient::select_representation`] with an index outside the
234 /// current variant list.
235 #[error("no such variant")]
236 NoSuchVariant,
237 /// [`super::DashClient::select_audio`] with an index outside the current
238 /// audio list.
239 #[error("no such audio set")]
240 NoSuchAudio,
241 /// A switch is only possible while the Period being played is the one
242 /// being fetched.
243 #[error("the Representation cannot be switched now")]
244 NotSwitchable,
245}
246
247/// Result alias.
248pub type Result<T> = core::result::Result<T, Error>;