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}