refactor tests, add seek beyond stream test

This commit is contained in:
dvdsk 2023-10-07 13:45:05 +02:00
parent 9a4dcb0c41
commit eb22ec5dac
No known key found for this signature in database
GPG key ID: 6CF9D20C5709A836
4 changed files with 57 additions and 23 deletions

View file

@ -88,7 +88,7 @@ where
#[inline] #[inline]
fn can_seek(&self) -> bool { fn can_seek(&self) -> bool {
true false
} }
} }

View file

@ -153,7 +153,7 @@ impl Source for SymphoniaDecoder {
.seek( .seek(
SeekMode::Accurate, SeekMode::Accurate,
SeekTo::Time { SeekTo::Time {
time: Time::new(pos.as_secs(), dbg!(pos_fract)), time: Time::new(pos.as_secs(), pos_fract),
track_id: None, track_id: None,
}, },
) )

View file

@ -201,19 +201,19 @@ impl Sink {
/// 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. /// 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 /// 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 /// 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 /// support seeking. If you find a reason you need `Sink::can_seek()` here please
/// open an issue /// open an issue
pub fn try_seek(&self, pos: Duration) -> Result<(), SeekNotSupported> { pub fn try_seek(&self, pos: Duration) -> Result<(), SeekNotSupported> {
let (order, feedback) = SeekOrder::new(pos); let (order, feedback) = SeekOrder::new(pos);
*self.controls.seek.lock().unwrap() = Some(order); *self.controls.seek.lock().unwrap() = Some(order);
match feedback.recv() { match feedback.recv() {
Ok(seek_res) => seek_res, 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 // invalidating this one and closing the feedback channel
// ... or the audio thread panicked. // ... or the audio thread panicked.
Err(_) => Ok(()), Err(_) => Ok(()),
} }
} }

View file

@ -1,21 +1,37 @@
use std::io::BufReader; use std::io::{BufReader, Read, Seek};
use std::path::Path; use std::path::Path;
use std::sync::Once;
use std::time::Duration; use std::time::Duration;
use rodio::source::SeekNotSupported; use rodio::{Decoder, OutputStream, OutputStreamHandle, Sink, Source};
fn play_and_seek(asset_path: &Path) -> Result<(), SeekNotSupported> { static mut STREAM: Option<OutputStream> = None;
let (_stream, handle) = rodio::OutputStream::try_default().unwrap(); static mut STREAM_HANDLE: Option<OutputStreamHandle> = None;
let sink = rodio::Sink::try_new(&handle).unwrap(); static INIT: Once = Once::new();
let file = std::fs::File::open(asset_path).unwrap(); fn global_stream_handle() -> &'static OutputStreamHandle {
sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap()); // mutable global access is guarded by Once therefore
sink.try_seek(Duration::from_secs(2)) // 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 sink_and_decoder(format: &str) -> (Sink, Decoder<impl Read + Seek>) {
fn seek_returns_err_if_unsupported() { let sink = rodio::Sink::try_new(global_stream_handle()).unwrap();
let formats = [ 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")] #[cfg(feature = "minimp3")]
("mp3", true, "minimp3"), ("mp3", true, "minimp3"),
#[cfg(feature = "symphonia-mp3")] #[cfg(feature = "symphonia-mp3")]
@ -34,12 +50,30 @@ fn seek_returns_err_if_unsupported() {
("flac", true, "symphonia"), ("flac", true, "symphonia"),
#[cfg(feature = "symphonia-isomp4")] #[cfg(feature = "symphonia-isomp4")]
("m4a", true, "_"), ("m4a", true, "_"),
]; ]
}
for (format, supported, decoder) in formats { #[test]
println!("trying: {format} by {decoder}, should support seek: {supported}"); fn seek_returns_err_if_unsupported() {
let asset = Path::new("assets/music").with_extension(format); for (format, supported, decoder) in format_decoder_info().iter().cloned() {
let res = play_and_seek(&asset); 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); 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();
}
}