Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implements Seek #513

Merged
merged 67 commits into from
Apr 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
fafe4ba
seek implemented through SourceExt trait
dvdsk Jan 2, 2021
023e833
request pos now uses mutable self
dvdsk Jan 13, 2021
204a3f8
switch to fork for cpal
dvdsk Jan 26, 2021
d47842f
remove seek trait from source mods, added it to symphonia decoder, re…
dvdsk Sep 30, 2023
745db82
removes SeekableSource adds failable try_seek to Source
dvdsk Oct 2, 2023
202687b
adds try_seek for sink and all sources
dvdsk Oct 2, 2023
1f3f36a
removes default try_seek impl, impl try_seek for decoders + refactors…
dvdsk Oct 4, 2023
5b933d7
refactors seektest and adds more formats
dvdsk Oct 4, 2023
961c3ef
Fix seeking for mix source
dvdsk Oct 4, 2023
e1092f7
refactors seek test, now covers all decoders/formats
dvdsk Oct 5, 2023
a3c55b8
fixes symphonia seek div by zero
dvdsk Oct 5, 2023
d8a8be4
add seek for lewton
dvdsk Oct 5, 2023
9a4dcb0
add can_seek method to source
dvdsk Oct 6, 2023
eb22ec5
refactor tests, add seek beyond stream test
dvdsk Oct 7, 2023
ebebe88
turns SeekNotSupported into a SeekError
dvdsk Oct 7, 2023
5d44bfe
fixes symphonia seek beyond end of file returning an error
dvdsk Oct 8, 2023
10262f8
removes can_seek in favor of rolling back seek operations (requires P…
dvdsk Oct 8, 2023
8416210
adds test verifying correct seek position after seek
dvdsk Oct 8, 2023
2b39d27
adds total_duration() impl to SymphoniaDecoder, makes seek saturating…
dvdsk Oct 10, 2023
5de7383
finishes doc for sink::try_seek
dvdsk Oct 10, 2023
f3a1966
symphonia throws error if duration is unknown and seek is beyond sour…
dvdsk Oct 10, 2023
560961f
adds try_seek to spatial source
dvdsk Oct 10, 2023
4237eff
revert to upstream non-seekable minimp3, comment out minimp3-seek sup…
dvdsk Oct 10, 2023
3f4b530
Language and spelling fixes by @naglis
dvdsk Oct 13, 2023
82bfa41
fixes seek in m4a files, fixes seeking having 1 second granularity
dvdsk Oct 11, 2023
d1a809f
formats everything with cargo fmt
dvdsk Oct 13, 2023
a24e0e6
speeds up correct_remaining_playtime test
dvdsk Oct 12, 2023
724df4d
completely remove sink usage from seek tests
dvdsk Oct 12, 2023
b26194d
make cargo fmt happy
dvdsk Oct 12, 2023
963a484
refactors symphonia try_seek
dvdsk Oct 12, 2023
c2c85e2
improve seek beyond end documentation
dvdsk Oct 13, 2023
7357f19
remove commented out dead code in tests/seek.rs
dvdsk Oct 13, 2023
8c77462
use From<f64> instead of custom `time_from_duration`
dvdsk Oct 13, 2023
5dd4135
Remove duplicate doc section and fix spelling in docs
dvdsk Oct 13, 2023
b7b5735
Merge branch 'master' into seek_runtime_err
dvdsk Oct 17, 2023
50a781a
Merge branch 'master' into seek_runtime_err
dvdsk Oct 21, 2023
57f2a3c
cargo fmt
dvdsk Oct 21, 2023
470eba8
Merge branch 'master' into seek_runtime_err
dvdsk Jan 26, 2024
4dea149
fixes spell errors in docs and SeekError
dvdsk Jan 26, 2024
fb44f71
Fixes seek example and various spell/grammar issues
dvdsk Jan 31, 2024
766fbbf
implement try_seek for SamplesBuffer
dvdsk Apr 1, 2024
c60819e
seek tests finds beep in stereo test file
dvdsk Apr 2, 2024
2e9d680
(seek/test) fixes duration calc in test
dvdsk Apr 2, 2024
67612b5
(seek/test) adds failing test for seeking in exausted source
dvdsk Apr 2, 2024
d0fce09
fix(seek) vorbis decoder crashing when seeking
dvdsk Apr 3, 2024
9ae1c55
test(seek) add test for channel order
dvdsk Apr 3, 2024
04c6957
test(seek) improve channel order test
dvdsk Apr 3, 2024
1c82136
fix(seek) vorbis decoder now respects channel order
dvdsk Apr 3, 2024
5562241
commit to save work on vorbis try_seek
dvdsk Apr 3, 2024
80add81
removes seeking from vorbis decoder, can not be implemented
dvdsk Apr 3, 2024
4e14b0a
Vorbis actually can be implemented since the sample rate is constant
dvdsk Apr 3, 2024
3bafe32
refactor(seek) explain why seek to sample works
dvdsk Apr 3, 2024
26e9db7
remove seek support for (lewton) vorbis
dvdsk Apr 3, 2024
7eb13be
(tests) use rstest to refactor and expand seek test
dvdsk Apr 4, 2024
f846cdf
fix(seek) hound(wav) now keeps channel order consistent
dvdsk Apr 4, 2024
a52a41b
refactor(decoder/symphonia) logic reorderd
dvdsk Apr 4, 2024
1fcf4b8
test(seek) made channel order more brittle
dvdsk Apr 5, 2024
40a9447
refactor(decoder/sympthonia) use for loop instead loop + match & escape
dvdsk Apr 5, 2024
6034af3
fix(decoder/symphonia) seek is no longer off
dvdsk Apr 5, 2024
aa0880d
seek/error symphonia seekerror is now more precise
dvdsk Apr 5, 2024
7cf0451
fix(seek/delay) seek < delay duration ate up the delay
dvdsk Apr 5, 2024
6f1f44f
Merge branch 'master' into seek_runtime_err
dvdsk Apr 6, 2024
34366fe
style clippy fixes
dvdsk Apr 6, 2024
a4d167f
fix(seek) conditional compilation
dvdsk Apr 6, 2024
b49b22a
Merge branch 'master' into seek_runtime_err
dvdsk Apr 6, 2024
bbc8f00
update changelog
dvdsk Apr 6, 2024
1ed1197
style, removes a needless clone()
dvdsk Apr 6, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
dvdsk marked this conversation as resolved.
Show resolved Hide resolved
#[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
dvdsk marked this conversation as resolved.
Show resolved Hide resolved
#[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
Loading