aboutsummaryrefslogtreecommitdiffstats
path: root/src/audio.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/audio.rs56
1 files changed, 56 insertions, 0 deletions
diff --git a/src/audio.rs b/src/audio.rs
index 8bac26f..5ce00ae 100644
--- a/src/audio.rs
+++ b/src/audio.rs
@@ -123,6 +123,7 @@ pub enum AudioCommand {
123 Pause, 123 Pause,
124 Resume, 124 Resume,
125 Stop, 125 Stop,
126 Seek(f64),
126 SetVolume(f32), 127 SetVolume(f32),
127} 128}
128 129
@@ -193,6 +194,11 @@ impl AudioEngine {
193 let _ = self.command_tx.send(AudioCommand::Stop); 194 let _ = self.command_tx.send(AudioCommand::Stop);
194 } 195 }
195 196
197 /// Jump to `position` seconds into the current track.
198 pub fn seek(&self, position: f64) {
199 let _ = self.command_tx.send(AudioCommand::Seek(position));
200 }
201
196 pub fn set_volume(&self, volume: f32) { 202 pub fn set_volume(&self, volume: f32) {
197 let _ = self.command_tx.send(AudioCommand::SetVolume(volume)); 203 let _ = self.command_tx.send(AudioCommand::SetVolume(volume));
198 } 204 }
@@ -799,6 +805,22 @@ fn audio_thread(
799 *track = None; 805 *track = None;
800 } 806 }
801 } 807 }
808 AudioCommand::Seek(seconds) => {
809 let target = seconds.max(0.0);
810 if let Ok(mut d) = ctx.audio_data.lock()
811 && let Some(data) = d.as_mut()
812 {
813 match seek_audio(data, target) {
814 Ok(()) => {
815 if let Ok(mut pos) = position.lock() {
816 *pos = target;
817 }
818 crate::debug_eprintln(format_args!("Seeked to {:.1}s", target));
819 }
820 Err(e) => eprintln!("Failed to seek: {}", e),
821 }
822 }
823 }
802 AudioCommand::SetVolume(vol) => { 824 AudioCommand::SetVolume(vol) => {
803 if let Ok(mut volume_ref) = volume.lock() { 825 if let Ok(mut volume_ref) = volume.lock() {
804 *volume_ref = vol; 826 *volume_ref = vol;
@@ -865,6 +887,40 @@ fn audio_thread(
865 } 887 }
866} 888}
867 889
890/// Moves the decoder to `seconds` into the current track.
891///
892/// Symphonia's accurate seek lands on the packet *before* the requested
893/// position, so packets short of the required timestamp are dropped before
894/// playback resumes. The cpal stream itself keeps running: only the decoded
895/// buffers are flushed, so there is no gap beyond the dropped packet.
896fn seek_audio(data: &mut AudioData, seconds: f64) -> Result<(), String> {
897 use symphonia::core::formats::{SeekMode, SeekTo};
898 use symphonia::core::units::Time;
899
900 let time = Time::try_from_secs_f64(seconds).ok_or("invalid seek position")?;
901 let seeked = data
902 .format
903 .seek(
904 SeekMode::Accurate,
905 SeekTo::Time {
906 time,
907 track_id: None,
908 },
909 )
910 .map_err(|e| e.to_string())?;
911
912 data.decoder.reset();
913 data.sample_buffer.clear();
914 data.buffer_pos = 0;
915 while let Ok(Some(packet)) = data.format.next_packet() {
916 if packet.pts >= seeked.required_ts {
917 break;
918 }
919 }
920
921 Ok(())
922}
923
868fn open_audio_file(path: &PathBuf) -> Result<AudioData, Box<dyn std::error::Error>> { 924fn open_audio_file(path: &PathBuf) -> Result<AudioData, Box<dyn std::error::Error>> {
869 use symphonia::core::audio::layouts::CHANNEL_LAYOUT_STEREO; 925 use symphonia::core::audio::layouts::CHANNEL_LAYOUT_STEREO;
870 use symphonia::core::codecs::audio::AudioDecoderOptions; 926 use symphonia::core::codecs::audio::AudioDecoderOptions;