rodio/decoder/builder.rs
1//! Builder pattern for configuring and constructing decoders.
2//!
3//! This module provides a flexible builder API for creating decoders with custom settings.
4//! The builder allows configuring format hints, seeking behavior, byte length and other
5//! parameters that affect decoder behavior.
6//!
7//! # Examples
8//!
9//! ```no_run
10//! use std::fs::File;
11//! use rodio::Decoder;
12//!
13//! fn main() -> Result<(), Box<dyn std::error::Error>> {
14//! let file = File::open("audio.mp3")?;
15//! let len = file.metadata()?.len();
16//!
17//! Decoder::builder()
18//! .with_data(file)
19//! .with_byte_len(len) // Enable seeking and duration calculation
20//! .with_hint("mp3") // Optional format hint
21//! .with_gapless(true) // Enable gapless playback
22//! .build()?;
23//!
24//! // Use the decoder...
25//! Ok(())
26//! }
27//! ```
28//!
29//! # Settings
30//!
31//! The following settings can be configured:
32//!
33//! - `byte_len` - Total length of the input data in bytes
34//! - `hint` - Format hint like "mp3", "wav", etc
35//! - `mime_type` - MIME type hint for container formats
36//! - `seekable` - Whether seeking operations are enabled
37//! - `gapless` - Enable gapless playback
38//! - `coarse_seek` - Use faster but less precise seeking
39
40use std::io::{Read, Seek};
41
42#[cfg(feature = "symphonia")]
43use self::read_seek_source::ReadSeekSource;
44#[cfg(feature = "symphonia")]
45use ::symphonia::core::io::{MediaSource, MediaSourceStream};
46
47use super::*;
48
49/// Audio decoder configuration settings.
50/// Support for these settings depends on the underlying decoder implementation.
51/// Currently, settings are only used by the Symphonia decoder.
52#[derive(Clone, Debug)]
53pub struct Settings {
54 /// The length of the stream in bytes.
55 /// This is required for:
56 /// - Reliable seeking operations
57 /// - Duration calculations in formats that lack timing information (e.g. MP3, Vorbis)
58 ///
59 /// Can be obtained from file metadata or by seeking to the end of the stream.
60 pub(crate) byte_len: Option<u64>,
61
62 /// Whether to use coarse seeking, or sample-accurate seeking instead.
63 pub(crate) coarse_seek: bool,
64
65 /// Whether to trim frames for gapless playback.
66 /// Note: Disabling this may affect duration calculations for some formats
67 /// as padding frames will be included.
68 pub(crate) gapless: bool,
69
70 /// An extension hint for the decoder about the format of the stream.
71 /// When known, this can help the decoder to select the correct codec.
72 pub(crate) hint: Option<String>,
73
74 /// An MIME type hint for the decoder about the format of the stream.
75 /// When known, this can help the decoder to select the correct demuxer.
76 pub(crate) mime_type: Option<String>,
77
78 /// Whether the decoder should report as seekable.
79 pub(crate) is_seekable: bool,
80}
81
82impl Default for Settings {
83 fn default() -> Self {
84 Self {
85 byte_len: None,
86 coarse_seek: false,
87 gapless: true,
88 hint: None,
89 mime_type: None,
90 is_seekable: false,
91 }
92 }
93}
94
95/// Builder for configuring and creating a decoder.
96///
97/// This provides a flexible way to configure decoder settings before creating
98/// the actual decoder instance.
99///
100/// # Examples
101///
102/// ```no_run
103/// use std::fs::File;
104/// use rodio::decoder::DecoderBuilder;
105///
106/// fn main() -> Result<(), Box<dyn std::error::Error>> {
107/// let file = File::open("audio.mp3")?;
108/// let decoder = DecoderBuilder::new()
109/// .with_data(file)
110/// .with_hint("mp3")
111/// .with_gapless(true)
112/// .build()?;
113///
114/// // Use the decoder...
115/// Ok(())
116/// }
117/// ```
118#[derive(Clone, Debug)]
119pub struct DecoderBuilder<R> {
120 /// The input data source to decode.
121 data: Option<R>,
122 /// Configuration settings for the decoder.
123 settings: Settings,
124}
125
126impl<R> Default for DecoderBuilder<R> {
127 fn default() -> Self {
128 Self {
129 data: None,
130 settings: Settings::default(),
131 }
132 }
133}
134
135impl<R: Read + Seek + Send + Sync + 'static> DecoderBuilder<R> {
136 /// Creates a new decoder builder with default settings.
137 ///
138 /// # Examples
139 /// ```no_run
140 /// use std::fs::File;
141 /// use rodio::decoder::DecoderBuilder;
142 ///
143 /// fn main() -> Result<(), Box<dyn std::error::Error>> {
144 /// let file = File::open("audio.mp3")?;
145 /// let decoder = DecoderBuilder::new()
146 /// .with_data(file)
147 /// .build()?;
148 ///
149 /// // Use the decoder...
150 /// Ok(())
151 /// }
152 /// ```
153 pub fn new() -> Self {
154 Self::default()
155 }
156
157 /// Sets the input data source to decode.
158 pub fn with_data(mut self, data: R) -> Self {
159 self.data = Some(data);
160 self
161 }
162
163 /// Sets the byte length of the stream.
164 /// This is required for:
165 /// - Reliable seeking operations
166 /// - Duration calculations in formats that lack timing information (e.g. MP3, Vorbis)
167 ///
168 /// Note that this also sets `is_seekable` to `true`.
169 ///
170 /// The byte length should typically be obtained from file metadata:
171 /// ```no_run
172 /// use std::fs::File;
173 /// use rodio::Decoder;
174 ///
175 /// fn main() -> Result<(), Box<dyn std::error::Error>> {
176 /// let file = File::open("audio.mp3")?;
177 /// let len = file.metadata()?.len();
178 /// let decoder = Decoder::builder()
179 /// .with_data(file)
180 /// .with_byte_len(len)
181 /// .build()?;
182 ///
183 /// // Use the decoder...
184 /// Ok(())
185 /// }
186 /// ```
187 ///
188 /// Alternatively, it can be obtained by seeking to the end of the stream.
189 ///
190 /// An incorrect byte length can lead to unexpected behavior, including but not limited to
191 /// incorrect duration calculations and seeking errors.
192 pub fn with_byte_len(mut self, byte_len: u64) -> Self {
193 self.settings.byte_len = Some(byte_len);
194 self.settings.is_seekable = true;
195 self
196 }
197
198 /// Enables or disables coarse seeking. This is disabled by default.
199 ///
200 /// This needs `byte_len` to be set. Coarse seeking is faster but less accurate:
201 /// it may seek to a position slightly before or after the requested one,
202 /// especially when the bitrate is variable.
203 pub fn with_coarse_seek(mut self, coarse_seek: bool) -> Self {
204 self.settings.coarse_seek = coarse_seek;
205 self
206 }
207
208 /// Enables or disables gapless playback. This is enabled by default.
209 ///
210 /// When enabled, removes silence between tracks for formats that support it.
211 pub fn with_gapless(mut self, gapless: bool) -> Self {
212 self.settings.gapless = gapless;
213 self
214 }
215
216 /// Sets a format hint for the decoder.
217 ///
218 /// When known, this can help the decoder to select the correct codec faster.
219 /// Common values are "mp3", "wav", "flac", "ogg", etc.
220 pub fn with_hint(mut self, hint: &str) -> Self {
221 self.settings.hint = Some(hint.to_string());
222 self
223 }
224
225 /// Sets a mime type hint for the decoder.
226 ///
227 /// When known, this can help the decoder to select the correct demuxer faster.
228 /// Common values are "audio/mpeg", "audio/vnd.wav", "audio/flac", "audio/ogg", etc.
229 pub fn with_mime_type(mut self, mime_type: &str) -> Self {
230 self.settings.mime_type = Some(mime_type.to_string());
231 self
232 }
233
234 /// Configure whether the data supports random access seeking. Without this,
235 /// only forward seeking may work.
236 ///
237 /// For reliable seeking behavior, `byte_len` should be set either from file metadata
238 /// or by seeking to the end of the stream. While seeking may work without `byte_len`
239 /// for some formats, it is not guaranteed.
240 ///
241 /// # Examples
242 /// ```no_run
243 /// use std::fs::File;
244 /// use rodio::Decoder;
245 ///
246 /// fn main() -> Result<(), Box<dyn std::error::Error>> {
247 /// let file = File::open("audio.mp3")?;
248 /// let len = file.metadata()?.len();
249 ///
250 /// // Recommended: Set both byte_len and seekable
251 /// let decoder = Decoder::builder()
252 /// .with_data(file)
253 /// .with_byte_len(len)
254 /// .with_seekable(true)
255 /// .build()?;
256 ///
257 /// // Use the decoder...
258 /// Ok(())
259 /// }
260 /// ```
261 pub fn with_seekable(mut self, is_seekable: bool) -> Self {
262 self.settings.is_seekable = is_seekable;
263 self
264 }
265
266 /// Creates the decoder implementation with configured settings.
267 fn build_impl(self) -> Result<(DecoderImpl<R>, Settings), DecoderError> {
268 let data = self.data.ok_or(DecoderError::UnrecognizedFormat)?;
269
270 #[cfg(all(feature = "hound", not(feature = "symphonia-wav")))]
271 let data = match wav::WavDecoder::new(data) {
272 Ok(decoder) => return Ok((DecoderImpl::Wav(decoder), self.settings)),
273 Err(data) => data,
274 };
275 #[cfg(all(feature = "claxon", not(feature = "symphonia-flac")))]
276 let data = match flac::FlacDecoder::new(data) {
277 Ok(decoder) => return Ok((DecoderImpl::Flac(decoder), self.settings)),
278 Err(data) => data,
279 };
280
281 #[cfg(all(feature = "lewton", not(feature = "symphonia-vorbis")))]
282 let data = match vorbis::VorbisDecoder::new(data) {
283 Ok(decoder) => return Ok((DecoderImpl::Vorbis(decoder), self.settings)),
284 Err(data) => data,
285 };
286
287 #[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))]
288 let data = match mp3::Mp3Decoder::new(data) {
289 Ok(decoder) => return Ok((DecoderImpl::Mp3(decoder), self.settings)),
290 Err(data) => data,
291 };
292
293 #[cfg(feature = "symphonia")]
294 {
295 let mss = MediaSourceStream::new(
296 Box::new(ReadSeekSource::new(data, &self.settings)) as Box<dyn MediaSource>,
297 Default::default(),
298 );
299
300 symphonia::SymphoniaDecoder::new(mss, &self.settings)
301 .map(|decoder| (DecoderImpl::Symphonia(decoder, PhantomData), self.settings))
302 }
303
304 #[cfg(not(feature = "symphonia"))]
305 Err(DecoderError::UnrecognizedFormat)
306 }
307
308 /// Creates a new decoder with previously configured settings.
309 ///
310 /// # Errors
311 ///
312 /// Returns `DecoderError::UnrecognizedFormat` if the audio format could not be determined
313 /// or is not supported.
314 pub fn build(self) -> Result<Decoder<R>, DecoderError> {
315 let (decoder, _) = self.build_impl()?;
316 Ok(Decoder(decoder))
317 }
318
319 /// Creates a new looped decoder with previously configured settings.
320 ///
321 /// # Errors
322 ///
323 /// Returns `DecoderError::UnrecognizedFormat` if the audio format could not be determined
324 /// or is not supported.
325 pub fn build_looped(self) -> Result<LoopedDecoder<R>, DecoderError> {
326 let (decoder, settings) = self.build_impl()?;
327 Ok(LoopedDecoder {
328 inner: Some(decoder),
329 settings,
330 })
331 }
332}