1
1
use std:: { io, time:: Duration } ;
2
2
3
3
use symphonia:: {
4
+ core:: meta:: { Metadata , MetadataOptions } ,
5
+ core:: probe:: { Hint , ProbedMetadata } ,
4
6
core:: {
5
7
audio:: SampleBuffer ,
6
8
codecs:: { Decoder , DecoderOptions } ,
@@ -27,6 +29,18 @@ pub struct SymphoniaDecoder {
27
29
format : Box < dyn FormatReader > ,
28
30
decoder : Box < dyn Decoder > ,
29
31
sample_buffer : Option < SampleBuffer < f64 > > ,
32
+ probed_metadata : Option < ProbedMetadata > ,
33
+ }
34
+
35
+ #[ derive( Default ) ]
36
+ pub ( crate ) struct LocalFileMetadata {
37
+ pub name : String ,
38
+ pub language : String ,
39
+ pub album : String ,
40
+ pub artists : String ,
41
+ pub album_artists : String ,
42
+ pub number : u32 ,
43
+ pub disc_number : u32 ,
30
44
}
31
45
32
46
impl SymphoniaDecoder {
@@ -94,20 +108,62 @@ impl SymphoniaDecoder {
94
108
// We set the sample buffer when decoding the first full packet,
95
109
// whose duration is also the ideal sample buffer size.
96
110
sample_buffer : None ,
111
+
112
+ probed_metadata : None ,
97
113
} )
98
114
}
99
115
100
- pub fn normalisation_data ( & mut self ) -> Option < NormalisationData > {
101
- let mut metadata = self . format . metadata ( ) ;
116
+ pub ( crate ) fn new_with_probe < R > ( src : R , extension : Option < & str > ) -> DecoderResult < Self >
117
+ where
118
+ R : MediaSource + ' static ,
119
+ {
120
+ let mss = MediaSourceStream :: new ( Box :: new ( src) , Default :: default ( ) ) ;
102
121
103
- // Advance to the latest metadata revision.
104
- // None means we hit the latest.
105
- loop {
106
- if metadata. pop ( ) . is_none ( ) {
107
- break ;
108
- }
122
+ let mut hint = Hint :: new ( ) ;
123
+
124
+ if let Some ( extension) = extension {
125
+ hint. with_extension ( extension) ;
126
+ }
127
+
128
+ let format_opts: FormatOptions = Default :: default ( ) ;
129
+ let metadata_opts: MetadataOptions = Default :: default ( ) ;
130
+ let decoder_opts: DecoderOptions = Default :: default ( ) ;
131
+
132
+ let probed =
133
+ symphonia:: default:: get_probe ( ) . format ( & hint, mss, & format_opts, & metadata_opts) ?;
134
+
135
+ let format = probed. format ;
136
+
137
+ let track = format. default_track ( ) . ok_or_else ( || {
138
+ DecoderError :: SymphoniaDecoder ( "Could not retrieve default track" . into ( ) )
139
+ } ) ?;
140
+
141
+ let decoder = symphonia:: default:: get_codecs ( ) . make ( & track. codec_params , & decoder_opts) ?;
142
+
143
+ let rate = decoder. codec_params ( ) . sample_rate . ok_or_else ( || {
144
+ DecoderError :: SymphoniaDecoder ( "Could not retrieve sample rate" . into ( ) )
145
+ } ) ?;
146
+
147
+ // TODO: The official client supports local files with sample rates other than 44,100 kHz.
148
+ // To play these accurately, we need to either resample the input audio, or introduce a way
149
+ // to change the player's current sample rate (likely by closing and re-opening the sink
150
+ // with new parameters).
151
+ if rate != SAMPLE_RATE {
152
+ return Err ( DecoderError :: SymphoniaDecoder ( format ! (
153
+ "Unsupported sample rate: {rate}. Local files must have a sample rate of {SAMPLE_RATE} Hz."
154
+ ) ) ) ;
109
155
}
110
156
157
+ Ok ( Self {
158
+ format,
159
+ decoder,
160
+ sample_buffer : None ,
161
+ probed_metadata : Some ( probed. metadata ) ,
162
+ } )
163
+ }
164
+
165
+ pub fn normalisation_data ( & mut self ) -> Option < NormalisationData > {
166
+ let metadata = self . metadata ( ) ?;
111
167
let tags = metadata. current ( ) ?. tags ( ) ;
112
168
113
169
if tags. is_empty ( ) {
@@ -131,6 +187,70 @@ impl SymphoniaDecoder {
131
187
}
132
188
}
133
189
190
+ pub ( crate ) fn local_file_metadata ( & mut self ) -> Option < LocalFileMetadata > {
191
+ let metadata = self . metadata ( ) ?;
192
+ let tags = metadata. current ( ) ?. tags ( ) ;
193
+ let mut metadata = LocalFileMetadata :: default ( ) ;
194
+
195
+ for tag in tags {
196
+ if let Value :: String ( value) = & tag. value {
197
+ match tag. std_key {
198
+ // We could possibly use mem::take here to avoid cloning, but that risks leaving
199
+ // the audio item metadata in a bad state.
200
+ Some ( StandardTagKey :: TrackTitle ) => metadata. name = value. clone ( ) ,
201
+ Some ( StandardTagKey :: Language ) => metadata. language = value. clone ( ) ,
202
+ Some ( StandardTagKey :: Artist ) => metadata. artists = value. clone ( ) ,
203
+ Some ( StandardTagKey :: AlbumArtist ) => metadata. album_artists = value. clone ( ) ,
204
+ Some ( StandardTagKey :: Album ) => metadata. album = value. clone ( ) ,
205
+ Some ( StandardTagKey :: TrackNumber ) => {
206
+ metadata. number = value. parse :: < u32 > ( ) . unwrap_or_default ( )
207
+ }
208
+ Some ( StandardTagKey :: DiscNumber ) => {
209
+ metadata. disc_number = value. parse :: < u32 > ( ) . unwrap_or_default ( )
210
+ }
211
+ _ => ( ) ,
212
+ }
213
+ } else if let Value :: UnsignedInt ( value) = & tag. value {
214
+ match tag. std_key {
215
+ Some ( StandardTagKey :: TrackNumber ) => metadata. number = * value as u32 ,
216
+ Some ( StandardTagKey :: DiscNumber ) => metadata. disc_number = * value as u32 ,
217
+ _ => ( ) ,
218
+ }
219
+ } else if let Value :: SignedInt ( value) = & tag. value {
220
+ match tag. std_key {
221
+ Some ( StandardTagKey :: TrackNumber ) => metadata. number = * value as u32 ,
222
+ Some ( StandardTagKey :: DiscNumber ) => metadata. disc_number = * value as u32 ,
223
+ _ => ( ) ,
224
+ }
225
+ }
226
+ }
227
+
228
+ Some ( metadata)
229
+ }
230
+
231
+ fn metadata ( & mut self ) -> Option < Metadata > {
232
+ let mut metadata = self . format . metadata ( ) ;
233
+
234
+ // If we can't get metadata from the container, fall back to other tags found by probing.
235
+ // Note that this is only relevant for local files.
236
+ if metadata. current ( ) . is_none ( )
237
+ && let Some ( ref mut probe_metadata) = self . probed_metadata
238
+ && let Some ( inner_probe_metadata) = probe_metadata. get ( )
239
+ {
240
+ metadata = inner_probe_metadata;
241
+ }
242
+
243
+ // Advance to the latest metadata revision.
244
+ // None means we hit the latest.
245
+ loop {
246
+ if metadata. pop ( ) . is_none ( ) {
247
+ break ;
248
+ }
249
+ }
250
+
251
+ Some ( metadata)
252
+ }
253
+
134
254
#[ inline]
135
255
fn ts_to_ms ( & self , ts : u64 ) -> u32 {
136
256
match self . decoder . codec_params ( ) . time_base {
0 commit comments