adds try_seek for sink and all sources

This commit is contained in:
dvdsk 2023-10-03 00:31:42 +02:00
parent 745db82876
commit 202687b934
No known key found for this signature in database
GPG key ID: 6CF9D20C5709A836
31 changed files with 316 additions and 77 deletions

View file

@ -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();
}

View file

@ -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<I> Iterator for ChannelCountConverter<I>

View file

@ -23,6 +23,12 @@ impl<I, O> DataConverter<I, O> {
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<I, O> Iterator for DataConverter<I, O>

View file

@ -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;

View file

@ -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<Result<(), SeekNotSupported>>,
}
impl SeekOrder {
fn new(pos: Duration) -> (Self, Receiver<Result<(), SeekNotSupported>>) {
#[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<S>(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<f32>,
stopped: AtomicBool,
speed: Mutex<f32>,
to_clear: Mutex<u32>,
seek: Mutex<Option<Duration>>,
seek: Mutex<Option<SeekOrder>>,
}
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<S>(&self, source: S)
where
S: Source + Send + 'static,
f32: FromSample<S::Item>,
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.

View file

@ -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<I>(input: I, factor: f32) -> Amplify<I>
where
@ -93,4 +95,9 @@ where
fn total_duration(&self) -> Option<Duration> {
self.input.total_duration()
}
#[inline]
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> {
self.input.try_seek(pos)
}
}

View file

@ -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<Duration> {
self.input.total_duration()
}
#[inline]
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> {
self.input.try_seek(pos)
}
}
#[derive(Clone, Debug)]

View file

@ -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<I>(input: I) -> Buffered<I>
@ -239,6 +241,11 @@ where
fn total_duration(&self) -> Option<Duration> {
self.total_duration
}
#[inline]
fn try_seek(&mut self, _: Duration) -> Result<(), SeekNotSupported> {
Err(SeekNotSupported)
}
}
impl<I> Clone for Buffered<I>

View file

@ -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<Duration> {
self.input.total_duration()
}
#[inline]
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> {
self.input.try_seek(pos)
}
}

View file

@ -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<I>(input: I, duration: Duration) -> Delay<I>
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)
}
}

View file

@ -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<I> {
@ -87,4 +89,9 @@ where
fn total_duration(&self) -> Option<Duration> {
self.input.total_duration()
}
#[inline]
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> {
self.input.try_seek(pos)
}
}

View file

@ -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<S>(PhantomData<S>);
@ -53,4 +55,9 @@ where
fn total_duration(&self) -> Option<Duration> {
Some(Duration::new(0, 0))
}
#[inline]
fn try_seek(&mut self, _: Duration) -> Result<(), SeekNotSupported> {
Err(SeekNotSupported)
}
}

View file

@ -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<S> {
pub phantom_data: PhantomData<S>,
@ -52,4 +54,9 @@ where
fn total_duration(&self) -> Option<Duration> {
Some(Duration::new(0, 0))
}
#[inline]
fn try_seek(&mut self, _: Duration) -> Result<(), SeekNotSupported> {
Err(SeekNotSupported)
}
}

View file

@ -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<I>(input: I, duration: Duration) -> FadeIn<I>
where
@ -105,4 +107,9 @@ where
fn total_duration(&self) -> Option<Duration> {
self.input.total_duration()
}
#[inline]
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> {
self.input.try_seek(pos)
}
}

View file

@ -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<Duration> {
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)]

View file

@ -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)
}
}

View file

@ -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<Duration> {
(**self).total_duration()
}
#[inline]
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> {
(**self).try_seek(pos)
}
}
impl<S> Source for Box<dyn Source<Item = S> + Send>
@ -421,6 +428,11 @@ where
fn total_duration(&self) -> Option<Duration> {
(**self).total_duration()
}
#[inline]
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> {
(**self).try_seek(pos)
}
}
impl<S> Source for Box<dyn Source<Item = S> + Send + Sync>
@ -446,5 +458,10 @@ where
fn total_duration(&self) -> Option<Duration> {
(**self).total_duration()
}
#[inline]
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> {
(**self).try_seek(pos)
}
}

View file

@ -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<I>(source: I, paused: bool) -> Pausable<I>
where
@ -115,4 +117,9 @@ where
fn total_duration(&self) -> Option<Duration> {
self.input.total_duration()
}
#[inline]
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> {
self.input.try_seek(pos)
}
}

View file

@ -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<I, F>(source: I, period: Duration, modifier: F) -> PeriodicAccess<I, F>
where
@ -117,6 +119,11 @@ where
fn total_duration(&self) -> Option<Duration> {
self.input.total_duration()
}
#[inline]
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> {
self.input.try_seek(pos)
}
}
#[cfg(test)]

View file

@ -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<I>(input: I) -> Repeat<I>
where
@ -84,6 +86,11 @@ where
fn total_duration(&self) -> Option<Duration> {
None
}
#[inline]
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> {
self.inner.try_seek(pos)
}
}
impl<I> Clone for Repeat<I>

View file

@ -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<Duration> {
self.inner.total_duration()
}
#[inline]
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> {
self.inner.try_seek(pos)
}
}

View file

@ -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<Duration> {
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)
}
}

View file

@ -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)]

View file

@ -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<I>(source: I) -> Skippable<I> {
Skippable {
@ -89,4 +91,9 @@ where
fn total_duration(&self) -> Option<Duration> {
self.input.total_duration()
}
#[inline]
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> {
self.input.try_seek(pos)
}
}

View file

@ -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<Duration> {
self.input.total_duration()
}
#[inline]
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> {
self.input.try_seek(pos)
}
}

View file

@ -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<I>(input: I, factor: f32) -> Speed<I> {
Speed { input, factor }
@ -91,16 +93,17 @@ where
#[inline]
fn total_duration(&self) -> Option<Duration> {
// 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.
* <dvdsk noreply@davidsk.dev> */
// 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)
}
}

View file

@ -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<I>(source: I) -> Stoppable<I> {
Stoppable {
@ -88,4 +90,9 @@ where
fn total_duration(&self) -> Option<Duration> {
self.input.total_duration()
}
#[inline]
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> {
self.input.try_seek(pos)
}
}

View file

@ -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<I>(input: I, duration: Duration) -> TakeDuration<I>
where
@ -176,4 +178,9 @@ where
None
}
}
#[inline]
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekNotSupported> {
self.input.try_seek(pos)
}
}

View file

@ -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<Duration> {
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<I> {
n: Option<usize>,
}
impl<I> Take<I> {
#[inline]
pub fn inner_mut(&mut self) -> &mut I {
&mut self.iter
}
}
impl<I> Iterator for Take<I>
where
I: Iterator,

View file

@ -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<S> {
@ -77,4 +79,9 @@ where
fn total_duration(&self) -> Option<Duration> {
None
}
#[inline]
fn try_seek(&mut self, _: Duration) -> Result<(), SeekNotSupported> {
Ok(())
}
}

28
tests/seek_error.rs Normal file
View file

@ -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(()));
}