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
5 changes: 5 additions & 0 deletions crates/kira/src/sound/streaming.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ pub(crate) struct CommandWriters {
stop: CommandWriter<Tween>,
seek_by: CommandWriter<f64>,
seek_to: CommandWriter<f64>,
on_sync: CommandWriter<()>,
}

pub(crate) struct CommandReaders {
Expand All @@ -60,6 +61,7 @@ pub(crate) struct CommandReaders {
pause: CommandReader<Tween>,
resume: CommandReader<(StartTime, Tween)>,
stop: CommandReader<Tween>,
on_sync: CommandReader<()>,
}

#[derive(Debug)]
Expand All @@ -84,6 +86,7 @@ fn command_writers_and_readers() -> (
let (stop_writer, stop_reader) = command_writer_and_reader();
let (seek_by_writer, seek_by_reader) = command_writer_and_reader();
let (seek_to_writer, seek_to_reader) = command_writer_and_reader();
let (on_sync_writer, on_sync_reader) = command_writer_and_reader();
(
CommandWriters {
set_volume: set_volume_writer,
Expand All @@ -95,6 +98,7 @@ fn command_writers_and_readers() -> (
stop: stop_writer,
seek_by: seek_by_writer,
seek_to: seek_to_writer,
on_sync: on_sync_writer,
},
CommandReaders {
set_volume: set_volume_reader,
Expand All @@ -103,6 +107,7 @@ fn command_writers_and_readers() -> (
pause: pause_reader,
resume: resume_reader,
stop: stop_reader,
on_sync: on_sync_reader,
},
DecodeSchedulerCommandReaders {
set_loop_region: set_loop_region_reader,
Expand Down
6 changes: 4 additions & 2 deletions crates/kira/src/sound/streaming/handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,12 +317,14 @@ impl<Error> StreamingSoundHandle<Error> {

/// Sets the playback position to the specified time in seconds.
pub fn seek_to(&mut self, position: f64) {
self.command_writers.seek_to.write(position)
self.command_writers.seek_to.write(position);
self.command_writers.on_sync.write(())
}

/// Moves the playback position by the specified amount of time in seconds.
pub fn seek_by(&mut self, amount: f64) {
self.command_writers.seek_by.write(amount)
self.command_writers.seek_by.write(amount);
self.command_writers.on_sync.write(())
}

/// Returns an error that occurred while decoding audio, if any.
Expand Down
14 changes: 14 additions & 0 deletions crates/kira/src/sound/streaming/sound.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,17 @@ impl StreamingSound {
self.update_shared_playback_state();
}

fn discard_buffered_frames(&mut self) {
if self.playback_state_manager.playback_state().is_advancing() {
return;
}
// the first frame in the ringbuffer is the previous frame, so we
// keep it to avoid consuming the first frame after seek
while self.frame_consumer.slots() > 1 {
self.frame_consumer.pop().ok();
}
}

fn read_commands(&mut self) {
read_commands_into_parameters!(self, volume, playback_rate, panning);
if let Some(tween) = self.command_readers.pause.read() {
Expand All @@ -187,6 +198,9 @@ impl StreamingSound {
if let Some(tween) = self.command_readers.stop.read() {
self.stop(tween);
}
if self.command_readers.on_sync.read().is_some() {
self.discard_buffered_frames();
}
}
}

Expand Down
87 changes: 87 additions & 0 deletions crates/kira/src/sound/streaming/sound/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -860,6 +860,93 @@ fn seek_to() {
expect_frame_soon(Frame::from_mono(15.0).panned(Panning::CENTER), &mut sound);
}

/// Tests that a `StreamingSound` that seeks while paused discards the audio
/// that was already decoded ahead of the playhead, so that no frames from the
/// old position are played when the sound resumes.
#[test]
fn seek_while_paused_discards_stale_frames() {
let data = StreamingSoundData {
decoder: Box::new(MockDecoder::new(
(0..100).map(|i| Frame::from_mono(i as f32)).collect(),
)),
settings: StreamingSoundSettings::new(),
slice: None,
};
let (mut sound, mut handle, mut scheduler) = data.split().unwrap();

// let the scheduler decode a chunk of audio ahead of the playhead
for _ in 0..10 {
scheduler.run().unwrap();
}

handle.pause(Tween {
duration: Duration::ZERO,
..Default::default()
});
sound.on_start_processing();
sound.process_one(1.0, &MockInfoBuilder::new().build());
assert_eq!(
sound.playback_state_manager.playback_state(),
PlaybackState::Paused
);

handle.seek_to(50.0);
sound.on_start_processing();
while matches!(scheduler.run().unwrap(), NextStep::Continue) {}
sound.on_start_processing();

handle.resume(Tween {
duration: Duration::ZERO,
..Default::default()
});
sound.on_start_processing();

// the first frame we hear after resuming should be the one we seeked to,
// not audio that was buffered before the seek
expect_frame_soon(Frame::from_mono(50.0).panned(Panning::CENTER), &mut sound);

// and playback should continue on from there
for i in 51..60 {
assert_eq!(
sound.process_one(1.0, &MockInfoBuilder::new().build()),
Frame::from_mono(i as f32).panned(Panning::CENTER)
);
}
}

/// Tests that a `StreamingSound` that seeks while playing does not discard
/// buffered audio, since those frames are the audio that's about to be heard.
#[test]
fn seek_while_playing_keeps_buffered_frames() {
let data = StreamingSoundData {
decoder: Box::new(MockDecoder::new(
(0..100).map(|i| Frame::from_mono(i as f32)).collect(),
)),
settings: StreamingSoundSettings::new(),
slice: None,
};
let (mut sound, mut handle, mut scheduler) = data.split().unwrap();

handle.seek_to(50.0);
sound.on_start_processing();
while matches!(scheduler.run().unwrap(), NextStep::Continue) {}
sound.on_start_processing();
expect_frame_soon(Frame::from_mono(50.0).panned(Panning::CENTER), &mut sound);

// seek again while the sound is still playing. the frames that are already
// buffered are the audio that's about to be heard, so they should keep
// playing until the sound catches up to the new position.
handle.seek_to(20.0);
sound.on_start_processing();

for i in 51..60 {
assert_eq!(
sound.process_one(1.0, &MockInfoBuilder::new().build()),
Frame::from_mono(i as f32).panned(Panning::CENTER)
);
}
}

/// Tests that a `StreamingSound` can seek by an amount of time.
#[test]
fn seek_by() {
Expand Down
Loading