From fafe4ba1afdd8d130354b5cbf3dc8535663c3563 Mon Sep 17 00:00:00 2001 From: dskleingeld <11743287+dskleingeld@users.noreply.github.com> Date: Sun, 3 Jan 2021 00:40:54 +0100 Subject: [PATCH 01/62] seek implemented through SourceExt trait --- Cargo.toml | 3 ++- examples/seek_mp3.rs | 14 +++++++++++ src/decoder/mod.rs | 40 +++++++++++++++++++++++++++++ src/decoder/mp3.rs | 15 +++++++++-- src/lib.rs | 2 +- src/sink.rs | 56 ++++++++++++++++++++++++++++++++++++++++- src/source/amplify.rs | 13 +++++++++- src/source/mod.rs | 5 ++++ src/source/pausable.rs | 13 +++++++++- src/source/stoppable.rs | 13 +++++++++- 10 files changed, 166 insertions(+), 8 deletions(-) create mode 100644 examples/seek_mp3.rs diff --git a/Cargo.toml b/Cargo.toml index 921294e7..600c2eb2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,8 @@ cpal = "0.15" claxon = { version = "0.4.2", optional = true } hound = { version = "3.3.1", optional = true } lewton = { version = "0.10", optional = true } -minimp3_fixed = { version = "0.5.4", optional = true} +# minimp3_fixed = { version = "0.5.4", optional = true} +minimp3 = { git = "https://github.com/dskleingeld/minimp3-rs.git", optional = true } symphonia = { version = "0.5.2", optional = true, default-features = false } crossbeam-channel = { version = "0.5.8", optional = true } diff --git a/examples/seek_mp3.rs b/examples/seek_mp3.rs new file mode 100644 index 00000000..2b11c9f2 --- /dev/null +++ b/examples/seek_mp3.rs @@ -0,0 +1,14 @@ +use std::io::BufReader; + +fn main() { + let (_stream, handle) = rodio::OutputStream::try_default().unwrap(); + let sink = rodio::Sink::try_new(&handle).unwrap(); + + let file = std::fs::File::open("examples/music.mp3").unwrap(); + sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap()); + + std::thread::sleep(std::time::Duration::from_secs(2)); + sink.set_pos(1.0); + + sink.sleep_until_end(); +} diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index ae5f4311..d4f81563 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -10,6 +10,7 @@ use std::str::FromStr; use std::time::Duration; use crate::Source; +use crate::SourceExt; #[cfg(feature = "symphonia")] use self::read_seek_source::ReadSeekSource; @@ -367,6 +368,45 @@ where } } +impl Decoder +where + R: Read + Seek, +{ + fn request_pos(&self, pos: f32) -> bool { + match self.0 { + #[cfg(feature = "wav")] + DecoderImpl::Wav(ref source) => false, + #[cfg(feature = "vorbis")] + DecoderImpl::Vorbis(ref source) => false, + #[cfg(feature = "flac")] + DecoderImpl::Flac(ref source) => false, + #[cfg(feature = "mp3")] + DecoderImpl::Mp3(ref source) => source.request_pos(pos), + DecoderImpl::None(_) => false, + } + } +} + +// impl SourceExt for Decoder +// where +// R: Read + Seek, +// { +// fn request_pos(&self, pos: f32) -> bool { +// match self.0 { +// #[cfg(feature = "wav")] +// DecoderImpl::Wav(ref source) => false, +// #[cfg(feature = "vorbis")] +// DecoderImpl::Vorbis(ref source) => false, +// #[cfg(feature = "flac")] +// DecoderImpl::Flac(ref source) => false, +// #[cfg(feature = "mp3")] +// DecoderImpl::Mp3(ref source) => {source.test(); false}, +// DecoderImpl::None(_) => false, +// }; +// todo!(); +// } +// } + impl Iterator for LoopedDecoder where R: Read + Seek, diff --git a/src/decoder/mp3.rs b/src/decoder/mp3.rs index 7e14e2cb..eba81e37 100644 --- a/src/decoder/mp3.rs +++ b/src/decoder/mp3.rs @@ -1,9 +1,10 @@ use std::io::{Read, Seek, SeekFrom}; use std::time::Duration; -use crate::Source; +use crate::{Source, SourceExt}; -use minimp3::{Decoder, Frame}; +use minimp3::{Frame, Decoder}; +// use minimp3::SeekDecoder as Decoder; pub struct Mp3Decoder where @@ -61,6 +62,16 @@ where } } +impl SourceExt for Mp3Decoder +where + R: Read + Seek, +{ + fn request_pos(&self, pos: f32) -> bool { + // self.seek_samples(0); //TODO + true + } +} + impl Iterator for Mp3Decoder where R: Read + Seek, diff --git a/src/lib.rs b/src/lib.rs index 51eefed5..05345b68 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -129,6 +129,6 @@ pub mod static_buffer; pub use crate::conversions::Sample; pub use crate::decoder::Decoder; pub use crate::sink::Sink; -pub use crate::source::Source; +pub use crate::source::{Source, SourceExt}; pub use crate::spatial_sink::SpatialSink; pub use crate::stream::{OutputStream, OutputStreamHandle, PlayError, StreamError}; diff --git a/src/sink.rs b/src/sink.rs index fac1ca63..ee906c19 100644 --- a/src/sink.rs +++ b/src/sink.rs @@ -8,7 +8,7 @@ use crossbeam_channel::Receiver; use std::sync::mpsc::Receiver; use crate::stream::{OutputStreamHandle, PlayError}; -use crate::{queue, source::Done, Sample, Source}; +use crate::{queue, source::Done, Sample, Source, SourceExt}; use cpal::FromSample; /// Handle to an device that outputs sounds. @@ -29,8 +29,13 @@ struct Controls { pause: AtomicBool, volume: Mutex, stopped: AtomicBool, +<<<<<<< HEAD speed: Mutex, to_clear: Mutex, +||||||| parent of 2cb7526 (seek implemented through SourceExt trait) +======= + set_pos: Mutex>, +>>>>>>> 2cb7526 (seek implemented through SourceExt trait) } impl Sink { @@ -54,8 +59,13 @@ impl Sink { pause: AtomicBool::new(false), volume: Mutex::new(1.0), stopped: AtomicBool::new(false), +<<<<<<< HEAD speed: Mutex::new(1.0), to_clear: Mutex::new(0), +||||||| parent of 2cb7526 (seek implemented through SourceExt trait) +======= + set_pos: Mutex::new(None), +>>>>>>> 2cb7526 (seek implemented through SourceExt trait) }), sound_count: Arc::new(AtomicUsize::new(0)), detached: false, @@ -115,6 +125,43 @@ impl Sink { *self.sleep_until_end.lock().unwrap() = Some(self.queue_tx.append_with_signal(source)); } + /// Appends a sound to the queue of sounds to play. + #[inline] + pub fn append_seekable(&self, source: S) + where + S: Source + Send + 'static, + S: SourceExt + Send + 'static, + S::Item: Sample, + S::Item: Send, + { + let controls = self.controls.clone(); + + let source = source + .pausable(false) + .amplify(1.0) + .stoppable() + .periodic_access(Duration::from_millis(5), move |src| { + if controls.stopped.load(Ordering::SeqCst) { + src.stop(); + } else { + src.inner_mut().set_factor(*controls.volume.lock().unwrap()); + src.inner_mut() + .inner_mut() + .set_paused(controls.pause.load(Ordering::SeqCst)); + if let Some(pos) = controls.set_pos.lock().unwrap().take() { + src.inner_mut() + .inner_mut() + .inner_mut() + .request_pos(pos); + } + } + }) + .convert_samples(); + self.sound_count.fetch_add(1, Ordering::Relaxed); + let source = Done::new(source, self.sound_count.clone()); + *self.sleep_until_end.lock().unwrap() = Some(self.queue_tx.append_with_signal(source)); + } + /// Gets the volume of the sound. /// /// The value `1.0` is the "normal" volume (unfiltered input). Any value other than 1.0 will @@ -159,6 +206,13 @@ impl Sink { self.controls.pause.store(false, Ordering::SeqCst); } + /// Set position + /// + /// No effect if source does not implement `SourceExt` + pub fn set_pos(&self, pos: f32) { + *self.controls.set_pos.lock().unwrap() = Some(pos); + } + /// Pauses playback of this sink. /// /// No effect if already paused. diff --git a/src/source/amplify.rs b/src/source/amplify.rs index 4f57a282..320b00f0 100644 --- a/src/source/amplify.rs +++ b/src/source/amplify.rs @@ -1,6 +1,6 @@ use std::time::Duration; -use crate::{Sample, Source}; +use crate::{Sample, Source, SourceExt}; /// Internal function that builds a `Amplify` object. pub fn amplify(input: I, factor: f32) -> Amplify @@ -94,3 +94,14 @@ where self.input.total_duration() } } + +impl SourceExt for Amplify +where + I: Source, + I: SourceExt, + I::Item: Sample, +{ + fn request_pos(&self, pos: f32) -> bool { + self.input.request_pos(pos) + } +} diff --git a/src/source/mod.rs b/src/source/mod.rs index c2d595a4..e8f9d72c 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -427,3 +427,8 @@ where (**self).total_duration() } } + +pub trait SourceExt { + /// Seek to pos and whether the seek succeeded + fn request_pos(&self, pos: f32) -> bool; +} diff --git a/src/source/pausable.rs b/src/source/pausable.rs index 75e102f4..2d0f251e 100644 --- a/src/source/pausable.rs +++ b/src/source/pausable.rs @@ -1,6 +1,6 @@ use std::time::Duration; -use crate::{Sample, Source}; +use crate::{Sample, Source, SourceExt}; /// Internal function that builds a `Pausable` object. pub fn pausable(source: I, paused: bool) -> Pausable @@ -116,3 +116,14 @@ where self.input.total_duration() } } + +impl SourceExt for Pausable +where + I: Source, + I: SourceExt, + I::Item: Sample, +{ + fn request_pos(&self, pos: f32) -> bool { + self.input.request_pos(pos) + } +} diff --git a/src/source/stoppable.rs b/src/source/stoppable.rs index afaa35f4..bef217ae 100644 --- a/src/source/stoppable.rs +++ b/src/source/stoppable.rs @@ -1,6 +1,6 @@ use std::time::Duration; -use crate::{Sample, Source}; +use crate::{Sample, Source, SourceExt}; /// Internal function that builds a `Stoppable` object. pub fn stoppable(source: I) -> Stoppable { @@ -89,3 +89,14 @@ where self.input.total_duration() } } + +impl SourceExt for Stoppable +where + I: Source, + I: SourceExt, + I::Item: Sample, +{ + fn request_pos(&self, pos: f32) -> bool { + self.input.request_pos(pos) + } +} From 023e833b016f382bd723d183bd01c05a7aea810e Mon Sep 17 00:00:00 2001 From: dskleingeld <11743287+dskleingeld@users.noreply.github.com> Date: Wed, 13 Jan 2021 23:31:05 +0100 Subject: [PATCH 02/62] request pos now uses mutable self --- examples/seek_mp3.rs | 9 ++++++--- src/decoder/mod.rs | 42 ++++++++++++++++++++--------------------- src/decoder/mp3.rs | 31 +++++++++++++++++------------- src/source/amplify.rs | 2 +- src/source/mod.rs | 2 +- src/source/pausable.rs | 2 +- src/source/stoppable.rs | 2 +- 7 files changed, 49 insertions(+), 41 deletions(-) diff --git a/examples/seek_mp3.rs b/examples/seek_mp3.rs index 2b11c9f2..d84b72c9 100644 --- a/examples/seek_mp3.rs +++ b/examples/seek_mp3.rs @@ -5,10 +5,13 @@ fn main() { let sink = rodio::Sink::try_new(&handle).unwrap(); let file = std::fs::File::open("examples/music.mp3").unwrap(); - sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap()); + sink.append_seekable(rodio::Decoder::new(BufReader::new(file)).unwrap()); - std::thread::sleep(std::time::Duration::from_secs(2)); - sink.set_pos(1.0); + loop { + std::thread::sleep(std::time::Duration::from_secs(2)); + sink.set_pos(2.0); + dbg!("setting pos"); + } sink.sleep_until_end(); } diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index d4f81563..59eacecf 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -372,7 +372,7 @@ impl Decoder where R: Read + Seek, { - fn request_pos(&self, pos: f32) -> bool { + fn request_pos(&mut self, pos: f32) -> bool { match self.0 { #[cfg(feature = "wav")] DecoderImpl::Wav(ref source) => false, @@ -381,31 +381,31 @@ where #[cfg(feature = "flac")] DecoderImpl::Flac(ref source) => false, #[cfg(feature = "mp3")] - DecoderImpl::Mp3(ref source) => source.request_pos(pos), + DecoderImpl::Mp3(ref mut source) => source.request_pos(pos), DecoderImpl::None(_) => false, } } } -// impl SourceExt for Decoder -// where -// R: Read + Seek, -// { -// fn request_pos(&self, pos: f32) -> bool { -// match self.0 { -// #[cfg(feature = "wav")] -// DecoderImpl::Wav(ref source) => false, -// #[cfg(feature = "vorbis")] -// DecoderImpl::Vorbis(ref source) => false, -// #[cfg(feature = "flac")] -// DecoderImpl::Flac(ref source) => false, -// #[cfg(feature = "mp3")] -// DecoderImpl::Mp3(ref source) => {source.test(); false}, -// DecoderImpl::None(_) => false, -// }; -// todo!(); -// } -// } +impl SourceExt for Decoder +where + R: Read + Seek, +{ + fn request_pos(&mut self, pos: f32) -> bool { + match self.0 { + #[cfg(feature = "wav")] + DecoderImpl::Wav(ref source) => false, + #[cfg(feature = "vorbis")] + DecoderImpl::Vorbis(ref source) => false, + #[cfg(feature = "flac")] + DecoderImpl::Flac(ref source) => false, + #[cfg(feature = "mp3")] + DecoderImpl::Mp3(ref mut source) => {source.request_pos(pos)}, + DecoderImpl::None(_) => false, + } + // todo!(); + } +} impl Iterator for LoopedDecoder where diff --git a/src/decoder/mp3.rs b/src/decoder/mp3.rs index eba81e37..6adebb9c 100644 --- a/src/decoder/mp3.rs +++ b/src/decoder/mp3.rs @@ -3,8 +3,8 @@ use std::time::Duration; use crate::{Source, SourceExt}; -use minimp3::{Frame, Decoder}; -// use minimp3::SeekDecoder as Decoder; +use minimp3::Frame; +use minimp3::SeekDecoder as Decoder; pub struct Mp3Decoder where @@ -23,8 +23,10 @@ where if !is_mp3(data.by_ref()) { return Err(data); } - let mut decoder = Decoder::new(data); - let current_frame = decoder.next_frame().unwrap(); + let mut decoder = Decoder::new(data).map_err(|_| ())?; + // TODO: figure out decode_frame vs next_frame + // let current_frame = decoder.next_frame().unwrap(); + let current_frame = decoder.decode_frame().map_err(|_| ())?; Ok(Mp3Decoder { decoder, @@ -66,9 +68,12 @@ impl SourceExt for Mp3Decoder where R: Read + Seek, { - fn request_pos(&self, pos: f32) -> bool { - // self.seek_samples(0); //TODO - true + fn request_pos(&mut self, pos: f32) -> bool { + let pos = (pos * 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 + dbg!("seek"); + self.decoder.seek_samples(pos).is_ok() } } @@ -78,14 +83,14 @@ 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.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/source/amplify.rs b/src/source/amplify.rs index 320b00f0..ccf0fdea 100644 --- a/src/source/amplify.rs +++ b/src/source/amplify.rs @@ -101,7 +101,7 @@ where I: SourceExt, I::Item: Sample, { - fn request_pos(&self, pos: f32) -> bool { + fn request_pos(&mut self, pos: f32) -> bool { self.input.request_pos(pos) } } diff --git a/src/source/mod.rs b/src/source/mod.rs index e8f9d72c..a777c723 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -430,5 +430,5 @@ where pub trait SourceExt { /// Seek to pos and whether the seek succeeded - fn request_pos(&self, pos: f32) -> bool; + fn request_pos(&mut self, pos: f32) -> bool; } diff --git a/src/source/pausable.rs b/src/source/pausable.rs index 2d0f251e..78173f64 100644 --- a/src/source/pausable.rs +++ b/src/source/pausable.rs @@ -123,7 +123,7 @@ where I: SourceExt, I::Item: Sample, { - fn request_pos(&self, pos: f32) -> bool { + fn request_pos(&mut self, pos: f32) -> bool { self.input.request_pos(pos) } } diff --git a/src/source/stoppable.rs b/src/source/stoppable.rs index bef217ae..964fe916 100644 --- a/src/source/stoppable.rs +++ b/src/source/stoppable.rs @@ -96,7 +96,7 @@ where I: SourceExt, I::Item: Sample, { - fn request_pos(&self, pos: f32) -> bool { + fn request_pos(&mut self, pos: f32) -> bool { self.input.request_pos(pos) } } From 204a3f89c99c86aa30f32375326d33b845244ce2 Mon Sep 17 00:00:00 2001 From: dskleingeld <11743287+dskleingeld@users.noreply.github.com> Date: Wed, 27 Jan 2021 00:12:23 +0100 Subject: [PATCH 03/62] switch to fork for cpal --- Cargo.toml | 2 +- src/decoder/mod.rs | 41 ++++++++++++++++++---------------- src/sink.rs | 55 +++++++++++++++++++++++++++------------------- 3 files changed, 56 insertions(+), 42 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 600c2eb2..2ea329b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ claxon = { version = "0.4.2", optional = true } hound = { version = "3.3.1", optional = true } lewton = { version = "0.10", optional = true } # minimp3_fixed = { version = "0.5.4", optional = true} -minimp3 = { git = "https://github.com/dskleingeld/minimp3-rs.git", optional = true } +minimp3_fixed = { package = "minimp3", git = "https://github.com/dvdsk/minimp3-rs.git", optional = true } symphonia = { version = "0.5.2", optional = true, default-features = false } crossbeam-channel = { version = "0.5.8", optional = true } diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 59eacecf..66380334 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -373,15 +373,17 @@ where R: Read + Seek, { fn request_pos(&mut self, pos: f32) -> bool { - match self.0 { - #[cfg(feature = "wav")] - DecoderImpl::Wav(ref source) => false, - #[cfg(feature = "vorbis")] - DecoderImpl::Vorbis(ref source) => false, - #[cfg(feature = "flac")] - DecoderImpl::Flac(ref source) => false, - #[cfg(feature = "mp3")] - DecoderImpl::Mp3(ref mut source) => source.request_pos(pos), + match &mut self.0 { + #[cfg(all(feature = "wav", not(feature = "symphonia-wav")))] + DecoderImpl::Wav(_) => false, + #[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))] + DecoderImpl::Vorbis(_) => false, + #[cfg(all(feature = "flac", not(feature = "symphonia-flac")))] + DecoderImpl::Flac(_) => false, + #[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))] + DecoderImpl::Mp3(source) => source.request_pos(pos), + #[cfg(feature = "symphonia")] + DecoderImpl::Symphonia(_) => false, DecoderImpl::None(_) => false, } } @@ -392,18 +394,19 @@ where R: Read + Seek, { fn request_pos(&mut self, pos: f32) -> bool { - match self.0 { - #[cfg(feature = "wav")] - DecoderImpl::Wav(ref source) => false, - #[cfg(feature = "vorbis")] - DecoderImpl::Vorbis(ref source) => false, - #[cfg(feature = "flac")] - DecoderImpl::Flac(ref source) => false, - #[cfg(feature = "mp3")] - DecoderImpl::Mp3(ref mut source) => {source.request_pos(pos)}, + match &mut self.0 { + #[cfg(all(feature = "wav", not(feature = "symphonia-wav")))] + DecoderImpl::Wav(_) => false, + #[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))] + DecoderImpl::Vorbis(_) => false, + #[cfg(all(feature = "flac", not(feature = "symphonia-flac")))] + DecoderImpl::Flac(_) => false, + #[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))] + DecoderImpl::Mp3(source) => source.request_pos(pos), + #[cfg(feature = "symphonia")] + DecoderImpl::Symphonia(_) => false, DecoderImpl::None(_) => false, } - // todo!(); } } diff --git a/src/sink.rs b/src/sink.rs index ee906c19..dd1a4954 100644 --- a/src/sink.rs +++ b/src/sink.rs @@ -29,13 +29,9 @@ struct Controls { pause: AtomicBool, volume: Mutex, stopped: AtomicBool, -<<<<<<< HEAD speed: Mutex, to_clear: Mutex, -||||||| parent of 2cb7526 (seek implemented through SourceExt trait) -======= set_pos: Mutex>, ->>>>>>> 2cb7526 (seek implemented through SourceExt trait) } impl Sink { @@ -59,13 +55,9 @@ impl Sink { pause: AtomicBool::new(false), volume: Mutex::new(1.0), stopped: AtomicBool::new(false), -<<<<<<< HEAD speed: Mutex::new(1.0), to_clear: Mutex::new(0), -||||||| parent of 2cb7526 (seek implemented through SourceExt trait) -======= set_pos: Mutex::new(None), ->>>>>>> 2cb7526 (seek implemented through SourceExt trait) }), sound_count: Arc::new(AtomicUsize::new(0)), detached: false, @@ -129,32 +121,51 @@ impl Sink { #[inline] pub fn append_seekable(&self, source: S) where - S: Source + Send + 'static, - S: SourceExt + Send + 'static, - S::Item: Sample, - S::Item: Send, + S: Source + SourceExt + Send + 'static, + f32: FromSample, + S::Item: Sample + Send, { + // Wait for queue to flush then resume stopped playback + if self.controls.stopped.load(Ordering::SeqCst) { + if self.sound_count.load(Ordering::SeqCst) > 0 { + self.sleep_until_end(); + } + self.controls.stopped.store(false, Ordering::SeqCst); + } + let controls = self.controls.clone(); + let start_played = AtomicBool::new(false); + let source = source + .speed(1.0) .pausable(false) .amplify(1.0) + .skippable() .stoppable() .periodic_access(Duration::from_millis(5), move |src| { if controls.stopped.load(Ordering::SeqCst) { src.stop(); - } else { - src.inner_mut().set_factor(*controls.volume.lock().unwrap()); - src.inner_mut() - .inner_mut() - .set_paused(controls.pause.load(Ordering::SeqCst)); - if let Some(pos) = controls.set_pos.lock().unwrap().take() { - src.inner_mut() - .inner_mut() - .inner_mut() - .request_pos(pos); + } + { + let mut to_clear = controls.to_clear.lock().unwrap(); + if *to_clear > 0 { + let _ = src.inner_mut().skip(); + *to_clear -= 1; } } + let amp = src.inner_mut().inner_mut(); + amp.set_factor(*controls.volume.lock().unwrap()); + amp.inner_mut() + .set_paused(controls.pause.load(Ordering::SeqCst)); + amp.inner_mut() + .inner_mut() + .set_factor(*controls.speed.lock().unwrap()); + let seekable = amp.inner_mut().inner_mut().inner_mut(); + if let Some(pos) = controls.set_pos.lock().unwrap().take() { + seekable.request_pos(pos); + } + start_played.store(true, Ordering::SeqCst); }) .convert_samples(); self.sound_count.fetch_add(1, Ordering::Relaxed); From d47842fe57b215230c28dd28e84d4faf7831645a Mon Sep 17 00:00:00 2001 From: dvdsk Date: Sat, 30 Sep 2023 21:53:56 +0200 Subject: [PATCH 04/62] remove seek trait from source mods, added it to symphonia decoder, renamed the trait to SeekableSource --- Cargo.toml | 4 ++-- examples/seek_mp3.rs | 11 ++++++----- src/decoder/mod.rs | 39 +++++++++------------------------------ src/decoder/mp3.rs | 27 +++++++++++++++------------ src/decoder/symphonia.rs | 21 +++++++++++++++++++-- src/lib.rs | 2 +- src/sink.rs | 16 ++++++++-------- src/source/amplify.rs | 13 +------------ src/source/mod.rs | 4 ++-- src/source/pausable.rs | 13 +------------ src/source/stoppable.rs | 13 +------------ 11 files changed, 65 insertions(+), 98 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2ea329b6..0f738a01 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ claxon = { version = "0.4.2", optional = true } hound = { version = "3.3.1", optional = true } lewton = { version = "0.10", optional = true } # minimp3_fixed = { version = "0.5.4", optional = true} -minimp3_fixed = { package = "minimp3", git = "https://github.com/dvdsk/minimp3-rs.git", optional = true } +minimp3 = { git = "https://github.com/dvdsk/minimp3-rs.git", optional = true } symphonia = { version = "0.5.2", optional = true, default-features = false } crossbeam-channel = { version = "0.5.8", optional = true } @@ -26,7 +26,7 @@ flac = ["claxon"] vorbis = ["lewton"] wav = ["hound"] mp3 = ["symphonia-mp3"] -minimp3 = ["dep:minimp3_fixed"] +minimp3 = ["dep:minimp3"] wasm-bindgen = ["cpal/wasm-bindgen"] symphonia-aac = ["symphonia/aac"] symphonia-all = ["symphonia-aac", "symphonia-flac", "symphonia-isomp4", "symphonia-mp3", "symphonia-vorbis", "symphonia-wav"] diff --git a/examples/seek_mp3.rs b/examples/seek_mp3.rs index d84b72c9..cb83e0a7 100644 --- a/examples/seek_mp3.rs +++ b/examples/seek_mp3.rs @@ -1,4 +1,5 @@ use std::io::BufReader; +use std::time::Duration; fn main() { let (_stream, handle) = rodio::OutputStream::try_default().unwrap(); @@ -7,11 +8,11 @@ fn main() { let file = std::fs::File::open("examples/music.mp3").unwrap(); sink.append_seekable(rodio::Decoder::new(BufReader::new(file)).unwrap()); - loop { - std::thread::sleep(std::time::Duration::from_secs(2)); - sink.set_pos(2.0); - dbg!("setting pos"); - } + std::thread::sleep(std::time::Duration::from_secs(2)); + sink.seek(Duration::from_secs(0)); + + std::thread::sleep(std::time::Duration::from_secs(2)); + sink.seek(Duration::from_secs(4)); sink.sleep_until_end(); } diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 66380334..cc3e033c 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -10,7 +10,7 @@ use std::str::FromStr; use std::time::Duration; use crate::Source; -use crate::SourceExt; +use crate::SeekableSource; #[cfg(feature = "symphonia")] use self::read_seek_source::ReadSeekSource; @@ -368,44 +368,23 @@ where } } -impl Decoder -where - R: Read + Seek, -{ - fn request_pos(&mut self, pos: f32) -> bool { - match &mut self.0 { - #[cfg(all(feature = "wav", not(feature = "symphonia-wav")))] - DecoderImpl::Wav(_) => false, - #[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))] - DecoderImpl::Vorbis(_) => false, - #[cfg(all(feature = "flac", not(feature = "symphonia-flac")))] - DecoderImpl::Flac(_) => false, - #[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))] - DecoderImpl::Mp3(source) => source.request_pos(pos), - #[cfg(feature = "symphonia")] - DecoderImpl::Symphonia(_) => false, - DecoderImpl::None(_) => false, - } - } -} - -impl SourceExt for Decoder +impl SeekableSource for Decoder where R: Read + Seek, { - fn request_pos(&mut self, pos: f32) -> bool { + fn seek(&mut self, pos: Duration) { match &mut self.0 { #[cfg(all(feature = "wav", not(feature = "symphonia-wav")))] - DecoderImpl::Wav(_) => false, + DecoderImpl::Wav(_) => (), #[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))] - DecoderImpl::Vorbis(_) => false, + DecoderImpl::Vorbis(_) => (), #[cfg(all(feature = "flac", not(feature = "symphonia-flac")))] - DecoderImpl::Flac(_) => false, + DecoderImpl::Flac(_) => (), #[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))] - DecoderImpl::Mp3(source) => source.request_pos(pos), + DecoderImpl::Mp3(source) => source.seek(pos), #[cfg(feature = "symphonia")] - DecoderImpl::Symphonia(_) => false, - DecoderImpl::None(_) => false, + DecoderImpl::Symphonia(source) => source.seek(pos), + DecoderImpl::None(_) => (), } } } diff --git a/src/decoder/mp3.rs b/src/decoder/mp3.rs index 6adebb9c..c83df4b7 100644 --- a/src/decoder/mp3.rs +++ b/src/decoder/mp3.rs @@ -1,16 +1,16 @@ use std::io::{Read, Seek, SeekFrom}; use std::time::Duration; -use crate::{Source, SourceExt}; +use crate::{SeekableSource, Source}; use minimp3::Frame; -use minimp3::SeekDecoder as Decoder; +use minimp3::{Decoder, SeekDecoder}; pub struct Mp3Decoder where R: Read + Seek, { - decoder: Decoder, + decoder: SeekDecoder, current_frame: Frame, current_frame_offset: usize, } @@ -23,10 +23,14 @@ where if !is_mp3(data.by_ref()) { return Err(data); } - let mut decoder = Decoder::new(data).map_err(|_| ())?; - // TODO: figure out decode_frame vs next_frame - // let current_frame = decoder.next_frame().unwrap(); - let current_frame = decoder.decode_frame().map_err(|_| ())?; + let mut decoder = SeekDecoder::new(data) + // paramaters 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() + // the reader makes enough data availible therefore + // if we crash here the invariant broken is: + .expect("data should not corrupt"); Ok(Mp3Decoder { decoder, @@ -64,15 +68,14 @@ where } } -impl SourceExt for Mp3Decoder -where +impl SeekableSource for Mp3Decoder +where R: Read + Seek, { - fn request_pos(&mut self, pos: f32) -> bool { - let pos = (pos * self.sample_rate() as f32) as u64; + fn seek(&mut self, pos: Duration) -> bool { + 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 - dbg!("seek"); self.decoder.seek_samples(pos).is_ok() } } diff --git a/src/decoder/symphonia.rs b/src/decoder/symphonia.rs index 19d57533..cfd1552d 100644 --- a/src/decoder/symphonia.rs +++ b/src/decoder/symphonia.rs @@ -8,12 +8,12 @@ use symphonia::{ io::MediaSourceStream, meta::MetadataOptions, probe::Hint, - units, + units::{self, Time}, }, default::get_probe, }; -use crate::Source; +use crate::{SeekableSource, Source}; use super::DecoderError; @@ -119,6 +119,23 @@ impl SymphoniaDecoder { } } +impl SeekableSource for SymphoniaDecoder { + fn seek(&mut self, pos: Duration) { + use symphonia::core::formats::{SeekMode, SeekTo}; + + let pos_fract = 1f64 / pos.subsec_nanos() as f64; + self.format + .seek( + SeekMode::Accurate, + SeekTo::Time { + time: Time::new(pos.as_secs(), pos_fract), + track_id: None, + }, + ) + .unwrap(); + } +} + impl Source for SymphoniaDecoder { #[inline] fn current_frame_len(&self) -> Option { diff --git a/src/lib.rs b/src/lib.rs index 05345b68..2797cb90 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -129,6 +129,6 @@ pub mod static_buffer; pub use crate::conversions::Sample; pub use crate::decoder::Decoder; pub use crate::sink::Sink; -pub use crate::source::{Source, SourceExt}; +pub use crate::source::{Source, SeekableSource}; pub use crate::spatial_sink::SpatialSink; pub use crate::stream::{OutputStream, OutputStreamHandle, PlayError, StreamError}; diff --git a/src/sink.rs b/src/sink.rs index dd1a4954..39cf6e4c 100644 --- a/src/sink.rs +++ b/src/sink.rs @@ -8,7 +8,7 @@ use crossbeam_channel::Receiver; use std::sync::mpsc::Receiver; use crate::stream::{OutputStreamHandle, PlayError}; -use crate::{queue, source::Done, Sample, Source, SourceExt}; +use crate::{queue, source::Done, Sample, Source, SeekableSource}; use cpal::FromSample; /// Handle to an device that outputs sounds. @@ -31,7 +31,7 @@ struct Controls { stopped: AtomicBool, speed: Mutex, to_clear: Mutex, - set_pos: Mutex>, + seek: Mutex>, } impl Sink { @@ -57,7 +57,7 @@ impl Sink { stopped: AtomicBool::new(false), speed: Mutex::new(1.0), to_clear: Mutex::new(0), - set_pos: Mutex::new(None), + seek: Mutex::new(None), }), sound_count: Arc::new(AtomicUsize::new(0)), detached: false, @@ -121,7 +121,7 @@ impl Sink { #[inline] pub fn append_seekable(&self, source: S) where - S: Source + SourceExt + Send + 'static, + S: Source + SeekableSource + Send + 'static, f32: FromSample, S::Item: Sample + Send, { @@ -162,8 +162,8 @@ impl Sink { .inner_mut() .set_factor(*controls.speed.lock().unwrap()); let seekable = amp.inner_mut().inner_mut().inner_mut(); - if let Some(pos) = controls.set_pos.lock().unwrap().take() { - seekable.request_pos(pos); + if let Some(pos) = controls.seek.lock().unwrap().take() { + seekable.seek(pos); } start_played.store(true, Ordering::SeqCst); }) @@ -220,8 +220,8 @@ impl Sink { /// Set position /// /// No effect if source does not implement `SourceExt` - pub fn set_pos(&self, pos: f32) { - *self.controls.set_pos.lock().unwrap() = Some(pos); + pub fn seek(&self, pos: Duration) { + *self.controls.seek.lock().unwrap() = Some(pos); } /// Pauses playback of this sink. diff --git a/src/source/amplify.rs b/src/source/amplify.rs index ccf0fdea..4f57a282 100644 --- a/src/source/amplify.rs +++ b/src/source/amplify.rs @@ -1,6 +1,6 @@ use std::time::Duration; -use crate::{Sample, Source, SourceExt}; +use crate::{Sample, Source}; /// Internal function that builds a `Amplify` object. pub fn amplify(input: I, factor: f32) -> Amplify @@ -94,14 +94,3 @@ where self.input.total_duration() } } - -impl SourceExt for Amplify -where - I: Source, - I: SourceExt, - I::Item: Sample, -{ - fn request_pos(&mut self, pos: f32) -> bool { - self.input.request_pos(pos) - } -} diff --git a/src/source/mod.rs b/src/source/mod.rs index a777c723..c3e2c247 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -428,7 +428,7 @@ where } } -pub trait SourceExt { +pub trait SeekableSource { /// Seek to pos and whether the seek succeeded - fn request_pos(&mut self, pos: f32) -> bool; + fn seek(&mut self, pos: Duration); } diff --git a/src/source/pausable.rs b/src/source/pausable.rs index 78173f64..75e102f4 100644 --- a/src/source/pausable.rs +++ b/src/source/pausable.rs @@ -1,6 +1,6 @@ use std::time::Duration; -use crate::{Sample, Source, SourceExt}; +use crate::{Sample, Source}; /// Internal function that builds a `Pausable` object. pub fn pausable(source: I, paused: bool) -> Pausable @@ -116,14 +116,3 @@ where self.input.total_duration() } } - -impl SourceExt for Pausable -where - I: Source, - I: SourceExt, - I::Item: Sample, -{ - fn request_pos(&mut self, pos: f32) -> bool { - self.input.request_pos(pos) - } -} diff --git a/src/source/stoppable.rs b/src/source/stoppable.rs index 964fe916..afaa35f4 100644 --- a/src/source/stoppable.rs +++ b/src/source/stoppable.rs @@ -1,6 +1,6 @@ use std::time::Duration; -use crate::{Sample, Source, SourceExt}; +use crate::{Sample, Source}; /// Internal function that builds a `Stoppable` object. pub fn stoppable(source: I) -> Stoppable { @@ -89,14 +89,3 @@ where self.input.total_duration() } } - -impl SourceExt for Stoppable -where - I: Source, - I: SourceExt, - I::Item: Sample, -{ - fn request_pos(&mut self, pos: f32) -> bool { - self.input.request_pos(pos) - } -} From 745db82876b53d63da351a9c60f0941aa378c6d8 Mon Sep 17 00:00:00 2001 From: dvdsk Date: Tue, 3 Oct 2023 00:02:37 +0200 Subject: [PATCH 05/62] removes SeekableSource adds failable try_seek to Source --- src/decoder/mod.rs | 22 ---------------------- src/decoder/mp3.rs | 12 ++++-------- src/decoder/symphonia.rs | 35 +++++++++++++++++------------------ src/lib.rs | 2 +- src/sink.rs | 6 +++--- src/source/mod.rs | 24 ++++++++++++++++++++---- 6 files changed, 45 insertions(+), 56 deletions(-) diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index cc3e033c..ae5f4311 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -10,7 +10,6 @@ use std::str::FromStr; use std::time::Duration; use crate::Source; -use crate::SeekableSource; #[cfg(feature = "symphonia")] use self::read_seek_source::ReadSeekSource; @@ -368,27 +367,6 @@ where } } -impl SeekableSource for Decoder -where - R: Read + Seek, -{ - fn seek(&mut self, pos: Duration) { - match &mut self.0 { - #[cfg(all(feature = "wav", not(feature = "symphonia-wav")))] - DecoderImpl::Wav(_) => (), - #[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))] - DecoderImpl::Vorbis(_) => (), - #[cfg(all(feature = "flac", not(feature = "symphonia-flac")))] - DecoderImpl::Flac(_) => (), - #[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))] - DecoderImpl::Mp3(source) => source.seek(pos), - #[cfg(feature = "symphonia")] - DecoderImpl::Symphonia(source) => source.seek(pos), - DecoderImpl::None(_) => (), - } - } -} - impl Iterator for LoopedDecoder where R: Read + Seek, diff --git a/src/decoder/mp3.rs b/src/decoder/mp3.rs index c83df4b7..93523528 100644 --- a/src/decoder/mp3.rs +++ b/src/decoder/mp3.rs @@ -1,7 +1,7 @@ use std::io::{Read, Seek, SeekFrom}; use std::time::Duration; -use crate::{SeekableSource, Source}; +use crate::Source; use minimp3::Frame; use minimp3::{Decoder, SeekDecoder}; @@ -66,17 +66,13 @@ where fn total_duration(&self) -> Option { None } -} -impl SeekableSource for Mp3Decoder -where - R: Read + Seek, -{ - fn seek(&mut self, pos: Duration) -> bool { + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { 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).is_ok() + self.decoder.seek_samples(pos).is_ok(); + Ok(()) } } diff --git a/src/decoder/symphonia.rs b/src/decoder/symphonia.rs index cfd1552d..f11ca3c7 100644 --- a/src/decoder/symphonia.rs +++ b/src/decoder/symphonia.rs @@ -13,7 +13,7 @@ use symphonia::{ default::get_probe, }; -use crate::{SeekableSource, Source}; +use crate::{Source, source::SeekNotSupported}; use super::DecoderError; @@ -119,23 +119,6 @@ impl SymphoniaDecoder { } } -impl SeekableSource for SymphoniaDecoder { - fn seek(&mut self, pos: Duration) { - use symphonia::core::formats::{SeekMode, SeekTo}; - - let pos_fract = 1f64 / pos.subsec_nanos() as f64; - self.format - .seek( - SeekMode::Accurate, - SeekTo::Time { - time: Time::new(pos.as_secs(), pos_fract), - track_id: None, - }, - ) - .unwrap(); - } -} - impl Source for SymphoniaDecoder { #[inline] fn current_frame_len(&self) -> Option { @@ -156,6 +139,22 @@ impl Source for SymphoniaDecoder { fn total_duration(&self) -> Option { None } + + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + use symphonia::core::formats::{SeekMode, SeekTo}; + + let pos_fract = 1f64 / pos.subsec_nanos() as f64; + self.format + .seek( + SeekMode::Accurate, + SeekTo::Time { + time: Time::new(pos.as_secs(), pos_fract), + track_id: None, + }, + ) + .unwrap(); + Ok(()) + } } impl Iterator for SymphoniaDecoder { diff --git a/src/lib.rs b/src/lib.rs index 2797cb90..51eefed5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -129,6 +129,6 @@ pub mod static_buffer; pub use crate::conversions::Sample; pub use crate::decoder::Decoder; pub use crate::sink::Sink; -pub use crate::source::{Source, SeekableSource}; +pub use crate::source::Source; pub use crate::spatial_sink::SpatialSink; pub use crate::stream::{OutputStream, OutputStreamHandle, PlayError, StreamError}; diff --git a/src/sink.rs b/src/sink.rs index 39cf6e4c..a3df6374 100644 --- a/src/sink.rs +++ b/src/sink.rs @@ -8,7 +8,7 @@ use crossbeam_channel::Receiver; use std::sync::mpsc::Receiver; use crate::stream::{OutputStreamHandle, PlayError}; -use crate::{queue, source::Done, Sample, Source, SeekableSource}; +use crate::{queue, source::Done, Sample, Source}; use cpal::FromSample; /// Handle to an device that outputs sounds. @@ -121,7 +121,7 @@ impl Sink { #[inline] pub fn append_seekable(&self, source: S) where - S: Source + SeekableSource + Send + 'static, + S: Source + Send + 'static, f32: FromSample, S::Item: Sample + Send, { @@ -163,7 +163,7 @@ impl Sink { .set_factor(*controls.speed.lock().unwrap()); let seekable = amp.inner_mut().inner_mut().inner_mut(); if let Some(pos) = controls.seek.lock().unwrap().take() { - seekable.seek(pos); + seekable.try_seek(pos).unwrap(); } start_played.store(true, Ordering::SeqCst); }) diff --git a/src/source/mod.rs b/src/source/mod.rs index c3e2c247..2748fca8 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -1,5 +1,7 @@ //! Sources of sound and various filters. +use std::fmt; +use std::error::Error; use std::time::Duration; use cpal::FromSample; @@ -151,6 +153,7 @@ where fn total_duration(&self) -> Option; /// Stores the source in a buffer in addition to returning it. This iterator can be cloned. + #[inline] fn buffered(self) -> Buffered where @@ -351,8 +354,25 @@ where { blt::high_pass(self, freq) } + + /// Try to seek to a pos, returns [`SeekNotSupported`] if seeking is not + /// supported by the current source. + fn try_seek(&mut self, _: Duration) -> Result<(), SeekNotSupported> { + Err(SeekNotSupported) + } } +#[derive(Debug, Clone)] +pub struct SeekNotSupported; + +impl fmt::Display for SeekNotSupported { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Seeking is not supported on this source") + } +} + +impl Error for SeekNotSupported {} + impl Source for Box> where S: Sample, @@ -428,7 +448,3 @@ where } } -pub trait SeekableSource { - /// Seek to pos and whether the seek succeeded - fn seek(&mut self, pos: Duration); -} From 202687b934fee3c419848c78a44a7f13cddc57b1 Mon Sep 17 00:00:00 2001 From: dvdsk Date: Tue, 3 Oct 2023 00:31:42 +0200 Subject: [PATCH 06/62] adds try_seek for sink and all sources --- examples/seek_mp3.rs | 6 +- src/conversions/channels.rs | 6 ++ src/conversions/sample.rs | 6 ++ src/conversions/sample_rate.rs | 6 ++ src/sink.rs | 110 ++++++++++++++------------------ src/source/amplify.rs | 7 ++ src/source/blt.rs | 7 ++ src/source/buffered.rs | 7 ++ src/source/channel_volume.rs | 7 ++ src/source/delay.rs | 8 +++ src/source/done.rs | 7 ++ src/source/empty.rs | 7 ++ src/source/empty_callback.rs | 7 ++ src/source/fadein.rs | 7 ++ src/source/from_iter.rs | 11 ++++ src/source/mix.rs | 7 ++ src/source/mod.rs | 19 +++++- src/source/pausable.rs | 7 ++ src/source/periodic.rs | 7 ++ src/source/repeat.rs | 7 ++ src/source/samples_converter.rs | 7 ++ src/source/sine.rs | 12 +++- src/source/skip.rs | 7 ++ src/source/skippable.rs | 7 ++ src/source/spatial.rs | 7 ++ src/source/speed.rs | 25 ++++---- src/source/stoppable.rs | 7 ++ src/source/take.rs | 7 ++ src/source/uniform.rs | 23 +++++++ src/source/zero.rs | 7 ++ tests/seek_error.rs | 28 ++++++++ 31 files changed, 316 insertions(+), 77 deletions(-) create mode 100644 tests/seek_error.rs diff --git a/examples/seek_mp3.rs b/examples/seek_mp3.rs index cb83e0a7..e104675b 100644 --- a/examples/seek_mp3.rs +++ b/examples/seek_mp3.rs @@ -6,13 +6,13 @@ fn main() { let sink = rodio::Sink::try_new(&handle).unwrap(); let file = std::fs::File::open("examples/music.mp3").unwrap(); - sink.append_seekable(rodio::Decoder::new(BufReader::new(file)).unwrap()); + sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap()); std::thread::sleep(std::time::Duration::from_secs(2)); - sink.seek(Duration::from_secs(0)); + sink.try_seek(Duration::from_secs(0)).unwrap(); std::thread::sleep(std::time::Duration::from_secs(2)); - sink.seek(Duration::from_secs(4)); + sink.try_seek(Duration::from_secs(4)).unwrap(); sink.sleep_until_end(); } diff --git a/src/conversions/channels.rs b/src/conversions/channels.rs index 19b61ab9..c42b0104 100644 --- a/src/conversions/channels.rs +++ b/src/conversions/channels.rs @@ -44,6 +44,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/sink.rs b/src/sink.rs index a3df6374..d7d485c2 100644 --- a/src/sink.rs +++ b/src/sink.rs @@ -3,10 +3,11 @@ use std::sync::{Arc, Mutex}; use std::time::Duration; #[cfg(feature = "crossbeam-channel")] -use crossbeam_channel::Receiver; +use crossbeam_channel::{Receiver, Sender}; #[cfg(not(feature = "crossbeam-channel"))] -use std::sync::mpsc::Receiver; +use std::sync::mpsc::{Receiver, Sender}; +use crate::source::SeekNotSupported; use crate::stream::{OutputStreamHandle, PlayError}; use crate::{queue, source::Done, Sample, Source}; use cpal::FromSample; @@ -25,13 +26,44 @@ pub struct Sink { detached: bool, } +struct SeekOrder { + pos: Duration, + feedback: Sender>, +} + +impl SeekOrder { + fn new(pos: Duration) -> (Self, Receiver>) { + #[cfg(not(feature = "crossbeam-channel"))] + let (tx, rx) = { + use std::sync::mpsc; + mpsc::channel() + }; + + #[cfg(feature = "crossbeam-channel")] + let (tx, rx) = { + use crossbeam_channel::bounded; + bounded(1) + }; + (Self { pos, feedback: tx }, rx) + } + + fn attempt(self, maybe_seekable: &mut S) + where + S: Source, + S::Item: Sample + Send, + { + let res = maybe_seekable.try_seek(self.pos); + let _ignore_reciever_dropped = self.feedback.send(res); + } +} + struct Controls { pause: AtomicBool, volume: Mutex, stopped: AtomicBool, speed: Mutex, to_clear: Mutex, - seek: Mutex>, + seek: Mutex>, } impl Sink { @@ -109,61 +141,8 @@ impl Sink { amp.inner_mut() .inner_mut() .set_factor(*controls.speed.lock().unwrap()); - start_played.store(true, Ordering::SeqCst); - }) - .convert_samples(); - self.sound_count.fetch_add(1, Ordering::Relaxed); - let source = Done::new(source, self.sound_count.clone()); - *self.sleep_until_end.lock().unwrap() = Some(self.queue_tx.append_with_signal(source)); - } - - /// Appends a sound to the queue of sounds to play. - #[inline] - pub fn append_seekable(&self, source: S) - where - S: Source + Send + 'static, - f32: FromSample, - S::Item: Sample + Send, - { - // Wait for queue to flush then resume stopped playback - if self.controls.stopped.load(Ordering::SeqCst) { - if self.sound_count.load(Ordering::SeqCst) > 0 { - self.sleep_until_end(); - } - self.controls.stopped.store(false, Ordering::SeqCst); - } - - let controls = self.controls.clone(); - - let start_played = AtomicBool::new(false); - - let source = source - .speed(1.0) - .pausable(false) - .amplify(1.0) - .skippable() - .stoppable() - .periodic_access(Duration::from_millis(5), move |src| { - if controls.stopped.load(Ordering::SeqCst) { - src.stop(); - } - { - let mut to_clear = controls.to_clear.lock().unwrap(); - if *to_clear > 0 { - let _ = src.inner_mut().skip(); - *to_clear -= 1; - } - } - let amp = src.inner_mut().inner_mut(); - amp.set_factor(*controls.volume.lock().unwrap()); - amp.inner_mut() - .set_paused(controls.pause.load(Ordering::SeqCst)); - amp.inner_mut() - .inner_mut() - .set_factor(*controls.speed.lock().unwrap()); - let seekable = amp.inner_mut().inner_mut().inner_mut(); - if let Some(pos) = controls.seek.lock().unwrap().take() { - seekable.try_seek(pos).unwrap(); + if let Some(seek) = controls.seek.lock().unwrap().take() { + seek.attempt(amp) } start_played.store(true, Ordering::SeqCst); }) @@ -219,9 +198,18 @@ impl Sink { /// Set position /// - /// No effect if source does not implement `SourceExt` - pub fn seek(&self, pos: Duration) { - *self.controls.seek.lock().unwrap() = Some(pos); + /// Try to seek to a pos, returns [`SeekNotSupported`] if seeking is not + /// supported by the current source. + pub fn try_seek(&self, pos: Duration) -> Result<(), SeekNotSupported> { + let (order, feedback) = SeekOrder::new(pos); + *self.controls.seek.lock().unwrap() = Some(order); + match feedback.recv() { + Ok(seek_res) => seek_res, + // The feedback channel closed. Probably another seekorder was set + // invalidating this one and closing the feedback channel + // ... or the audio thread panicked. + Err(_) => Ok(()), + } } /// Pauses playback of this sink. diff --git a/src/source/amplify.rs b/src/source/amplify.rs index 4f57a282..0d2bc270 100644 --- a/src/source/amplify.rs +++ b/src/source/amplify.rs @@ -2,6 +2,8 @@ use std::time::Duration; use crate::{Sample, Source}; +use super::SeekNotSupported; + /// Internal function that builds a `Amplify` object. pub fn amplify(input: I, factor: f32) -> Amplify where @@ -93,4 +95,9 @@ where fn total_duration(&self) -> Option { self.input.total_duration() } + + #[inline] + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + self.input.try_seek(pos) + } } diff --git a/src/source/blt.rs b/src/source/blt.rs index fa3e33cd..f5b882f6 100644 --- a/src/source/blt.rs +++ b/src/source/blt.rs @@ -3,6 +3,8 @@ use std::time::Duration; use crate::Source; +use super::SeekNotSupported; + // Implemented following http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt /// Internal function that builds a `BltFilter` object. @@ -147,6 +149,11 @@ where fn total_duration(&self) -> Option { self.input.total_duration() } + + #[inline] + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + self.input.try_seek(pos) + } } #[derive(Clone, Debug)] diff --git a/src/source/buffered.rs b/src/source/buffered.rs index 6bd8b4e2..ad3a5bea 100644 --- a/src/source/buffered.rs +++ b/src/source/buffered.rs @@ -5,6 +5,8 @@ use std::time::Duration; use crate::{Sample, Source}; +use super::SeekNotSupported; + /// Internal function that builds a `Buffered` object. #[inline] pub fn buffered(input: I) -> Buffered @@ -239,6 +241,11 @@ where fn total_duration(&self) -> Option { self.total_duration } + + #[inline] + fn try_seek(&mut self, _: Duration) -> Result<(), SeekNotSupported> { + Err(SeekNotSupported) + } } impl Clone for Buffered diff --git a/src/source/channel_volume.rs b/src/source/channel_volume.rs index 69dc869e..3544dcdd 100644 --- a/src/source/channel_volume.rs +++ b/src/source/channel_volume.rs @@ -2,6 +2,8 @@ use std::time::Duration; use crate::{Sample, Source}; +use super::SeekNotSupported; + /// Combines channels in input into a single mono source, then plays that mono sound /// to each channel at the volume given for that channel. #[derive(Clone, Debug)] @@ -138,4 +140,9 @@ where fn total_duration(&self) -> Option { self.input.total_duration() } + + #[inline] + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + self.input.try_seek(pos) + } } diff --git a/src/source/delay.rs b/src/source/delay.rs index 5233f3e3..1f70d672 100644 --- a/src/source/delay.rs +++ b/src/source/delay.rs @@ -2,6 +2,8 @@ use std::time::Duration; use crate::{Sample, Source}; +use super::SeekNotSupported; + /// Internal function that builds a `Delay` object. pub fn delay(input: I, duration: Duration) -> Delay where @@ -105,4 +107,10 @@ where .total_duration() .map(|val| val + self.requested_duration) } + + #[inline] + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + let pos_without_delay = pos.saturating_sub(self.requested_duration); + self.input.try_seek(pos_without_delay) + } } diff --git a/src/source/done.rs b/src/source/done.rs index c1fd76ca..99ed06c3 100644 --- a/src/source/done.rs +++ b/src/source/done.rs @@ -4,6 +4,8 @@ use std::time::Duration; use crate::{Sample, Source}; +use super::SeekNotSupported; + /// When the inner source is empty this decrements an `AtomicUsize`. #[derive(Debug, Clone)] pub struct Done { @@ -87,4 +89,9 @@ where fn total_duration(&self) -> Option { self.input.total_duration() } + + #[inline] + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + self.input.try_seek(pos) + } } diff --git a/src/source/empty.rs b/src/source/empty.rs index d4c808b1..e4f118db 100644 --- a/src/source/empty.rs +++ b/src/source/empty.rs @@ -3,6 +3,8 @@ use std::time::Duration; use crate::{Sample, Source}; +use super::SeekNotSupported; + /// An empty source. #[derive(Debug, Copy, Clone)] pub struct Empty(PhantomData); @@ -53,4 +55,9 @@ where fn total_duration(&self) -> Option { Some(Duration::new(0, 0)) } + + #[inline] + fn try_seek(&mut self, _: Duration) -> Result<(), SeekNotSupported> { + Err(SeekNotSupported) + } } diff --git a/src/source/empty_callback.rs b/src/source/empty_callback.rs index 163f3bea..26812cb6 100644 --- a/src/source/empty_callback.rs +++ b/src/source/empty_callback.rs @@ -3,6 +3,8 @@ use std::time::Duration; use crate::{Sample, Source}; +use super::SeekNotSupported; + /// An empty source which executes a callback function pub struct EmptyCallback { pub phantom_data: PhantomData, @@ -52,4 +54,9 @@ where fn total_duration(&self) -> Option { Some(Duration::new(0, 0)) } + + #[inline] + fn try_seek(&mut self, _: Duration) -> Result<(), SeekNotSupported> { + Err(SeekNotSupported) + } } diff --git a/src/source/fadein.rs b/src/source/fadein.rs index 25d83d24..518a139e 100644 --- a/src/source/fadein.rs +++ b/src/source/fadein.rs @@ -2,6 +2,8 @@ use std::time::Duration; use crate::{Sample, Source}; +use super::SeekNotSupported; + /// Internal function that builds a `FadeIn` object. pub fn fadein(input: I, duration: Duration) -> FadeIn where @@ -105,4 +107,9 @@ where fn total_duration(&self) -> Option { self.input.total_duration() } + + #[inline] + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + self.input.try_seek(pos) + } } diff --git a/src/source/from_iter.rs b/src/source/from_iter.rs index 1ea46024..b40a2953 100644 --- a/src/source/from_iter.rs +++ b/src/source/from_iter.rs @@ -2,6 +2,8 @@ use std::time::Duration; use crate::{Sample, Source}; +use super::SeekNotSupported; + /// Builds a source that chains sources provided by an iterator. /// /// The `iterator` parameter is an iterator that produces a source. The source is then played. @@ -135,6 +137,15 @@ where fn total_duration(&self) -> Option { None } + + #[inline] + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + if let Some(source) = self.current_source.as_mut() { + source.try_seek(pos) + } else { + Ok(()) + } + } } #[cfg(test)] diff --git a/src/source/mix.rs b/src/source/mix.rs index 8afb99fd..d055797f 100644 --- a/src/source/mix.rs +++ b/src/source/mix.rs @@ -1,6 +1,7 @@ use std::cmp; use std::time::Duration; +use crate::source::SeekNotSupported; use crate::source::uniform::UniformSourceIterator; use crate::{Sample, Source}; use cpal::{FromSample, Sample as CpalSample}; @@ -119,4 +120,10 @@ where _ => None, } } + + #[inline] + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + self.input1.try_seek(pos)?; + self.input1.try_seek(pos) + } } diff --git a/src/source/mod.rs b/src/source/mod.rs index 2748fca8..7d54de54 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -355,6 +355,8 @@ where blt::high_pass(self, freq) } + /// Set position + /// /// Try to seek to a pos, returns [`SeekNotSupported`] if seeking is not /// supported by the current source. fn try_seek(&mut self, _: Duration) -> Result<(), SeekNotSupported> { @@ -362,7 +364,7 @@ where } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct SeekNotSupported; impl fmt::Display for SeekNotSupported { @@ -396,6 +398,11 @@ where fn total_duration(&self) -> Option { (**self).total_duration() } + + #[inline] + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + (**self).try_seek(pos) + } } impl Source for Box + Send> @@ -421,6 +428,11 @@ where fn total_duration(&self) -> Option { (**self).total_duration() } + + #[inline] + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + (**self).try_seek(pos) + } } impl Source for Box + Send + Sync> @@ -446,5 +458,10 @@ where fn total_duration(&self) -> Option { (**self).total_duration() } + + #[inline] + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + (**self).try_seek(pos) + } } diff --git a/src/source/pausable.rs b/src/source/pausable.rs index 75e102f4..5a0f5ba1 100644 --- a/src/source/pausable.rs +++ b/src/source/pausable.rs @@ -2,6 +2,8 @@ use std::time::Duration; use crate::{Sample, Source}; +use super::SeekNotSupported; + /// Internal function that builds a `Pausable` object. pub fn pausable(source: I, paused: bool) -> Pausable where @@ -115,4 +117,9 @@ where fn total_duration(&self) -> Option { self.input.total_duration() } + + #[inline] + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + self.input.try_seek(pos) + } } diff --git a/src/source/periodic.rs b/src/source/periodic.rs index 4d718a1c..b4a5ed99 100644 --- a/src/source/periodic.rs +++ b/src/source/periodic.rs @@ -2,6 +2,8 @@ use std::time::Duration; use crate::{Sample, Source}; +use super::SeekNotSupported; + /// Internal function that builds a `PeriodicAccess` object. pub fn periodic(source: I, period: Duration, modifier: F) -> PeriodicAccess where @@ -117,6 +119,11 @@ where fn total_duration(&self) -> Option { self.input.total_duration() } + + #[inline] + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + self.input.try_seek(pos) + } } #[cfg(test)] diff --git a/src/source/repeat.rs b/src/source/repeat.rs index c8c8695d..30d4411b 100644 --- a/src/source/repeat.rs +++ b/src/source/repeat.rs @@ -4,6 +4,8 @@ use crate::source::buffered::Buffered; use crate::{Sample, Source}; +use super::SeekNotSupported; + /// Internal function that builds a `Repeat` object. pub fn repeat(input: I) -> Repeat where @@ -84,6 +86,11 @@ where fn total_duration(&self) -> Option { None } + + #[inline] + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + self.inner.try_seek(pos) + } } impl Clone for Repeat diff --git a/src/source/samples_converter.rs b/src/source/samples_converter.rs index 827f071b..929476d8 100644 --- a/src/source/samples_converter.rs +++ b/src/source/samples_converter.rs @@ -4,6 +4,8 @@ use std::time::Duration; use crate::{Sample, Source}; use cpal::{FromSample, Sample as CpalSample}; +use super::SeekNotSupported; + /// An iterator that reads from a `Source` and converts the samples to a specific rate and /// channels count. /// @@ -95,4 +97,9 @@ where fn total_duration(&self) -> Option { self.inner.total_duration() } + + #[inline] + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + self.inner.try_seek(pos) + } } diff --git a/src/source/sine.rs b/src/source/sine.rs index cbf9881d..a90e4007 100644 --- a/src/source/sine.rs +++ b/src/source/sine.rs @@ -3,6 +3,8 @@ use std::time::Duration; use crate::Source; +use super::SeekNotSupported; + /// An infinite source that produces a sine. /// /// Always has a rate of 48kHz and one channel. @@ -17,7 +19,7 @@ impl SineWave { #[inline] pub fn new(freq: f32) -> SineWave { SineWave { - freq: freq, + freq, num_sample: 0, } } @@ -55,4 +57,12 @@ impl Source for SineWave { fn total_duration(&self) -> Option { None } + + #[inline] + fn try_seek(&mut self, _: Duration) -> Result<(), SeekNotSupported> { + // It is possible to write an implementation that skips the right + // amount of samples to get into the right phase. I do not think there + // is a use case for that however. + Err(SeekNotSupported) + } } diff --git a/src/source/skip.rs b/src/source/skip.rs index a933af46..e9813cb5 100644 --- a/src/source/skip.rs +++ b/src/source/skip.rs @@ -2,6 +2,8 @@ use std::time::Duration; use crate::{Sample, Source}; +use super::SeekNotSupported; + const NS_PER_SECOND: u128 = 1_000_000_000; /// Internal function that builds a `SkipDuration` object. @@ -157,6 +159,11 @@ where .unwrap_or_else(|| Duration::from_secs(0)) }) } + + #[inline] + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + self.input.try_seek(pos) + } } #[cfg(test)] diff --git a/src/source/skippable.rs b/src/source/skippable.rs index 44ee7a7d..f0d4fb38 100644 --- a/src/source/skippable.rs +++ b/src/source/skippable.rs @@ -3,6 +3,8 @@ use std::time::Duration; use crate::Sample; use crate::Source; +use super::SeekNotSupported; + /// Internal function that builds a `Skippable` object. pub fn skippable(source: I) -> Skippable { Skippable { @@ -89,4 +91,9 @@ where fn total_duration(&self) -> Option { self.input.total_duration() } + + #[inline] + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + self.input.try_seek(pos) + } } diff --git a/src/source/spatial.rs b/src/source/spatial.rs index e846ecd1..f89c3cc2 100644 --- a/src/source/spatial.rs +++ b/src/source/spatial.rs @@ -3,6 +3,8 @@ use std::time::Duration; use crate::source::ChannelVolume; use crate::{Sample, Source}; +use super::SeekNotSupported; + /// Combines channels in input into a single mono source, then plays that mono sound /// to each channel at the volume given for that channel. #[derive(Clone)] @@ -117,4 +119,9 @@ where fn total_duration(&self) -> Option { self.input.total_duration() } + + #[inline] + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + self.input.try_seek(pos) + } } diff --git a/src/source/speed.rs b/src/source/speed.rs index 5598be9f..e73c93bd 100644 --- a/src/source/speed.rs +++ b/src/source/speed.rs @@ -2,6 +2,8 @@ use std::time::Duration; use crate::{Sample, Source}; +use super::SeekNotSupported; + /// Internal function that builds a `Speed` object. pub fn speed(input: I, factor: f32) -> Speed { Speed { input, factor } @@ -91,16 +93,17 @@ where #[inline] fn total_duration(&self) -> Option { - // TODO: the crappy API of duration makes this code difficult to write - if let Some(duration) = self.input.total_duration() { - let as_ns = duration.as_secs() * 1000000000 + duration.subsec_nanos() as u64; - let new_val = (as_ns as f32 / self.factor) as u64; - Some(Duration::new( - new_val / 1000000000, - (new_val % 1000000000) as u32, - )) - } else { - None - } + self.input.total_duration().map(|d| d.mul_f32(self.factor)) + } + + #[inline] + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + /* TODO: This might be wrong, I do not know how speed achieves its speedup + * so I can not reason about the correctness. + * */ + + // even after 24 hours of playback f32 has enough precision + let pos_accounting_for_speedup = pos.mul_f32(self.factor); + self.input.try_seek(pos_accounting_for_speedup) } } diff --git a/src/source/stoppable.rs b/src/source/stoppable.rs index afaa35f4..fe46fe51 100644 --- a/src/source/stoppable.rs +++ b/src/source/stoppable.rs @@ -2,6 +2,8 @@ use std::time::Duration; use crate::{Sample, Source}; +use super::SeekNotSupported; + /// Internal function that builds a `Stoppable` object. pub fn stoppable(source: I) -> Stoppable { Stoppable { @@ -88,4 +90,9 @@ where fn total_duration(&self) -> Option { self.input.total_duration() } + + #[inline] + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + self.input.try_seek(pos) + } } diff --git a/src/source/take.rs b/src/source/take.rs index 9e682ec5..39dba28a 100644 --- a/src/source/take.rs +++ b/src/source/take.rs @@ -2,6 +2,8 @@ use std::time::Duration; use crate::{Sample, Source}; +use super::SeekNotSupported; + /// Internal function that builds a `TakeDuration` object. pub fn take_duration(input: I, duration: Duration) -> TakeDuration where @@ -176,4 +178,9 @@ where None } } + + #[inline] + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + self.input.try_seek(pos) + } } diff --git a/src/source/uniform.rs b/src/source/uniform.rs index 823cb5db..2680ae76 100644 --- a/src/source/uniform.rs +++ b/src/source/uniform.rs @@ -6,6 +6,8 @@ use cpal::FromSample; use crate::conversions::{ChannelCountConverter, DataConverter, SampleRateConverter}; use crate::{Sample, Source}; +use super::SeekNotSupported; + /// An iterator that reads from a `Source` and converts the samples to a specific rate and /// channels count. /// @@ -137,6 +139,20 @@ where fn total_duration(&self) -> Option { self.total_duration } + + #[inline] + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + if let Some(input) = self.inner.as_mut() { + input + .inner_mut() + .inner_mut() + .inner_mut() + .inner_mut() + .try_seek(pos) + } else { + Ok(()) + } + } } #[derive(Clone, Debug)] @@ -145,6 +161,13 @@ struct Take { n: Option, } +impl Take { + #[inline] + pub fn inner_mut(&mut self) -> &mut I { + &mut self.iter + } +} + impl Iterator for Take where I: Iterator, diff --git a/src/source/zero.rs b/src/source/zero.rs index 079d8588..87731f44 100644 --- a/src/source/zero.rs +++ b/src/source/zero.rs @@ -3,6 +3,8 @@ use std::time::Duration; use crate::{Sample, Source}; +use super::SeekNotSupported; + /// An infinite source that produces zero. #[derive(Clone, Debug)] pub struct Zero { @@ -77,4 +79,9 @@ where fn total_duration(&self) -> Option { None } + + #[inline] + fn try_seek(&mut self, _: Duration) -> Result<(), SeekNotSupported> { + Ok(()) + } } diff --git a/tests/seek_error.rs b/tests/seek_error.rs new file mode 100644 index 00000000..453376f1 --- /dev/null +++ b/tests/seek_error.rs @@ -0,0 +1,28 @@ +use std::io::BufReader; +use std::time::Duration; + +// hound wav decoder does not support seeking +#[cfg(feature = "hound")] +#[test] +fn seek_not_supported_returns_err() { + let (_stream, handle) = rodio::OutputStream::try_default().unwrap(); + let sink = rodio::Sink::try_new(&handle).unwrap(); + + let file = std::fs::File::open("assets/music.wav").unwrap(); + sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap()); + let res = sink.try_seek(Duration::from_secs(5)); + assert_eq!(res, Err(rodio::source::SeekNotSupported)); +} + +// mp3 decoder does support seeking +#[cfg(feature = "mp3")] +#[test] +fn seek_supported_returns_ok() { + 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()); + let res = sink.try_seek(Duration::from_secs(5)); + assert_eq!(res, Ok(())); +} From 1f3f36a4c8ac3a8d87a69cc45ab40c8760b39579 Mon Sep 17 00:00:00 2001 From: dvdsk Date: Wed, 4 Oct 2023 12:05:03 +0200 Subject: [PATCH 07/62] removes default try_seek impl, impl try_seek for decoders + refactors decoderimpl a bit --- src/buffer.rs | 6 + src/decoder/flac.rs | 6 + src/decoder/mod.rs | 288 +++++++++++++++++------------------ src/decoder/mp3.rs | 2 +- src/decoder/vorbis.rs | 6 + src/decoder/wav.rs | 6 + src/dynamic_mixer.rs | 12 +- src/queue.rs | 8 +- src/source/buffered.rs | 2 +- src/source/empty.rs | 2 +- src/source/empty_callback.rs | 2 +- src/source/mod.rs | 6 +- src/source/sine.rs | 2 +- src/static_buffer.rs | 6 + tests/seek_error.rs | 2 +- 15 files changed, 200 insertions(+), 156 deletions(-) diff --git a/src/buffer.rs b/src/buffer.rs index 916de48b..cfde0f45 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -13,6 +13,7 @@ use std::time::Duration; use std::vec::IntoIter as VecIntoIter; +use crate::source::SeekNotSupported; use crate::{Sample, Source}; /// A buffer of samples treated as a source. @@ -84,6 +85,11 @@ where fn total_duration(&self) -> Option { Some(self.duration) } + + #[inline] + fn try_seek(&mut self, _: Duration) -> Result<(), SeekNotSupported> { + Err(SeekNotSupported { source: std::any::type_name::() }) + } } impl Iterator for SamplesBuffer diff --git a/src/decoder/flac.rs b/src/decoder/flac.rs index 7b2d360e..f33d0c1e 100644 --- a/src/decoder/flac.rs +++ b/src/decoder/flac.rs @@ -4,6 +4,7 @@ use std::mem; use std::time::Duration; use crate::Source; +use crate::source::SeekNotSupported; use claxon::FlacReader; @@ -79,6 +80,11 @@ 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<(), SeekNotSupported> { + Err(SeekNotSupported { source: std::any::type_name::() }) + } } impl Iterator for FlacDecoder diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index ae5f4311..ae6c1d7a 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -9,6 +9,7 @@ use std::mem; use std::str::FromStr; use std::time::Duration; +use crate::source::SeekNotSupported; use crate::Source; #[cfg(feature = "symphonia")] @@ -57,6 +58,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<(), SeekNotSupported> { + 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(SeekNotSupported { + source: "DecoderImpl::None", + }), + } + } +} + impl Decoder where R: Read + Seek + Send + Sync + 'static, @@ -261,36 +385,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() } } @@ -300,70 +400,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<(), SeekNotSupported> { + self.0.try_seek(pos) } } @@ -375,19 +431,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())); @@ -444,19 +488,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() } } @@ -466,59 +498,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<(), SeekNotSupported> { + 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 93523528..91e6edfe 100644 --- a/src/decoder/mp3.rs +++ b/src/decoder/mp3.rs @@ -71,7 +71,7 @@ where 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).is_ok(); + let ignore_err = self.decoder.seek_samples(pos); Ok(()) } } diff --git a/src/decoder/vorbis.rs b/src/decoder/vorbis.rs index 11a5bd9e..97933e98 100644 --- a/src/decoder/vorbis.rs +++ b/src/decoder/vorbis.rs @@ -3,6 +3,7 @@ use std::time::Duration; use std::vec; use crate::Source; +use crate::source::SeekNotSupported; use lewton::inside_ogg::OggStreamReader; @@ -73,6 +74,11 @@ where fn total_duration(&self) -> Option { None } + + #[inline] + fn try_seek(&mut self, _: Duration) -> Result<(), SeekNotSupported> { + Err(SeekNotSupported { source: std::any::type_name::() }) + } } impl Iterator for VorbisDecoder diff --git a/src/decoder/wav.rs b/src/decoder/wav.rs index 5c611ee9..bb0ce3e1 100644 --- a/src/decoder/wav.rs +++ b/src/decoder/wav.rs @@ -2,6 +2,7 @@ use std::io::{Read, Seek, SeekFrom}; use std::time::Duration; use crate::Source; +use crate::source::SeekNotSupported; use hound::{SampleFormat, WavReader}; @@ -128,6 +129,11 @@ where fn total_duration(&self) -> Option { Some(self.total_duration) } + + #[inline] + fn try_seek(&mut self, _: Duration) -> Result<(), SeekNotSupported> { + Err(SeekNotSupported { source: std::any::type_name::() }) + } } impl Iterator for WavDecoder diff --git a/src/dynamic_mixer.rs b/src/dynamic_mixer.rs index 045aed8e..25261f33 100644 --- a/src/dynamic_mixer.rs +++ b/src/dynamic_mixer.rs @@ -4,7 +4,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; use std::time::Duration; -use crate::source::{Source, UniformSourceIterator}; +use crate::source::{SeekNotSupported, Source, UniformSourceIterator}; use crate::Sample; /// Builds a new mixer. @@ -106,6 +106,16 @@ where fn total_duration(&self) -> Option { None } + + #[inline] + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + todo!("needs Source::can_seek") + // if self.current_sources.iter().all(Source::can_seek) { + // for source in self.current_sources { + // source.try_seek().expect("we just verified they can") + // } + // } + } } impl Iterator for DynamicMixer diff --git a/src/queue.rs b/src/queue.rs index 0cfe9f53..efc39159 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -4,7 +4,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; use std::time::Duration; -use crate::source::{Empty, Source, Zero}; +use crate::source::{Empty, Source, Zero, SeekNotSupported}; use crate::Sample; #[cfg(feature = "crossbeam-channel")] @@ -169,6 +169,12 @@ where fn total_duration(&self) -> Option { None } + + #[inline] + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + self.current.try_seek(pos) + + } } impl Iterator for SourcesQueueOutput diff --git a/src/source/buffered.rs b/src/source/buffered.rs index ad3a5bea..9ba1624c 100644 --- a/src/source/buffered.rs +++ b/src/source/buffered.rs @@ -244,7 +244,7 @@ where #[inline] fn try_seek(&mut self, _: Duration) -> Result<(), SeekNotSupported> { - Err(SeekNotSupported) + Err(SeekNotSupported { source: std::any::type_name::() }) } } diff --git a/src/source/empty.rs b/src/source/empty.rs index e4f118db..c7adb8e3 100644 --- a/src/source/empty.rs +++ b/src/source/empty.rs @@ -58,6 +58,6 @@ where #[inline] fn try_seek(&mut self, _: Duration) -> Result<(), SeekNotSupported> { - Err(SeekNotSupported) + Err(SeekNotSupported { source: std::any::type_name::() }) } } diff --git a/src/source/empty_callback.rs b/src/source/empty_callback.rs index 26812cb6..0ebf25e9 100644 --- a/src/source/empty_callback.rs +++ b/src/source/empty_callback.rs @@ -57,6 +57,6 @@ where #[inline] fn try_seek(&mut self, _: Duration) -> Result<(), SeekNotSupported> { - Err(SeekNotSupported) + Err(SeekNotSupported { source: std::any::type_name::() }) } } diff --git a/src/source/mod.rs b/src/source/mod.rs index 7d54de54..1d115bf6 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -359,13 +359,11 @@ where /// /// Try to seek to a pos, returns [`SeekNotSupported`] if seeking is not /// supported by the current source. - fn try_seek(&mut self, _: Duration) -> Result<(), SeekNotSupported> { - Err(SeekNotSupported) - } + fn try_seek(&mut self, _: Duration) -> Result<(), SeekNotSupported>; } #[derive(Debug, Clone, PartialEq, Eq)] -pub struct SeekNotSupported; +pub struct SeekNotSupported{ pub source: &'static str } impl fmt::Display for SeekNotSupported { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/src/source/sine.rs b/src/source/sine.rs index a90e4007..3985270f 100644 --- a/src/source/sine.rs +++ b/src/source/sine.rs @@ -63,6 +63,6 @@ impl Source for SineWave { // It is possible to write an implementation that skips the right // amount of samples to get into the right phase. I do not think there // is a use case for that however. - Err(SeekNotSupported) + Err(SeekNotSupported { source: std::any::type_name::() }) } } diff --git a/src/static_buffer.rs b/src/static_buffer.rs index c6264aa1..cb0cd4f5 100644 --- a/src/static_buffer.rs +++ b/src/static_buffer.rs @@ -13,6 +13,7 @@ use std::slice::Iter as SliceIter; use std::time::Duration; +use crate::source::SeekNotSupported; use crate::{Sample, Source}; /// A buffer of samples treated as a source. @@ -84,6 +85,11 @@ where fn total_duration(&self) -> Option { Some(self.duration) } + + #[inline] + fn try_seek(&mut self, _: Duration) -> Result<(), SeekNotSupported> { + Err(SeekNotSupported { source: std::any::type_name::() }) + } } impl Iterator for StaticSamplesBuffer diff --git a/tests/seek_error.rs b/tests/seek_error.rs index 453376f1..ca43ab7c 100644 --- a/tests/seek_error.rs +++ b/tests/seek_error.rs @@ -11,7 +11,7 @@ fn seek_not_supported_returns_err() { let file = std::fs::File::open("assets/music.wav").unwrap(); sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap()); let res = sink.try_seek(Duration::from_secs(5)); - assert_eq!(res, Err(rodio::source::SeekNotSupported)); + assert_eq!(res, Err(rodio::source::SeekNotSupported { source: "test" })); } // mp3 decoder does support seeking From 5b933d7af1e65e53c687525e98a4be9f483cd18a Mon Sep 17 00:00:00 2001 From: dvdsk Date: Wed, 4 Oct 2023 16:49:35 +0200 Subject: [PATCH 08/62] refactors seektest and adds more formats --- tests/seek.rs | 52 +++++++++++++++++++++++++++++++++++++++++++++ tests/seek_error.rs | 28 ------------------------ 2 files changed, 52 insertions(+), 28 deletions(-) create mode 100644 tests/seek.rs delete mode 100644 tests/seek_error.rs diff --git a/tests/seek.rs b/tests/seek.rs new file mode 100644 index 00000000..9a6bd117 --- /dev/null +++ b/tests/seek.rs @@ -0,0 +1,52 @@ +use std::io::BufReader; +use std::path::Path; +use std::time::Duration; + +use rodio::source::SeekNotSupported; + +// hound wav decoder does not support seeking +#[cfg(feature = "hound")] +#[test] +fn seek_not_supported_returns_err() { + let (_stream, handle) = rodio::OutputStream::try_default().unwrap(); + let sink = rodio::Sink::try_new(&handle).unwrap(); + + let file = std::fs::File::open("assets/music.wav").unwrap(); + sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap()); + let res = sink.try_seek(Duration::from_secs(5)); + + let Err(rodio::source::SeekNotSupported { source }) = res else { + panic!("result of try_seek should be error SourceNotSupported") + }; + + assert!(source.starts_with("rodio::decoder::wav::WavDecoder")); +} + +fn play_and_seek(asset_path: &Path) -> Result<(), SeekNotSupported> { + let (_stream, handle) = rodio::OutputStream::try_default().unwrap(); + let sink = rodio::Sink::try_new(&handle).unwrap(); + + let file = std::fs::File::open(asset_path).unwrap(); + sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap()); + sink.try_seek(Duration::from_secs(2)) +} + +#[test] +fn seek_returns_err_if_unsupported() { + let formats = ["mp3", "wav", "ogg", "flac", "m4a"].into_iter(); + #[cfg(not(feature = "symphonia"))] + let supported = [true, false, false, false, false].into_iter(); + #[cfg(feature = "symphonia")] + let supported = [true, true, true, true, true].into_iter(); + #[cfg(not(feature = "symphonia"))] + let decoder = ["minimp3", "hound", "lewton", "claxon", "_"].into_iter(); + #[cfg(feature = "symphonia")] + let decoder = ["symphonia"].into_iter().cycle(); + + for ((format, supported), decoder) in formats.zip(supported).zip(decoder) { + println!("trying: {format} by {decoder}, should support seek: {supported}"); + let asset = Path::new("assets/music").with_extension(format); + let res = play_and_seek(&asset); + assert_eq!(res.is_ok(), supported); + } +} diff --git a/tests/seek_error.rs b/tests/seek_error.rs deleted file mode 100644 index ca43ab7c..00000000 --- a/tests/seek_error.rs +++ /dev/null @@ -1,28 +0,0 @@ -use std::io::BufReader; -use std::time::Duration; - -// hound wav decoder does not support seeking -#[cfg(feature = "hound")] -#[test] -fn seek_not_supported_returns_err() { - let (_stream, handle) = rodio::OutputStream::try_default().unwrap(); - let sink = rodio::Sink::try_new(&handle).unwrap(); - - let file = std::fs::File::open("assets/music.wav").unwrap(); - sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap()); - let res = sink.try_seek(Duration::from_secs(5)); - assert_eq!(res, Err(rodio::source::SeekNotSupported { source: "test" })); -} - -// mp3 decoder does support seeking -#[cfg(feature = "mp3")] -#[test] -fn seek_supported_returns_ok() { - 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()); - let res = sink.try_seek(Duration::from_secs(5)); - assert_eq!(res, Ok(())); -} From 961c3efbb0e2cfed34b80086222e905e4975b7f7 Mon Sep 17 00:00:00 2001 From: David Kleingeld Date: Wed, 4 Oct 2023 16:50:31 +0200 Subject: [PATCH 09/62] Fix seeking for mix source add todo about can_seek to mix Co-authored-by: naglis <827324+naglis@users.noreply.github.com> --- src/source/mix.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/source/mix.rs b/src/source/mix.rs index d055797f..3c011b88 100644 --- a/src/source/mix.rs +++ b/src/source/mix.rs @@ -123,7 +123,8 @@ where #[inline] fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + todo!("should check if both inputs support seeking"); self.input1.try_seek(pos)?; - self.input1.try_seek(pos) + self.input2.try_seek(pos) } } From e1092f712da23ee6060369bb9a2dba131f4b3e46 Mon Sep 17 00:00:00 2001 From: dvdsk Date: Thu, 5 Oct 2023 13:28:59 +0200 Subject: [PATCH 10/62] refactors seek test, now covers all decoders/formats --- tests/seek.rs | 51 ++++++++++++++++++++++----------------------------- 1 file changed, 22 insertions(+), 29 deletions(-) diff --git a/tests/seek.rs b/tests/seek.rs index 9a6bd117..ed8600b7 100644 --- a/tests/seek.rs +++ b/tests/seek.rs @@ -4,24 +4,6 @@ use std::time::Duration; use rodio::source::SeekNotSupported; -// hound wav decoder does not support seeking -#[cfg(feature = "hound")] -#[test] -fn seek_not_supported_returns_err() { - let (_stream, handle) = rodio::OutputStream::try_default().unwrap(); - let sink = rodio::Sink::try_new(&handle).unwrap(); - - let file = std::fs::File::open("assets/music.wav").unwrap(); - sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap()); - let res = sink.try_seek(Duration::from_secs(5)); - - let Err(rodio::source::SeekNotSupported { source }) = res else { - panic!("result of try_seek should be error SourceNotSupported") - }; - - assert!(source.starts_with("rodio::decoder::wav::WavDecoder")); -} - fn play_and_seek(asset_path: &Path) -> Result<(), SeekNotSupported> { let (_stream, handle) = rodio::OutputStream::try_default().unwrap(); let sink = rodio::Sink::try_new(&handle).unwrap(); @@ -33,17 +15,28 @@ fn play_and_seek(asset_path: &Path) -> Result<(), SeekNotSupported> { #[test] fn seek_returns_err_if_unsupported() { - let formats = ["mp3", "wav", "ogg", "flac", "m4a"].into_iter(); - #[cfg(not(feature = "symphonia"))] - let supported = [true, false, false, false, false].into_iter(); - #[cfg(feature = "symphonia")] - let supported = [true, true, true, true, true].into_iter(); - #[cfg(not(feature = "symphonia"))] - let decoder = ["minimp3", "hound", "lewton", "claxon", "_"].into_iter(); - #[cfg(feature = "symphonia")] - let decoder = ["symphonia"].into_iter().cycle(); - - for ((format, supported), decoder) in formats.zip(supported).zip(decoder) { + let formats = [ + #[cfg(feature = "minimp3")] + ("mp3", true, "minimp3"), + #[cfg(feature = "symphonia-mp3")] + ("mp3", true, "symphonia"), + #[cfg(feature = "hound")] + ("wav", false, "hound"), + #[cfg(feature = "symphonia-wav")] + ("wav", true, "symphonia"), + #[cfg(feature = "lewton")] + ("ogg", false, "lewton"), + #[cfg(feature = "symphonia-vorbis")] + ("ogg", true, "symphonia"), + #[cfg(feature = "claxon")] + ("flac", false, "claxon"), + #[cfg(feature = "symphonia-flac")] + ("flac", true, "symphonia"), + #[cfg(feature = "symphonia-isomp4")] + ("m4a", true, "_"), + ]; + + for (format, supported, decoder) in formats { println!("trying: {format} by {decoder}, should support seek: {supported}"); let asset = Path::new("assets/music").with_extension(format); let res = play_and_seek(&asset); From a3c55b8337b4a33e2737437a60f1c9a940a7290c Mon Sep 17 00:00:00 2001 From: dvdsk Date: Thu, 5 Oct 2023 13:29:16 +0200 Subject: [PATCH 11/62] fixes symphonia seek div by zero --- src/decoder/symphonia.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/decoder/symphonia.rs b/src/decoder/symphonia.rs index f11ca3c7..af33d344 100644 --- a/src/decoder/symphonia.rs +++ b/src/decoder/symphonia.rs @@ -13,7 +13,7 @@ use symphonia::{ default::get_probe, }; -use crate::{Source, source::SeekNotSupported}; +use crate::{source::SeekNotSupported, Source}; use super::DecoderError; @@ -143,12 +143,17 @@ impl Source for SymphoniaDecoder { fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { use symphonia::core::formats::{SeekMode, SeekTo}; - let pos_fract = 1f64 / pos.subsec_nanos() as f64; + let pos_fract = if pos.subsec_nanos() == 0 { + 0f64 + } else { + 1f64 / pos.subsec_nanos() as f64 + }; + self.format .seek( SeekMode::Accurate, SeekTo::Time { - time: Time::new(pos.as_secs(), pos_fract), + time: Time::new(pos.as_secs(), dbg!(pos_fract)), track_id: None, }, ) From d8a8be4a2feefb10fa917627346246fcf74ed296 Mon Sep 17 00:00:00 2001 From: dvdsk Date: Thu, 5 Oct 2023 13:44:10 +0200 Subject: [PATCH 12/62] add seek for lewton --- src/decoder/vorbis.rs | 7 +++++-- tests/seek.rs | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/decoder/vorbis.rs b/src/decoder/vorbis.rs index 97933e98..3588e4f4 100644 --- a/src/decoder/vorbis.rs +++ b/src/decoder/vorbis.rs @@ -76,8 +76,11 @@ where } #[inline] - fn try_seek(&mut self, _: Duration) -> Result<(), SeekNotSupported> { - Err(SeekNotSupported { source: std::any::type_name::() }) + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + // number of PCM samples per channel + let samples = pos.as_secs_f32() * self.sample_rate() as f32; + self.stream_reader.seek_absgp_pg(samples as u64).unwrap(); + Ok(()) } } diff --git a/tests/seek.rs b/tests/seek.rs index ed8600b7..7a64d0ee 100644 --- a/tests/seek.rs +++ b/tests/seek.rs @@ -25,7 +25,7 @@ fn seek_returns_err_if_unsupported() { #[cfg(feature = "symphonia-wav")] ("wav", true, "symphonia"), #[cfg(feature = "lewton")] - ("ogg", false, "lewton"), + ("ogg", true, "lewton"), #[cfg(feature = "symphonia-vorbis")] ("ogg", true, "symphonia"), #[cfg(feature = "claxon")] From 9a4dcb0c4147e39149fb9d64aa70a9a518325ed4 Mon Sep 17 00:00:00 2001 From: dvdsk Date: Fri, 6 Oct 2023 13:30:32 +0200 Subject: [PATCH 13/62] add can_seek method to source document why Sink::can_seek does not exist --- src/buffer.rs | 5 +++++ src/conversions/channels.rs | 6 ++++++ src/conversions/sample.rs | 6 ++++++ src/conversions/sample_rate.rs | 6 ++++++ src/decoder/flac.rs | 5 +++++ src/decoder/mod.rs | 26 ++++++++++++++++++++++++++ src/decoder/symphonia.rs | 5 +++++ src/decoder/vorbis.rs | 9 ++++++++- src/decoder/wav.rs | 13 ++++++++++--- src/dynamic_mixer.rs | 22 ++++++++++++++++------ src/queue.rs | 6 +++++- src/sink.rs | 6 ++++++ src/source/amplify.rs | 5 +++++ src/source/blt.rs | 5 +++++ src/source/buffered.rs | 5 +++++ src/source/channel_volume.rs | 5 +++++ src/source/delay.rs | 5 +++++ src/source/done.rs | 5 +++++ src/source/empty.rs | 5 +++++ src/source/empty_callback.rs | 5 +++++ src/source/fadein.rs | 5 +++++ src/source/from_iter.rs | 11 +++++++++++ src/source/mix.rs | 20 ++++++++++++++++---- src/source/mod.rs | 19 +++++++++++++++++++ src/source/pausable.rs | 5 +++++ src/source/periodic.rs | 5 +++++ src/source/repeat.rs | 5 +++++ src/source/samples_converter.rs | 5 +++++ src/source/sine.rs | 13 +++++++++---- src/source/skip.rs | 5 +++++ src/source/skippable.rs | 5 +++++ src/source/spatial.rs | 5 +++++ src/source/speed.rs | 5 +++++ src/source/stoppable.rs | 5 +++++ src/source/take.rs | 5 +++++ src/source/uniform.rs | 14 ++++++++++++++ src/source/zero.rs | 5 +++++ src/static_buffer.rs | 5 +++++ tests/seek.rs | 2 +- 39 files changed, 279 insertions(+), 20 deletions(-) diff --git a/src/buffer.rs b/src/buffer.rs index cfde0f45..57ad81f2 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -90,6 +90,11 @@ where fn try_seek(&mut self, _: Duration) -> Result<(), SeekNotSupported> { Err(SeekNotSupported { source: std::any::type_name::() }) } + + #[inline] + fn can_seek(&self) -> bool { + false + } } impl Iterator for SamplesBuffer diff --git a/src/conversions/channels.rs b/src/conversions/channels.rs index c42b0104..25834c63 100644 --- a/src/conversions/channels.rs +++ b/src/conversions/channels.rs @@ -50,6 +50,12 @@ where pub fn inner_mut(&mut self) -> &mut I { &mut self.input } + + /// Get a reference to the iterator + #[inline] + pub fn inner(&self) -> &I { + &self.input + } } impl Iterator for ChannelCountConverter diff --git a/src/conversions/sample.rs b/src/conversions/sample.rs index 7ee97624..39fe3d8d 100644 --- a/src/conversions/sample.rs +++ b/src/conversions/sample.rs @@ -29,6 +29,12 @@ impl DataConverter { pub fn inner_mut(&mut self) -> &mut I { &mut self.input } + + /// get a reference to the iterator + #[inline] + pub fn inner(&self) -> &I { + &self.input + } } impl Iterator for DataConverter diff --git a/src/conversions/sample_rate.rs b/src/conversions/sample_rate.rs index 36aa5a5a..c059a6fe 100644 --- a/src/conversions/sample_rate.rs +++ b/src/conversions/sample_rate.rs @@ -108,6 +108,12 @@ where &mut self.input } + /// get a reference to the iterator + #[inline] + pub fn inner(&self) -> &I { + &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 f33d0c1e..27c761c7 100644 --- a/src/decoder/flac.rs +++ b/src/decoder/flac.rs @@ -85,6 +85,11 @@ where fn try_seek(&mut self, _: Duration) -> Result<(), SeekNotSupported> { Err(SeekNotSupported { source: std::any::type_name::() }) } + + #[inline] + fn can_seek(&self) -> bool { + true + } } impl Iterator for FlacDecoder diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index ae6c1d7a..b0077a9a 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -179,6 +179,22 @@ impl DecoderImpl { }), } } + + fn can_seek(&self) -> bool { + match self { + #[cfg(all(feature = "wav", not(feature = "symphonia-wav")))] + DecoderImpl::Wav(source) => source.can_seek(), + #[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))] + DecoderImpl::Vorbis(source) => source.can_seek(), + #[cfg(all(feature = "flac", not(feature = "symphonia-flac")))] + DecoderImpl::Flac(source) => source.can_seek(), + #[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))] + DecoderImpl::Mp3(source) => source.can_seek(), + #[cfg(feature = "symphonia")] + DecoderImpl::Symphonia(source) => source.can_seek(), + DecoderImpl::None(_) => false, + } + } } impl Decoder @@ -421,6 +437,11 @@ where fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { self.0.try_seek(pos) } + + #[inline] + fn can_seek(&self) -> bool { + self.0.can_seek() + } } impl Iterator for LoopedDecoder @@ -519,6 +540,11 @@ where fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { self.0.try_seek(pos) } + + #[inline] + fn can_seek(&self) -> bool { + self.0.can_seek() + } } /// Error that can happen when creating a decoder. diff --git a/src/decoder/symphonia.rs b/src/decoder/symphonia.rs index af33d344..96638824 100644 --- a/src/decoder/symphonia.rs +++ b/src/decoder/symphonia.rs @@ -160,6 +160,11 @@ impl Source for SymphoniaDecoder { .unwrap(); Ok(()) } + + #[inline] + fn can_seek(&self) -> bool { + true + } } impl Iterator for SymphoniaDecoder { diff --git a/src/decoder/vorbis.rs b/src/decoder/vorbis.rs index 3588e4f4..68bb788c 100644 --- a/src/decoder/vorbis.rs +++ b/src/decoder/vorbis.rs @@ -2,8 +2,8 @@ use std::io::{Read, Seek, SeekFrom}; use std::time::Duration; use std::vec; -use crate::Source; use crate::source::SeekNotSupported; +use crate::Source; use lewton::inside_ogg::OggStreamReader; @@ -77,11 +77,18 @@ where #[inline] fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + // number of PCM samples per channel + // number of PCM samples per channel let samples = pos.as_secs_f32() * self.sample_rate() as f32; self.stream_reader.seek_absgp_pg(samples as u64).unwrap(); Ok(()) } + + #[inline] + fn can_seek(&self) -> bool { + true + } } impl Iterator for VorbisDecoder diff --git a/src/decoder/wav.rs b/src/decoder/wav.rs index bb0ce3e1..b670bbae 100644 --- a/src/decoder/wav.rs +++ b/src/decoder/wav.rs @@ -1,8 +1,8 @@ use std::io::{Read, Seek, SeekFrom}; use std::time::Duration; -use crate::Source; use crate::source::SeekNotSupported; +use crate::Source; use hound::{SampleFormat, WavReader}; @@ -131,8 +131,15 @@ where } #[inline] - fn try_seek(&mut self, _: Duration) -> Result<(), SeekNotSupported> { - Err(SeekNotSupported { source: std::any::type_name::() }) + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + let samples = pos.as_secs_f32() * self.sample_rate() as f32; + self.reader.reader.seek(samples as u32).unwrap(); + Ok(()) + } + + #[inline] + fn can_seek(&self) -> bool { + true } } diff --git a/src/dynamic_mixer.rs b/src/dynamic_mixer.rs index 25261f33..4ecb08c2 100644 --- a/src/dynamic_mixer.rs +++ b/src/dynamic_mixer.rs @@ -109,12 +109,22 @@ where #[inline] fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { - todo!("needs Source::can_seek") - // if self.current_sources.iter().all(Source::can_seek) { - // for source in self.current_sources { - // source.try_seek().expect("we just verified they can") - // } - // } + for source in &self.current_sources { + if !source.can_seek() { + return Err(SeekNotSupported { + source: "unknown, one of the sources added to the DynamicMixer", + }); + } + } + for source in &mut self.current_sources { + source.try_seek(pos).expect("we just verified they can") + } + Ok(()) + } + + #[inline] + fn can_seek(&self) -> bool { + self.current_sources.iter().all(Source::can_seek) } } diff --git a/src/queue.rs b/src/queue.rs index efc39159..18fe2a64 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -4,7 +4,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; use std::time::Duration; -use crate::source::{Empty, Source, Zero, SeekNotSupported}; +use crate::source::{Empty, SeekNotSupported, Source, Zero}; use crate::Sample; #[cfg(feature = "crossbeam-channel")] @@ -173,7 +173,11 @@ where #[inline] fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { self.current.try_seek(pos) + } + #[inline] + fn can_seek(&self) -> bool { + self.current.can_seek() } } diff --git a/src/sink.rs b/src/sink.rs index d7d485c2..cbccfe7a 100644 --- a/src/sink.rs +++ b/src/sink.rs @@ -200,6 +200,12 @@ impl Sink { /// /// Try to seek to a pos, returns [`SeekNotSupported`] if seeking is not /// supported by the current source. + /// + /// We do not expose a `can_seek()` method here on purpose as its impossible to + /// use correctly. In between checking if the playing source supports seeking and + /// actually seeking the sink can switch to a new source that potentially does not + /// support seeking. If you find a reason you need `Sink::can_seek()` here please + /// open an issue pub fn try_seek(&self, pos: Duration) -> Result<(), SeekNotSupported> { let (order, feedback) = SeekOrder::new(pos); *self.controls.seek.lock().unwrap() = Some(order); diff --git a/src/source/amplify.rs b/src/source/amplify.rs index 0d2bc270..76a9168d 100644 --- a/src/source/amplify.rs +++ b/src/source/amplify.rs @@ -100,4 +100,9 @@ where fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { self.input.try_seek(pos) } + + #[inline] + fn can_seek(&self) -> bool { + self.input.can_seek() + } } diff --git a/src/source/blt.rs b/src/source/blt.rs index f5b882f6..9a91e55f 100644 --- a/src/source/blt.rs +++ b/src/source/blt.rs @@ -154,6 +154,11 @@ where fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { self.input.try_seek(pos) } + + #[inline] + fn can_seek(&self) -> bool { + self.input.can_seek() + } } #[derive(Clone, Debug)] diff --git a/src/source/buffered.rs b/src/source/buffered.rs index 9ba1624c..db1d7079 100644 --- a/src/source/buffered.rs +++ b/src/source/buffered.rs @@ -246,6 +246,11 @@ where fn try_seek(&mut self, _: Duration) -> Result<(), SeekNotSupported> { Err(SeekNotSupported { source: std::any::type_name::() }) } + + #[inline] + fn can_seek(&self) -> bool { + true + } } impl Clone for Buffered diff --git a/src/source/channel_volume.rs b/src/source/channel_volume.rs index 3544dcdd..8b4b2461 100644 --- a/src/source/channel_volume.rs +++ b/src/source/channel_volume.rs @@ -145,4 +145,9 @@ where fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { self.input.try_seek(pos) } + + #[inline] + fn can_seek(&self) -> bool { + self.input.can_seek() + } } diff --git a/src/source/delay.rs b/src/source/delay.rs index 1f70d672..9e75168f 100644 --- a/src/source/delay.rs +++ b/src/source/delay.rs @@ -113,4 +113,9 @@ where let pos_without_delay = pos.saturating_sub(self.requested_duration); self.input.try_seek(pos_without_delay) } + + #[inline] + fn can_seek(&self) -> bool { + self.input.can_seek() + } } diff --git a/src/source/done.rs b/src/source/done.rs index 99ed06c3..93a94fcf 100644 --- a/src/source/done.rs +++ b/src/source/done.rs @@ -94,4 +94,9 @@ where fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { self.input.try_seek(pos) } + + #[inline] + fn can_seek(&self) -> bool { + self.input.can_seek() + } } diff --git a/src/source/empty.rs b/src/source/empty.rs index c7adb8e3..559b1c4d 100644 --- a/src/source/empty.rs +++ b/src/source/empty.rs @@ -60,4 +60,9 @@ where fn try_seek(&mut self, _: Duration) -> Result<(), SeekNotSupported> { Err(SeekNotSupported { source: std::any::type_name::() }) } + + #[inline] + fn can_seek(&self) -> bool { + true + } } diff --git a/src/source/empty_callback.rs b/src/source/empty_callback.rs index 0ebf25e9..0d758bb3 100644 --- a/src/source/empty_callback.rs +++ b/src/source/empty_callback.rs @@ -59,4 +59,9 @@ where fn try_seek(&mut self, _: Duration) -> Result<(), SeekNotSupported> { Err(SeekNotSupported { source: std::any::type_name::() }) } + + #[inline] + fn can_seek(&self) -> bool { + true + } } diff --git a/src/source/fadein.rs b/src/source/fadein.rs index 518a139e..52845c48 100644 --- a/src/source/fadein.rs +++ b/src/source/fadein.rs @@ -112,4 +112,9 @@ where fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { self.input.try_seek(pos) } + + #[inline] + fn can_seek(&self) -> bool { + self.input.can_seek() + } } diff --git a/src/source/from_iter.rs b/src/source/from_iter.rs index b40a2953..d669e17f 100644 --- a/src/source/from_iter.rs +++ b/src/source/from_iter.rs @@ -146,6 +146,17 @@ where Ok(()) } } + + #[inline] + fn can_seek(&self) -> bool { + if let Some(source) = &self.current_source { + source.can_seek() + } else { + // no seeking would happen in this case, + // so the seek operation cant fail + true + } + } } #[cfg(test)] diff --git a/src/source/mix.rs b/src/source/mix.rs index 3c011b88..98b654c9 100644 --- a/src/source/mix.rs +++ b/src/source/mix.rs @@ -1,8 +1,8 @@ use std::cmp; use std::time::Duration; -use crate::source::SeekNotSupported; use crate::source::uniform::UniformSourceIterator; +use crate::source::SeekNotSupported; use crate::{Sample, Source}; use cpal::{FromSample, Sample as CpalSample}; @@ -121,10 +121,22 @@ where } } + /// Will only attempt a seek if both underlying sources support seek. #[inline] fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { - todo!("should check if both inputs support seeking"); - self.input1.try_seek(pos)?; - self.input2.try_seek(pos) + if self.can_seek() { + self.input1.try_seek(pos)?; + self.input2.try_seek(pos)?; + Ok(()) + } else { + Err(SeekNotSupported { + source: std::any::type_name::(), + }) + } + } + + #[inline] + fn can_seek(&self) -> bool { + self.input1.can_seek() && self.input2.can_seek() } } diff --git a/src/source/mod.rs b/src/source/mod.rs index 1d115bf6..d12d469f 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -360,6 +360,10 @@ where /// Try to seek to a pos, returns [`SeekNotSupported`] if seeking is not /// supported by the current source. fn try_seek(&mut self, _: Duration) -> Result<(), SeekNotSupported>; + + /// Returns if seeking is possible. If it is not [`try_seek`] will return + /// Err([`SeekNotSupported`]) + fn can_seek(&self) -> bool; } #[derive(Debug, Clone, PartialEq, Eq)] @@ -401,6 +405,11 @@ where fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { (**self).try_seek(pos) } + + #[inline] + fn can_seek(&self) -> bool { + (**self).can_seek() + } } impl Source for Box + Send> @@ -431,6 +440,11 @@ where fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { (**self).try_seek(pos) } + + #[inline] + fn can_seek(&self) -> bool { + (**self).can_seek() + } } impl Source for Box + Send + Sync> @@ -461,5 +475,10 @@ where fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { (**self).try_seek(pos) } + + #[inline] + fn can_seek(&self) -> bool { + (**self).can_seek() + } } diff --git a/src/source/pausable.rs b/src/source/pausable.rs index 5a0f5ba1..58ed82f0 100644 --- a/src/source/pausable.rs +++ b/src/source/pausable.rs @@ -122,4 +122,9 @@ where fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { self.input.try_seek(pos) } + + #[inline] + fn can_seek(&self) -> bool { + self.input.can_seek() + } } diff --git a/src/source/periodic.rs b/src/source/periodic.rs index b4a5ed99..963171ff 100644 --- a/src/source/periodic.rs +++ b/src/source/periodic.rs @@ -124,6 +124,11 @@ where fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { self.input.try_seek(pos) } + + #[inline] + fn can_seek(&self) -> bool { + self.input.can_seek() + } } #[cfg(test)] diff --git a/src/source/repeat.rs b/src/source/repeat.rs index 30d4411b..88f2d583 100644 --- a/src/source/repeat.rs +++ b/src/source/repeat.rs @@ -91,6 +91,11 @@ where fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { self.inner.try_seek(pos) } + + #[inline] + fn can_seek(&self) -> bool { + self.inner.can_seek() + } } impl Clone for Repeat diff --git a/src/source/samples_converter.rs b/src/source/samples_converter.rs index 929476d8..c99cb906 100644 --- a/src/source/samples_converter.rs +++ b/src/source/samples_converter.rs @@ -102,4 +102,9 @@ where fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { self.inner.try_seek(pos) } + + #[inline] + fn can_seek(&self) -> bool { + self.inner.can_seek() + } } diff --git a/src/source/sine.rs b/src/source/sine.rs index 3985270f..c1033a90 100644 --- a/src/source/sine.rs +++ b/src/source/sine.rs @@ -60,9 +60,14 @@ impl Source for SineWave { #[inline] fn try_seek(&mut self, _: Duration) -> Result<(), SeekNotSupported> { - // It is possible to write an implementation that skips the right - // amount of samples to get into the right phase. I do not think there - // is a use case for that however. - Err(SeekNotSupported { source: std::any::type_name::() }) + // This is a constant sound, normal seeking would not have any effect. + // While changing the phase of the sine wave could change how it sounds in + // combination with another sound (beating) such precision is not the intend + // of seeking + Ok(()) + } + #[inline] + fn can_seek(&self) -> bool { + true } } diff --git a/src/source/skip.rs b/src/source/skip.rs index e9813cb5..3b29e7e2 100644 --- a/src/source/skip.rs +++ b/src/source/skip.rs @@ -164,6 +164,11 @@ where fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { self.input.try_seek(pos) } + + #[inline] + fn can_seek(&self) -> bool { + self.input.can_seek() + } } #[cfg(test)] diff --git a/src/source/skippable.rs b/src/source/skippable.rs index f0d4fb38..10abb9ee 100644 --- a/src/source/skippable.rs +++ b/src/source/skippable.rs @@ -96,4 +96,9 @@ where fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { self.input.try_seek(pos) } + + #[inline] + fn can_seek(&self) -> bool { + self.input.can_seek() + } } diff --git a/src/source/spatial.rs b/src/source/spatial.rs index f89c3cc2..bd0ff8d1 100644 --- a/src/source/spatial.rs +++ b/src/source/spatial.rs @@ -124,4 +124,9 @@ where fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { self.input.try_seek(pos) } + + #[inline] + fn can_seek(&self) -> bool { + self.input.can_seek() + } } diff --git a/src/source/speed.rs b/src/source/speed.rs index e73c93bd..0325b2ad 100644 --- a/src/source/speed.rs +++ b/src/source/speed.rs @@ -106,4 +106,9 @@ where let pos_accounting_for_speedup = pos.mul_f32(self.factor); self.input.try_seek(pos_accounting_for_speedup) } + + #[inline] + fn can_seek(&self) -> bool { + true + } } diff --git a/src/source/stoppable.rs b/src/source/stoppable.rs index fe46fe51..7a6b8a10 100644 --- a/src/source/stoppable.rs +++ b/src/source/stoppable.rs @@ -95,4 +95,9 @@ where fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { self.input.try_seek(pos) } + + #[inline] + fn can_seek(&self) -> bool { + self.input.can_seek() + } } diff --git a/src/source/take.rs b/src/source/take.rs index 39dba28a..d0129023 100644 --- a/src/source/take.rs +++ b/src/source/take.rs @@ -183,4 +183,9 @@ where fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { self.input.try_seek(pos) } + + #[inline] + fn can_seek(&self) -> bool { + self.input.can_seek() + } } diff --git a/src/source/uniform.rs b/src/source/uniform.rs index 2680ae76..b867e335 100644 --- a/src/source/uniform.rs +++ b/src/source/uniform.rs @@ -153,6 +153,15 @@ where Ok(()) } } + + #[inline] + fn can_seek(&self) -> bool { + if let Some(input) = self.inner.as_ref() { + input.inner().inner().inner().inner().can_seek() + } else { + true + } + } } #[derive(Clone, Debug)] @@ -166,6 +175,11 @@ impl Take { pub fn inner_mut(&mut self) -> &mut I { &mut self.iter } + + #[inline] + pub fn inner(&self) -> &I { + &self.iter + } } impl Iterator for Take diff --git a/src/source/zero.rs b/src/source/zero.rs index 87731f44..263b4ffc 100644 --- a/src/source/zero.rs +++ b/src/source/zero.rs @@ -84,4 +84,9 @@ where fn try_seek(&mut self, _: Duration) -> Result<(), SeekNotSupported> { Ok(()) } + + #[inline] + fn can_seek(&self) -> bool { + true + } } diff --git a/src/static_buffer.rs b/src/static_buffer.rs index cb0cd4f5..5eaa64f6 100644 --- a/src/static_buffer.rs +++ b/src/static_buffer.rs @@ -90,6 +90,11 @@ where fn try_seek(&mut self, _: Duration) -> Result<(), SeekNotSupported> { Err(SeekNotSupported { source: std::any::type_name::() }) } + + #[inline] + fn can_seek(&self) -> bool { + true + } } impl Iterator for StaticSamplesBuffer diff --git a/tests/seek.rs b/tests/seek.rs index 7a64d0ee..046ec2a5 100644 --- a/tests/seek.rs +++ b/tests/seek.rs @@ -21,7 +21,7 @@ fn seek_returns_err_if_unsupported() { #[cfg(feature = "symphonia-mp3")] ("mp3", true, "symphonia"), #[cfg(feature = "hound")] - ("wav", false, "hound"), + ("wav", true, "hound"), #[cfg(feature = "symphonia-wav")] ("wav", true, "symphonia"), #[cfg(feature = "lewton")] From eb22ec5dac4e84a1fd8af5a3d4e6841105eef3f3 Mon Sep 17 00:00:00 2001 From: dvdsk Date: Sat, 7 Oct 2023 13:45:05 +0200 Subject: [PATCH 14/62] refactor tests, add seek beyond stream test --- src/decoder/flac.rs | 2 +- src/decoder/symphonia.rs | 2 +- src/sink.rs | 10 +++--- tests/seek.rs | 66 ++++++++++++++++++++++++++++++---------- 4 files changed, 57 insertions(+), 23 deletions(-) diff --git a/src/decoder/flac.rs b/src/decoder/flac.rs index 27c761c7..88d3d0ae 100644 --- a/src/decoder/flac.rs +++ b/src/decoder/flac.rs @@ -88,7 +88,7 @@ where #[inline] fn can_seek(&self) -> bool { - true + false } } diff --git a/src/decoder/symphonia.rs b/src/decoder/symphonia.rs index 96638824..85fcb67f 100644 --- a/src/decoder/symphonia.rs +++ b/src/decoder/symphonia.rs @@ -153,7 +153,7 @@ impl Source for SymphoniaDecoder { .seek( SeekMode::Accurate, SeekTo::Time { - time: Time::new(pos.as_secs(), dbg!(pos_fract)), + time: Time::new(pos.as_secs(), pos_fract), track_id: None, }, ) diff --git a/src/sink.rs b/src/sink.rs index cbccfe7a..db26cec7 100644 --- a/src/sink.rs +++ b/src/sink.rs @@ -201,19 +201,19 @@ impl Sink { /// Try to seek to a pos, returns [`SeekNotSupported`] if seeking is not /// supported by the current source. /// - /// We do not expose a `can_seek()` method here on purpose as its impossible to + /// We do not expose a `can_seek()` method here on purpose as its impossible to /// use correctly. In between checking if the playing source supports seeking and - /// actually seeking the sink can switch to a new source that potentially does not - /// support seeking. If you find a reason you need `Sink::can_seek()` here please + /// actually seeking the sink can switch to a new source that potentially does not + /// support seeking. If you find a reason you need `Sink::can_seek()` here please /// open an issue pub fn try_seek(&self, pos: Duration) -> Result<(), SeekNotSupported> { let (order, feedback) = SeekOrder::new(pos); *self.controls.seek.lock().unwrap() = Some(order); match feedback.recv() { Ok(seek_res) => seek_res, - // The feedback channel closed. Probably another seekorder was set + // The feedback channel closed. Probably another seekorder was set // invalidating this one and closing the feedback channel - // ... or the audio thread panicked. + // ... or the audio thread panicked. Err(_) => Ok(()), } } diff --git a/tests/seek.rs b/tests/seek.rs index 046ec2a5..76c08905 100644 --- a/tests/seek.rs +++ b/tests/seek.rs @@ -1,21 +1,37 @@ -use std::io::BufReader; +use std::io::{BufReader, Read, Seek}; use std::path::Path; +use std::sync::Once; use std::time::Duration; -use rodio::source::SeekNotSupported; +use rodio::{Decoder, OutputStream, OutputStreamHandle, Sink, Source}; -fn play_and_seek(asset_path: &Path) -> Result<(), SeekNotSupported> { - let (_stream, handle) = rodio::OutputStream::try_default().unwrap(); - let sink = rodio::Sink::try_new(&handle).unwrap(); +static mut STREAM: Option = None; +static mut STREAM_HANDLE: Option = None; +static INIT: Once = Once::new(); - let file = std::fs::File::open(asset_path).unwrap(); - sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap()); - sink.try_seek(Duration::from_secs(2)) +fn global_stream_handle() -> &'static OutputStreamHandle { + // mutable global access is guarded by Once therefore + // can only happen Once and will not race + unsafe { + INIT.call_once(|| { + let (stream, handle) = rodio::OutputStream::try_default().unwrap(); + STREAM = Some(stream); + STREAM_HANDLE = Some(handle); + }); + STREAM_HANDLE.as_ref().unwrap() + } } -#[test] -fn seek_returns_err_if_unsupported() { - let formats = [ +fn sink_and_decoder(format: &str) -> (Sink, Decoder) { + let sink = rodio::Sink::try_new(global_stream_handle()).unwrap(); + let asset = Path::new("assets/music").with_extension(format); + let file = std::fs::File::open(asset).unwrap(); + let decoder = rodio::Decoder::new(BufReader::new(file)).unwrap(); + (sink, decoder) +} + +fn format_decoder_info() -> &'static [(&'static str, bool, &'static str)] { + &[ #[cfg(feature = "minimp3")] ("mp3", true, "minimp3"), #[cfg(feature = "symphonia-mp3")] @@ -34,12 +50,30 @@ fn seek_returns_err_if_unsupported() { ("flac", true, "symphonia"), #[cfg(feature = "symphonia-isomp4")] ("m4a", true, "_"), - ]; + ] +} - for (format, supported, decoder) in formats { - println!("trying: {format} by {decoder}, should support seek: {supported}"); - let asset = Path::new("assets/music").with_extension(format); - let res = play_and_seek(&asset); +#[test] +fn seek_returns_err_if_unsupported() { + for (format, supported, decoder) in format_decoder_info().iter().cloned() { + println!("trying: {format},\t\tby: {decoder},\t\tshould support seek: {supported}"); + let (sink, decoder) = sink_and_decoder(format); + assert_eq!(decoder.can_seek(), supported); + sink.append(decoder); + let res = sink.try_seek(Duration::from_secs(2)); assert_eq!(res.is_ok(), supported); } } + +#[test] +fn seek_beyond_end_does_not_crash() { + for (format, _, decoder_name) in format_decoder_info().iter().cloned() { + let (sink, decoder) = sink_and_decoder(format); + if !decoder.can_seek() { + continue; + } + println!("seeking beyond end for: {format}\t decoded by: {decoder_name}"); + sink.append(decoder); + sink.try_seek(Duration::from_secs(999)).unwrap(); + } +} From ebebe883399554a76b5b2800dbeb7fbb18c3f855 Mon Sep 17 00:00:00 2001 From: dvdsk Date: Sat, 7 Oct 2023 14:35:23 +0200 Subject: [PATCH 15/62] turns SeekNotSupported into a SeekError --- Cargo.toml | 1 + src/buffer.rs | 6 ++--- src/decoder/flac.rs | 6 ++--- src/decoder/mod.rs | 12 +++++----- src/decoder/mp3.rs | 9 +++++-- src/decoder/symphonia.rs | 21 ++++++++--------- src/decoder/vorbis.rs | 6 ++--- src/decoder/wav.rs | 7 +++--- src/dynamic_mixer.rs | 8 +++---- src/queue.rs | 4 ++-- src/sink.rs | 8 +++---- src/source/amplify.rs | 4 ++-- src/source/blt.rs | 4 ++-- src/source/buffered.rs | 6 ++--- src/source/channel_volume.rs | 4 ++-- src/source/delay.rs | 4 ++-- src/source/done.rs | 4 ++-- src/source/empty.rs | 6 ++--- src/source/empty_callback.rs | 6 ++--- src/source/fadein.rs | 4 ++-- src/source/from_iter.rs | 4 ++-- src/source/mix.rs | 16 +++++++------ src/source/mod.rs | 42 +++++++++++++++++++-------------- src/source/pausable.rs | 4 ++-- src/source/periodic.rs | 4 ++-- src/source/repeat.rs | 4 ++-- src/source/samples_converter.rs | 4 ++-- src/source/sine.rs | 4 ++-- src/source/skip.rs | 4 ++-- src/source/skippable.rs | 4 ++-- src/source/spatial.rs | 4 ++-- src/source/speed.rs | 4 ++-- src/source/stoppable.rs | 4 ++-- src/source/take.rs | 4 ++-- src/source/uniform.rs | 4 ++-- src/source/zero.rs | 4 ++-- src/static_buffer.rs | 6 ++--- 37 files changed, 131 insertions(+), 119 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0f738a01..66629584 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ lewton = { version = "0.10", optional = true } minimp3 = { git = "https://github.com/dvdsk/minimp3-rs.git", 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"] diff --git a/src/buffer.rs b/src/buffer.rs index 57ad81f2..5dda1d66 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -13,7 +13,7 @@ use std::time::Duration; use std::vec::IntoIter as VecIntoIter; -use crate::source::SeekNotSupported; +use crate::source::SeekError; use crate::{Sample, Source}; /// A buffer of samples treated as a source. @@ -87,8 +87,8 @@ where } #[inline] - fn try_seek(&mut self, _: Duration) -> Result<(), SeekNotSupported> { - Err(SeekNotSupported { source: std::any::type_name::() }) + fn try_seek(&mut self, _: Duration) -> Result<(), SeekError> { + Err(SeekError::NotSupported { underlying_source: std::any::type_name::() }) } #[inline] diff --git a/src/decoder/flac.rs b/src/decoder/flac.rs index 88d3d0ae..bf7dd9df 100644 --- a/src/decoder/flac.rs +++ b/src/decoder/flac.rs @@ -4,7 +4,7 @@ use std::mem; use std::time::Duration; use crate::Source; -use crate::source::SeekNotSupported; +use crate::source::SeekError; use claxon::FlacReader; @@ -82,8 +82,8 @@ where } #[inline] - fn try_seek(&mut self, _: Duration) -> Result<(), SeekNotSupported> { - Err(SeekNotSupported { source: std::any::type_name::() }) + fn try_seek(&mut self, _: Duration) -> Result<(), SeekError> { + Err(SeekError::NotSupported { underlying_source: std::any::type_name::() }) } #[inline] diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index b0077a9a..8566b38c 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -9,7 +9,7 @@ use std::mem; use std::str::FromStr; use std::time::Duration; -use crate::source::SeekNotSupported; +use crate::source::SeekError; use crate::Source; #[cfg(feature = "symphonia")] @@ -162,7 +162,7 @@ impl DecoderImpl { } #[inline] - fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + 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), @@ -174,8 +174,8 @@ impl DecoderImpl { DecoderImpl::Mp3(source) => source.try_seek(pos), #[cfg(feature = "symphonia")] DecoderImpl::Symphonia(source) => source.try_seek(pos), - DecoderImpl::None(_) => Err(SeekNotSupported { - source: "DecoderImpl::None", + DecoderImpl::None(_) => Err(SeekError::NotSupported { + underlying_source: "DecoderImpl::None", }), } } @@ -434,7 +434,7 @@ where } #[inline] - fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { self.0.try_seek(pos) } @@ -537,7 +537,7 @@ where None } - fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { self.0.try_seek(pos) } diff --git a/src/decoder/mp3.rs b/src/decoder/mp3.rs index 91e6edfe..c001259d 100644 --- a/src/decoder/mp3.rs +++ b/src/decoder/mp3.rs @@ -2,6 +2,7 @@ use std::io::{Read, Seek, SeekFrom}; use std::time::Duration; use crate::Source; +use crate::source::SeekError; use minimp3::Frame; use minimp3::{Decoder, SeekDecoder}; @@ -67,13 +68,17 @@ where None } - fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { 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 - let ignore_err = self.decoder.seek_samples(pos); + self.decoder.seek_samples(pos)?; Ok(()) } + + fn can_seek(&self) -> bool { + true + } } impl Iterator for Mp3Decoder diff --git a/src/decoder/symphonia.rs b/src/decoder/symphonia.rs index 85fcb67f..509b82ab 100644 --- a/src/decoder/symphonia.rs +++ b/src/decoder/symphonia.rs @@ -13,7 +13,7 @@ use symphonia::{ default::get_probe, }; -use crate::{source::SeekNotSupported, Source}; +use crate::{source::SeekError, Source}; use super::DecoderError; @@ -140,7 +140,8 @@ impl Source for SymphoniaDecoder { None } - fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + // TODO: do we return till where we seeked? + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { use symphonia::core::formats::{SeekMode, SeekTo}; let pos_fract = if pos.subsec_nanos() == 0 { @@ -149,15 +150,13 @@ impl Source for SymphoniaDecoder { 1f64 / pos.subsec_nanos() as f64 }; - self.format - .seek( - SeekMode::Accurate, - SeekTo::Time { - time: Time::new(pos.as_secs(), pos_fract), - track_id: None, - }, - ) - .unwrap(); + self.format.seek( + SeekMode::Accurate, + SeekTo::Time { + time: Time::new(pos.as_secs(), pos_fract), + track_id: None, + }, + )?; Ok(()) } diff --git a/src/decoder/vorbis.rs b/src/decoder/vorbis.rs index 68bb788c..5b18ade8 100644 --- a/src/decoder/vorbis.rs +++ b/src/decoder/vorbis.rs @@ -2,7 +2,7 @@ use std::io::{Read, Seek, SeekFrom}; use std::time::Duration; use std::vec; -use crate::source::SeekNotSupported; +use crate::source::SeekError; use crate::Source; use lewton::inside_ogg::OggStreamReader; @@ -76,12 +76,12 @@ where } #[inline] - fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { // number of PCM samples per channel // number of PCM samples per channel let samples = pos.as_secs_f32() * self.sample_rate() as f32; - self.stream_reader.seek_absgp_pg(samples as u64).unwrap(); + self.stream_reader.seek_absgp_pg(samples as u64)?; Ok(()) } diff --git a/src/decoder/wav.rs b/src/decoder/wav.rs index b670bbae..e5930b5e 100644 --- a/src/decoder/wav.rs +++ b/src/decoder/wav.rs @@ -1,7 +1,7 @@ use std::io::{Read, Seek, SeekFrom}; use std::time::Duration; -use crate::source::SeekNotSupported; +use crate::source::SeekError; use crate::Source; use hound::{SampleFormat, WavReader}; @@ -131,10 +131,9 @@ where } #[inline] - fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { let samples = pos.as_secs_f32() * self.sample_rate() as f32; - self.reader.reader.seek(samples as u32).unwrap(); - Ok(()) + self.reader.reader.seek(samples as u32).map_err(SeekError::Hound) } #[inline] diff --git a/src/dynamic_mixer.rs b/src/dynamic_mixer.rs index 4ecb08c2..0e0dd9b8 100644 --- a/src/dynamic_mixer.rs +++ b/src/dynamic_mixer.rs @@ -4,7 +4,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; use std::time::Duration; -use crate::source::{SeekNotSupported, Source, UniformSourceIterator}; +use crate::source::{SeekError, Source, UniformSourceIterator}; use crate::Sample; /// Builds a new mixer. @@ -108,11 +108,11 @@ where } #[inline] - fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { for source in &self.current_sources { if !source.can_seek() { - return Err(SeekNotSupported { - source: "unknown, one of the sources added to the DynamicMixer", + return Err(SeekError::NotSupported { + underlying_source: "unknown, one of the sources added to the DynamicMixer", }); } } diff --git a/src/queue.rs b/src/queue.rs index 18fe2a64..e9f9bbd5 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -4,7 +4,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; use std::time::Duration; -use crate::source::{Empty, SeekNotSupported, Source, Zero}; +use crate::source::{Empty, SeekError, Source, Zero}; use crate::Sample; #[cfg(feature = "crossbeam-channel")] @@ -171,7 +171,7 @@ where } #[inline] - fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { self.current.try_seek(pos) } diff --git a/src/sink.rs b/src/sink.rs index db26cec7..89bce739 100644 --- a/src/sink.rs +++ b/src/sink.rs @@ -7,7 +7,7 @@ use crossbeam_channel::{Receiver, Sender}; #[cfg(not(feature = "crossbeam-channel"))] use std::sync::mpsc::{Receiver, Sender}; -use crate::source::SeekNotSupported; +use crate::source::SeekError; use crate::stream::{OutputStreamHandle, PlayError}; use crate::{queue, source::Done, Sample, Source}; use cpal::FromSample; @@ -28,11 +28,11 @@ pub struct Sink { struct SeekOrder { pos: Duration, - feedback: Sender>, + feedback: Sender>, } impl SeekOrder { - fn new(pos: Duration) -> (Self, Receiver>) { + fn new(pos: Duration) -> (Self, Receiver>) { #[cfg(not(feature = "crossbeam-channel"))] let (tx, rx) = { use std::sync::mpsc; @@ -206,7 +206,7 @@ impl Sink { /// actually seeking the sink can switch to a new source that potentially does not /// support seeking. If you find a reason you need `Sink::can_seek()` here please /// open an issue - pub fn try_seek(&self, pos: Duration) -> Result<(), SeekNotSupported> { + pub fn try_seek(&self, pos: Duration) -> Result<(), SeekError> { let (order, feedback) = SeekOrder::new(pos); *self.controls.seek.lock().unwrap() = Some(order); match feedback.recv() { diff --git a/src/source/amplify.rs b/src/source/amplify.rs index 76a9168d..3b254002 100644 --- a/src/source/amplify.rs +++ b/src/source/amplify.rs @@ -2,7 +2,7 @@ use std::time::Duration; use crate::{Sample, Source}; -use super::SeekNotSupported; +use super::SeekError; /// Internal function that builds a `Amplify` object. pub fn amplify(input: I, factor: f32) -> Amplify @@ -97,7 +97,7 @@ where } #[inline] - fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { self.input.try_seek(pos) } diff --git a/src/source/blt.rs b/src/source/blt.rs index 9a91e55f..1d32a99b 100644 --- a/src/source/blt.rs +++ b/src/source/blt.rs @@ -3,7 +3,7 @@ use std::time::Duration; use crate::Source; -use super::SeekNotSupported; +use super::SeekError; // Implemented following http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt @@ -151,7 +151,7 @@ where } #[inline] - fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { self.input.try_seek(pos) } diff --git a/src/source/buffered.rs b/src/source/buffered.rs index db1d7079..1b720e31 100644 --- a/src/source/buffered.rs +++ b/src/source/buffered.rs @@ -5,7 +5,7 @@ use std::time::Duration; use crate::{Sample, Source}; -use super::SeekNotSupported; +use super::SeekError; /// Internal function that builds a `Buffered` object. #[inline] @@ -243,8 +243,8 @@ where } #[inline] - fn try_seek(&mut self, _: Duration) -> Result<(), SeekNotSupported> { - Err(SeekNotSupported { source: std::any::type_name::() }) + fn try_seek(&mut self, _: Duration) -> Result<(), SeekError> { + Err(SeekError::NotSupported { underlying_source: std::any::type_name::() }) } #[inline] diff --git a/src/source/channel_volume.rs b/src/source/channel_volume.rs index 8b4b2461..d1a724ad 100644 --- a/src/source/channel_volume.rs +++ b/src/source/channel_volume.rs @@ -2,7 +2,7 @@ use std::time::Duration; use crate::{Sample, Source}; -use super::SeekNotSupported; +use super::SeekError; /// Combines channels in input into a single mono source, then plays that mono sound /// to each channel at the volume given for that channel. @@ -142,7 +142,7 @@ where } #[inline] - fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { self.input.try_seek(pos) } diff --git a/src/source/delay.rs b/src/source/delay.rs index 9e75168f..847b7dc3 100644 --- a/src/source/delay.rs +++ b/src/source/delay.rs @@ -2,7 +2,7 @@ use std::time::Duration; use crate::{Sample, Source}; -use super::SeekNotSupported; +use super::SeekError; /// Internal function that builds a `Delay` object. pub fn delay(input: I, duration: Duration) -> Delay @@ -109,7 +109,7 @@ where } #[inline] - fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { let pos_without_delay = pos.saturating_sub(self.requested_duration); self.input.try_seek(pos_without_delay) } diff --git a/src/source/done.rs b/src/source/done.rs index 93a94fcf..06fb6f51 100644 --- a/src/source/done.rs +++ b/src/source/done.rs @@ -4,7 +4,7 @@ use std::time::Duration; use crate::{Sample, Source}; -use super::SeekNotSupported; +use super::SeekError; /// When the inner source is empty this decrements an `AtomicUsize`. #[derive(Debug, Clone)] @@ -91,7 +91,7 @@ where } #[inline] - fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { self.input.try_seek(pos) } diff --git a/src/source/empty.rs b/src/source/empty.rs index 559b1c4d..fa92a54c 100644 --- a/src/source/empty.rs +++ b/src/source/empty.rs @@ -3,7 +3,7 @@ use std::time::Duration; use crate::{Sample, Source}; -use super::SeekNotSupported; +use super::SeekError; /// An empty source. #[derive(Debug, Copy, Clone)] @@ -57,8 +57,8 @@ where } #[inline] - fn try_seek(&mut self, _: Duration) -> Result<(), SeekNotSupported> { - Err(SeekNotSupported { source: std::any::type_name::() }) + fn try_seek(&mut self, _: Duration) -> Result<(), SeekError> { + Err(SeekError::NotSupported { underlying_source: std::any::type_name::() }) } #[inline] diff --git a/src/source/empty_callback.rs b/src/source/empty_callback.rs index 0d758bb3..dadfa14e 100644 --- a/src/source/empty_callback.rs +++ b/src/source/empty_callback.rs @@ -3,7 +3,7 @@ use std::time::Duration; use crate::{Sample, Source}; -use super::SeekNotSupported; +use super::SeekError; /// An empty source which executes a callback function pub struct EmptyCallback { @@ -56,8 +56,8 @@ where } #[inline] - fn try_seek(&mut self, _: Duration) -> Result<(), SeekNotSupported> { - Err(SeekNotSupported { source: std::any::type_name::() }) + fn try_seek(&mut self, _: Duration) -> Result<(), SeekError> { + Err(SeekError::NotSupported { underlying_source: std::any::type_name::() }) } #[inline] diff --git a/src/source/fadein.rs b/src/source/fadein.rs index 52845c48..4c93bfc2 100644 --- a/src/source/fadein.rs +++ b/src/source/fadein.rs @@ -2,7 +2,7 @@ use std::time::Duration; use crate::{Sample, Source}; -use super::SeekNotSupported; +use super::SeekError; /// Internal function that builds a `FadeIn` object. pub fn fadein(input: I, duration: Duration) -> FadeIn @@ -109,7 +109,7 @@ where } #[inline] - fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { self.input.try_seek(pos) } diff --git a/src/source/from_iter.rs b/src/source/from_iter.rs index d669e17f..c3406762 100644 --- a/src/source/from_iter.rs +++ b/src/source/from_iter.rs @@ -2,7 +2,7 @@ use std::time::Duration; use crate::{Sample, Source}; -use super::SeekNotSupported; +use super::SeekError; /// Builds a source that chains sources provided by an iterator. /// @@ -139,7 +139,7 @@ where } #[inline] - fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { if let Some(source) = self.current_source.as_mut() { source.try_seek(pos) } else { diff --git a/src/source/mix.rs b/src/source/mix.rs index 98b654c9..7c54e414 100644 --- a/src/source/mix.rs +++ b/src/source/mix.rs @@ -2,7 +2,7 @@ use std::cmp; use std::time::Duration; use crate::source::uniform::UniformSourceIterator; -use crate::source::SeekNotSupported; +use crate::source::SeekError; use crate::{Sample, Source}; use cpal::{FromSample, Sample as CpalSample}; @@ -123,15 +123,17 @@ where /// Will only attempt a seek if both underlying sources support seek. #[inline] - fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { - if self.can_seek() { + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { + // if a source can not seek we call try_seek only on that source + // such that we get its SeekError::NotSupported error + if !self.input1.can_seek() { + self.input1.try_seek(pos) + } else if !self.input2.can_seek() { + self.input2.try_seek(pos) + } else { self.input1.try_seek(pos)?; self.input2.try_seek(pos)?; Ok(()) - } else { - Err(SeekNotSupported { - source: std::any::type_name::(), - }) } } diff --git a/src/source/mod.rs b/src/source/mod.rs index d12d469f..76832bbf 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -1,7 +1,5 @@ //! Sources of sound and various filters. -use std::fmt; -use std::error::Error; use std::time::Duration; use cpal::FromSample; @@ -357,26 +355,35 @@ where /// Set position /// - /// Try to seek to a pos, returns [`SeekNotSupported`] if seeking is not + /// Try to seek to a pos, returns [`SeekNotSupported`] if seeking is not /// supported by the current source. - fn try_seek(&mut self, _: Duration) -> Result<(), SeekNotSupported>; + fn try_seek(&mut self, _: Duration) -> Result<(), SeekError>; /// Returns if seeking is possible. If it is not [`try_seek`] will return /// Err([`SeekNotSupported`]) fn can_seek(&self) -> bool; } - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct SeekNotSupported{ pub source: &'static str } - -impl fmt::Display for SeekNotSupported { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Seeking is not supported on this source") - } +// we might add decoders requiring new error types, would non_exhaustive +// this would break users builds +#[non_exhaustive] +#[derive(Debug, thiserror::Error)] +pub enum SeekError { + #[error("Streaming is not supported by source: {underlying_source}")] + NotSupported { underlying_source: &'static str }, + #[cfg(feature = "symphonia")] + #[error("Error seeking: {0}")] + SymphoniaSeekFailed(#[from] symphonia::core::errors::Error), + #[cfg(all(feature = "wav", not(feature = "symphonia-wav")))] + #[error("Error seeking in wav source: {0}")] + Hound(std::io::Error), + #[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))] + #[error("Error seeking in ogg source: {0}")] + Lewton(#[from] lewton::VorbisError), + #[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))] + #[error("Error seeking in mp3 source: {0}")] + Minimp3(#[from] minimp3::Error), } -impl Error for SeekNotSupported {} - impl Source for Box> where S: Sample, @@ -402,7 +409,7 @@ where } #[inline] - fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { (**self).try_seek(pos) } @@ -437,7 +444,7 @@ where } #[inline] - fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { (**self).try_seek(pos) } @@ -472,7 +479,7 @@ where } #[inline] - fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { (**self).try_seek(pos) } @@ -481,4 +488,3 @@ where (**self).can_seek() } } - diff --git a/src/source/pausable.rs b/src/source/pausable.rs index 58ed82f0..ccd0b6af 100644 --- a/src/source/pausable.rs +++ b/src/source/pausable.rs @@ -2,7 +2,7 @@ use std::time::Duration; use crate::{Sample, Source}; -use super::SeekNotSupported; +use super::SeekError; /// Internal function that builds a `Pausable` object. pub fn pausable(source: I, paused: bool) -> Pausable @@ -119,7 +119,7 @@ where } #[inline] - fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { self.input.try_seek(pos) } diff --git a/src/source/periodic.rs b/src/source/periodic.rs index 963171ff..e2723c36 100644 --- a/src/source/periodic.rs +++ b/src/source/periodic.rs @@ -2,7 +2,7 @@ use std::time::Duration; use crate::{Sample, Source}; -use super::SeekNotSupported; +use super::SeekError; /// Internal function that builds a `PeriodicAccess` object. pub fn periodic(source: I, period: Duration, modifier: F) -> PeriodicAccess @@ -121,7 +121,7 @@ where } #[inline] - fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { self.input.try_seek(pos) } diff --git a/src/source/repeat.rs b/src/source/repeat.rs index 88f2d583..be91b521 100644 --- a/src/source/repeat.rs +++ b/src/source/repeat.rs @@ -4,7 +4,7 @@ use crate::source::buffered::Buffered; use crate::{Sample, Source}; -use super::SeekNotSupported; +use super::SeekError; /// Internal function that builds a `Repeat` object. pub fn repeat(input: I) -> Repeat @@ -88,7 +88,7 @@ where } #[inline] - fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { self.inner.try_seek(pos) } diff --git a/src/source/samples_converter.rs b/src/source/samples_converter.rs index c99cb906..28f99c09 100644 --- a/src/source/samples_converter.rs +++ b/src/source/samples_converter.rs @@ -4,7 +4,7 @@ use std::time::Duration; use crate::{Sample, Source}; use cpal::{FromSample, Sample as CpalSample}; -use super::SeekNotSupported; +use super::SeekError; /// An iterator that reads from a `Source` and converts the samples to a specific rate and /// channels count. @@ -99,7 +99,7 @@ where } #[inline] - fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { self.inner.try_seek(pos) } diff --git a/src/source/sine.rs b/src/source/sine.rs index c1033a90..8c67c832 100644 --- a/src/source/sine.rs +++ b/src/source/sine.rs @@ -3,7 +3,7 @@ use std::time::Duration; use crate::Source; -use super::SeekNotSupported; +use super::SeekError; /// An infinite source that produces a sine. /// @@ -59,7 +59,7 @@ impl Source for SineWave { } #[inline] - fn try_seek(&mut self, _: Duration) -> Result<(), SeekNotSupported> { + fn try_seek(&mut self, _: Duration) -> Result<(), SeekError> { // This is a constant sound, normal seeking would not have any effect. // While changing the phase of the sine wave could change how it sounds in // combination with another sound (beating) such precision is not the intend diff --git a/src/source/skip.rs b/src/source/skip.rs index 3b29e7e2..5fc8a6d3 100644 --- a/src/source/skip.rs +++ b/src/source/skip.rs @@ -2,7 +2,7 @@ use std::time::Duration; use crate::{Sample, Source}; -use super::SeekNotSupported; +use super::SeekError; const NS_PER_SECOND: u128 = 1_000_000_000; @@ -161,7 +161,7 @@ where } #[inline] - fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { self.input.try_seek(pos) } diff --git a/src/source/skippable.rs b/src/source/skippable.rs index 10abb9ee..c2051bba 100644 --- a/src/source/skippable.rs +++ b/src/source/skippable.rs @@ -3,7 +3,7 @@ use std::time::Duration; use crate::Sample; use crate::Source; -use super::SeekNotSupported; +use super::SeekError; /// Internal function that builds a `Skippable` object. pub fn skippable(source: I) -> Skippable { @@ -93,7 +93,7 @@ where } #[inline] - fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { self.input.try_seek(pos) } diff --git a/src/source/spatial.rs b/src/source/spatial.rs index bd0ff8d1..ca11c31e 100644 --- a/src/source/spatial.rs +++ b/src/source/spatial.rs @@ -3,7 +3,7 @@ use std::time::Duration; use crate::source::ChannelVolume; use crate::{Sample, Source}; -use super::SeekNotSupported; +use super::SeekError; /// Combines channels in input into a single mono source, then plays that mono sound /// to each channel at the volume given for that channel. @@ -121,7 +121,7 @@ where } #[inline] - fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { self.input.try_seek(pos) } diff --git a/src/source/speed.rs b/src/source/speed.rs index 0325b2ad..23e28bca 100644 --- a/src/source/speed.rs +++ b/src/source/speed.rs @@ -2,7 +2,7 @@ use std::time::Duration; use crate::{Sample, Source}; -use super::SeekNotSupported; +use super::SeekError; /// Internal function that builds a `Speed` object. pub fn speed(input: I, factor: f32) -> Speed { @@ -97,7 +97,7 @@ where } #[inline] - fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { /* TODO: This might be wrong, I do not know how speed achieves its speedup * so I can not reason about the correctness. * */ diff --git a/src/source/stoppable.rs b/src/source/stoppable.rs index 7a6b8a10..a0bd69f7 100644 --- a/src/source/stoppable.rs +++ b/src/source/stoppable.rs @@ -2,7 +2,7 @@ use std::time::Duration; use crate::{Sample, Source}; -use super::SeekNotSupported; +use super::SeekError; /// Internal function that builds a `Stoppable` object. pub fn stoppable(source: I) -> Stoppable { @@ -92,7 +92,7 @@ where } #[inline] - fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { self.input.try_seek(pos) } diff --git a/src/source/take.rs b/src/source/take.rs index d0129023..34ac1349 100644 --- a/src/source/take.rs +++ b/src/source/take.rs @@ -2,7 +2,7 @@ use std::time::Duration; use crate::{Sample, Source}; -use super::SeekNotSupported; +use super::SeekError; /// Internal function that builds a `TakeDuration` object. pub fn take_duration(input: I, duration: Duration) -> TakeDuration @@ -180,7 +180,7 @@ where } #[inline] - fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { self.input.try_seek(pos) } diff --git a/src/source/uniform.rs b/src/source/uniform.rs index b867e335..9a1ade4d 100644 --- a/src/source/uniform.rs +++ b/src/source/uniform.rs @@ -6,7 +6,7 @@ use cpal::FromSample; use crate::conversions::{ChannelCountConverter, DataConverter, SampleRateConverter}; use crate::{Sample, Source}; -use super::SeekNotSupported; +use super::SeekError; /// An iterator that reads from a `Source` and converts the samples to a specific rate and /// channels count. @@ -141,7 +141,7 @@ where } #[inline] - fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> { + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { if let Some(input) = self.inner.as_mut() { input .inner_mut() diff --git a/src/source/zero.rs b/src/source/zero.rs index 263b4ffc..5a2bb71a 100644 --- a/src/source/zero.rs +++ b/src/source/zero.rs @@ -3,7 +3,7 @@ use std::time::Duration; use crate::{Sample, Source}; -use super::SeekNotSupported; +use super::SeekError; /// An infinite source that produces zero. #[derive(Clone, Debug)] @@ -81,7 +81,7 @@ where } #[inline] - fn try_seek(&mut self, _: Duration) -> Result<(), SeekNotSupported> { + fn try_seek(&mut self, _: Duration) -> Result<(), SeekError> { Ok(()) } diff --git a/src/static_buffer.rs b/src/static_buffer.rs index 5eaa64f6..aa356778 100644 --- a/src/static_buffer.rs +++ b/src/static_buffer.rs @@ -13,7 +13,7 @@ use std::slice::Iter as SliceIter; use std::time::Duration; -use crate::source::SeekNotSupported; +use crate::source::SeekError; use crate::{Sample, Source}; /// A buffer of samples treated as a source. @@ -87,8 +87,8 @@ where } #[inline] - fn try_seek(&mut self, _: Duration) -> Result<(), SeekNotSupported> { - Err(SeekNotSupported { source: std::any::type_name::() }) + fn try_seek(&mut self, _: Duration) -> Result<(), SeekError> { + Err(SeekError::NotSupported { underlying_source: std::any::type_name::() }) } #[inline] From 5d44bfe63622da3b0b89c62b7768c156d557d559 Mon Sep 17 00:00:00 2001 From: dvdsk Date: Sun, 8 Oct 2023 13:03:06 +0200 Subject: [PATCH 16/62] fixes symphonia seek beyond end of file returning an error --- src/decoder/symphonia.rs | 15 +++++++++++---- src/decoder/wav.rs | 2 +- src/source/mod.rs | 8 ++++---- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/decoder/symphonia.rs b/src/decoder/symphonia.rs index 509b82ab..0e426a27 100644 --- a/src/decoder/symphonia.rs +++ b/src/decoder/symphonia.rs @@ -1,4 +1,4 @@ -use std::time::Duration; +use std::{io::ErrorKind, time::Duration}; use symphonia::{ core::{ audio::{AudioBufferRef, SampleBuffer, SignalSpec}, @@ -142,6 +142,7 @@ impl Source for SymphoniaDecoder { // TODO: do we return till where we seeked? fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { + use symphonia::core::errors::SeekErrorKind; use symphonia::core::formats::{SeekMode, SeekTo}; let pos_fract = if pos.subsec_nanos() == 0 { @@ -150,14 +151,20 @@ impl Source for SymphoniaDecoder { 1f64 / pos.subsec_nanos() as f64 }; - self.format.seek( + let res = self.format.seek( SeekMode::Accurate, SeekTo::Time { time: Time::new(pos.as_secs(), pos_fract), track_id: None, }, - )?; - Ok(()) + ); + + match res { + Err(Error::IoError(e)) if e.kind() == ErrorKind::UnexpectedEof => Ok(()), + Err(Error::SeekError(SeekErrorKind::OutOfRange)) => Ok(()), + Err(e) => Err(SeekError::SymphoniaDecoder(e)), + Ok(_) => Ok(()), + } } #[inline] diff --git a/src/decoder/wav.rs b/src/decoder/wav.rs index e5930b5e..682f4732 100644 --- a/src/decoder/wav.rs +++ b/src/decoder/wav.rs @@ -133,7 +133,7 @@ where #[inline] fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { let samples = pos.as_secs_f32() * self.sample_rate() as f32; - self.reader.reader.seek(samples as u32).map_err(SeekError::Hound) + self.reader.reader.seek(samples as u32).map_err(SeekError::HoundDecoder) } #[inline] diff --git a/src/source/mod.rs b/src/source/mod.rs index 76832bbf..fa7dcb18 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -372,16 +372,16 @@ pub enum SeekError { NotSupported { underlying_source: &'static str }, #[cfg(feature = "symphonia")] #[error("Error seeking: {0}")] - SymphoniaSeekFailed(#[from] symphonia::core::errors::Error), + SymphoniaDecoder(#[from] symphonia::core::errors::Error), #[cfg(all(feature = "wav", not(feature = "symphonia-wav")))] #[error("Error seeking in wav source: {0}")] - Hound(std::io::Error), + HoundDecoder(std::io::Error), #[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))] #[error("Error seeking in ogg source: {0}")] - Lewton(#[from] lewton::VorbisError), + LewtonDecoder(#[from] lewton::VorbisError), #[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))] #[error("Error seeking in mp3 source: {0}")] - Minimp3(#[from] minimp3::Error), + Minimp3Decorder(#[from] minimp3::Error), } impl Source for Box> From 10262f8134494b0e1a9ef0ba44acc0fa5d6cd4e8 Mon Sep 17 00:00:00 2001 From: dvdsk Date: Sun, 8 Oct 2023 13:34:05 +0200 Subject: [PATCH 17/62] removes can_seek in favor of rolling back seek operations (requires PR #510) --- src/buffer.rs | 6 ++--- src/conversions/channels.rs | 6 ----- src/conversions/sample.rs | 6 ----- src/conversions/sample_rate.rs | 6 ----- src/decoder/flac.rs | 5 ---- src/decoder/mod.rs | 26 ------------------ src/decoder/mp3.rs | 5 ---- src/decoder/symphonia.rs | 5 ---- src/decoder/vorbis.rs | 5 ---- src/decoder/wav.rs | 5 ---- src/dynamic_mixer.rs | 48 +++++++++++++++++++++------------ src/queue.rs | 5 ---- src/source/amplify.rs | 6 ++--- src/source/blt.rs | 6 ++--- src/source/buffered.rs | 6 ++--- src/source/channel_volume.rs | 6 ++--- src/source/delay.rs | 6 ++--- src/source/done.rs | 6 ++--- src/source/empty.rs | 6 ++--- src/source/empty_callback.rs | 6 ++--- src/source/fadein.rs | 6 ++--- src/source/from_iter.rs | 12 ++------- src/source/mix.rs | 33 +++++++++++------------ src/source/mod.rs | 23 +++++----------- src/source/pausable.rs | 6 ++--- src/source/periodic.rs | 6 ++--- src/source/repeat.rs | 6 ++--- src/source/samples_converter.rs | 6 ++--- src/source/sine.rs | 6 ++--- src/source/skip.rs | 6 ++--- src/source/skippable.rs | 6 ++--- src/source/spatial.rs | 6 ++--- src/source/speed.rs | 6 ++--- src/source/stoppable.rs | 6 ++--- src/source/take.rs | 6 ++--- src/source/uniform.rs | 14 ---------- src/source/zero.rs | 6 ++--- src/static_buffer.rs | 9 +++---- 38 files changed, 103 insertions(+), 242 deletions(-) diff --git a/src/buffer.rs b/src/buffer.rs index 5dda1d66..9bd97706 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -91,10 +91,8 @@ where Err(SeekError::NotSupported { underlying_source: std::any::type_name::() }) } - #[inline] - fn can_seek(&self) -> bool { - false - } + + } impl Iterator for SamplesBuffer diff --git a/src/conversions/channels.rs b/src/conversions/channels.rs index 25834c63..c42b0104 100644 --- a/src/conversions/channels.rs +++ b/src/conversions/channels.rs @@ -50,12 +50,6 @@ where pub fn inner_mut(&mut self) -> &mut I { &mut self.input } - - /// Get a reference to the iterator - #[inline] - pub fn inner(&self) -> &I { - &self.input - } } impl Iterator for ChannelCountConverter diff --git a/src/conversions/sample.rs b/src/conversions/sample.rs index 39fe3d8d..7ee97624 100644 --- a/src/conversions/sample.rs +++ b/src/conversions/sample.rs @@ -29,12 +29,6 @@ impl DataConverter { pub fn inner_mut(&mut self) -> &mut I { &mut self.input } - - /// get a reference to the iterator - #[inline] - pub fn inner(&self) -> &I { - &self.input - } } impl Iterator for DataConverter diff --git a/src/conversions/sample_rate.rs b/src/conversions/sample_rate.rs index c059a6fe..36aa5a5a 100644 --- a/src/conversions/sample_rate.rs +++ b/src/conversions/sample_rate.rs @@ -108,12 +108,6 @@ where &mut self.input } - /// get a reference to the iterator - #[inline] - pub fn inner(&self) -> &I { - &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 bf7dd9df..77fa73e7 100644 --- a/src/decoder/flac.rs +++ b/src/decoder/flac.rs @@ -85,11 +85,6 @@ where fn try_seek(&mut self, _: Duration) -> Result<(), SeekError> { Err(SeekError::NotSupported { underlying_source: std::any::type_name::() }) } - - #[inline] - fn can_seek(&self) -> bool { - false - } } impl Iterator for FlacDecoder diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 8566b38c..1fa7272a 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -179,22 +179,6 @@ impl DecoderImpl { }), } } - - fn can_seek(&self) -> bool { - match self { - #[cfg(all(feature = "wav", not(feature = "symphonia-wav")))] - DecoderImpl::Wav(source) => source.can_seek(), - #[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))] - DecoderImpl::Vorbis(source) => source.can_seek(), - #[cfg(all(feature = "flac", not(feature = "symphonia-flac")))] - DecoderImpl::Flac(source) => source.can_seek(), - #[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))] - DecoderImpl::Mp3(source) => source.can_seek(), - #[cfg(feature = "symphonia")] - DecoderImpl::Symphonia(source) => source.can_seek(), - DecoderImpl::None(_) => false, - } - } } impl Decoder @@ -437,11 +421,6 @@ where fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { self.0.try_seek(pos) } - - #[inline] - fn can_seek(&self) -> bool { - self.0.can_seek() - } } impl Iterator for LoopedDecoder @@ -540,11 +519,6 @@ where fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { self.0.try_seek(pos) } - - #[inline] - fn can_seek(&self) -> bool { - self.0.can_seek() - } } /// Error that can happen when creating a decoder. diff --git a/src/decoder/mp3.rs b/src/decoder/mp3.rs index c001259d..78f19e8e 100644 --- a/src/decoder/mp3.rs +++ b/src/decoder/mp3.rs @@ -74,11 +74,6 @@ where // as the seek only takes effect after the current frame is done self.decoder.seek_samples(pos)?; Ok(()) - } - - fn can_seek(&self) -> bool { - true - } } impl Iterator for Mp3Decoder diff --git a/src/decoder/symphonia.rs b/src/decoder/symphonia.rs index 0e426a27..7d69c316 100644 --- a/src/decoder/symphonia.rs +++ b/src/decoder/symphonia.rs @@ -166,11 +166,6 @@ impl Source for SymphoniaDecoder { Ok(_) => Ok(()), } } - - #[inline] - fn can_seek(&self) -> bool { - true - } } impl Iterator for SymphoniaDecoder { diff --git a/src/decoder/vorbis.rs b/src/decoder/vorbis.rs index 5b18ade8..033c6cda 100644 --- a/src/decoder/vorbis.rs +++ b/src/decoder/vorbis.rs @@ -84,11 +84,6 @@ where self.stream_reader.seek_absgp_pg(samples as u64)?; Ok(()) } - - #[inline] - fn can_seek(&self) -> bool { - true - } } impl Iterator for VorbisDecoder diff --git a/src/decoder/wav.rs b/src/decoder/wav.rs index 682f4732..0468db84 100644 --- a/src/decoder/wav.rs +++ b/src/decoder/wav.rs @@ -135,11 +135,6 @@ where let samples = pos.as_secs_f32() * self.sample_rate() as f32; self.reader.reader.seek(samples as u32).map_err(SeekError::HoundDecoder) } - - #[inline] - fn can_seek(&self) -> bool { - true - } } impl Iterator for WavDecoder diff --git a/src/dynamic_mixer.rs b/src/dynamic_mixer.rs index 0e0dd9b8..0db887ec 100644 --- a/src/dynamic_mixer.rs +++ b/src/dynamic_mixer.rs @@ -108,23 +108,37 @@ where } #[inline] - fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { - for source in &self.current_sources { - if !source.can_seek() { - return Err(SeekError::NotSupported { - underlying_source: "unknown, one of the sources added to the DynamicMixer", - }); - } - } - for source in &mut self.current_sources { - source.try_seek(pos).expect("we just verified they can") - } - Ok(()) - } - - #[inline] - fn can_seek(&self) -> bool { - self.current_sources.iter().all(Source::can_seek) + fn try_seek(&mut self, _: Duration) -> Result<(), SeekError> { + Err(SeekError::NotSupported { underlying_source: std::any::type_name::() }) + + // uncomment when #510 is implemented (query position of playback) + + // let mut org_positions = Vec::with_capacity(self.current_sources.len()); + // let mut encounterd_err = None; + // + // for source in &mut self.current_sources { + // let pos = /* source.playback_pos() */ todo!(); + // if let Err(e) = source.try_seek(pos) { + // encounterd_err = Some(e); + // break; + // } else { + // // store pos in case we need to roll back + // org_positions.push(pos); + // } + // } + // + // if let Some(e) = encounterd_err { + // // rollback seeks that happend before err + // for (pos, source) in org_positions + // .into_iter() + // .zip(self.current_sources.iter_mut()) + // { + // source.try_seek(pos)?; + // } + // Err(e) + // } else { + // Ok(()) + // } } } diff --git a/src/queue.rs b/src/queue.rs index e9f9bbd5..560fbfb5 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -174,11 +174,6 @@ where fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { self.current.try_seek(pos) } - - #[inline] - fn can_seek(&self) -> bool { - self.current.can_seek() - } } impl Iterator for SourcesQueueOutput diff --git a/src/source/amplify.rs b/src/source/amplify.rs index 3b254002..bdb5324f 100644 --- a/src/source/amplify.rs +++ b/src/source/amplify.rs @@ -101,8 +101,6 @@ where self.input.try_seek(pos) } - #[inline] - fn can_seek(&self) -> bool { - self.input.can_seek() - } + + } diff --git a/src/source/blt.rs b/src/source/blt.rs index 1d32a99b..ac20307e 100644 --- a/src/source/blt.rs +++ b/src/source/blt.rs @@ -155,10 +155,8 @@ where self.input.try_seek(pos) } - #[inline] - fn can_seek(&self) -> bool { - self.input.can_seek() - } + + } #[derive(Clone, Debug)] diff --git a/src/source/buffered.rs b/src/source/buffered.rs index 1b720e31..908d9a30 100644 --- a/src/source/buffered.rs +++ b/src/source/buffered.rs @@ -247,10 +247,8 @@ where Err(SeekError::NotSupported { underlying_source: std::any::type_name::() }) } - #[inline] - fn can_seek(&self) -> bool { - true - } + + } impl Clone for Buffered diff --git a/src/source/channel_volume.rs b/src/source/channel_volume.rs index d1a724ad..ee60f952 100644 --- a/src/source/channel_volume.rs +++ b/src/source/channel_volume.rs @@ -146,8 +146,6 @@ where self.input.try_seek(pos) } - #[inline] - fn can_seek(&self) -> bool { - self.input.can_seek() - } + + } diff --git a/src/source/delay.rs b/src/source/delay.rs index 847b7dc3..d6cff305 100644 --- a/src/source/delay.rs +++ b/src/source/delay.rs @@ -114,8 +114,6 @@ where self.input.try_seek(pos_without_delay) } - #[inline] - fn can_seek(&self) -> bool { - self.input.can_seek() - } + + } diff --git a/src/source/done.rs b/src/source/done.rs index 06fb6f51..2ab03cbd 100644 --- a/src/source/done.rs +++ b/src/source/done.rs @@ -95,8 +95,6 @@ where self.input.try_seek(pos) } - #[inline] - fn can_seek(&self) -> bool { - self.input.can_seek() - } + + } diff --git a/src/source/empty.rs b/src/source/empty.rs index fa92a54c..5c9ae953 100644 --- a/src/source/empty.rs +++ b/src/source/empty.rs @@ -61,8 +61,6 @@ where Err(SeekError::NotSupported { underlying_source: std::any::type_name::() }) } - #[inline] - fn can_seek(&self) -> bool { - true - } + + } diff --git a/src/source/empty_callback.rs b/src/source/empty_callback.rs index dadfa14e..d003b4af 100644 --- a/src/source/empty_callback.rs +++ b/src/source/empty_callback.rs @@ -60,8 +60,6 @@ where Err(SeekError::NotSupported { underlying_source: std::any::type_name::() }) } - #[inline] - fn can_seek(&self) -> bool { - true - } + + } diff --git a/src/source/fadein.rs b/src/source/fadein.rs index 4c93bfc2..79a14c39 100644 --- a/src/source/fadein.rs +++ b/src/source/fadein.rs @@ -113,8 +113,6 @@ where self.input.try_seek(pos) } - #[inline] - fn can_seek(&self) -> bool { - self.input.can_seek() - } + + } diff --git a/src/source/from_iter.rs b/src/source/from_iter.rs index c3406762..67f64683 100644 --- a/src/source/from_iter.rs +++ b/src/source/from_iter.rs @@ -147,16 +147,8 @@ where } } - #[inline] - fn can_seek(&self) -> bool { - if let Some(source) = &self.current_source { - source.can_seek() - } else { - // no seeking would happen in this case, - // so the seek operation cant fail - true - } - } + + } #[cfg(test)] diff --git a/src/source/mix.rs b/src/source/mix.rs index 7c54e414..adeb4dd3 100644 --- a/src/source/mix.rs +++ b/src/source/mix.rs @@ -123,22 +123,21 @@ where /// Will only attempt a seek if both underlying sources support seek. #[inline] - fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { - // if a source can not seek we call try_seek only on that source - // such that we get its SeekError::NotSupported error - if !self.input1.can_seek() { - self.input1.try_seek(pos) - } else if !self.input2.can_seek() { - self.input2.try_seek(pos) - } else { - self.input1.try_seek(pos)?; - self.input2.try_seek(pos)?; - Ok(()) - } - } - - #[inline] - fn can_seek(&self) -> bool { - self.input1.can_seek() && self.input2.can_seek() + fn try_seek(&mut self, _: Duration) -> Result<(), SeekError> { + Err(SeekError::NotSupported { + underlying_source: std::any::type_name::(), + }) + + // uncomment when #510 is implemented (query position of playback) + + // let org_pos = self.input1.playback_pos(); + // self.input1.try_seek(pos)?; + // + // let res = self.input2.try_seek(pos); + // if res.is_err() { // rollback seek in input1 + // self.input1.try_seek(org_pos)?; + // } + // + // res } } diff --git a/src/source/mod.rs b/src/source/mod.rs index fa7dcb18..b5e210ed 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -358,11 +358,8 @@ where /// Try to seek to a pos, returns [`SeekNotSupported`] if seeking is not /// supported by the current source. fn try_seek(&mut self, _: Duration) -> Result<(), SeekError>; - - /// Returns if seeking is possible. If it is not [`try_seek`] will return - /// Err([`SeekNotSupported`]) - fn can_seek(&self) -> bool; } + // we might add decoders requiring new error types, would non_exhaustive // this would break users builds #[non_exhaustive] @@ -413,10 +410,8 @@ where (**self).try_seek(pos) } - #[inline] - fn can_seek(&self) -> bool { - (**self).can_seek() - } + + } impl Source for Box + Send> @@ -448,10 +443,8 @@ where (**self).try_seek(pos) } - #[inline] - fn can_seek(&self) -> bool { - (**self).can_seek() - } + + } impl Source for Box + Send + Sync> @@ -483,8 +476,6 @@ where (**self).try_seek(pos) } - #[inline] - fn can_seek(&self) -> bool { - (**self).can_seek() - } + + } diff --git a/src/source/pausable.rs b/src/source/pausable.rs index ccd0b6af..05b3ead1 100644 --- a/src/source/pausable.rs +++ b/src/source/pausable.rs @@ -123,8 +123,6 @@ where self.input.try_seek(pos) } - #[inline] - fn can_seek(&self) -> bool { - self.input.can_seek() - } + + } diff --git a/src/source/periodic.rs b/src/source/periodic.rs index e2723c36..97671126 100644 --- a/src/source/periodic.rs +++ b/src/source/periodic.rs @@ -125,10 +125,8 @@ where self.input.try_seek(pos) } - #[inline] - fn can_seek(&self) -> bool { - self.input.can_seek() - } + + } #[cfg(test)] diff --git a/src/source/repeat.rs b/src/source/repeat.rs index be91b521..b9d39bd1 100644 --- a/src/source/repeat.rs +++ b/src/source/repeat.rs @@ -92,10 +92,8 @@ where self.inner.try_seek(pos) } - #[inline] - fn can_seek(&self) -> bool { - self.inner.can_seek() - } + + } impl Clone for Repeat diff --git a/src/source/samples_converter.rs b/src/source/samples_converter.rs index 28f99c09..637b7dea 100644 --- a/src/source/samples_converter.rs +++ b/src/source/samples_converter.rs @@ -103,8 +103,6 @@ where self.inner.try_seek(pos) } - #[inline] - fn can_seek(&self) -> bool { - self.inner.can_seek() - } + + } diff --git a/src/source/sine.rs b/src/source/sine.rs index 8c67c832..6c815b5f 100644 --- a/src/source/sine.rs +++ b/src/source/sine.rs @@ -66,8 +66,6 @@ impl Source for SineWave { // of seeking Ok(()) } - #[inline] - fn can_seek(&self) -> bool { - true - } + + } diff --git a/src/source/skip.rs b/src/source/skip.rs index 5fc8a6d3..e8ee170e 100644 --- a/src/source/skip.rs +++ b/src/source/skip.rs @@ -165,10 +165,8 @@ where self.input.try_seek(pos) } - #[inline] - fn can_seek(&self) -> bool { - self.input.can_seek() - } + + } #[cfg(test)] diff --git a/src/source/skippable.rs b/src/source/skippable.rs index c2051bba..c2413412 100644 --- a/src/source/skippable.rs +++ b/src/source/skippable.rs @@ -97,8 +97,6 @@ where self.input.try_seek(pos) } - #[inline] - fn can_seek(&self) -> bool { - self.input.can_seek() - } + + } diff --git a/src/source/spatial.rs b/src/source/spatial.rs index ca11c31e..1dc08c5b 100644 --- a/src/source/spatial.rs +++ b/src/source/spatial.rs @@ -125,8 +125,6 @@ where self.input.try_seek(pos) } - #[inline] - fn can_seek(&self) -> bool { - self.input.can_seek() - } + + } diff --git a/src/source/speed.rs b/src/source/speed.rs index 23e28bca..09db9168 100644 --- a/src/source/speed.rs +++ b/src/source/speed.rs @@ -107,8 +107,6 @@ where self.input.try_seek(pos_accounting_for_speedup) } - #[inline] - fn can_seek(&self) -> bool { - true - } + + } diff --git a/src/source/stoppable.rs b/src/source/stoppable.rs index a0bd69f7..af176858 100644 --- a/src/source/stoppable.rs +++ b/src/source/stoppable.rs @@ -96,8 +96,6 @@ where self.input.try_seek(pos) } - #[inline] - fn can_seek(&self) -> bool { - self.input.can_seek() - } + + } diff --git a/src/source/take.rs b/src/source/take.rs index 34ac1349..d843b495 100644 --- a/src/source/take.rs +++ b/src/source/take.rs @@ -184,8 +184,6 @@ where self.input.try_seek(pos) } - #[inline] - fn can_seek(&self) -> bool { - self.input.can_seek() - } + + } diff --git a/src/source/uniform.rs b/src/source/uniform.rs index 9a1ade4d..fcaee5c5 100644 --- a/src/source/uniform.rs +++ b/src/source/uniform.rs @@ -153,15 +153,6 @@ where Ok(()) } } - - #[inline] - fn can_seek(&self) -> bool { - if let Some(input) = self.inner.as_ref() { - input.inner().inner().inner().inner().can_seek() - } else { - true - } - } } #[derive(Clone, Debug)] @@ -175,11 +166,6 @@ impl Take { pub fn inner_mut(&mut self) -> &mut I { &mut self.iter } - - #[inline] - pub fn inner(&self) -> &I { - &self.iter - } } impl Iterator for Take diff --git a/src/source/zero.rs b/src/source/zero.rs index 5a2bb71a..e1ceb3c9 100644 --- a/src/source/zero.rs +++ b/src/source/zero.rs @@ -85,8 +85,6 @@ where Ok(()) } - #[inline] - fn can_seek(&self) -> bool { - true - } + + } diff --git a/src/static_buffer.rs b/src/static_buffer.rs index aa356778..95184423 100644 --- a/src/static_buffer.rs +++ b/src/static_buffer.rs @@ -88,12 +88,9 @@ where #[inline] fn try_seek(&mut self, _: Duration) -> Result<(), SeekError> { - Err(SeekError::NotSupported { underlying_source: std::any::type_name::() }) - } - - #[inline] - fn can_seek(&self) -> bool { - true + Err(SeekError::NotSupported { + underlying_source: std::any::type_name::(), + }) } } From 8416210628b062f56b493e8eeca680081f78635d Mon Sep 17 00:00:00 2001 From: dvdsk Date: Sun, 8 Oct 2023 14:05:34 +0200 Subject: [PATCH 18/62] adds test verifying correct seek position after seek --- tests/seek.rs | 53 ++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/tests/seek.rs b/tests/seek.rs index 76c08905..8daa98a3 100644 --- a/tests/seek.rs +++ b/tests/seek.rs @@ -1,7 +1,7 @@ use std::io::{BufReader, Read, Seek}; use std::path::Path; use std::sync::Once; -use std::time::Duration; +use std::time::{Duration, Instant}; use rodio::{Decoder, OutputStream, OutputStreamHandle, Sink, Source}; @@ -58,7 +58,6 @@ fn seek_returns_err_if_unsupported() { for (format, supported, decoder) in format_decoder_info().iter().cloned() { println!("trying: {format},\t\tby: {decoder},\t\tshould support seek: {supported}"); let (sink, decoder) = sink_and_decoder(format); - assert_eq!(decoder.can_seek(), supported); sink.append(decoder); let res = sink.try_seek(Duration::from_secs(2)); assert_eq!(res.is_ok(), supported); @@ -67,13 +66,53 @@ fn seek_returns_err_if_unsupported() { #[test] fn seek_beyond_end_does_not_crash() { - for (format, _, decoder_name) in format_decoder_info().iter().cloned() { + for (format, _, decoder_name) in format_decoder_info() + .iter() + .cloned() + .filter(|(_, supported, _)| *supported) + { let (sink, decoder) = sink_and_decoder(format); - if !decoder.can_seek() { - continue; - } - println!("seeking beyond end for: {format}\t decoded by: {decoder_name}"); + println!("seeking beyond end in: {format}\t decoded by: {decoder_name}"); sink.append(decoder); sink.try_seek(Duration::from_secs(999)).unwrap(); } } + +#[ignore] +#[test] // in the future use PR #510 (playback position) to speed this up +fn seek_results_in_correct_remaining_playtime() { + for (format, _, decoder_name) in format_decoder_info() + .iter() + .cloned() + .filter(|(_, supported, _)| *supported) + { + let (sink, decoder) = sink_and_decoder(format); + println!("checking seek time in: {format}\t decoded by: {decoder_name}"); + let total_duration = match decoder.total_duration() { + Some(d) => d, + None => { + let now = Instant::now(); + sink.append(decoder); + sink.sleep_until_end(); + now.elapsed() + } + }; + + let (sink, decoder) = sink_and_decoder(format); + sink.append(decoder); + + const SEEK_BEFORE_END: Duration = Duration::from_secs(5); + sink.try_seek(total_duration - SEEK_BEFORE_END).unwrap(); + + let now = Instant::now(); + sink.sleep_until_end(); + let elapsed = now.elapsed(); + let expected = SEEK_BEFORE_END; + + if elapsed.as_millis().abs_diff(expected.as_millis()) > 250 { + panic!("Seek did not result in expected leftover playtime + leftover time: {elapsed:?} + expected time left in source: {SEEK_BEFORE_END:?}"); + } + } +} From 2b39d27aaf6630decdab12bb48793f2816a11cf2 Mon Sep 17 00:00:00 2001 From: dvdsk Date: Tue, 10 Oct 2023 02:25:34 +0200 Subject: [PATCH 19/62] adds total_duration() impl to SymphoniaDecoder, makes seek saturating at source end if total_duration known --- src/decoder/mp3.rs | 1 + src/decoder/symphonia.rs | 20 +++++++++++-- src/decoder/wav.rs | 5 +++- src/source/mod.rs | 5 +++- tests/seek.rs | 64 ++++++++++++++++++++++++++-------------- 5 files changed, 69 insertions(+), 26 deletions(-) diff --git a/src/decoder/mp3.rs b/src/decoder/mp3.rs index 78f19e8e..1ee4c06a 100644 --- a/src/decoder/mp3.rs +++ b/src/decoder/mp3.rs @@ -74,6 +74,7 @@ where // as the seek only takes effect after the current frame is done self.decoder.seek_samples(pos)?; Ok(()) + } } impl Iterator for Mp3Decoder diff --git a/src/decoder/symphonia.rs b/src/decoder/symphonia.rs index 7d69c316..6eef152e 100644 --- a/src/decoder/symphonia.rs +++ b/src/decoder/symphonia.rs @@ -26,6 +26,7 @@ pub struct SymphoniaDecoder { decoder: Box, current_frame_offset: usize, format: Box, + total_duration: Option