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