diff --git a/CHANGELOG.md b/CHANGELOG.md index 78e4c663..a5f5717a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Changed +- Adds a new method `try_seek` to all sources. It returns either an error or + seeks to the given position. A few sources are "unsupported" they return the + error `Unsupported`. - `Source` trait is now also implemented for `Box` and `&mut Source` - `fn new_vorbis` is now also available when the `symphonia-vorbis` feature is enabled diff --git a/Cargo.toml b/Cargo.toml index 2fd86547..dce36150 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ lewton = { version = "0.10", optional = true } minimp3_fixed = { version = "0.5.4", optional = true} symphonia = { version = "0.5.2", optional = true, default-features = false } crossbeam-channel = { version = "0.5.8", optional = true } +thiserror = "1.0.49" [features] default = ["flac", "vorbis", "wav", "mp3"] @@ -38,6 +39,8 @@ symphonia-wav = ["symphonia/wav", "symphonia/pcm", "symphonia/adpcm"] [dev-dependencies] quickcheck = "0.9.2" +rstest = "0.18.2" +rstest_reuse = "0.6.0" [[example]] name = "music_m4a" diff --git a/assets/RL.flac b/assets/RL.flac new file mode 100644 index 00000000..f95dceb3 Binary files /dev/null and b/assets/RL.flac differ diff --git a/assets/RL.m4a b/assets/RL.m4a new file mode 100644 index 00000000..1df9a800 Binary files /dev/null and b/assets/RL.m4a differ diff --git a/assets/RL.mp3 b/assets/RL.mp3 new file mode 100644 index 00000000..ffc8c185 Binary files /dev/null and b/assets/RL.mp3 differ diff --git a/assets/RL.wav b/assets/RL.wav new file mode 100644 index 00000000..7124123e Binary files /dev/null and b/assets/RL.wav differ diff --git a/examples/seek_mp3.rs b/examples/seek_mp3.rs new file mode 100644 index 00000000..a384d047 --- /dev/null +++ b/examples/seek_mp3.rs @@ -0,0 +1,18 @@ +use std::io::BufReader; +use std::time::Duration; + +fn main() { + let (_stream, handle) = rodio::OutputStream::try_default().unwrap(); + let sink = rodio::Sink::try_new(&handle).unwrap(); + + let file = std::fs::File::open("assets/music.mp3").unwrap(); + sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap()); + + std::thread::sleep(std::time::Duration::from_secs(2)); + sink.try_seek(Duration::from_secs(0)).unwrap(); + + std::thread::sleep(std::time::Duration::from_secs(2)); + sink.try_seek(Duration::from_secs(4)).unwrap(); + + sink.sleep_until_end(); +} diff --git a/src/buffer.rs b/src/buffer.rs index 916de48b..46d24f06 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -11,13 +11,14 @@ //! use std::time::Duration; -use std::vec::IntoIter as VecIntoIter; +use crate::source::SeekError; use crate::{Sample, Source}; /// A buffer of samples treated as a source. pub struct SamplesBuffer { - data: VecIntoIter, + data: Vec, + pos: usize, channels: u16, sample_rate: u32, duration: Duration, @@ -53,7 +54,8 @@ where ); SamplesBuffer { - data: data.into_iter(), + data, + pos: 0, channels, sample_rate, duration, @@ -84,6 +86,27 @@ where fn total_duration(&self) -> Option { Some(self.duration) } + + // this is fast because all the samples are in memory already + // and due to the constant sample_rate we can jump to the right + // sample directly + // + /// This jumps in memory till the sample for `pos`. + #[inline] + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { + let curr_channel = self.pos % self.channels() as usize; + let new_pos = pos.as_secs_f32() * self.sample_rate() as f32 * self.channels() as f32; + // saturate pos at the end of the source + let new_pos = new_pos as usize; + let new_pos = new_pos.min(self.data.len()); + + // make sure the next sample is for the right channel + let new_pos = new_pos.next_multiple_of(self.channels() as usize); + let new_pos = new_pos - curr_channel; + + self.pos = new_pos; + Ok(()) + } } impl Iterator for SamplesBuffer @@ -94,12 +117,14 @@ where #[inline] fn next(&mut self) -> Option { - self.data.next() + let sample = self.data.get(self.pos)?; + self.pos += 1; + Some(*sample) } #[inline] fn size_hint(&self) -> (usize, Option) { - self.data.size_hint() + (self.data.len(), Some(self.data.len())) } } @@ -144,4 +169,32 @@ mod tests { assert_eq!(buf.next(), Some(6)); assert_eq!(buf.next(), None); } + + #[cfg(test)] + mod try_seek { + use super::*; + use std::time::Duration; + + #[test] + fn channel_order_stays_correct() { + const SAMPLE_RATE: u32 = 100; + const CHANNELS: u16 = 2; + let mut buf = SamplesBuffer::new( + CHANNELS, + SAMPLE_RATE, + (0..2000i16).into_iter().collect::>(), + ); + buf.try_seek(Duration::from_secs(5)).unwrap(); + assert_eq!( + buf.next(), + Some(5i16 * SAMPLE_RATE as i16 * CHANNELS as i16) + ); + + assert!(buf.next().is_some_and(|s| s % 2 == 1)); + assert!(buf.next().is_some_and(|s| s % 2 == 0)); + + buf.try_seek(Duration::from_secs(6)).unwrap(); + assert!(buf.next().is_some_and(|s| s % 2 == 1),); + } + } } diff --git a/src/conversions/channels.rs b/src/conversions/channels.rs index 6b472e98..ddc12868 100644 --- a/src/conversions/channels.rs +++ b/src/conversions/channels.rs @@ -46,6 +46,12 @@ where pub fn into_inner(self) -> I { self.input } + + /// Get mutable access to the iterator + #[inline] + pub fn inner_mut(&mut self) -> &mut I { + &mut self.input + } } impl Iterator for ChannelCountConverter diff --git a/src/conversions/sample.rs b/src/conversions/sample.rs index ad11d12d..7ee97624 100644 --- a/src/conversions/sample.rs +++ b/src/conversions/sample.rs @@ -23,6 +23,12 @@ impl DataConverter { pub fn into_inner(self) -> I { self.input } + + /// get mutable access to the iterator + #[inline] + pub fn inner_mut(&mut self) -> &mut I { + &mut self.input + } } impl Iterator for DataConverter diff --git a/src/conversions/sample_rate.rs b/src/conversions/sample_rate.rs index 21e11e57..36aa5a5a 100644 --- a/src/conversions/sample_rate.rs +++ b/src/conversions/sample_rate.rs @@ -102,6 +102,12 @@ where self.input } + /// get mutable access to the iterator + #[inline] + pub fn inner_mut(&mut self) -> &mut I { + &mut self.input + } + fn next_input_frame(&mut self) { self.current_frame_pos_in_chunk += 1; diff --git a/src/decoder/flac.rs b/src/decoder/flac.rs index 3d789f4b..9dbc0c43 100644 --- a/src/decoder/flac.rs +++ b/src/decoder/flac.rs @@ -3,6 +3,7 @@ use std::io::{Read, Seek, SeekFrom}; use std::mem; use std::time::Duration; +use crate::source::SeekError; use crate::Source; use claxon::FlacReader; @@ -79,6 +80,13 @@ where self.samples .map(|s| Duration::from_micros(s * 1_000_000 / self.sample_rate as u64)) } + + #[inline] + fn try_seek(&mut self, _: Duration) -> Result<(), SeekError> { + Err(SeekError::NotSupported { + underlying_source: std::any::type_name::(), + }) + } } impl Iterator for FlacDecoder diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index f887ce78..56230d08 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -8,6 +8,7 @@ use std::mem; use std::str::FromStr; use std::time::Duration; +use crate::source::SeekError; use crate::Source; #[cfg(feature = "symphonia")] @@ -22,7 +23,7 @@ mod mp3; #[cfg(feature = "symphonia")] mod read_seek_source; #[cfg(feature = "symphonia")] -mod symphonia; +pub mod symphonia; #[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))] mod vorbis; #[cfg(all(feature = "wav", not(feature = "symphonia-wav")))] @@ -59,6 +60,129 @@ where None(::std::marker::PhantomData), } +impl DecoderImpl { + #[inline] + fn next(&mut self) -> Option { + match self { + #[cfg(all(feature = "wav", not(feature = "symphonia-wav")))] + DecoderImpl::Wav(source) => source.next(), + #[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))] + DecoderImpl::Vorbis(source) => source.next(), + #[cfg(all(feature = "flac", not(feature = "symphonia-flac")))] + DecoderImpl::Flac(source) => source.next(), + #[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))] + DecoderImpl::Mp3(source) => source.next(), + #[cfg(feature = "symphonia")] + DecoderImpl::Symphonia(source) => source.next(), + DecoderImpl::None(_) => None, + } + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + match self { + #[cfg(all(feature = "wav", not(feature = "symphonia-wav")))] + DecoderImpl::Wav(source) => source.size_hint(), + #[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))] + DecoderImpl::Vorbis(source) => source.size_hint(), + #[cfg(all(feature = "flac", not(feature = "symphonia-flac")))] + DecoderImpl::Flac(source) => source.size_hint(), + #[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))] + DecoderImpl::Mp3(source) => source.size_hint(), + #[cfg(feature = "symphonia")] + DecoderImpl::Symphonia(source) => source.size_hint(), + DecoderImpl::None(_) => (0, None), + } + } + + #[inline] + fn current_frame_len(&self) -> Option { + match self { + #[cfg(all(feature = "wav", not(feature = "symphonia-wav")))] + DecoderImpl::Wav(source) => source.current_frame_len(), + #[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))] + DecoderImpl::Vorbis(source) => source.current_frame_len(), + #[cfg(all(feature = "flac", not(feature = "symphonia-flac")))] + DecoderImpl::Flac(source) => source.current_frame_len(), + #[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))] + DecoderImpl::Mp3(source) => source.current_frame_len(), + #[cfg(feature = "symphonia")] + DecoderImpl::Symphonia(source) => source.current_frame_len(), + DecoderImpl::None(_) => Some(0), + } + } + + #[inline] + fn channels(&self) -> u16 { + match self { + #[cfg(all(feature = "wav", not(feature = "symphonia-wav")))] + DecoderImpl::Wav(source) => source.channels(), + #[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))] + DecoderImpl::Vorbis(source) => source.channels(), + #[cfg(all(feature = "flac", not(feature = "symphonia-flac")))] + DecoderImpl::Flac(source) => source.channels(), + #[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))] + DecoderImpl::Mp3(source) => source.channels(), + #[cfg(feature = "symphonia")] + DecoderImpl::Symphonia(source) => source.channels(), + DecoderImpl::None(_) => 0, + } + } + + #[inline] + fn sample_rate(&self) -> u32 { + match self { + #[cfg(all(feature = "wav", not(feature = "symphonia-wav")))] + DecoderImpl::Wav(source) => source.sample_rate(), + #[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))] + DecoderImpl::Vorbis(source) => source.sample_rate(), + #[cfg(all(feature = "flac", not(feature = "symphonia-flac")))] + DecoderImpl::Flac(source) => source.sample_rate(), + #[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))] + DecoderImpl::Mp3(source) => source.sample_rate(), + #[cfg(feature = "symphonia")] + DecoderImpl::Symphonia(source) => source.sample_rate(), + DecoderImpl::None(_) => 1, + } + } + + #[inline] + fn total_duration(&self) -> Option { + match self { + #[cfg(all(feature = "wav", not(feature = "symphonia-wav")))] + DecoderImpl::Wav(source) => source.total_duration(), + #[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))] + DecoderImpl::Vorbis(source) => source.total_duration(), + #[cfg(all(feature = "flac", not(feature = "symphonia-flac")))] + DecoderImpl::Flac(source) => source.total_duration(), + #[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))] + DecoderImpl::Mp3(source) => source.total_duration(), + #[cfg(feature = "symphonia")] + DecoderImpl::Symphonia(source) => source.total_duration(), + DecoderImpl::None(_) => Some(Duration::default()), + } + } + + #[inline] + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { + match self { + #[cfg(all(feature = "wav", not(feature = "symphonia-wav")))] + DecoderImpl::Wav(source) => source.try_seek(pos), + #[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))] + DecoderImpl::Vorbis(source) => source.try_seek(pos), + #[cfg(all(feature = "flac", not(feature = "symphonia-flac")))] + DecoderImpl::Flac(source) => source.try_seek(pos), + #[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))] + DecoderImpl::Mp3(source) => source.try_seek(pos), + #[cfg(feature = "symphonia")] + DecoderImpl::Symphonia(source) => source.try_seek(pos), + DecoderImpl::None(_) => Err(SeekError::NotSupported { + underlying_source: "DecoderImpl::None", + }), + } + } +} + impl Decoder where R: Read + Seek + Send + Sync + 'static, @@ -265,36 +389,12 @@ where #[inline] fn next(&mut self) -> Option { - match &mut self.0 { - #[cfg(all(feature = "wav", not(feature = "symphonia-wav")))] - DecoderImpl::Wav(source) => source.next(), - #[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))] - DecoderImpl::Vorbis(source) => source.next(), - #[cfg(all(feature = "flac", not(feature = "symphonia-flac")))] - DecoderImpl::Flac(source) => source.next(), - #[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))] - DecoderImpl::Mp3(source) => source.next(), - #[cfg(feature = "symphonia")] - DecoderImpl::Symphonia(source) => source.next(), - DecoderImpl::None(_) => None, - } + self.0.next() } #[inline] fn size_hint(&self) -> (usize, Option) { - match &self.0 { - #[cfg(all(feature = "wav", not(feature = "symphonia-wav")))] - DecoderImpl::Wav(source) => source.size_hint(), - #[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))] - DecoderImpl::Vorbis(source) => source.size_hint(), - #[cfg(all(feature = "flac", not(feature = "symphonia-flac")))] - DecoderImpl::Flac(source) => source.size_hint(), - #[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))] - DecoderImpl::Mp3(source) => source.size_hint(), - #[cfg(feature = "symphonia")] - DecoderImpl::Symphonia(source) => source.size_hint(), - DecoderImpl::None(_) => (0, None), - } + self.0.size_hint() } } @@ -304,70 +404,26 @@ where { #[inline] fn current_frame_len(&self) -> Option { - match &self.0 { - #[cfg(all(feature = "wav", not(feature = "symphonia-wav")))] - DecoderImpl::Wav(source) => source.current_frame_len(), - #[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))] - DecoderImpl::Vorbis(source) => source.current_frame_len(), - #[cfg(all(feature = "flac", not(feature = "symphonia-flac")))] - DecoderImpl::Flac(source) => source.current_frame_len(), - #[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))] - DecoderImpl::Mp3(source) => source.current_frame_len(), - #[cfg(feature = "symphonia")] - DecoderImpl::Symphonia(source) => source.current_frame_len(), - DecoderImpl::None(_) => Some(0), - } + self.0.current_frame_len() } #[inline] fn channels(&self) -> u16 { - match &self.0 { - #[cfg(all(feature = "wav", not(feature = "symphonia-wav")))] - DecoderImpl::Wav(source) => source.channels(), - #[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))] - DecoderImpl::Vorbis(source) => source.channels(), - #[cfg(all(feature = "flac", not(feature = "symphonia-flac")))] - DecoderImpl::Flac(source) => source.channels(), - #[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))] - DecoderImpl::Mp3(source) => source.channels(), - #[cfg(feature = "symphonia")] - DecoderImpl::Symphonia(source) => source.channels(), - DecoderImpl::None(_) => 0, - } + self.0.channels() } - #[inline] fn sample_rate(&self) -> u32 { - match &self.0 { - #[cfg(all(feature = "wav", not(feature = "symphonia-wav")))] - DecoderImpl::Wav(source) => source.sample_rate(), - #[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))] - DecoderImpl::Vorbis(source) => source.sample_rate(), - #[cfg(all(feature = "flac", not(feature = "symphonia-flac")))] - DecoderImpl::Flac(source) => source.sample_rate(), - #[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))] - DecoderImpl::Mp3(source) => source.sample_rate(), - #[cfg(feature = "symphonia")] - DecoderImpl::Symphonia(source) => source.sample_rate(), - DecoderImpl::None(_) => 1, - } + self.0.sample_rate() } #[inline] fn total_duration(&self) -> Option { - match &self.0 { - #[cfg(all(feature = "wav", not(feature = "symphonia-wav")))] - DecoderImpl::Wav(source) => source.total_duration(), - #[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))] - DecoderImpl::Vorbis(source) => source.total_duration(), - #[cfg(all(feature = "flac", not(feature = "symphonia-flac")))] - DecoderImpl::Flac(source) => source.total_duration(), - #[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))] - DecoderImpl::Mp3(source) => source.total_duration(), - #[cfg(feature = "symphonia")] - DecoderImpl::Symphonia(source) => source.total_duration(), - DecoderImpl::None(_) => Some(Duration::default()), - } + self.0.total_duration() + } + + #[inline] + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { + self.0.try_seek(pos) } } @@ -379,19 +435,7 @@ where #[inline] fn next(&mut self) -> Option { - if let Some(sample) = match &mut self.0 { - #[cfg(all(feature = "wav", not(feature = "symphonia-wav")))] - DecoderImpl::Wav(source) => source.next(), - #[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))] - DecoderImpl::Vorbis(source) => source.next(), - #[cfg(all(feature = "flac", not(feature = "symphonia-flac")))] - DecoderImpl::Flac(source) => source.next(), - #[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))] - DecoderImpl::Mp3(source) => source.next(), - #[cfg(feature = "symphonia")] - DecoderImpl::Symphonia(source) => source.next(), - DecoderImpl::None(_) => None, - } { + if let Some(sample) = self.0.next() { Some(sample) } else { let decoder = mem::replace(&mut self.0, DecoderImpl::None(Default::default())); @@ -448,19 +492,7 @@ where #[inline] fn size_hint(&self) -> (usize, Option) { - match &self.0 { - #[cfg(all(feature = "wav", not(feature = "symphonia-wav")))] - DecoderImpl::Wav(source) => (source.size_hint().0, None), - #[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))] - DecoderImpl::Vorbis(source) => (source.size_hint().0, None), - #[cfg(all(feature = "flac", not(feature = "symphonia-flac")))] - DecoderImpl::Flac(source) => (source.size_hint().0, None), - #[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))] - DecoderImpl::Mp3(source) => (source.size_hint().0, None), - #[cfg(feature = "symphonia")] - DecoderImpl::Symphonia(source) => (source.size_hint().0, None), - DecoderImpl::None(_) => (0, None), - } + self.0.size_hint() } } @@ -470,59 +502,27 @@ where { #[inline] fn current_frame_len(&self) -> Option { - match &self.0 { - #[cfg(all(feature = "wav", not(feature = "symphonia-wav")))] - DecoderImpl::Wav(source) => source.current_frame_len(), - #[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))] - DecoderImpl::Vorbis(source) => source.current_frame_len(), - #[cfg(all(feature = "flac", not(feature = "symphonia-flac")))] - DecoderImpl::Flac(source) => source.current_frame_len(), - #[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))] - DecoderImpl::Mp3(source) => source.current_frame_len(), - #[cfg(feature = "symphonia")] - DecoderImpl::Symphonia(source) => source.current_frame_len(), - DecoderImpl::None(_) => Some(0), - } + self.0.current_frame_len() } #[inline] fn channels(&self) -> u16 { - match &self.0 { - #[cfg(all(feature = "wav", not(feature = "symphonia-wav")))] - DecoderImpl::Wav(source) => source.channels(), - #[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))] - DecoderImpl::Vorbis(source) => source.channels(), - #[cfg(all(feature = "flac", not(feature = "symphonia-flac")))] - DecoderImpl::Flac(source) => source.channels(), - #[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))] - DecoderImpl::Mp3(source) => source.channels(), - #[cfg(feature = "symphonia")] - DecoderImpl::Symphonia(source) => source.channels(), - DecoderImpl::None(_) => 0, - } + self.0.channels() } #[inline] fn sample_rate(&self) -> u32 { - match &self.0 { - #[cfg(all(feature = "wav", not(feature = "symphonia-wav")))] - DecoderImpl::Wav(source) => source.sample_rate(), - #[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))] - DecoderImpl::Vorbis(source) => source.sample_rate(), - #[cfg(all(feature = "flac", not(feature = "symphonia-flac")))] - DecoderImpl::Flac(source) => source.sample_rate(), - #[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))] - DecoderImpl::Mp3(source) => source.sample_rate(), - #[cfg(feature = "symphonia")] - DecoderImpl::Symphonia(source) => source.sample_rate(), - DecoderImpl::None(_) => 1, - } + self.0.sample_rate() } #[inline] fn total_duration(&self) -> Option { None } + + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { + self.0.try_seek(pos) + } } /// Error that can happen when creating a decoder. diff --git a/src/decoder/mp3.rs b/src/decoder/mp3.rs index b40118cc..de53f61a 100644 --- a/src/decoder/mp3.rs +++ b/src/decoder/mp3.rs @@ -1,14 +1,18 @@ use std::io::{Read, Seek, SeekFrom}; use std::time::Duration; +use crate::source::SeekError; use crate::Source; -use minimp3_fixed::{Decoder, Frame}; +use minimp3::Decoder; +use minimp3::Frame; +use minimp3_fixed as minimp3; pub struct Mp3Decoder where R: Read + Seek, { + // decoder: SeekDecoder, decoder: Decoder, current_frame: Frame, current_frame_offset: usize, @@ -22,8 +26,16 @@ where if !is_mp3(data.by_ref()) { return Err(data); } + // let mut decoder = SeekDecoder::new(data) let mut decoder = Decoder::new(data); - let current_frame = decoder.next_frame().unwrap(); + // parameters are correct and minimp3 is used correctly + // thus if we crash here one of these invariants is broken: + // .expect("should be able to allocate memory, perform IO"); + // let current_frame = decoder.decode_frame() + let current_frame = decoder.next_frame() + // the reader makes enough data available therefore + // if we crash here the invariant broken is: + .expect("data should not corrupt"); Ok(Mp3Decoder { decoder, @@ -59,6 +71,20 @@ where fn total_duration(&self) -> Option { None } + + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { + // TODO waiting for PR in minimp3_fixed or minimp3 + + // let pos = (pos.as_secs_f32() * self.sample_rate() as f32) as u64; + // // do not trigger a sample_rate, channels and frame len update + // // as the seek only takes effect after the current frame is done + // self.decoder.seek_samples(pos)?; + // Ok(()) + + Err(SeekError::NotSupported { + underlying_source: std::any::type_name::(), + }) + } } impl Iterator for Mp3Decoder @@ -67,14 +93,15 @@ where { type Item = i16; - #[inline] fn next(&mut self) -> Option { - if self.current_frame_offset == self.current_frame.data.len() { - match self.decoder.next_frame() { - Ok(frame) => self.current_frame = frame, - _ => return None, + if self.current_frame_offset == self.current_frame_len().unwrap() { + if let Ok(frame) = self.decoder.next_frame() { + // if let Ok(frame) = self.decoder.decode_frame() { + self.current_frame = frame; + self.current_frame_offset = 0; + } else { + return None; } - self.current_frame_offset = 0; } let v = self.current_frame.data[self.current_frame_offset]; diff --git a/src/decoder/symphonia.rs b/src/decoder/symphonia.rs index dde0f819..b3e5b215 100644 --- a/src/decoder/symphonia.rs +++ b/src/decoder/symphonia.rs @@ -4,34 +4,38 @@ use symphonia::{ audio::{AudioBufferRef, SampleBuffer, SignalSpec}, codecs::{Decoder, DecoderOptions}, errors::Error, - formats::{FormatOptions, FormatReader}, + formats::{FormatOptions, FormatReader, SeekedTo}, io::MediaSourceStream, meta::MetadataOptions, probe::Hint, - units, + units::{self, Time}, }, default::get_probe, }; -use crate::Source; +use crate::{source, Source}; use super::DecoderError; // Decoder errors are not considered fatal. // The correct action is to just get a new packet and try again. // But a decode error in more than 3 consecutive packets is fatal. -const MAX_DECODE_ERRORS: usize = 3; +const MAX_DECODE_RETRIES: usize = 3; -pub struct SymphoniaDecoder { +pub(crate) struct SymphoniaDecoder { decoder: Box, current_frame_offset: usize, format: Box, + total_duration: Option