Skip to content

Commit cb958a0

Browse files
authored
Saturate seek position to track duration (#1483)
The fix prevents seeking past the end of a track by capping the requested seek position to the actual duration.
1 parent 5981b88 commit cb958a0

3 files changed

Lines changed: 18 additions & 14 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4141
- [metadata] `Show::trailer_uri` is now optional since it isn't always present (breaking)
4242
- [connect] Handle transfer of playback with empty "uri" field
4343
- [connect] Correctly apply playing/paused state when transferring playback
44+
- [player] Saturate invalid seek positions to track duration
4445

4546
### Deprecated
4647

playback/src/decoder/symphonia_decoder.rs

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::io;
1+
use std::{io, time::Duration};
22

33
use symphonia::{
44
core::{
@@ -8,7 +8,6 @@ use symphonia::{
88
formats::{FormatOptions, FormatReader, SeekMode, SeekTo},
99
io::{MediaSource, MediaSourceStream, MediaSourceStreamOptions},
1010
meta::{StandardTagKey, Value},
11-
units::Time,
1211
},
1312
default::{
1413
codecs::{MpaDecoder, VorbisDecoder},
@@ -133,30 +132,34 @@ impl SymphoniaDecoder {
133132
}
134133

135134
fn ts_to_ms(&self, ts: u64) -> u32 {
136-
let time_base = self.decoder.codec_params().time_base;
137-
let seeked_to_ms = match time_base {
135+
match self.decoder.codec_params().time_base {
138136
Some(time_base) => {
139-
let time = time_base.calc_time(ts);
140-
(time.seconds as f64 + time.frac) * 1000.
137+
let time = Duration::from(time_base.calc_time(ts));
138+
time.as_millis() as u32
141139
}
142140
// Fallback in the unexpected case that the format has no base time set.
143-
None => ts as f64 * PAGES_PER_MS,
144-
};
145-
seeked_to_ms as u32
141+
None => (ts as f64 * PAGES_PER_MS) as u32,
142+
}
146143
}
147144
}
148145

149146
impl AudioDecoder for SymphoniaDecoder {
150147
fn seek(&mut self, position_ms: u32) -> Result<u32, DecoderError> {
151-
let seconds = position_ms as u64 / 1000;
152-
let frac = (position_ms as f64 % 1000.) / 1000.;
153-
let time = Time::new(seconds, frac);
148+
// "Saturate" the position_ms to the duration of the track if it exceeds it.
149+
let mut target = Duration::from_millis(position_ms.into());
150+
let codec_params = self.decoder.codec_params();
151+
if let (Some(time_base), Some(n_frames)) = (codec_params.time_base, codec_params.n_frames) {
152+
let duration = Duration::from(time_base.calc_time(n_frames));
153+
if target > duration {
154+
target = duration;
155+
}
156+
}
154157

155158
// `track_id: None` implies the default track ID (of the container, not of Spotify).
156159
let seeked_to_ts = self.format.seek(
157160
SeekMode::Accurate,
158161
SeekTo::Time {
159-
time,
162+
time: target.into(),
160163
track_id: None,
161164
},
162165
)?;

playback/src/player.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1780,7 +1780,7 @@ impl PlayerInternal {
17801780
self.ensure_sink_stopped(play);
17811781
}
17821782

1783-
if matches!(self.state, PlayerState::Invalid { .. }) {
1783+
if matches!(self.state, PlayerState::Invalid) {
17841784
return Err(Error::internal(format!(
17851785
"Player::handle_command_load called from invalid state: {:?}",
17861786
self.state

0 commit comments

Comments
 (0)