1+ use std:: {
2+ io:: { Read , Seek } ,
3+ marker:: PhantomData ,
4+ sync:: Arc ,
5+ time:: Duration ,
6+ } ;
7+
8+ use crate :: {
9+ common:: { ChannelCount , SampleRate } ,
10+ math:: nz,
11+ source:: { SeekError , Source } ,
12+ BitDepth , Sample ,
13+ } ;
14+
15+ use super :: { builder:: Settings , DecoderError , DecoderImpl } ;
16+
17+ #[ cfg( feature = "claxon" ) ]
18+ use super :: flac;
19+ #[ cfg( feature = "minimp3" ) ]
20+ use super :: mp3;
21+ #[ cfg( feature = "symphonia" ) ]
22+ use super :: symphonia;
23+ #[ cfg( feature = "lewton" ) ]
24+ use super :: vorbis;
25+ #[ cfg( feature = "hound" ) ]
26+ use super :: wav;
27+
28+ /// Source of audio samples from decoding a file that never ends.
29+ /// When the end of the file is reached, the decoder starts again from the beginning.
30+ ///
31+ /// A `LoopedDecoder` will attempt to seek back to the start of the stream when it reaches
32+ /// the end. If seeking fails for any reason (like IO errors), iteration will stop.
33+ ///
34+ /// For seekable sources with gapless playback enabled, this uses `try_seek(Duration::ZERO)`
35+ /// which is fast. For non-seekable sources or when gapless is disabled, it recreates the
36+ /// decoder but caches metadata from the first iteration to avoid expensive file scanning
37+ /// on subsequent loops.
38+ ///
39+ /// # Examples
40+ ///
41+ /// ```no_run
42+ /// use std::fs::File;
43+ /// use rodio::Decoder;
44+ ///
45+ /// let file = File::open("audio.mp3").unwrap();
46+ /// let looped_decoder = Decoder::new_looped(file).unwrap();
47+ /// ```
48+ #[ allow( dead_code) ]
49+ pub struct LoopedDecoder < R : Read + Seek > {
50+ /// The underlying decoder implementation.
51+ pub ( super ) inner : Option < DecoderImpl < R > > ,
52+ /// Configuration settings for the decoder.
53+ pub ( super ) settings : Settings ,
54+ /// Used to avoid expensive file scanning on subsequent loops.
55+ cached_duration : Option < Duration > ,
56+ }
57+
58+ impl < R > LoopedDecoder < R >
59+ where
60+ R : Read + Seek ,
61+ {
62+ /// Create a new `LoopedDecoder` with the given decoder and settings.
63+ pub ( super ) fn new ( decoder : DecoderImpl < R > , settings : Settings ) -> Self {
64+ Self {
65+ inner : Some ( decoder) ,
66+ settings,
67+ cached_duration : None ,
68+ }
69+ }
70+
71+ /// Recreate decoder with cached metadata to avoid expensive file scanning.
72+ fn recreate_decoder_with_cache (
73+ & mut self ,
74+ decoder : DecoderImpl < R > ,
75+ ) -> Option < ( DecoderImpl < R > , Option < Sample > ) > {
76+ // Create settings with cached metadata for fast recreation.
77+ // Note: total_duration is important even though LoopedDecoder::total_duration() returns
78+ // None, because the individual decoder's total_duration() is used for seek saturation
79+ // (clamping seeks beyond the end to the end position).
80+ let mut fast_settings = self . settings . clone ( ) ;
81+ fast_settings. total_duration = self . cached_duration ;
82+
83+ let ( new_decoder, sample) = match decoder {
84+ #[ cfg( feature = "hound" ) ]
85+ DecoderImpl :: Wav ( source) => {
86+ let mut reader = source. into_inner ( ) ;
87+ reader. rewind ( ) . ok ( ) ?;
88+ let mut source = wav:: WavDecoder :: new_with_settings ( reader, & fast_settings) . ok ( ) ?;
89+ let sample = source. next ( ) ;
90+ ( DecoderImpl :: Wav ( source) , sample)
91+ }
92+ #[ cfg( feature = "lewton" ) ]
93+ DecoderImpl :: Vorbis ( source) => {
94+ let mut reader = source. into_inner ( ) . into_inner ( ) . into_inner ( ) ;
95+ reader. rewind ( ) . ok ( ) ?;
96+ let mut source =
97+ vorbis:: VorbisDecoder :: new_with_settings ( reader, & fast_settings) . ok ( ) ?;
98+ let sample = source. next ( ) ;
99+ ( DecoderImpl :: Vorbis ( source) , sample)
100+ }
101+ #[ cfg( feature = "claxon" ) ]
102+ DecoderImpl :: Flac ( source) => {
103+ let mut reader = source. into_inner ( ) ;
104+ reader. rewind ( ) . ok ( ) ?;
105+ let mut source =
106+ flac:: FlacDecoder :: new_with_settings ( reader, & fast_settings) . ok ( ) ?;
107+ let sample = source. next ( ) ;
108+ ( DecoderImpl :: Flac ( source) , sample)
109+ }
110+ #[ cfg( feature = "minimp3" ) ]
111+ DecoderImpl :: Mp3 ( source) => {
112+ let mut reader = source. into_inner ( ) ;
113+ reader. rewind ( ) . ok ( ) ?;
114+ let mut source = mp3:: Mp3Decoder :: new_with_settings ( reader, & fast_settings) . ok ( ) ?;
115+ let sample = source. next ( ) ;
116+ ( DecoderImpl :: Mp3 ( source) , sample)
117+ }
118+ #[ cfg( feature = "symphonia" ) ]
119+ DecoderImpl :: Symphonia ( source, PhantomData ) => {
120+ let mut reader = source. into_inner ( ) ;
121+ reader. rewind ( ) . ok ( ) ?;
122+ let mut source =
123+ symphonia:: SymphoniaDecoder :: new_with_settings ( reader, & fast_settings) . ok ( ) ?;
124+ let sample = source. next ( ) ;
125+ ( DecoderImpl :: Symphonia ( source, PhantomData ) , sample)
126+ }
127+ DecoderImpl :: None ( _, _) => return None ,
128+ } ;
129+ Some ( ( new_decoder, sample) )
130+ }
131+ }
132+
133+ impl < R > Iterator for LoopedDecoder < R >
134+ where
135+ R : Read + Seek ,
136+ {
137+ type Item = Sample ;
138+
139+ /// Returns the next sample in the audio stream.
140+ ///
141+ /// When the end of the stream is reached, attempts to seek back to the start and continue
142+ /// playing. For seekable sources with gapless playback, this uses fast seeking. For
143+ /// non-seekable sources or when gapless is disabled, recreates the decoder using cached
144+ /// metadata to avoid expensive file scanning.
145+ fn next ( & mut self ) -> Option < Self :: Item > {
146+ if let Some ( inner) = & mut self . inner {
147+ if let Some ( sample) = inner. next ( ) {
148+ return Some ( sample) ;
149+ }
150+
151+ // Cache duration from current decoder before resetting (first time only)
152+ if self . cached_duration . is_none ( ) {
153+ self . cached_duration = inner. total_duration ( ) ;
154+ }
155+
156+ // Try seeking first for seekable sources - this is fast and gapless
157+ // Only use fast seeking when gapless=true, otherwise recreate normally
158+ if self . settings . gapless
159+ && self . settings . is_seekable
160+ && inner. try_seek ( Duration :: ZERO ) . is_ok ( )
161+ {
162+ return inner. next ( ) ;
163+ }
164+
165+ // Fall back to recreation with cached metadata to avoid expensive scanning
166+ let decoder = self . inner . take ( ) ?;
167+ let ( new_decoder, sample) = self . recreate_decoder_with_cache ( decoder) ?;
168+ self . inner = Some ( new_decoder) ;
169+ sample
170+ } else {
171+ None
172+ }
173+ }
174+
175+ /// Returns the size hint for this iterator.
176+ ///
177+ /// The lower bound is:
178+ /// - The minimum number of samples remaining in the current iteration if there is an active
179+ /// decoder
180+ /// - 0 if there is no active decoder (inner is None)
181+ ///
182+ /// The upper bound is always `None` since the decoder loops indefinitely.
183+ ///
184+ /// Note that even with an active decoder, reaching the end of the stream may result in the
185+ /// decoder becoming inactive if seeking back to the start fails.
186+ #[ inline]
187+ fn size_hint ( & self ) -> ( usize , Option < usize > ) {
188+ (
189+ self . inner . as_ref ( ) . map_or ( 0 , |inner| inner. size_hint ( ) . 0 ) ,
190+ None ,
191+ )
192+ }
193+ }
194+
195+ impl < R > Source for LoopedDecoder < R >
196+ where
197+ R : Read + Seek ,
198+ {
199+ /// Returns the current span length of the underlying decoder.
200+ ///
201+ /// Returns `None` if there is no active decoder.
202+ #[ inline]
203+ fn current_span_len ( & self ) -> Option < usize > {
204+ self . inner . as_ref ( ) ?. current_span_len ( )
205+ }
206+
207+ /// Returns the number of channels in the audio stream.
208+ ///
209+ /// Returns the default channel count if there is no active decoder.
210+ #[ inline]
211+ fn channels ( & self ) -> ChannelCount {
212+ self . inner . as_ref ( ) . map_or ( nz ! ( 1 ) , |inner| inner. channels ( ) )
213+ }
214+
215+ /// Returns the sample rate of the audio stream.
216+ ///
217+ /// Returns the default sample rate if there is no active decoder.
218+ #[ inline]
219+ fn sample_rate ( & self ) -> SampleRate {
220+ self . inner
221+ . as_ref ( )
222+ . map_or ( nz ! ( 44100 ) , |inner| inner. sample_rate ( ) )
223+ }
224+
225+ /// Returns the total duration of this audio source.
226+ ///
227+ /// Always returns `None` for looped decoders since they have no fixed end point -
228+ /// they will continue playing indefinitely by seeking back to the start when reaching
229+ /// the end of the audio data.
230+ #[ inline]
231+ fn total_duration ( & self ) -> Option < Duration > {
232+ None
233+ }
234+
235+ /// Returns the bits per sample of the underlying decoder, if available.
236+ #[ inline]
237+ fn bits_per_sample ( & self ) -> Option < BitDepth > {
238+ self . inner . as_ref ( ) ?. bits_per_sample ( )
239+ }
240+
241+ /// Attempts to seek to a specific position in the audio stream.
242+ ///
243+ /// # Errors
244+ ///
245+ /// Returns `SeekError::NotSupported` if:
246+ /// - There is no active decoder
247+ /// - The underlying decoder does not support seeking
248+ ///
249+ /// May also return other `SeekError` variants if the underlying decoder's seek operation fails.
250+ ///
251+ /// # Note
252+ ///
253+ /// Even for looped playback, seeking past the end of the stream will not automatically
254+ /// wrap around to the beginning - it will return an error just like a normal decoder.
255+ /// Looping only occurs when reaching the end through normal playback.
256+ fn try_seek ( & mut self , pos : Duration ) -> Result < ( ) , SeekError > {
257+ match & mut self . inner {
258+ Some ( inner) => inner. try_seek ( pos) ,
259+ None => Err ( SeekError :: Other ( Arc :: new ( DecoderError :: IoError (
260+ "Looped source ended when it failed to loop back" . to_string ( ) ,
261+ ) ) ) ) ,
262+ }
263+ }
264+ }
0 commit comments