Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,22 @@ required-features = ["playback", "wav"]
name = "custom_config"
required-features = ["playback", "wav"]

[[example]]
name = "distortion"
required-features = ["playback"]

[[example]]
name = "distortion_mp3"
required-features = ["playback", "mp3"]

[[example]]
name = "distortion_wav"
required-features = ["playback", "wav"]

[[example]]
name = "distortion_wav_alternate"
required-features = ["playback", "wav"]

[[example]]
name = "error_callback"
required-features = ["playback"]
Expand All @@ -124,6 +140,10 @@ required-features = ["playback", "wav"]
name = "mix_multiple_sources"
required-features = ["playback"]

[[example]]
name = "music_flac"
required-features = ["playback", "flac"]

[[example]]
name = "music_m4a"
required-features = ["playback", "symphonia-isomp4", "symphonia-aac"]
Expand Down
10 changes: 7 additions & 3 deletions src/decoder/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,8 @@ impl<R: Read + Seek + Send + Sync + 'static> DecoderBuilder<R> {
/// - Reliable seeking operations
/// - Duration calculations in formats that lack timing information (e.g. MP3, Vorbis)
///
/// Note that this also sets `is_seekable` to `true`.
///
/// The byte length should typically be obtained from file metadata:
/// ```no_run
/// use std::fs::File;
Expand All @@ -189,10 +191,11 @@ impl<R: Read + Seek + Send + Sync + 'static> DecoderBuilder<R> {
/// incorrect duration calculations and seeking errors.
pub fn with_byte_len(mut self, byte_len: u64) -> Self {
self.settings.byte_len = Some(byte_len);
self.settings.is_seekable = true;
self
}

/// Enables or disables coarse seeking.
/// Enables or disables coarse seeking. This is disabled by default.
///
/// This needs `byte_len` to be set. Coarse seeking is faster but less accurate:
/// it may seek to a position slightly before or after the requested one,
Expand All @@ -202,7 +205,7 @@ impl<R: Read + Seek + Send + Sync + 'static> DecoderBuilder<R> {
self
}

/// Enables or disables gapless playback.
/// Enables or disables gapless playback. This is enabled by default.
///
/// When enabled, removes silence between tracks for formats that support it.
pub fn with_gapless(mut self, gapless: bool) -> Self {
Expand All @@ -228,7 +231,8 @@ impl<R: Read + Seek + Send + Sync + 'static> DecoderBuilder<R> {
self
}

/// Configure whether the decoder should report as seekable.
/// Configure whether the data supports random access seeking. Without this,
/// only forward seeking may work.
///
/// For reliable seeking behavior, `byte_len` should be set either from file metadata
/// or by seeking to the end of the stream. While seeking may work without `byte_len`
Expand Down
47 changes: 31 additions & 16 deletions src/decoder/symphonia.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,16 +199,20 @@ impl Source for SymphoniaDecoder {
// Remember the current channel, so we can restore it after seeking.
let active_channel = self.current_span_offset % self.channels() as usize;

let seek_res = self
.format
.seek(
self.seek_mode,
SeekTo::Time {
time: target.into(),
track_id: None,
},
)
.map_err(SeekError::BaseSeek)?;
let seek_res = match self.format.seek(
self.seek_mode,
SeekTo::Time {
time: target.into(),
track_id: None,
},
) {
Err(Error::SeekError(symphonia::core::errors::SeekErrorKind::ForwardOnly)) => {
return Err(source::SeekError::SymphoniaDecoder(
SeekError::RandomAccessNotSupported,
));
}
other => other.map_err(SeekError::Demuxer),
}?;

// Seeking is a demuxer operation without the decoder knowing about it,
// so we need to reset the decoder to make sure it's in sync and prevent
Expand Down Expand Up @@ -242,18 +246,28 @@ pub enum SeekError {
/// This error occurs when the decoder cannot extract time base information from the source.
/// You may catch this error to try a coarse seek instead.
AccurateSeekNotSupported,
/// Format reader failed to seek
BaseSeek(symphonia::core::errors::Error),
/// The decoder does not support random access seeking
///
/// This error occurs when the source is not seekable or does not have a known byte length.
RandomAccessNotSupported,
/// Demuxer failed to seek
Demuxer(symphonia::core::errors::Error),
}

impl fmt::Display for SeekError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SeekError::AccurateSeekNotSupported => {
write!(f, "Accurate seeking is not supported")
write!(
f,
"Accurate seeking is not supported on this file/byte stream that lacks time base information"
)
}
SeekError::RandomAccessNotSupported => {
write!(f, "The decoder needs to know the length of the file/byte stream to be able to seek backwards. You can set that by using the `DecoderBuilder` or creating a decoder using `Decoder::try_from(some_file)`.")
}
SeekError::BaseSeek(err) => {
write!(f, "Format reader failed to seek: {:?}", err)
SeekError::Demuxer(err) => {
write!(f, "Demuxer failed to seek: {:?}", err)
}
}
}
Expand All @@ -263,7 +277,8 @@ impl std::error::Error for SeekError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
SeekError::AccurateSeekNotSupported => None,
SeekError::BaseSeek(err) => Some(err),
SeekError::RandomAccessNotSupported => None,
SeekError::Demuxer(err) => Some(err),
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion tests/mp4a_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ fn test_mp4a_encodings() {
// Licensed under Creative Commons: By Attribution 3.0
// http://creativecommons.org/licenses/by/3.0/
let file = std::fs::File::open("assets/monkeys.mp4a").unwrap();
let mut decoder = rodio::Decoder::try_from(file).unwrap();
// Open with `new` instead of `try_from` to ensure it works even without is_seekable
let mut decoder = rodio::Decoder::new(file).unwrap();
assert!(decoder.any(|x| x != 0.0)); // Assert not all zeros
}
34 changes: 34 additions & 0 deletions tests/seek.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#![allow(dead_code)]
#![allow(unused_imports)]

#[cfg(feature = "symphonia-mp3")]
use rodio::{decoder::symphonia, source::SeekError};
use rodio::{ChannelCount, Decoder, Source};
use rstest::rstest;
use rstest_reuse::{self, *};
Expand Down Expand Up @@ -239,6 +241,38 @@ fn seek_does_not_break_channel_order(
}
}

#[cfg(feature = "symphonia-mp3")]
#[test]
fn random_access_seeks() {
// Decoder::new::<File> does *not* set byte_len and is_seekable
let mp3_file = std::fs::File::open("assets/music.mp3").unwrap();
let mut decoder = Decoder::new(mp3_file).unwrap();
assert!(
decoder.try_seek(Duration::from_secs(2)).is_ok(),
"forward seek should work without byte_len"
);
assert!(
matches!(
decoder.try_seek(Duration::from_secs(1)),
Err(SeekError::SymphoniaDecoder(
symphonia::SeekError::RandomAccessNotSupported,
))
),
"backward seek should fail without byte_len"
);

// Decoder::try_from::<File> sets byte_len and is_seekable
let mut decoder = get_music("mp3");
assert!(
decoder.try_seek(Duration::from_secs(2)).is_ok(),
"forward seek should work with byte_len"
);
assert!(
decoder.try_seek(Duration::from_secs(1)).is_ok(),
"backward seek should work with byte_len"
);
}

fn second_channel_beep_range<R: rodio::Source>(source: &mut R) -> std::ops::Range<usize> {
let channels = source.channels() as usize;
let samples: Vec<f32> = source.by_ref().collect();
Expand Down