2023-10-07 11:45:05 +00:00
|
|
|
use std::io::{BufReader, Read, Seek};
|
2023-10-04 14:49:35 +00:00
|
|
|
use std::path::Path;
|
2023-10-12 11:50:47 +00:00
|
|
|
use std::time::Duration;
|
2023-10-04 14:49:35 +00:00
|
|
|
|
2023-10-12 11:50:47 +00:00
|
|
|
use rodio::{Decoder, Source};
|
2023-10-04 14:49:35 +00:00
|
|
|
|
2023-10-12 11:39:16 +00:00
|
|
|
fn time_remaining(decoder: Decoder<impl Read + Seek>) -> Duration {
|
|
|
|
let rate = decoder.sample_rate() as f64;
|
|
|
|
let n_channels = decoder.channels() as f64;
|
|
|
|
let n_samples = decoder.into_iter().count() as f64;
|
|
|
|
Duration::from_secs_f64(n_samples / rate / n_channels)
|
|
|
|
}
|
|
|
|
|
2023-10-12 11:50:47 +00:00
|
|
|
fn get_decoder(format: &str) -> Decoder<impl Read + Seek> {
|
2023-10-07 11:45:05 +00:00
|
|
|
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();
|
2023-10-12 11:50:47 +00:00
|
|
|
decoder
|
2023-10-07 11:45:05 +00:00
|
|
|
}
|
|
|
|
|
2023-10-12 11:50:47 +00:00
|
|
|
// run tests twice to test all decoders
|
2023-10-12 11:54:27 +00:00
|
|
|
// cargo test
|
2023-10-12 11:50:47 +00:00
|
|
|
// cargo test --features symphonia-all
|
2023-10-07 11:45:05 +00:00
|
|
|
fn format_decoder_info() -> &'static [(&'static str, bool, &'static str)] {
|
|
|
|
&[
|
2023-10-11 12:14:51 +00:00
|
|
|
#[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))]
|
2023-10-10 12:26:12 +00:00
|
|
|
("mp3", false, "minimp3"),
|
2023-10-05 11:28:59 +00:00
|
|
|
#[cfg(feature = "symphonia-mp3")]
|
|
|
|
("mp3", true, "symphonia"),
|
2023-10-11 12:14:51 +00:00
|
|
|
#[cfg(all(feature = "wav", not(feature = "symphonia-wav")))]
|
2023-10-06 11:30:32 +00:00
|
|
|
("wav", true, "hound"),
|
2023-10-05 11:28:59 +00:00
|
|
|
#[cfg(feature = "symphonia-wav")]
|
|
|
|
("wav", true, "symphonia"),
|
2023-10-11 12:14:51 +00:00
|
|
|
#[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))]
|
2023-10-05 11:44:10 +00:00
|
|
|
("ogg", true, "lewton"),
|
2023-10-11 12:14:51 +00:00
|
|
|
// note: disabled, broken decoder see issue: #516
|
2023-10-10 00:25:34 +00:00
|
|
|
// #[cfg(feature = "symphonia-vorbis")]
|
|
|
|
// ("ogg", true, "symphonia"),
|
2023-10-11 12:14:51 +00:00
|
|
|
#[cfg(all(feature = "flac", not(feature = "symphonia-flac")))]
|
2023-10-05 11:28:59 +00:00
|
|
|
("flac", false, "claxon"),
|
|
|
|
#[cfg(feature = "symphonia-flac")]
|
|
|
|
("flac", true, "symphonia"),
|
2023-10-10 00:25:34 +00:00
|
|
|
// note: disabled, symphonia returns error unsupported format
|
2023-10-11 12:14:51 +00:00
|
|
|
#[cfg(feature = "symphonia-isomp4")]
|
|
|
|
("m4a", true, "symphonia"),
|
2023-10-07 11:45:05 +00:00
|
|
|
]
|
|
|
|
}
|
2023-10-05 11:28:59 +00:00
|
|
|
|
2023-10-07 11:45:05 +00:00
|
|
|
#[test]
|
|
|
|
fn seek_returns_err_if_unsupported() {
|
2023-10-11 12:14:51 +00:00
|
|
|
for (format, supported, decoder_name) in format_decoder_info().iter().cloned() {
|
|
|
|
println!("trying: {format},\t\tby: {decoder_name},\t\tshould support seek: {supported}");
|
2023-10-12 11:50:47 +00:00
|
|
|
let mut decoder = get_decoder(format);
|
|
|
|
let res = decoder.try_seek(Duration::from_millis(2500));
|
2023-10-11 12:14:51 +00:00
|
|
|
assert_eq!(res.is_ok(), supported, "decoder: {decoder_name}");
|
2023-10-04 14:49:35 +00:00
|
|
|
}
|
|
|
|
}
|
2023-10-07 11:45:05 +00:00
|
|
|
|
2023-10-10 00:25:34 +00:00
|
|
|
// #[ignore]
|
|
|
|
#[test] // in the future use PR #510 (playback position) to speed this up
|
|
|
|
fn seek_beyond_end_saturates() {
|
2023-10-08 12:05:34 +00:00
|
|
|
for (format, _, decoder_name) in format_decoder_info()
|
|
|
|
.iter()
|
|
|
|
.cloned()
|
|
|
|
.filter(|(_, supported, _)| *supported)
|
|
|
|
{
|
2023-10-12 11:50:47 +00:00
|
|
|
let mut decoder = get_decoder(format);
|
2023-10-10 00:25:34 +00:00
|
|
|
|
|
|
|
println!("seeking beyond end for: {format}\t decoded by: {decoder_name}");
|
2023-10-12 11:50:47 +00:00
|
|
|
let res = decoder.try_seek(Duration::from_secs(999));
|
2023-10-13 13:47:28 +00:00
|
|
|
assert!(res.is_ok());
|
2023-10-10 00:25:34 +00:00
|
|
|
|
2023-10-12 11:50:47 +00:00
|
|
|
assert!(time_remaining(decoder) < Duration::from_secs(1));
|
2023-10-10 00:25:34 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-12 11:39:16 +00:00
|
|
|
#[test]
|
2023-10-08 12:05:34 +00:00
|
|
|
fn seek_results_in_correct_remaining_playtime() {
|
|
|
|
for (format, _, decoder_name) in format_decoder_info()
|
|
|
|
.iter()
|
|
|
|
.cloned()
|
|
|
|
.filter(|(_, supported, _)| *supported)
|
|
|
|
{
|
2023-10-10 00:25:34 +00:00
|
|
|
println!("checking seek duration for: {format}\t decoded by: {decoder_name}");
|
2023-10-08 12:05:34 +00:00
|
|
|
|
2023-10-12 11:50:47 +00:00
|
|
|
let decoder = get_decoder(format);
|
2023-10-12 11:39:16 +00:00
|
|
|
let total_duration = time_remaining(decoder);
|
2023-10-08 12:05:34 +00:00
|
|
|
|
|
|
|
const SEEK_BEFORE_END: Duration = Duration::from_secs(5);
|
2023-10-12 11:50:47 +00:00
|
|
|
let mut decoder = get_decoder(format);
|
2023-10-12 11:39:16 +00:00
|
|
|
decoder.try_seek(total_duration - SEEK_BEFORE_END).unwrap();
|
2023-10-08 12:05:34 +00:00
|
|
|
|
2023-10-12 11:39:16 +00:00
|
|
|
let after_seek = time_remaining(decoder);
|
2023-10-08 12:05:34 +00:00
|
|
|
let expected = SEEK_BEFORE_END;
|
|
|
|
|
2023-10-12 11:39:16 +00:00
|
|
|
if after_seek.as_millis().abs_diff(expected.as_millis()) > 250 {
|
2023-10-10 00:25:34 +00:00
|
|
|
panic!(
|
|
|
|
"Seek did not result in expected leftover playtime
|
2023-10-12 11:39:16 +00:00
|
|
|
leftover time: {after_seek:?}
|
2023-10-10 00:25:34 +00:00
|
|
|
expected time left in source: {SEEK_BEFORE_END:?}"
|
|
|
|
);
|
2023-10-08 12:05:34 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|