Skip to main content

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>;