rodio/decoder/
mod.rs

1//! Decodes audio samples from various audio file formats.
2//!
3//! This module provides decoders for common audio formats like MP3, WAV, Vorbis and FLAC.
4//! It supports both one-shot playback and looped playback of audio files.
5//!
6//! # Usage
7//!
8//! The simplest way to decode files (automatically sets up seeking and duration):
9//! ```no_run
10//! use std::fs::File;
11//! use rodio::Decoder;
12//!
13//! let file = File::open("audio.mp3").unwrap();
14//! let decoder = Decoder::try_from(file).unwrap();  // Automatically sets byte_len from metadata
15//! ```
16//!
17//! For more control over decoder settings, use the builder pattern:
18//! ```no_run
19//! use std::fs::File;
20//! use rodio::Decoder;
21//!
22//! let file = File::open("audio.mp3").unwrap();
23//! let len = file.metadata().unwrap().len();
24//!
25//! let decoder = Decoder::builder()
26//!     .with_data(file)
27//!     .with_byte_len(len)      // Enable seeking and duration calculation
28//!     .with_seekable(true)     // Enable seeking operations
29//!     .with_hint("mp3")        // Optional format hint
30//!     .with_gapless(true)      // Enable gapless playback
31//!     .build()
32//!     .unwrap();
33//! ```
34//!
35//! # Features
36//!
37//! The following audio formats are supported based on enabled features:
38//!
39//! - `wav` - WAV format support
40//! - `flac` - FLAC format support
41//! - `vorbis` - Vorbis format support
42//! - `mp3` - MP3 format support via minimp3
43//! - `symphonia` - Enhanced format support via the Symphonia backend
44//!
45//! When using `symphonia`, additional formats like AAC and MP4 containers become available
46//! if the corresponding features are enabled.
47
48use std::{
49    error::Error,
50    fmt,
51    io::{BufReader, Read, Seek},
52    marker::PhantomData,
53    time::Duration,
54};
55
56#[allow(unused_imports)]
57use std::io::SeekFrom;
58
59use crate::{
60    common::{ChannelCount, SampleRate},
61    source::{SeekError, Source},
62    Sample,
63};
64
65pub mod builder;
66pub use builder::{DecoderBuilder, Settings};
67
68#[cfg(all(feature = "claxon", not(feature = "symphonia-flac")))]
69mod flac;
70#[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))]
71mod mp3;
72#[cfg(feature = "symphonia")]
73mod read_seek_source;
74#[cfg(feature = "symphonia")]
75/// Symphonia decoders types
76pub mod symphonia;
77#[cfg(all(feature = "lewton", not(feature = "symphonia-vorbis")))]
78mod vorbis;
79#[cfg(all(feature = "hound", not(feature = "symphonia-wav")))]
80mod wav;
81
82/// Source of audio samples decoded from an input stream.
83/// See the [module-level documentation](self) for examples and usage.
84pub struct Decoder<R: Read + Seek>(DecoderImpl<R>);
85
86/// Source of audio samples from decoding a file that never ends.
87/// When the end of the file is reached, the decoder starts again from the beginning.
88///
89/// A `LoopedDecoder` will attempt to seek back to the start of the stream when it reaches
90/// the end. If seeking fails for any reason (like IO errors), iteration will stop.
91///
92/// # Examples
93///
94/// ```no_run
95/// use std::fs::File;
96/// use rodio::Decoder;
97///
98/// let file = File::open("audio.mp3").unwrap();
99/// let looped_decoder = Decoder::new_looped(file).unwrap();
100/// ```
101pub struct LoopedDecoder<R: Read + Seek> {
102    /// The underlying decoder implementation.
103    inner: Option<DecoderImpl<R>>,
104    /// Configuration settings for the decoder.
105    settings: Settings,
106}
107
108// Cannot really reduce the size of the VorbisDecoder. There are not any
109// arrays just a lot of struct fields.
110#[allow(clippy::large_enum_variant)]
111enum DecoderImpl<R: Read + Seek> {
112    #[cfg(all(feature = "hound", not(feature = "symphonia-wav")))]
113    Wav(wav::WavDecoder<R>),
114    #[cfg(all(feature = "lewton", not(feature = "symphonia-vorbis")))]
115    Vorbis(vorbis::VorbisDecoder<R>),
116    #[cfg(all(feature = "claxon", not(feature = "symphonia-flac")))]
117    Flac(flac::FlacDecoder<R>),
118    #[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))]
119    Mp3(mp3::Mp3Decoder<R>),
120    #[cfg(feature = "symphonia")]
121    Symphonia(symphonia::SymphoniaDecoder, PhantomData<R>),
122    // This variant is here just to satisfy the compiler when there are no decoders enabled.
123    // It is unreachable and should never be constructed.
124    #[allow(dead_code)]
125    None(Unreachable, PhantomData<R>),
126}
127
128enum Unreachable {}
129
130impl<R: Read + Seek> DecoderImpl<R> {
131    #[inline]
132    fn next(&mut self) -> Option<Sample> {
133        match self {
134            #[cfg(all(feature = "hound", not(feature = "symphonia-wav")))]
135            DecoderImpl::Wav(source) => source.next(),
136            #[cfg(all(feature = "lewton", not(feature = "symphonia-vorbis")))]
137            DecoderImpl::Vorbis(source) => source.next(),
138            #[cfg(all(feature = "claxon", not(feature = "symphonia-flac")))]
139            DecoderImpl::Flac(source) => source.next(),
140            #[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))]
141            DecoderImpl::Mp3(source) => source.next(),
142            #[cfg(feature = "symphonia")]
143            DecoderImpl::Symphonia(source, PhantomData) => source.next(),
144            DecoderImpl::None(_, _) => unreachable!(),
145        }
146    }
147
148    #[inline]
149    fn size_hint(&self) -> (usize, Option<usize>) {
150        match self {
151            #[cfg(all(feature = "hound", not(feature = "symphonia-wav")))]
152            DecoderImpl::Wav(source) => source.size_hint(),
153            #[cfg(all(feature = "lewton", not(feature = "symphonia-vorbis")))]
154            DecoderImpl::Vorbis(source) => source.size_hint(),
155            #[cfg(all(feature = "claxon", not(feature = "symphonia-flac")))]
156            DecoderImpl::Flac(source) => source.size_hint(),
157            #[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))]
158            DecoderImpl::Mp3(source) => source.size_hint(),
159            #[cfg(feature = "symphonia")]
160            DecoderImpl::Symphonia(source, PhantomData) => source.size_hint(),
161            DecoderImpl::None(_, _) => unreachable!(),
162        }
163    }
164
165    #[inline]
166    fn current_span_len(&self) -> Option<usize> {
167        match self {
168            #[cfg(all(feature = "hound", not(feature = "symphonia-wav")))]
169            DecoderImpl::Wav(source) => source.current_span_len(),
170            #[cfg(all(feature = "lewton", not(feature = "symphonia-vorbis")))]
171            DecoderImpl::Vorbis(source) => source.current_span_len(),
172            #[cfg(all(feature = "claxon", not(feature = "symphonia-flac")))]
173            DecoderImpl::Flac(source) => source.current_span_len(),
174            #[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))]
175            DecoderImpl::Mp3(source) => source.current_span_len(),
176            #[cfg(feature = "symphonia")]
177            DecoderImpl::Symphonia(source, PhantomData) => source.current_span_len(),
178            DecoderImpl::None(_, _) => unreachable!(),
179        }
180    }
181
182    #[inline]
183    fn channels(&self) -> ChannelCount {
184        match self {
185            #[cfg(all(feature = "hound", not(feature = "symphonia-wav")))]
186            DecoderImpl::Wav(source) => source.channels(),
187            #[cfg(all(feature = "lewton", not(feature = "symphonia-vorbis")))]
188            DecoderImpl::Vorbis(source) => source.channels(),
189            #[cfg(all(feature = "claxon", not(feature = "symphonia-flac")))]
190            DecoderImpl::Flac(source) => source.channels(),
191            #[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))]
192            DecoderImpl::Mp3(source) => source.channels(),
193            #[cfg(feature = "symphonia")]
194            DecoderImpl::Symphonia(source, PhantomData) => source.channels(),
195            DecoderImpl::None(_, _) => unreachable!(),
196        }
197    }
198
199    #[inline]
200    fn sample_rate(&self) -> SampleRate {
201        match self {
202            #[cfg(all(feature = "hound", not(feature = "symphonia-wav")))]
203            DecoderImpl::Wav(source) => source.sample_rate(),
204            #[cfg(all(feature = "lewton", not(feature = "symphonia-vorbis")))]
205            DecoderImpl::Vorbis(source) => source.sample_rate(),
206            #[cfg(all(feature = "claxon", not(feature = "symphonia-flac")))]
207            DecoderImpl::Flac(source) => source.sample_rate(),
208            #[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))]
209            DecoderImpl::Mp3(source) => source.sample_rate(),
210            #[cfg(feature = "symphonia")]
211            DecoderImpl::Symphonia(source, PhantomData) => source.sample_rate(),
212            DecoderImpl::None(_, _) => unreachable!(),
213        }
214    }
215
216    /// Returns the total duration of this audio source.
217    ///
218    /// # Symphonia Notes
219    ///
220    /// For formats that lack timing information like MP3 and Vorbis, this requires the decoder to
221    /// be initialized with the correct byte length via `Decoder::builder().with_byte_len()`.
222    #[inline]
223    fn total_duration(&self) -> Option<Duration> {
224        match self {
225            #[cfg(all(feature = "hound", not(feature = "symphonia-wav")))]
226            DecoderImpl::Wav(source) => source.total_duration(),
227            #[cfg(all(feature = "lewton", not(feature = "symphonia-vorbis")))]
228            DecoderImpl::Vorbis(source) => source.total_duration(),
229            #[cfg(all(feature = "claxon", not(feature = "symphonia-flac")))]
230            DecoderImpl::Flac(source) => source.total_duration(),
231            #[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))]
232            DecoderImpl::Mp3(source) => source.total_duration(),
233            #[cfg(feature = "symphonia")]
234            DecoderImpl::Symphonia(source, PhantomData) => source.total_duration(),
235            DecoderImpl::None(_, _) => unreachable!(),
236        }
237    }
238
239    #[inline]
240    fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
241        match self {
242            #[cfg(all(feature = "hound", not(feature = "symphonia-wav")))]
243            DecoderImpl::Wav(source) => source.try_seek(pos),
244            #[cfg(all(feature = "lewton", not(feature = "symphonia-vorbis")))]
245            DecoderImpl::Vorbis(source) => source.try_seek(pos),
246            #[cfg(all(feature = "claxon", not(feature = "symphonia-flac")))]
247            DecoderImpl::Flac(source) => source.try_seek(pos),
248            #[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))]
249            DecoderImpl::Mp3(source) => source.try_seek(pos),
250            #[cfg(feature = "symphonia")]
251            DecoderImpl::Symphonia(source, PhantomData) => source.try_seek(pos),
252            DecoderImpl::None(_, _) => unreachable!(),
253        }
254    }
255}
256
257/// Converts a `File` into a `Decoder` with automatic optimizations.
258/// This is the preferred way to decode files as it enables seeking optimizations
259/// and accurate duration calculations.
260///
261/// This implementation:
262/// - Wraps the file in a `BufReader` for better performance
263/// - Gets the file length from metadata to improve seeking operations and duration accuracy
264/// - Enables seeking by default
265///
266/// # Errors
267///
268/// Returns an error if:
269/// - The file metadata cannot be read
270/// - The audio format cannot be recognized or is not supported
271///
272/// # Examples
273/// ```no_run
274/// use std::fs::File;
275/// use rodio::Decoder;
276///
277/// let file = File::open("audio.mp3").unwrap();
278/// let decoder = Decoder::try_from(file).unwrap();
279/// ```
280impl TryFrom<std::fs::File> for Decoder<BufReader<std::fs::File>> {
281    type Error = DecoderError;
282
283    fn try_from(file: std::fs::File) -> Result<Self, Self::Error> {
284        let len = file
285            .metadata()
286            .map_err(|e| Self::Error::IoError(e.to_string()))?
287            .len();
288
289        Self::builder()
290            .with_data(BufReader::new(file))
291            .with_byte_len(len)
292            .with_seekable(true)
293            .build()
294    }
295}
296
297/// Converts a `BufReader` into a `Decoder`.
298/// When working with files, prefer `TryFrom<File>` as it will automatically set byte_len
299/// for better seeking performance.
300///
301/// # Errors
302///
303/// Returns `DecoderError::UnrecognizedFormat` if the audio format could not be determined
304/// or is not supported.
305///
306/// # Examples
307/// ```no_run
308/// use std::fs::File;
309/// use std::io::BufReader;
310/// use rodio::Decoder;
311///
312/// let file = File::open("audio.mp3").unwrap();
313/// let reader = BufReader::new(file);
314/// let decoder = Decoder::try_from(reader).unwrap();
315/// ```
316impl<R> TryFrom<BufReader<R>> for Decoder<BufReader<R>>
317where
318    R: Read + Seek + Send + Sync + 'static,
319{
320    type Error = DecoderError;
321
322    fn try_from(data: BufReader<R>) -> Result<Self, Self::Error> {
323        Self::new(data)
324    }
325}
326
327/// Converts a `Cursor` into a `Decoder`.
328/// When working with files, prefer `TryFrom<File>` as it will automatically set byte_len
329/// for better seeking performance.
330///
331/// This is useful for decoding audio data that's already in memory.
332///
333/// # Errors
334///
335/// Returns `DecoderError::UnrecognizedFormat` if the audio format could not be determined
336/// or is not supported.
337///
338/// # Examples
339/// ```no_run
340/// use std::io::Cursor;
341/// use rodio::Decoder;
342///
343/// let data = std::fs::read("audio.mp3").unwrap();
344/// let cursor = Cursor::new(data);
345/// let decoder = Decoder::try_from(cursor).unwrap();
346/// ```
347impl<T> TryFrom<std::io::Cursor<T>> for Decoder<std::io::Cursor<T>>
348where
349    T: AsRef<[u8]> + Send + Sync + 'static,
350{
351    type Error = DecoderError;
352
353    fn try_from(data: std::io::Cursor<T>) -> Result<Self, Self::Error> {
354        Self::new(data)
355    }
356}
357
358impl<R: Read + Seek + Send + Sync + 'static> Decoder<R> {
359    /// Returns a builder for creating a new decoder with customizable settings.
360    ///
361    /// # Examples
362    /// ```no_run
363    /// use std::fs::File;
364    /// use rodio::Decoder;
365    ///
366    /// let file = File::open("audio.mp3").unwrap();
367    /// let decoder = Decoder::builder()
368    ///     .with_data(file)
369    ///     .with_hint("mp3")
370    ///     .with_gapless(true)
371    ///     .build()
372    ///     .unwrap();
373    /// ```
374    pub fn builder() -> DecoderBuilder<R> {
375        DecoderBuilder::new()
376    }
377
378    /// Builds a new decoder with default settings.
379    ///
380    /// Attempts to automatically detect the format of the source of data.
381    ///
382    /// # Errors
383    ///
384    /// Returns `DecoderError::UnrecognizedFormat` if the audio format could not be determined
385    /// or is not supported.
386    pub fn new(data: R) -> Result<Self, DecoderError> {
387        DecoderBuilder::new().with_data(data).build()
388    }
389
390    /// Builds a new looped decoder with default settings.
391    ///
392    /// Attempts to automatically detect the format of the source of data.
393    /// The decoder will restart from the beginning when it reaches the end.
394    ///
395    /// # Errors
396    ///
397    /// Returns `DecoderError::UnrecognizedFormat` if the audio format could not be determined
398    /// or is not supported.
399    pub fn new_looped(data: R) -> Result<LoopedDecoder<R>, DecoderError> {
400        DecoderBuilder::new().with_data(data).build_looped()
401    }
402
403    /// Builds a new decoder with WAV format hint.
404    ///
405    /// This method provides a hint that the data is WAV format, which may help the decoder
406    /// identify the format more quickly. However, if WAV decoding fails, other formats
407    /// will still be attempted.
408    ///
409    /// # Errors
410    ///
411    /// Returns `DecoderError::UnrecognizedFormat` if no suitable decoder was found.
412    ///
413    /// # Examples
414    /// ```no_run
415    /// use rodio::Decoder;
416    /// use std::fs::File;
417    ///
418    /// let file = File::open("audio.wav").unwrap();
419    /// let decoder = Decoder::new_wav(file).unwrap();
420    /// ```
421    #[cfg(any(feature = "hound", feature = "symphonia-wav"))]
422    pub fn new_wav(data: R) -> Result<Self, DecoderError> {
423        DecoderBuilder::new()
424            .with_data(data)
425            .with_hint("wav")
426            .build()
427    }
428
429    /// Builds a new decoder with FLAC format hint.
430    ///
431    /// This method provides a hint that the data is FLAC format, which may help the decoder
432    /// identify the format more quickly. However, if FLAC decoding fails, other formats
433    /// will still be attempted.
434    ///
435    /// # Errors
436    ///
437    /// Returns `DecoderError::UnrecognizedFormat` if no suitable decoder was found.
438    ///
439    /// # Examples
440    /// ```no_run
441    /// use rodio::Decoder;
442    /// use std::fs::File;
443    ///
444    /// let file = File::open("audio.flac").unwrap();
445    /// let decoder = Decoder::new_flac(file).unwrap();
446    /// ```
447    #[cfg(any(feature = "claxon", feature = "symphonia-flac"))]
448    pub fn new_flac(data: R) -> Result<Self, DecoderError> {
449        DecoderBuilder::new()
450            .with_data(data)
451            .with_hint("flac")
452            .build()
453    }
454
455    /// Builds a new decoder with Vorbis format hint.
456    ///
457    /// This method provides a hint that the data is Vorbis format, which may help the decoder
458    /// identify the format more quickly. However, if Vorbis decoding fails, other formats
459    /// will still be attempted.
460    ///
461    /// # Errors
462    ///
463    /// Returns `DecoderError::UnrecognizedFormat` if no suitable decoder was found.
464    ///
465    /// # Examples
466    /// ```no_run
467    /// use rodio::Decoder;
468    /// use std::fs::File;
469    ///
470    /// let file = File::open("audio.ogg").unwrap();
471    /// let decoder = Decoder::new_vorbis(file).unwrap();
472    /// ```
473    #[cfg(any(feature = "lewton", feature = "symphonia-vorbis"))]
474    pub fn new_vorbis(data: R) -> Result<Self, DecoderError> {
475        DecoderBuilder::new()
476            .with_data(data)
477            .with_hint("ogg")
478            .build()
479    }
480
481    /// Builds a new decoder with MP3 format hint.
482    ///
483    /// This method provides a hint that the data is MP3 format, which may help the decoder
484    /// identify the format more quickly. However, if MP3 decoding fails, other formats
485    /// will still be attempted.
486    ///
487    /// # Errors
488    ///
489    /// Returns `DecoderError::UnrecognizedFormat` if no suitable decoder was found.
490    ///
491    /// # Examples
492    /// ```no_run
493    /// use rodio::Decoder;
494    /// use std::fs::File;
495    ///
496    /// let file = File::open("audio.mp3").unwrap();
497    /// let decoder = Decoder::new_mp3(file).unwrap();
498    /// ```
499    #[cfg(any(feature = "minimp3", feature = "symphonia-mp3"))]
500    pub fn new_mp3(data: R) -> Result<Self, DecoderError> {
501        DecoderBuilder::new()
502            .with_data(data)
503            .with_hint("mp3")
504            .build()
505    }
506
507    /// Builds a new decoder with AAC format hint.
508    ///
509    /// This method provides a hint that the data is AAC format, which may help the decoder
510    /// identify the format more quickly. However, if AAC decoding fails, other formats
511    /// will still be attempted.
512    ///
513    /// # Errors
514    ///
515    /// Returns `DecoderError::UnrecognizedFormat` if no suitable decoder was found.
516    ///
517    /// # Examples
518    /// ```no_run
519    /// use rodio::Decoder;
520    /// use std::fs::File;
521    ///
522    /// let file = File::open("audio.aac").unwrap();
523    /// let decoder = Decoder::new_aac(file).unwrap();
524    /// ```
525    #[cfg(feature = "symphonia-aac")]
526    pub fn new_aac(data: R) -> Result<Self, DecoderError> {
527        DecoderBuilder::new()
528            .with_data(data)
529            .with_hint("aac")
530            .build()
531    }
532
533    /// Builds a new decoder with MP4 container format hint.
534    ///
535    /// This method provides a hint that the data is in MP4 container format by setting
536    /// the MIME type to "audio/mp4". This may help the decoder identify the format
537    /// more quickly. However, if MP4 decoding fails, other formats will still be attempted.
538    ///
539    /// # Errors
540    ///
541    /// Returns `DecoderError::UnrecognizedFormat` if no suitable decoder was found.
542    ///
543    /// # Examples
544    /// ```no_run
545    /// use rodio::Decoder;
546    /// use std::fs::File;
547    ///
548    /// let file = File::open("audio.m4a").unwrap();
549    /// let decoder = Decoder::new_mp4(file).unwrap();
550    /// ```
551    #[cfg(feature = "symphonia-isomp4")]
552    pub fn new_mp4(data: R) -> Result<Self, DecoderError> {
553        DecoderBuilder::new()
554            .with_data(data)
555            .with_mime_type("audio/mp4")
556            .build()
557    }
558}
559
560impl<R> Iterator for Decoder<R>
561where
562    R: Read + Seek,
563{
564    type Item = Sample;
565
566    #[inline]
567    fn next(&mut self) -> Option<Self::Item> {
568        self.0.next()
569    }
570
571    #[inline]
572    fn size_hint(&self) -> (usize, Option<usize>) {
573        self.0.size_hint()
574    }
575}
576
577impl<R> Source for Decoder<R>
578where
579    R: Read + Seek,
580{
581    #[inline]
582    fn current_span_len(&self) -> Option<usize> {
583        self.0.current_span_len()
584    }
585
586    #[inline]
587    fn channels(&self) -> ChannelCount {
588        self.0.channels()
589    }
590
591    fn sample_rate(&self) -> SampleRate {
592        self.0.sample_rate()
593    }
594
595    #[inline]
596    fn total_duration(&self) -> Option<Duration> {
597        self.0.total_duration()
598    }
599
600    #[inline]
601    fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
602        self.0.try_seek(pos)
603    }
604}
605
606impl<R> Iterator for LoopedDecoder<R>
607where
608    R: Read + Seek,
609{
610    type Item = Sample;
611
612    /// Returns the next sample in the audio stream.
613    ///
614    /// When the end of the stream is reached, attempts to seek back to the start
615    /// and continue playing. If seeking fails, or if no decoder is available,
616    /// returns `None`.
617    fn next(&mut self) -> Option<Self::Item> {
618        if let Some(inner) = &mut self.inner {
619            if let Some(sample) = inner.next() {
620                return Some(sample);
621            }
622
623            // Take ownership of the decoder to reset it
624            let decoder = self.inner.take()?;
625            let (new_decoder, sample) = match decoder {
626                #[cfg(all(feature = "hound", not(feature = "symphonia-wav")))]
627                DecoderImpl::Wav(source) => {
628                    let mut reader = source.into_inner();
629                    reader.seek(SeekFrom::Start(0)).ok()?;
630                    let mut source = wav::WavDecoder::new(reader).ok()?;
631                    let sample = source.next();
632                    (DecoderImpl::Wav(source), sample)
633                }
634                #[cfg(all(feature = "lewton", not(feature = "symphonia-vorbis")))]
635                DecoderImpl::Vorbis(source) => {
636                    use lewton::inside_ogg::OggStreamReader;
637                    let mut reader = source.into_inner().into_inner();
638                    reader.seek_bytes(SeekFrom::Start(0)).ok()?;
639                    let mut source = vorbis::VorbisDecoder::from_stream_reader(
640                        OggStreamReader::from_ogg_reader(reader).ok()?,
641                    );
642                    let sample = source.next();
643                    (DecoderImpl::Vorbis(source), sample)
644                }
645                #[cfg(all(feature = "claxon", not(feature = "symphonia-flac")))]
646                DecoderImpl::Flac(source) => {
647                    let mut reader = source.into_inner();
648                    reader.seek(SeekFrom::Start(0)).ok()?;
649                    let mut source = flac::FlacDecoder::new(reader).ok()?;
650                    let sample = source.next();
651                    (DecoderImpl::Flac(source), sample)
652                }
653                #[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))]
654                DecoderImpl::Mp3(source) => {
655                    let mut reader = source.into_inner();
656                    reader.seek(SeekFrom::Start(0)).ok()?;
657                    let mut source = mp3::Mp3Decoder::new(reader).ok()?;
658                    let sample = source.next();
659                    (DecoderImpl::Mp3(source), sample)
660                }
661                #[cfg(feature = "symphonia")]
662                DecoderImpl::Symphonia(source, PhantomData) => {
663                    let mut reader = source.into_inner();
664                    reader.seek(SeekFrom::Start(0)).ok()?;
665                    let mut source =
666                        symphonia::SymphoniaDecoder::new(reader, &self.settings).ok()?;
667                    let sample = source.next();
668                    (DecoderImpl::Symphonia(source, PhantomData), sample)
669                }
670            };
671            self.inner = Some(new_decoder);
672            sample
673        } else {
674            None
675        }
676    }
677
678    /// Returns the size hint for this iterator.
679    ///
680    /// The lower bound is:
681    /// - The minimum number of samples remaining in the current iteration if there is an active decoder
682    /// - 0 if there is no active decoder (inner is None)
683    ///
684    /// The upper bound is always `None` since the decoder loops indefinitely.
685    /// This differs from non-looped decoders which may provide a finite upper bound.
686    ///
687    /// Note that even with an active decoder, reaching the end of the stream may result
688    /// in the decoder becoming inactive if seeking back to the start fails.
689    #[inline]
690    fn size_hint(&self) -> (usize, Option<usize>) {
691        (
692            self.inner.as_ref().map_or(0, |inner| inner.size_hint().0),
693            None,
694        )
695    }
696}
697
698impl<R> Source for LoopedDecoder<R>
699where
700    R: Read + Seek,
701{
702    /// Returns the current span length of the underlying decoder.
703    ///
704    /// Returns `None` if there is no active decoder.
705    #[inline]
706    fn current_span_len(&self) -> Option<usize> {
707        self.inner.as_ref()?.current_span_len()
708    }
709
710    /// Returns the number of channels in the audio stream.
711    ///
712    /// Returns the default channel count if there is no active decoder.
713    #[inline]
714    fn channels(&self) -> ChannelCount {
715        self.inner
716            .as_ref()
717            .map_or(ChannelCount::default(), |inner| inner.channels())
718    }
719
720    /// Returns the sample rate of the audio stream.
721    ///
722    /// Returns the default sample rate if there is no active decoder.
723    #[inline]
724    fn sample_rate(&self) -> SampleRate {
725        self.inner
726            .as_ref()
727            .map_or(SampleRate::default(), |inner| inner.sample_rate())
728    }
729
730    /// Returns the total duration of this audio source.
731    ///
732    /// Always returns `None` for looped decoders since they have no fixed end point -
733    /// they will continue playing indefinitely by seeking back to the start when reaching
734    /// the end of the audio data.
735    #[inline]
736    fn total_duration(&self) -> Option<Duration> {
737        None
738    }
739
740    /// Attempts to seek to a specific position in the audio stream.
741    ///
742    /// # Errors
743    ///
744    /// Returns `SeekError::NotSupported` if:
745    /// - There is no active decoder
746    /// - The underlying decoder does not support seeking
747    ///
748    /// May also return other `SeekError` variants if the underlying decoder's seek operation fails.
749    ///
750    /// # Note
751    ///
752    /// Even for looped playback, seeking past the end of the stream will not automatically
753    /// wrap around to the beginning - it will return an error just like a normal decoder.
754    /// Looping only occurs when reaching the end through normal playback.
755    fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
756        match &mut self.inner {
757            Some(inner) => inner.try_seek(pos),
758            None => Err(SeekError::Other(Box::new(DecoderError::IoError(
759                "Looped source ended when it failed to loop back".to_string(),
760            )))),
761        }
762    }
763}
764
765/// Errors that can occur when creating a decoder.
766#[derive(Debug, Clone)]
767pub enum DecoderError {
768    /// The format of the data has not been recognized.
769    UnrecognizedFormat,
770
771    /// An IO error occurred while reading, writing, or seeking the stream.
772    IoError(String),
773
774    /// The stream contained malformed data and could not be decoded or demuxed.
775    #[cfg(feature = "symphonia")]
776    DecodeError(&'static str),
777
778    /// A default or user-defined limit was reached while decoding or demuxing the stream. Limits
779    /// are used to prevent denial-of-service attacks from malicious streams.
780    #[cfg(feature = "symphonia")]
781    LimitError(&'static str),
782
783    /// The demuxer or decoder needs to be reset before continuing.
784    #[cfg(feature = "symphonia")]
785    ResetRequired,
786
787    /// No streams were found by the decoder.
788    #[cfg(feature = "symphonia")]
789    NoStreams,
790}
791
792impl fmt::Display for DecoderError {
793    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
794        let text = match self {
795            DecoderError::UnrecognizedFormat => "Unrecognized format",
796            DecoderError::IoError(msg) => &msg[..],
797            #[cfg(feature = "symphonia")]
798            DecoderError::DecodeError(msg) => msg,
799            #[cfg(feature = "symphonia")]
800            DecoderError::LimitError(msg) => msg,
801            #[cfg(feature = "symphonia")]
802            DecoderError::ResetRequired => "Reset required",
803            #[cfg(feature = "symphonia")]
804            DecoderError::NoStreams => "No streams",
805        };
806        write!(f, "{text}")
807    }
808}
809
810impl Error for DecoderError {}