mirror of
https://github.com/RustAudio/rodio
synced 2024-11-10 06:04:16 +00:00
adds total_duration() impl to SymphoniaDecoder, makes seek saturating at source end if total_duration known
This commit is contained in:
parent
8416210628
commit
2b39d27aaf
5 changed files with 69 additions and 26 deletions
|
@ -74,6 +74,7 @@ where
|
||||||
// as the seek only takes effect after the current frame is done
|
// as the seek only takes effect after the current frame is done
|
||||||
self.decoder.seek_samples(pos)?;
|
self.decoder.seek_samples(pos)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R> Iterator for Mp3Decoder<R>
|
impl<R> Iterator for Mp3Decoder<R>
|
||||||
|
|
|
@ -26,6 +26,7 @@ pub struct SymphoniaDecoder {
|
||||||
decoder: Box<dyn Decoder>,
|
decoder: Box<dyn Decoder>,
|
||||||
current_frame_offset: usize,
|
current_frame_offset: usize,
|
||||||
format: Box<dyn FormatReader>,
|
format: Box<dyn FormatReader>,
|
||||||
|
total_duration: Option<Time>,
|
||||||
buffer: SampleBuffer<i16>,
|
buffer: SampleBuffer<i16>,
|
||||||
spec: SignalSpec,
|
spec: SignalSpec,
|
||||||
}
|
}
|
||||||
|
@ -79,6 +80,11 @@ impl SymphoniaDecoder {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
let total_duration = stream
|
||||||
|
.codec_params
|
||||||
|
.time_base
|
||||||
|
.zip(stream.codec_params.n_frames)
|
||||||
|
.map(|(base, frames)| base.calc_time(frames));
|
||||||
|
|
||||||
let mut decode_errors: usize = 0;
|
let mut decode_errors: usize = 0;
|
||||||
let decoded = loop {
|
let decoded = loop {
|
||||||
|
@ -105,6 +111,7 @@ impl SymphoniaDecoder {
|
||||||
decoder,
|
decoder,
|
||||||
current_frame_offset: 0,
|
current_frame_offset: 0,
|
||||||
format: probed.format,
|
format: probed.format,
|
||||||
|
total_duration,
|
||||||
buffer,
|
buffer,
|
||||||
spec,
|
spec,
|
||||||
}));
|
}));
|
||||||
|
@ -137,7 +144,8 @@ impl Source for SymphoniaDecoder {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn total_duration(&self) -> Option<Duration> {
|
fn total_duration(&self) -> Option<Duration> {
|
||||||
None
|
self.total_duration
|
||||||
|
.map(|Time { seconds, frac }| Duration::new(seconds, (1f64 / frac) as u32))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: do we return till where we seeked? <dvdsk noreply@davidsk.dev>
|
// TODO: do we return till where we seeked? <dvdsk noreply@davidsk.dev>
|
||||||
|
@ -151,13 +159,21 @@ impl Source for SymphoniaDecoder {
|
||||||
1f64 / pos.subsec_nanos() as f64
|
1f64 / pos.subsec_nanos() as f64
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mut seek_to_time = Time::new(pos.as_secs(), pos_fract);
|
||||||
|
if let Some(total_duration) = self.total_duration() {
|
||||||
|
if total_duration.saturating_sub(pos).as_millis() < 1 {
|
||||||
|
seek_to_time = self.total_duration.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let res = self.format.seek(
|
let res = self.format.seek(
|
||||||
SeekMode::Accurate,
|
SeekMode::Accurate,
|
||||||
SeekTo::Time {
|
SeekTo::Time {
|
||||||
time: Time::new(pos.as_secs(), pos_fract),
|
time: seek_to_time,
|
||||||
track_id: None,
|
track_id: None,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
assert!(self.total_duration().is_some());
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Err(Error::IoError(e)) if e.kind() == ErrorKind::UnexpectedEof => Ok(()),
|
Err(Error::IoError(e)) if e.kind() == ErrorKind::UnexpectedEof => Ok(()),
|
||||||
|
|
|
@ -133,7 +133,10 @@ where
|
||||||
#[inline]
|
#[inline]
|
||||||
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
|
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
|
||||||
let samples = pos.as_secs_f32() * self.sample_rate() as f32;
|
let samples = pos.as_secs_f32() * self.sample_rate() as f32;
|
||||||
self.reader.reader.seek(samples as u32).map_err(SeekError::HoundDecoder)
|
self.reader
|
||||||
|
.reader
|
||||||
|
.seek(samples as u32)
|
||||||
|
.map_err(SeekError::HoundDecoder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -357,7 +357,10 @@ where
|
||||||
///
|
///
|
||||||
/// 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.
|
||||||
fn try_seek(&mut self, _: Duration) -> Result<(), SeekError>;
|
///
|
||||||
|
/// If the duration of the source is known and the seek position lies beyond
|
||||||
|
/// it the saturates, that is the position is then at the end of the source.
|
||||||
|
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// we might add decoders requiring new error types, would non_exhaustive
|
// we might add decoders requiring new error types, would non_exhaustive
|
||||||
|
|
|
@ -30,6 +30,10 @@ fn sink_and_decoder(format: &str) -> (Sink, Decoder<impl Read + Seek>) {
|
||||||
(sink, decoder)
|
(sink, decoder)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// run the following to get the other configuration:
|
||||||
|
// cargo test --no-default-features
|
||||||
|
// --features symphonia-wav --features symphonia-vorbis
|
||||||
|
// --features symphonia-flac --features symphonia-isomp4 --features minimp3
|
||||||
fn format_decoder_info() -> &'static [(&'static str, bool, &'static str)] {
|
fn format_decoder_info() -> &'static [(&'static str, bool, &'static str)] {
|
||||||
&[
|
&[
|
||||||
#[cfg(feature = "minimp3")]
|
#[cfg(feature = "minimp3")]
|
||||||
|
@ -42,14 +46,16 @@ fn format_decoder_info() -> &'static [(&'static str, bool, &'static str)] {
|
||||||
("wav", true, "symphonia"),
|
("wav", true, "symphonia"),
|
||||||
#[cfg(feature = "lewton")]
|
#[cfg(feature = "lewton")]
|
||||||
("ogg", true, "lewton"),
|
("ogg", true, "lewton"),
|
||||||
#[cfg(feature = "symphonia-vorbis")]
|
// note: disabled, symphonia returns error unsupported format
|
||||||
("ogg", true, "symphonia"),
|
// #[cfg(feature = "symphonia-vorbis")]
|
||||||
|
// ("ogg", true, "symphonia"),
|
||||||
#[cfg(feature = "claxon")]
|
#[cfg(feature = "claxon")]
|
||||||
("flac", false, "claxon"),
|
("flac", false, "claxon"),
|
||||||
#[cfg(feature = "symphonia-flac")]
|
#[cfg(feature = "symphonia-flac")]
|
||||||
("flac", true, "symphonia"),
|
("flac", true, "symphonia"),
|
||||||
#[cfg(feature = "symphonia-isomp4")]
|
// note: disabled, symphonia returns error unsupported format
|
||||||
("m4a", true, "_"),
|
// #[cfg(feature = "symphonia-isomp4")]
|
||||||
|
// ("m4a", true, "symphonia"),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,17 +70,38 @@ fn seek_returns_err_if_unsupported() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
// #[ignore]
|
||||||
fn seek_beyond_end_does_not_crash() {
|
#[test] // in the future use PR #510 (playback position) to speed this up
|
||||||
|
fn seek_beyond_end_saturates() {
|
||||||
for (format, _, decoder_name) in format_decoder_info()
|
for (format, _, decoder_name) in format_decoder_info()
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
.filter(|(_, supported, _)| *supported)
|
.filter(|(_, supported, _)| *supported)
|
||||||
{
|
{
|
||||||
let (sink, decoder) = sink_and_decoder(format);
|
let (sink, decoder) = sink_and_decoder(format);
|
||||||
println!("seeking beyond end in: {format}\t decoded by: {decoder_name}");
|
|
||||||
sink.append(decoder);
|
sink.append(decoder);
|
||||||
sink.try_seek(Duration::from_secs(999)).unwrap();
|
|
||||||
|
println!("seeking beyond end for: {format}\t decoded by: {decoder_name}");
|
||||||
|
let res = sink.try_seek(Duration::from_secs(999));
|
||||||
|
assert!(res.is_ok());
|
||||||
|
|
||||||
|
let now = Instant::now();
|
||||||
|
sink.sleep_until_end();
|
||||||
|
let elapsed = now.elapsed();
|
||||||
|
assert!(elapsed.as_secs() < 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn total_duration(format: &'static str) -> Duration {
|
||||||
|
let (sink, decoder) = sink_and_decoder(format);
|
||||||
|
match decoder.total_duration() {
|
||||||
|
Some(d) => d,
|
||||||
|
None => {
|
||||||
|
let now = Instant::now();
|
||||||
|
sink.append(decoder);
|
||||||
|
sink.sleep_until_end();
|
||||||
|
now.elapsed()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,23 +113,14 @@ fn seek_results_in_correct_remaining_playtime() {
|
||||||
.cloned()
|
.cloned()
|
||||||
.filter(|(_, supported, _)| *supported)
|
.filter(|(_, supported, _)| *supported)
|
||||||
{
|
{
|
||||||
let (sink, decoder) = sink_and_decoder(format);
|
println!("checking seek duration for: {format}\t decoded by: {decoder_name}");
|
||||||
println!("checking seek time in: {format}\t decoded by: {decoder_name}");
|
|
||||||
let total_duration = match decoder.total_duration() {
|
|
||||||
Some(d) => d,
|
|
||||||
None => {
|
|
||||||
let now = Instant::now();
|
|
||||||
sink.append(decoder);
|
|
||||||
sink.sleep_until_end();
|
|
||||||
now.elapsed()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let (sink, decoder) = sink_and_decoder(format);
|
let (sink, decoder) = sink_and_decoder(format);
|
||||||
sink.append(decoder);
|
sink.append(decoder);
|
||||||
|
|
||||||
const SEEK_BEFORE_END: Duration = Duration::from_secs(5);
|
const SEEK_BEFORE_END: Duration = Duration::from_secs(5);
|
||||||
sink.try_seek(total_duration - SEEK_BEFORE_END).unwrap();
|
sink.try_seek(total_duration(format) - SEEK_BEFORE_END)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
sink.sleep_until_end();
|
sink.sleep_until_end();
|
||||||
|
@ -110,9 +128,11 @@ fn seek_results_in_correct_remaining_playtime() {
|
||||||
let expected = SEEK_BEFORE_END;
|
let expected = SEEK_BEFORE_END;
|
||||||
|
|
||||||
if elapsed.as_millis().abs_diff(expected.as_millis()) > 250 {
|
if elapsed.as_millis().abs_diff(expected.as_millis()) > 250 {
|
||||||
panic!("Seek did not result in expected leftover playtime
|
panic!(
|
||||||
|
"Seek did not result in expected leftover playtime
|
||||||
leftover time: {elapsed:?}
|
leftover time: {elapsed:?}
|
||||||
expected time left in source: {SEEK_BEFORE_END:?}");
|
expected time left in source: {SEEK_BEFORE_END:?}"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue