Skip to content

Commit b2940a2

Browse files
committed
adds total_duration() impl to SymphoniaDecoder, makes seek saturating at source end if total_duration known
1 parent e02c99b commit b2940a2

File tree

5 files changed

+69
-26
lines changed

5 files changed

+69
-26
lines changed

src/decoder/mp3.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ where
7474
// as the seek only takes effect after the current frame is done
7575
self.decoder.seek_samples(pos)?;
7676
Ok(())
77+
}
7778
}
7879

7980
impl<R> Iterator for Mp3Decoder<R>

src/decoder/symphonia.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ pub struct SymphoniaDecoder {
2626
decoder: Box<dyn Decoder>,
2727
current_frame_offset: usize,
2828
format: Box<dyn FormatReader>,
29+
total_duration: Option<Time>,
2930
buffer: SampleBuffer<i16>,
3031
spec: SignalSpec,
3132
}
@@ -79,6 +80,11 @@ impl SymphoniaDecoder {
7980
..Default::default()
8081
},
8182
)?;
83+
let total_duration = stream
84+
.codec_params
85+
.time_base
86+
.zip(stream.codec_params.n_frames)
87+
.map(|(base, frames)| base.calc_time(frames));
8288

8389
let mut decode_errors: usize = 0;
8490
let decoded = loop {
@@ -105,6 +111,7 @@ impl SymphoniaDecoder {
105111
decoder,
106112
current_frame_offset: 0,
107113
format: probed.format,
114+
total_duration,
108115
buffer,
109116
spec,
110117
}));
@@ -137,7 +144,8 @@ impl Source for SymphoniaDecoder {
137144

138145
#[inline]
139146
fn total_duration(&self) -> Option<Duration> {
140-
None
147+
self.total_duration
148+
.map(|Time { seconds, frac }| Duration::new(seconds, (1f64 / frac) as u32))
141149
}
142150

143151
// TODO: do we return till where we seeked? <dvdsk [email protected]>
@@ -151,13 +159,21 @@ impl Source for SymphoniaDecoder {
151159
1f64 / pos.subsec_nanos() as f64
152160
};
153161

162+
let mut seek_to_time = Time::new(pos.as_secs(), pos_fract);
163+
if let Some(total_duration) = self.total_duration() {
164+
if total_duration.saturating_sub(pos).as_millis() < 1 {
165+
seek_to_time = self.total_duration.unwrap();
166+
}
167+
}
168+
154169
let res = self.format.seek(
155170
SeekMode::Accurate,
156171
SeekTo::Time {
157-
time: Time::new(pos.as_secs(), pos_fract),
172+
time: seek_to_time,
158173
track_id: None,
159174
},
160175
);
176+
assert!(self.total_duration().is_some());
161177

162178
match res {
163179
Err(Error::IoError(e)) if e.kind() == ErrorKind::UnexpectedEof => Ok(()),

src/decoder/wav.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,10 @@ where
133133
#[inline]
134134
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
135135
let samples = pos.as_secs_f32() * self.sample_rate() as f32;
136-
self.reader.reader.seek(samples as u32).map_err(SeekError::HoundDecoder)
136+
self.reader
137+
.reader
138+
.seek(samples as u32)
139+
.map_err(SeekError::HoundDecoder)
137140
}
138141
}
139142

src/source/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,10 @@ where
357357
///
358358
/// Try to seek to a pos, returns [`SeekNotSupported`] if seeking is not
359359
/// supported by the current source.
360-
fn try_seek(&mut self, _: Duration) -> Result<(), SeekError>;
360+
///
361+
/// If the duration of the source is known and the seek position lies beyond
362+
/// it the saturates, that is the position is then at the end of the source.
363+
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError>;
361364
}
362365

363366
// we might add decoders requiring new error types, would non_exhaustive

tests/seek.rs

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ fn sink_and_decoder(format: &str) -> (Sink, Decoder<impl Read + Seek>) {
3030
(sink, decoder)
3131
}
3232

33+
// run the following to get the other configuration:
34+
// cargo test --no-default-features
35+
// --features symphonia-wav --features symphonia-vorbis
36+
// --features symphonia-flac --features symphonia-isomp4 --features minimp3
3337
fn format_decoder_info() -> &'static [(&'static str, bool, &'static str)] {
3438
&[
3539
#[cfg(feature = "minimp3")]
@@ -42,14 +46,16 @@ fn format_decoder_info() -> &'static [(&'static str, bool, &'static str)] {
4246
("wav", true, "symphonia"),
4347
#[cfg(feature = "lewton")]
4448
("ogg", true, "lewton"),
45-
#[cfg(feature = "symphonia-vorbis")]
46-
("ogg", true, "symphonia"),
49+
// note: disabled, symphonia returns error unsupported format
50+
// #[cfg(feature = "symphonia-vorbis")]
51+
// ("ogg", true, "symphonia"),
4752
#[cfg(feature = "claxon")]
4853
("flac", false, "claxon"),
4954
#[cfg(feature = "symphonia-flac")]
5055
("flac", true, "symphonia"),
51-
#[cfg(feature = "symphonia-isomp4")]
52-
("m4a", true, "_"),
56+
// note: disabled, symphonia returns error unsupported format
57+
// #[cfg(feature = "symphonia-isomp4")]
58+
// ("m4a", true, "symphonia"),
5359
]
5460
}
5561

@@ -64,17 +70,38 @@ fn seek_returns_err_if_unsupported() {
6470
}
6571
}
6672

67-
#[test]
68-
fn seek_beyond_end_does_not_crash() {
73+
// #[ignore]
74+
#[test] // in the future use PR #510 (playback position) to speed this up
75+
fn seek_beyond_end_saturates() {
6976
for (format, _, decoder_name) in format_decoder_info()
7077
.iter()
7178
.cloned()
7279
.filter(|(_, supported, _)| *supported)
7380
{
7481
let (sink, decoder) = sink_and_decoder(format);
75-
println!("seeking beyond end in: {format}\t decoded by: {decoder_name}");
7682
sink.append(decoder);
77-
sink.try_seek(Duration::from_secs(999)).unwrap();
83+
84+
println!("seeking beyond end for: {format}\t decoded by: {decoder_name}");
85+
let res = sink.try_seek(Duration::from_secs(999));
86+
assert!(res.is_ok());
87+
88+
let now = Instant::now();
89+
sink.sleep_until_end();
90+
let elapsed = now.elapsed();
91+
assert!(elapsed.as_secs() < 1);
92+
}
93+
}
94+
95+
fn total_duration(format: &'static str) -> Duration {
96+
let (sink, decoder) = sink_and_decoder(format);
97+
match decoder.total_duration() {
98+
Some(d) => d,
99+
None => {
100+
let now = Instant::now();
101+
sink.append(decoder);
102+
sink.sleep_until_end();
103+
now.elapsed()
104+
}
78105
}
79106
}
80107

@@ -86,33 +113,26 @@ fn seek_results_in_correct_remaining_playtime() {
86113
.cloned()
87114
.filter(|(_, supported, _)| *supported)
88115
{
89-
let (sink, decoder) = sink_and_decoder(format);
90-
println!("checking seek time in: {format}\t decoded by: {decoder_name}");
91-
let total_duration = match decoder.total_duration() {
92-
Some(d) => d,
93-
None => {
94-
let now = Instant::now();
95-
sink.append(decoder);
96-
sink.sleep_until_end();
97-
now.elapsed()
98-
}
99-
};
116+
println!("checking seek duration for: {format}\t decoded by: {decoder_name}");
100117

101118
let (sink, decoder) = sink_and_decoder(format);
102119
sink.append(decoder);
103120

104121
const SEEK_BEFORE_END: Duration = Duration::from_secs(5);
105-
sink.try_seek(total_duration - SEEK_BEFORE_END).unwrap();
122+
sink.try_seek(total_duration(format) - SEEK_BEFORE_END)
123+
.unwrap();
106124

107125
let now = Instant::now();
108126
sink.sleep_until_end();
109127
let elapsed = now.elapsed();
110128
let expected = SEEK_BEFORE_END;
111129

112130
if elapsed.as_millis().abs_diff(expected.as_millis()) > 250 {
113-
panic!("Seek did not result in expected leftover playtime
131+
panic!(
132+
"Seek did not result in expected leftover playtime
114133
leftover time: {elapsed:?}
115-
expected time left in source: {SEEK_BEFORE_END:?}");
134+
expected time left in source: {SEEK_BEFORE_END:?}"
135+
);
116136
}
117137
}
118138
}

0 commit comments

Comments
 (0)