moq_mux/error.rs
1/// Renders an error and its `source()` chain into a single message.
2///
3/// Dependency errors are stored as messages so their crates stay out of this crate's public
4/// API. Several of them keep the actionable half in `source()` and nothing but a category in
5/// `Display`, so a plain `to_string()` would drop the only detail worth reporting.
6pub(crate) fn message(err: impl std::error::Error) -> String {
7 use std::fmt::Write;
8
9 let mut out = err.to_string();
10 let mut source = err.source();
11 while let Some(err) = source {
12 let _ = write!(out, ": {err}");
13 source = err.source();
14 }
15 out
16}
17
18/// Errors from moq-mux operations.
19///
20/// Most variants are delegations to underlying layers: [`moq_net::Error`] for
21/// transport / pub-sub failures, [`hang::Error`] for catalog/codec parsing, the
22/// per-format Errors for container shape problems, and the per-codec Errors for
23/// bitstream parsing problems.
24#[derive(Debug, Clone, thiserror::Error)]
25#[non_exhaustive]
26pub enum Error {
27 /// Error from the underlying moq-net transport.
28 #[error("moq: {0}")]
29 Moq(#[from] moq_net::Error),
30
31 /// Error from the hang catalog/codec layer.
32 #[error("hang: {0}")]
33 Hang(#[from] hang::Error),
34
35 /// Error publishing or consuming JSON over a track.
36 #[error("json: {0}")]
37 Json(#[from] moq_json::Error),
38
39 /// Error publishing or consuming binary payloads over a track.
40 #[error("binary: {0}")]
41 Binary(#[from] moq_binary::Error),
42
43 /// A catalog entry declares a track mode this build does not implement.
44 #[error("unsupported track mode: {0}")]
45 UnsupportedMode(String),
46
47 /// A catalog entry declares a compression this build does not implement.
48 #[error("unsupported track compression: {0}")]
49 UnsupportedCompression(String),
50
51 /// A locally published track's catalog entry points at another broadcast.
52 #[error("a locally published track can't reference another broadcast")]
53 ForeignBroadcast,
54
55 /// Error parsing or building CMAF moof+mdat fragments.
56 #[error("cmaf: {0}")]
57 Cmaf(#[from] crate::container::fmp4::Error),
58
59 /// Error parsing or building MKV / WebM streams.
60 #[error("mkv: {0}")]
61 Mkv(#[from] crate::container::mkv::Error),
62
63 /// Error decoding the MSF catalog.
64 #[error("msf: {0}")]
65 Msf(#[from] crate::catalog::msf::Error),
66
67 /// Error parsing or building LOC frames.
68 #[error("loc: {0}")]
69 Loc(#[from] moq_loc::Error),
70
71 /// Error parsing an Annex B NAL stream.
72 #[error("annexb: {0}")]
73 Annexb(#[from] crate::codec::annexb::Error),
74
75 /// Error parsing AAC.
76 #[error("aac: {0}")]
77 Aac(#[from] crate::codec::aac::Error),
78
79 /// Error parsing Opus.
80 #[error("opus: {0}")]
81 Opus(#[from] crate::codec::opus::Error),
82
83 /// Error parsing FLAC.
84 #[error("flac: {0}")]
85 Flac(#[from] crate::codec::flac::Error),
86
87 /// Error parsing MP3.
88 #[error("mp3: {0}")]
89 Mp3(#[from] crate::codec::mp3::Error),
90
91 /// Error parsing H.264.
92 #[error("h264: {0}")]
93 H264(#[from] crate::codec::h264::Error),
94
95 /// Error parsing H.265.
96 #[error("h265: {0}")]
97 H265(#[from] crate::codec::h265::Error),
98
99 /// Error parsing AV1.
100 #[error("av1: {0}")]
101 Av1(#[from] crate::codec::av1::Error),
102
103 /// Error parsing VP8.
104 #[error("vp8: {0}")]
105 Vp8(#[from] crate::codec::vp8::Error),
106
107 /// Error parsing VP9.
108 #[error("vp9: {0}")]
109 Vp9(#[from] crate::codec::vp9::Error),
110
111 /// Error parsing legacy audio (MP2 / AC-3 / E-AC-3).
112 #[error("legacy: {0}")]
113 Legacy(#[from] crate::codec::legacy::Error),
114
115 /// Timestamp overflow when converting between timescales.
116 #[error("timestamp overflow")]
117 TimestampOverflow(#[from] moq_net::TimeOverflow),
118
119 /// Error decoding or encoding an mp4 atom.
120 #[error("mp4: {0}")]
121 Mp4(std::sync::Arc<mp4_atom::Error>),
122
123 /// I/O error.
124 #[error("io: {0}")]
125 Io(std::sync::Arc<std::io::Error>),
126
127 /// URL parse error.
128 #[error("url: {0}")]
129 Url(String),
130
131 /// Unknown media format.
132 #[error("unknown format: {0}")]
133 UnknownFormat(String),
134
135 /// A video format that a raw byte stream cannot be split into.
136 ///
137 /// Only the self-delimiting codecs (Annex-B H.264/H.265, AV1 OBUs) carry their own frame
138 /// boundaries. The rest need length prefixes or an out-of-band config record, so they can only
139 /// be imported as whole frames via [`Track::video`](crate::import::Track::video).
140 #[error("{0} is not self-describing, so its frame boundaries can't be inferred from a stream")]
141 NotSelfDescribing(String),
142
143 /// A format was handed to a constructor for a different kind of import.
144 ///
145 /// Each entry point takes only the fields its kind can honor, so the format has to match: an
146 /// audio format carries no video hint, and a container has no single rendition to label.
147 #[error("{format} is a {actual} format, not {wanted}")]
148 WrongKind {
149 /// The format string the caller passed.
150 format: String,
151 /// The kind that actually handles it.
152 actual: &'static str,
153 /// The kind the constructor expected.
154 wanted: &'static str,
155 },
156
157 /// A non-keyframe frame was received before any keyframe opened a group.
158 /// A track joining mid-stream should skip frames until the first keyframe.
159 #[error("{0}")]
160 MissingKeyframe(#[from] crate::container::MissingKeyframe),
161
162 /// An explicit video endpoint precedes its last frame.
163 #[error("{0}")]
164 InvalidEnd(#[from] crate::container::InvalidEnd),
165
166 /// A frame's timestamp sits below the live edge earlier groups reached.
167 #[error("{0}")]
168 TimestampRewind(#[from] crate::container::TimestampRewind),
169
170 /// A rendition was modified before its initial config was published.
171 #[error("rendition is not published")]
172 NotPublished,
173
174 /// A FLV video frame resolved to a negative presentation timestamp.
175 #[error("negative FLV video presentation timestamp: dts={dts_ms}ms composition_time={composition_time_ms}ms")]
176 NegativeFlvPts {
177 /// The FLV tag decode timestamp in milliseconds.
178 dts_ms: u64,
179 /// The signed FLV composition-time offset in milliseconds.
180 composition_time_ms: i32,
181 },
182
183 /// A segment ran past the [`duration_max`](crate::timeline::Config::duration_max) the
184 /// catalog advertised, so the timeline stopped publishing rather than contradict it. The
185 /// publisher declared a bound its media can't honor.
186 #[error("timeline segment {segment} lasted {duration:?}, over the declared maximum {duration_max:?}")]
187 TimelineOverrun {
188 /// The segment that broke the bound.
189 segment: u64,
190 /// How long it actually ran.
191 duration: std::time::Duration,
192 /// The bound the catalog advertised.
193 duration_max: std::time::Duration,
194 },
195
196 /// [`timeline::Producer::finish`](crate::timeline::Producer::finish) was called before its
197 /// deferred [`timeline::Segmenter`](crate::timeline::Segmenter) completed and every record
198 /// was committed.
199 #[error("finish and commit every deferred timeline record before closing the Producer")]
200 TimelineDeferredPending,
201
202 /// [`timeline::Producer::push`](crate::timeline::Producer::push) received a pending record that
203 /// its [`timeline::Deferred`](crate::timeline::Deferred) did not yield.
204 #[error("timeline segment {0} was not yielded for deferred publication")]
205 TimelineDeferredRecord(u64),
206
207 /// Error from a muxer/demuxer that reports via `anyhow` (currently MPEG-TS).
208 /// Boxed in an `Arc` so the enum stays `Clone` (`anyhow::Error` is not).
209 #[error("{0}")]
210 Other(std::sync::Arc<anyhow::Error>),
211
212 /// A timeline catalog section declared a timescale that isn't a valid
213 /// [`moq_net::Timescale`] (zero, or too large).
214 #[error("invalid timeline timescale: {0}")]
215 InvalidTimescale(u32),
216
217 /// A source timestamp cannot be mapped onto the broadcast clock: it would land before the
218 /// broadcast began or outside the representable range.
219 #[error("timestamp cannot be mapped onto the broadcast clock: {0}")]
220 UnmappableTimestamp(String),
221 /// Tried to set an application catalog section whose name collides with a
222 /// reserved media section (`video`/`audio`).
223 #[error("reserved catalog section: {0}")]
224 ReservedSection(String),
225
226 /// A rendition declared a container `kind` this build does not recognize, so its
227 /// frames cannot be parsed. Such a rendition must be ignored, not guessed at.
228 #[error("unsupported container: {0}")]
229 UnsupportedContainer(String),
230
231 /// A rendition's `broadcast` reference walks above the root, so it names no broadcast.
232 ///
233 /// The root is the consumer's authorized subtree, so such a reference is an attempt to
234 /// name content the consumer cannot reach. It rejects the whole catalog
235 /// ([`catalog::hang::Consumer`](crate::catalog::hang::Consumer)) rather than the one
236 /// rendition, and is also what a direct [`Source::resolve`](crate::Source::resolve) /
237 /// [`Source::subscribe_track`](crate::Source::subscribe_track) reports.
238 #[error("broadcast reference escapes the root: {0}")]
239 EscapingBroadcast(String),
240
241 /// A rendition tried to lower jitter already advertised to subscribers.
242 #[error("catalog jitter cannot decrease for a published rendition")]
243 JitterDecreased,
244
245 /// A rendition tried to lower delay already advertised to subscribers.
246 #[error("catalog delay cannot decrease for a published rendition")]
247 DelayDecreased,
248
249 /// A capture instant is ahead of the broadcast clock's now, or before its epoch.
250 #[error("capture time is outside the broadcast clock")]
251 InvalidCapture,
252}
253
254impl Error {
255 /// The error for a rendition whose container this build does not recognize.
256 pub(crate) fn unsupported_container(container: &hang::catalog::UnknownContainer) -> Self {
257 Self::UnsupportedContainer(container.kind().unwrap_or("<missing>").to_string())
258 }
259}
260
261impl From<anyhow::Error> for Error {
262 fn from(err: anyhow::Error) -> Self {
263 Error::Other(std::sync::Arc::new(err))
264 }
265}
266
267impl From<mp4_atom::Error> for Error {
268 fn from(err: mp4_atom::Error) -> Self {
269 Error::Mp4(std::sync::Arc::new(err))
270 }
271}
272
273// Flattened to its message so `url` stays out of this crate's public API.
274impl From<url::ParseError> for Error {
275 fn from(err: url::ParseError) -> Self {
276 Error::Url(message(err))
277 }
278}
279
280impl From<std::io::Error> for Error {
281 fn from(err: std::io::Error) -> Self {
282 Error::Io(std::sync::Arc::new(err))
283 }
284}
285
286/// A Result type alias for moq-mux operations.
287pub type Result<T> = std::result::Result<T, Error>;