Skip to content

Commit

Permalink
Merge pull request #513 from dvdsk/seek_runtime_err
Browse files Browse the repository at this point in the history
Implements Seek
  • Loading branch information
dvdsk authored Apr 6, 2024
2 parents 2a7b351 + 1ed1197 commit d103517
Show file tree
Hide file tree
Showing 48 changed files with 1,174 additions and 220 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]

### Changed
- Adds a new method `try_seek` to all sources. It returns either an error or
seeks to the given position. A few sources are "unsupported" they return the
error `Unsupported`.
- `Source` trait is now also implemented for `Box<dyn Source>` and `&mut Source`
- `fn new_vorbis` is now also available when the `symphonia-vorbis` feature is enabled

Expand Down
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ lewton = { version = "0.10", optional = true }
minimp3_fixed = { version = "0.5.4", optional = true}
symphonia = { version = "0.5.2", optional = true, default-features = false }
crossbeam-channel = { version = "0.5.8", optional = true }
thiserror = "1.0.49"

[features]
default = ["flac", "vorbis", "wav", "mp3"]
Expand All @@ -38,6 +39,8 @@ symphonia-wav = ["symphonia/wav", "symphonia/pcm", "symphonia/adpcm"]

[dev-dependencies]
quickcheck = "0.9.2"
rstest = "0.18.2"
rstest_reuse = "0.6.0"

[[example]]
name = "music_m4a"
Expand Down
Binary file added assets/RL.flac
Binary file not shown.
Binary file added assets/RL.m4a
Binary file not shown.
Binary file added assets/RL.mp3
Binary file not shown.
Binary file added assets/RL.wav
Binary file not shown.
18 changes: 18 additions & 0 deletions examples/seek_mp3.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use std::io::BufReader;
use std::time::Duration;

fn main() {
let (_stream, handle) = rodio::OutputStream::try_default().unwrap();
let sink = rodio::Sink::try_new(&handle).unwrap();

let file = std::fs::File::open("assets/music.mp3").unwrap();
sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap());

std::thread::sleep(std::time::Duration::from_secs(2));
sink.try_seek(Duration::from_secs(0)).unwrap();

std::thread::sleep(std::time::Duration::from_secs(2));
sink.try_seek(Duration::from_secs(4)).unwrap();

sink.sleep_until_end();
}
63 changes: 58 additions & 5 deletions src/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@
//!

use std::time::Duration;
use std::vec::IntoIter as VecIntoIter;

use crate::source::SeekError;
use crate::{Sample, Source};

/// A buffer of samples treated as a source.
pub struct SamplesBuffer<S> {
data: VecIntoIter<S>,
data: Vec<S>,
pos: usize,
channels: u16,
sample_rate: u32,
duration: Duration,
Expand Down Expand Up @@ -53,7 +54,8 @@ where
);

SamplesBuffer {
data: data.into_iter(),
data,
pos: 0,
channels,
sample_rate,
duration,
Expand Down Expand Up @@ -84,6 +86,27 @@ where
fn total_duration(&self) -> Option<Duration> {
Some(self.duration)
}

// this is fast because all the samples are in memory already
// and due to the constant sample_rate we can jump to the right
// sample directly
//
/// This jumps in memory till the sample for `pos`.
#[inline]
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
let curr_channel = self.pos % self.channels() as usize;
let new_pos = pos.as_secs_f32() * self.sample_rate() as f32 * self.channels() as f32;
// saturate pos at the end of the source
let new_pos = new_pos as usize;
let new_pos = new_pos.min(self.data.len());

// make sure the next sample is for the right channel
let new_pos = new_pos.next_multiple_of(self.channels() as usize);
let new_pos = new_pos - curr_channel;

self.pos = new_pos;
Ok(())
}
}

impl<S> Iterator for SamplesBuffer<S>
Expand All @@ -94,12 +117,14 @@ where

#[inline]
fn next(&mut self) -> Option<S> {
self.data.next()
let sample = self.data.get(self.pos)?;
self.pos += 1;
Some(*sample)
}

#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.data.size_hint()
(self.data.len(), Some(self.data.len()))
}
}

Expand Down Expand Up @@ -144,4 +169,32 @@ mod tests {
assert_eq!(buf.next(), Some(6));
assert_eq!(buf.next(), None);
}

#[cfg(test)]
mod try_seek {
use super::*;
use std::time::Duration;

#[test]
fn channel_order_stays_correct() {
const SAMPLE_RATE: u32 = 100;
const CHANNELS: u16 = 2;
let mut buf = SamplesBuffer::new(
CHANNELS,
SAMPLE_RATE,
(0..2000i16).into_iter().collect::<Vec<_>>(),
);
buf.try_seek(Duration::from_secs(5)).unwrap();
assert_eq!(
buf.next(),
Some(5i16 * SAMPLE_RATE as i16 * CHANNELS as i16)
);

assert!(buf.next().is_some_and(|s| s % 2 == 1));
assert!(buf.next().is_some_and(|s| s % 2 == 0));

buf.try_seek(Duration::from_secs(6)).unwrap();
assert!(buf.next().is_some_and(|s| s % 2 == 1),);
}
}
}
6 changes: 6 additions & 0 deletions src/conversions/channels.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ where
pub fn into_inner(self) -> I {
self.input
}

/// Get mutable access to the iterator
#[inline]
pub fn inner_mut(&mut self) -> &mut I {
&mut self.input
}
}

impl<I> Iterator for ChannelCountConverter<I>
Expand Down
6 changes: 6 additions & 0 deletions src/conversions/sample.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
6 changes: 6 additions & 0 deletions src/conversions/sample_rate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
8 changes: 8 additions & 0 deletions src/decoder/flac.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::io::{Read, Seek, SeekFrom};
use std::mem;
use std::time::Duration;

use crate::source::SeekError;
use crate::Source;

use claxon::FlacReader;
Expand Down Expand Up @@ -79,6 +80,13 @@ where
self.samples
.map(|s| Duration::from_micros(s * 1_000_000 / self.sample_rate as u64))
}

#[inline]
fn try_seek(&mut self, _: Duration) -> Result<(), SeekError> {
Err(SeekError::NotSupported {
underlying_source: std::any::type_name::<Self>(),
})
}
}

impl<R> Iterator for FlacDecoder<R>
Expand Down
Loading

0 comments on commit d103517

Please sign in to comment.