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}