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
+ /// Decoder that loops indefinitely by seeking back to the start when reaching the end.
29
+ ///
30
+ /// Uses fast seeking for seekable sources with gapless playback, otherwise recreates the
31
+ /// decoder while caching metadata to avoid expensive file scanning.
32
+ pub struct LoopedDecoder < R : Read + Seek > {
33
+ pub ( super ) inner : Option < DecoderImpl < R > > ,
34
+ pub ( super ) settings : Settings ,
35
+ cached_duration : Option < Duration > ,
36
+ }
37
+
38
+ impl < R > LoopedDecoder < R >
39
+ where
40
+ R : Read + Seek ,
41
+ {
42
+ pub ( super ) fn new ( decoder : DecoderImpl < R > , settings : Settings ) -> Self {
43
+ Self {
44
+ inner : Some ( decoder) ,
45
+ settings,
46
+ cached_duration : None ,
47
+ }
48
+ }
49
+
50
+ /// Recreates decoder with cached metadata to avoid expensive file scanning.
51
+ fn recreate_decoder_with_cache (
52
+ & mut self ,
53
+ decoder : DecoderImpl < R > ,
54
+ ) -> Option < ( DecoderImpl < R > , Option < Sample > ) > {
55
+ let mut fast_settings = self . settings . clone ( ) ;
56
+ fast_settings. total_duration = self . cached_duration ;
57
+
58
+ let ( new_decoder, sample) = match decoder {
59
+ #[ cfg( feature = "hound" ) ]
60
+ DecoderImpl :: Wav ( source) => {
61
+ let mut reader = source. into_inner ( ) ;
62
+ reader. rewind ( ) . ok ( ) ?;
63
+ let mut source = wav:: WavDecoder :: new_with_settings ( reader, & fast_settings) . ok ( ) ?;
64
+ let sample = source. next ( ) ;
65
+ ( DecoderImpl :: Wav ( source) , sample)
66
+ }
67
+ #[ cfg( feature = "lewton" ) ]
68
+ DecoderImpl :: Vorbis ( source) => {
69
+ let mut reader = source. into_inner ( ) . into_inner ( ) . into_inner ( ) ;
70
+ reader. rewind ( ) . ok ( ) ?;
71
+ let mut source =
72
+ vorbis:: VorbisDecoder :: new_with_settings ( reader, & fast_settings) . ok ( ) ?;
73
+ let sample = source. next ( ) ;
74
+ ( DecoderImpl :: Vorbis ( source) , sample)
75
+ }
76
+ #[ cfg( feature = "claxon" ) ]
77
+ DecoderImpl :: Flac ( source) => {
78
+ let mut reader = source. into_inner ( ) ;
79
+ reader. rewind ( ) . ok ( ) ?;
80
+ let mut source =
81
+ flac:: FlacDecoder :: new_with_settings ( reader, & fast_settings) . ok ( ) ?;
82
+ let sample = source. next ( ) ;
83
+ ( DecoderImpl :: Flac ( source) , sample)
84
+ }
85
+ #[ cfg( feature = "minimp3" ) ]
86
+ DecoderImpl :: Mp3 ( source) => {
87
+ let mut reader = source. into_inner ( ) ;
88
+ reader. rewind ( ) . ok ( ) ?;
89
+ let mut source = mp3:: Mp3Decoder :: new_with_settings ( reader, & fast_settings) . ok ( ) ?;
90
+ let sample = source. next ( ) ;
91
+ ( DecoderImpl :: Mp3 ( source) , sample)
92
+ }
93
+ #[ cfg( feature = "symphonia" ) ]
94
+ DecoderImpl :: Symphonia ( source, PhantomData ) => {
95
+ let mut reader = source. into_inner ( ) ;
96
+ reader. rewind ( ) . ok ( ) ?;
97
+ let mut source =
98
+ symphonia:: SymphoniaDecoder :: new_with_settings ( reader, & fast_settings) . ok ( ) ?;
99
+ let sample = source. next ( ) ;
100
+ ( DecoderImpl :: Symphonia ( source, PhantomData ) , sample)
101
+ }
102
+ DecoderImpl :: None ( _, _) => return None ,
103
+ } ;
104
+ Some ( ( new_decoder, sample) )
105
+ }
106
+ }
107
+
108
+ impl < R > Iterator for LoopedDecoder < R >
109
+ where
110
+ R : Read + Seek ,
111
+ {
112
+ type Item = Sample ;
113
+
114
+ fn next ( & mut self ) -> Option < Self :: Item > {
115
+ if let Some ( inner) = & mut self . inner {
116
+ if let Some ( sample) = inner. next ( ) {
117
+ return Some ( sample) ;
118
+ }
119
+
120
+ // Cache duration on first loop to avoid recalculation
121
+ if self . cached_duration . is_none ( ) {
122
+ self . cached_duration = inner. total_duration ( ) ;
123
+ }
124
+
125
+ // Fast gapless seeking when available
126
+ if self . settings . gapless
127
+ && self . settings . is_seekable
128
+ && inner. try_seek ( Duration :: ZERO ) . is_ok ( )
129
+ {
130
+ return inner. next ( ) ;
131
+ }
132
+
133
+ // Recreation fallback with cached metadata
134
+ let decoder = self . inner . take ( ) ?;
135
+ let ( new_decoder, sample) = self . recreate_decoder_with_cache ( decoder) ?;
136
+ self . inner = Some ( new_decoder) ;
137
+ sample
138
+ } else {
139
+ None
140
+ }
141
+ }
142
+
143
+ fn size_hint ( & self ) -> ( usize , Option < usize > ) {
144
+ (
145
+ self . inner . as_ref ( ) . map_or ( 0 , |inner| inner. size_hint ( ) . 0 ) ,
146
+ None , // Infinite
147
+ )
148
+ }
149
+ }
150
+
151
+ impl < R > Source for LoopedDecoder < R >
152
+ where
153
+ R : Read + Seek ,
154
+ {
155
+ fn current_span_len ( & self ) -> Option < usize > {
156
+ self . inner . as_ref ( ) ?. current_span_len ( )
157
+ }
158
+
159
+ fn channels ( & self ) -> ChannelCount {
160
+ self . inner . as_ref ( ) . map_or ( nz ! ( 1 ) , |inner| inner. channels ( ) )
161
+ }
162
+
163
+ fn sample_rate ( & self ) -> SampleRate {
164
+ self . inner
165
+ . as_ref ( )
166
+ . map_or ( nz ! ( 44100 ) , |inner| inner. sample_rate ( ) )
167
+ }
168
+
169
+ /// Always returns `None` since looped decoders have no fixed end.
170
+ fn total_duration ( & self ) -> Option < Duration > {
171
+ None
172
+ }
173
+
174
+ fn bits_per_sample ( & self ) -> Option < BitDepth > {
175
+ self . inner . as_ref ( ) ?. bits_per_sample ( )
176
+ }
177
+
178
+ fn try_seek ( & mut self , pos : Duration ) -> Result < ( ) , SeekError > {
179
+ match & mut self . inner {
180
+ Some ( inner) => inner. try_seek ( pos) ,
181
+ None => Err ( SeekError :: Other ( Arc :: new ( DecoderError :: IoError (
182
+ "Looped source ended when it failed to loop back" . to_string ( ) ,
183
+ ) ) ) ) ,
184
+ }
185
+ }
186
+ }
0 commit comments