mecomp_core/audio/
mod.rs

1#![allow(clippy::module_name_repetitions)]
2
3use std::{
4    fs::File,
5    io::BufReader,
6    ops::Range,
7    sync::{
8        Arc, Mutex,
9        atomic::AtomicBool,
10        mpsc::{Receiver, Sender},
11    },
12    time::Duration,
13};
14
15use log::{debug, error};
16use rodio::{Decoder, Source, source::SeekError};
17use tracing::instrument;
18
19use crate::{
20    errors::LibraryError,
21    format_duration,
22    state::{Percent, SeekType, StateAudio, StateRuntime, Status},
23    udp::StateChange,
24};
25use mecomp_storage::db::schemas::song::Song;
26use one_or_many::OneOrMany;
27
28pub mod commands;
29pub mod queue;
30
31use commands::{AudioCommand, QueueCommand, VolumeCommand};
32use queue::Queue;
33
34const DURATION_WATCHER_TICK: Duration = Duration::from_millis(50);
35const DURATION_WATCHER_NEXT_SONG_THRESHOLD: Duration = Duration::from_millis(100);
36
37/// The minimum volume that can be set, currently set to 0.0 (no sound)
38const MIN_VOLUME: f32 = 0.0;
39/// The maximum volume that can be set, currently set to 10.0 (10x volume)
40const MAX_VOLUME: f32 = 10.0;
41
42#[derive(Debug, Clone)]
43pub struct AudioKernelSender {
44    tx: Sender<(AudioCommand, tracing::Span)>,
45}
46
47impl AudioKernelSender {
48    /// Starts the audio kernel in a detached thread and returns a sender to be used to send commands to the audio kernel.
49    /// The audio kernel will transmit state changes to the provided event transmitter.
50    ///
51    /// # Returns
52    ///
53    /// A sender to be used to send commands to the audio kernel.
54    ///
55    /// # Panics
56    ///
57    /// Panics if there is an issue spawning the audio kernel thread (if the name contains null bytes, which it doesn't, so this should never happen)
58    #[must_use]
59    #[inline]
60    pub fn start(event_tx: Sender<StateChange>) -> Arc<Self> {
61        let (tx, rx) = std::sync::mpsc::channel();
62        let tx_clone = tx.clone();
63        std::thread::Builder::new()
64            .name(String::from("Audio Kernel"))
65            .spawn(move || {
66                let kernel = AudioKernel::new(event_tx);
67                kernel.init(tx_clone, rx);
68            })
69            .unwrap();
70        Arc::new(Self::new(tx))
71    }
72
73    #[must_use]
74    #[inline]
75    pub(crate) const fn new(tx: Sender<(AudioCommand, tracing::Span)>) -> Self {
76        Self { tx }
77    }
78
79    /// Send a command to the audio kernel
80    #[instrument(skip(self))]
81    pub fn send(&self, command: AudioCommand) {
82        let ctx =
83            tracing::info_span!("Sending Audio Command to Kernel", command = ?command).or_current();
84
85        if let Err(e) = self.tx.send((command, ctx)) {
86            error!("Failed to send command to audio kernel: {e}");
87            panic!("Failed to send command to audio kernel: {e}");
88        }
89    }
90}
91
92#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
93struct DurationInfo {
94    time_played: Duration,
95    current_duration: Duration,
96}
97
98pub(crate) struct AudioKernel {
99    /// this is not used, but is needed to keep the stream alive
100    #[cfg(not(feature = "mock_playback"))]
101    _music_output: (rodio::OutputStream, rodio::OutputStreamHandle),
102    #[cfg(feature = "mock_playback")]
103    queue_rx_end_tx: tokio::sync::oneshot::Sender<()>,
104    // /// Transmitter used to send commands to the audio kernel
105    // tx: Sender<(AudioCommand, tracing::Span)>,
106    /// the rodio sink used to play audio
107    player: Arc<rodio::Sink>,
108    /// the queue of songs to play
109    queue: Arc<Mutex<Queue>>,
110    /// The value `1.0` is the "normal" volume (unfiltered input). Any value other than `1.0` will multiply each sample by this value.
111    volume: Arc<Mutex<f32>>,
112    /// whether the audio is muted
113    muted: Arc<AtomicBool>,
114    /// the current song duration and the time played
115    duration_info: Arc<Mutex<DurationInfo>>,
116    /// whether the audio kernel is paused, playlist, or stopped
117    status: Arc<Mutex<Status>>,
118    /// Event publisher for when the audio kernel changes state
119    event_tx: Sender<StateChange>,
120}
121
122impl AudioKernel {
123    /// this function initializes the audio kernel
124    ///
125    /// # Panics
126    ///
127    /// panics if the rodio stream cannot be created
128    #[must_use]
129    #[cfg(not(feature = "mock_playback"))]
130    pub fn new(event_tx: Sender<StateChange>) -> Self {
131        let (stream, stream_handle) = rodio::OutputStream::try_default().unwrap();
132
133        let sink = rodio::Sink::try_new(&stream_handle).unwrap();
134        sink.pause();
135        let queue = Queue::new();
136
137        Self {
138            _music_output: (stream, stream_handle),
139            player: sink.into(),
140            queue: Arc::new(Mutex::new(queue)),
141            volume: Arc::new(Mutex::new(1.0)),
142            muted: Arc::new(AtomicBool::new(false)),
143            duration_info: Arc::new(Mutex::new(DurationInfo::default())),
144            status: Arc::new(Mutex::new(Status::Stopped)),
145            event_tx,
146        }
147    }
148
149    /// this function initializes the audio kernel
150    ///
151    /// this is the version for tests, where we don't create the actual audio stream since we don't need to play audio
152    ///
153    /// # Panics
154    ///
155    /// panics if the tokio runtime cannot be created
156    #[must_use]
157    #[cfg(feature = "mock_playback")]
158    pub fn new(event_tx: Sender<StateChange>) -> Self {
159        let (sink, mut queue_rx) = rodio::Sink::new_idle();
160
161        // start a detached thread that continuously polls the queue_rx, until it receives a command to exit
162        let (tx, mut rx) = tokio::sync::oneshot::channel();
163
164        std::thread::spawn(move || {
165            // basically, call rx.await and while it is waiting for a command, poll the queue_rx
166            loop {
167                if matches!(
168                    rx.try_recv(),
169                    Ok(()) | Err(tokio::sync::oneshot::error::TryRecvError::Closed)
170                ) {
171                    break;
172                }
173                queue_rx.next();
174                std::thread::sleep(Duration::from_millis(1));
175            }
176        });
177
178        sink.pause();
179
180        Self {
181            player: sink.into(),
182            queue_rx_end_tx: tx,
183            queue: Arc::new(Mutex::new(Queue::new())),
184            volume: Arc::new(Mutex::new(1.0)),
185            muted: Arc::new(AtomicBool::new(false)),
186            duration_info: Arc::new(Mutex::new(DurationInfo::default())),
187            status: Arc::new(Mutex::new(Status::Stopped)),
188            event_tx,
189        }
190    }
191
192    /// Spawn the audio kernel, taking ownership of self
193    ///
194    /// this function should be called in a detached thread to keep the audio kernel running,
195    /// this function will block until the `Exit` command is received
196    ///
197    /// # Arguments
198    ///
199    /// * `tx` - the transmitter used to send commands to the audio kernel (this is used by the duration watcher to tell the kernel when to skip to the next song)
200    /// * `rx` - the receiver used to receive commands from the audio kernel, this is what the audio kernel receives commands from
201    ///
202    /// # Panics
203    ///
204    /// The function may panic if one of the Mutexes is poisoned
205    ///
206    /// if the `mock_playback` feature is enabled, this function may panic if it is unable to signal the `queue_rx` thread to end.
207    pub fn init(
208        self,
209        tx: Sender<(AudioCommand, tracing::Span)>,
210        rx: Receiver<(AudioCommand, tracing::Span)>,
211    ) {
212        // duration watcher signalers
213        let (dw_tx, mut dw_rx) = tokio::sync::oneshot::channel();
214
215        // we won't be able to access this AudioKernel instance reliably,
216        // so we need to clone the Arcs to all the values we need
217        let duration_info = self.duration_info.clone();
218        let status = self.status.clone();
219
220        // NOTE: as of rodio v0.19.0, we have access to the `get_pos` command,
221        // which allows us to get the current position of the audio stream
222        // it may seem like this means we don't need to have a duration watcher,
223        // but the key point is that we need to know when to skip to the next song
224        // the duration watcher both tracks the duration of the song,
225        // and skips to the next song when the song is over
226        let _duration_watcher = std::thread::Builder::new()
227            .name(String::from("Duration Watcher"))
228            .spawn(move || {
229                const NEXT_SONG_COMMAND: AudioCommand =
230                    AudioCommand::Queue(QueueCommand::PlayNextSong);
231
232                log::info!("Duration Watcher started");
233
234                loop {
235                    if matches!(
236                        dw_rx.try_recv(),
237                        Ok(()) | Err(tokio::sync::oneshot::error::TryRecvError::Closed)
238                    ) {
239                        break;
240                    }
241                    let mut duration_info = duration_info.lock().unwrap();
242                    if *status.lock().unwrap() == Status::Playing {
243                        // if we aren't paused, increment the time played
244                        duration_info.time_played += DURATION_WATCHER_TICK;
245                        // if we're within the threshold of the end of the song,
246                        // signal the audio kernel to skip to the next song
247                        let time_to_start_next_song = duration_info
248                            .current_duration
249                            .saturating_sub(DURATION_WATCHER_NEXT_SONG_THRESHOLD);
250                        if duration_info.time_played >= time_to_start_next_song {
251                            if let Err(e) = tx.send((NEXT_SONG_COMMAND, tracing::Span::current())) {
252                                error!("Failed to send command to audio kernel: {e}");
253                                panic!("Failed to send command to audio kernel: {e}");
254                            }
255                        }
256                    }
257                    drop(duration_info);
258                    std::thread::sleep(DURATION_WATCHER_TICK);
259                }
260            });
261
262        for (command, ctx) in rx {
263            let _guard = ctx.enter();
264
265            let prev_status = *self.status.lock().unwrap();
266
267            match command {
268                AudioCommand::Play => self.play(),
269                AudioCommand::Pause => self.pause(),
270                AudioCommand::TogglePlayback => self.toggle_playback(),
271                AudioCommand::RestartSong => {
272                    self.restart_song();
273                    let _ = self
274                        .event_tx
275                        .send(StateChange::Seeked(Duration::from_secs(0)));
276                }
277                AudioCommand::ClearPlayer => self.clear_player(),
278                AudioCommand::Queue(command) => self.queue_control(command),
279                AudioCommand::Exit => break,
280                AudioCommand::ReportStatus(tx) => {
281                    let state = self.state();
282
283                    if let Err(e) = tx.send(state) {
284                        // if there was an error, then the receiver will never receive the state,
285                        // this can cause a permanent hang
286                        // so we stop the audio kernel if this happens
287                        // (which will cause any future calls to `send` to panic)
288                        error!(
289                            "Audio Kernel failed to send state to the receiver, state receiver likely has been dropped. State: {e}"
290                        );
291                        break;
292                    }
293                }
294                AudioCommand::Volume(command) => self.volume_control(command),
295                AudioCommand::Seek(seek, duration) => {
296                    self.seek(seek, duration);
297                    let _ = self.event_tx.send(StateChange::Seeked(
298                        self.duration_info.lock().unwrap().time_played,
299                    ));
300                }
301                AudioCommand::Stop if prev_status != Status::Stopped => {
302                    self.stop();
303                    let _ = self
304                        .event_tx
305                        .send(StateChange::Seeked(Duration::from_secs(0)));
306                }
307                AudioCommand::Stop => {}
308            }
309
310            let new_status = *self.status.lock().unwrap();
311
312            if prev_status != new_status {
313                let _ = self.event_tx.send(StateChange::StatusChanged(new_status));
314            }
315        }
316
317        #[cfg(feature = "mock_playback")]
318        self.queue_rx_end_tx.send(()).unwrap();
319        dw_tx.send(()).unwrap();
320    }
321
322    #[instrument(skip(self))]
323    fn play(&self) {
324        if self.player.empty() {
325            return;
326        }
327        self.player.play();
328        *self.status.lock().unwrap() = Status::Playing;
329    }
330
331    #[instrument(skip(self))]
332    fn pause(&self) {
333        self.player.pause();
334        *self.status.lock().unwrap() = Status::Paused;
335    }
336
337    #[instrument(skip(self))]
338    fn stop(&self) {
339        self.player.pause();
340        self.seek(SeekType::Absolute, Duration::from_secs(0));
341        *self.status.lock().unwrap() = Status::Stopped;
342    }
343
344    #[instrument(skip(self))]
345    fn toggle_playback(&self) {
346        if self.player.is_paused() {
347            self.play();
348        } else {
349            self.pause();
350        }
351    }
352
353    #[instrument(skip(self))]
354    fn restart_song(&self) {
355        let status = *self.status.lock().unwrap();
356        self.clear_player();
357
358        if let Some(song) = self.queue.lock().unwrap().current_song() {
359            if let Err(e) = self.append_song_to_player(song) {
360                error!("Failed to append song to player: {e}");
361            }
362
363            match status {
364                // if it was previously stopped, we don't need to do anything here
365                Status::Stopped => {}
366                // if it was previously paused, we need to re-pause
367                Status::Paused => self.pause(),
368                // if it was previously playing, we need to play
369                Status::Playing => self.play(),
370            }
371        }
372    }
373
374    #[instrument(skip(self))]
375    fn clear(&self) {
376        self.clear_player();
377        self.queue.lock().unwrap().clear();
378    }
379
380    #[instrument(skip(self))]
381    fn clear_player(&self) {
382        self.player.clear();
383        *self.status.lock().unwrap() = Status::Stopped;
384        *self.duration_info.lock().unwrap() = DurationInfo::default();
385    }
386
387    #[instrument(skip(self))]
388    fn queue_control(&self, command: QueueCommand) {
389        let prev_song = self.queue.lock().unwrap().current_song().cloned();
390        match command {
391            QueueCommand::Clear => self.clear(),
392            QueueCommand::PlayNextSong => self.start_next_song(),
393            QueueCommand::SkipForward(n) => self.skip_forward(n),
394            QueueCommand::SkipBackward(n) => self.skip_backward(n),
395            QueueCommand::SetPosition(n) => self.set_position(n),
396            QueueCommand::Shuffle => self.queue.lock().unwrap().shuffle(),
397            QueueCommand::AddToQueue(song_box) => match *song_box {
398                OneOrMany::None => {}
399                OneOrMany::One(song) => self.add_song_to_queue(song),
400                OneOrMany::Many(songs) => self.add_songs_to_queue(songs),
401            },
402            QueueCommand::RemoveRange(range) => self.remove_range_from_queue(range),
403            QueueCommand::SetRepeatMode(mode) => {
404                self.queue.lock().unwrap().set_repeat_mode(mode);
405                let _ = self.event_tx.send(StateChange::RepeatModeChanged(mode));
406            }
407        }
408
409        let new_song = self.queue.lock().unwrap().current_song().cloned();
410
411        if prev_song != new_song {
412            let _ = self
413                .event_tx
414                .send(StateChange::TrackChanged(new_song.map(|s| s.id.into())));
415        }
416    }
417
418    #[instrument(skip(self))]
419    fn state(&self) -> StateAudio {
420        let queue = self.queue.lock().unwrap();
421        let queue_position = queue.current_index();
422        let current_song = queue.current_song().cloned();
423        let repeat_mode = queue.get_repeat_mode();
424        let runtime = current_song.as_ref().map(|_| {
425            let duration_info = self.duration_info.lock().unwrap();
426            let seek_position = duration_info.time_played;
427            let duration = duration_info.current_duration;
428            drop(duration_info);
429            let seek_percent =
430                Percent::new(seek_position.as_secs_f32() / duration.as_secs_f32() * 100.0);
431            StateRuntime {
432                seek_position,
433                seek_percent,
434                duration,
435            }
436        });
437        let status = *self.status.lock().unwrap();
438        let status = if self.player.is_paused() {
439            debug_assert!(matches!(status, Status::Paused | Status::Stopped));
440            status
441        } else {
442            debug_assert_eq!(status, Status::Playing);
443            Status::Playing
444        };
445
446        let muted = self.muted.load(std::sync::atomic::Ordering::Relaxed);
447        let volume = *self.volume.lock().unwrap();
448
449        let queued_songs = queue.queued_songs();
450        drop(queue);
451
452        StateAudio {
453            queue: queued_songs,
454            queue_position,
455            current_song,
456            repeat_mode,
457            runtime,
458            status,
459            muted,
460            volume,
461        }
462    }
463
464    #[instrument(skip(self))]
465    fn start_next_song(&self) {
466        self.clear_player();
467        let next_song = self.queue.lock().unwrap().next_song().cloned();
468
469        if let Some(song) = next_song {
470            if let Err(e) = self.append_song_to_player(&song) {
471                error!("Failed to append song to player: {e}");
472            }
473
474            let binding = self.queue.lock().unwrap();
475            // we have not just finished the queue
476            // (this makes it so if we hit the end of the queue on RepeatMode::None, we don't start playing again)
477            if binding.get_repeat_mode().is_all() || binding.current_index().is_some() {
478                self.play();
479            }
480        }
481    }
482
483    #[instrument(skip(self))]
484    fn skip_forward(&self, n: usize) {
485        let status = *self.status.lock().unwrap();
486        self.clear_player();
487
488        let next_song = self.queue.lock().unwrap().skip_forward(n).cloned();
489
490        if let Some(song) = next_song {
491            if let Err(e) = self.append_song_to_player(&song) {
492                error!("Failed to append song to player: {e}");
493            }
494
495            let binding = self.queue.lock().unwrap();
496            match status {
497                Status::Paused => self.pause(),
498                // we were playing and we have not just finished the queue
499                // (this makes it so if we hit the end of the queue on RepeatMode::None, we don't start playing again)
500                Status::Playing
501                    if binding.get_repeat_mode().is_all() || binding.current_index().is_some() =>
502                {
503                    self.play();
504                }
505                _ => {}
506            }
507        }
508    }
509
510    #[instrument(skip(self))]
511    fn skip_backward(&self, n: usize) {
512        let status = *self.status.lock().unwrap();
513        self.clear_player();
514
515        let next_song = self.queue.lock().unwrap().skip_backward(n).cloned();
516
517        if let Some(song) = next_song {
518            if let Err(e) = self.append_song_to_player(&song) {
519                error!("Failed to append song to player: {e}");
520            }
521            match status {
522                Status::Stopped => {}
523                Status::Paused => self.pause(),
524                Status::Playing => self.play(),
525            }
526        }
527    }
528
529    #[instrument(skip(self))]
530    fn set_position(&self, n: usize) {
531        let status = *self.status.lock().unwrap();
532        self.clear_player();
533
534        let mut binding = self.queue.lock().unwrap();
535        binding.set_current_index(n);
536        let next_song = binding.current_song().cloned();
537        drop(binding);
538
539        if let Some(song) = next_song {
540            if let Err(e) = self.append_song_to_player(&song) {
541                error!("Failed to append song to player: {e}");
542            }
543
544            match status {
545                Status::Stopped => {}
546                Status::Paused => self.pause(),
547                Status::Playing => self.play(),
548            }
549        }
550    }
551
552    #[instrument(skip(self))]
553    fn add_song_to_queue(&self, song: Song) {
554        self.queue.lock().unwrap().add_song(song);
555
556        // if the player is empty, start playback
557        if self.player.empty() {
558            let current_index = self.queue.lock().unwrap().current_index();
559
560            if let Some(song) =
561                current_index.map_or_else(|| self.get_next_song(), |_| self.get_current_song())
562            {
563                if let Err(e) = self.append_song_to_player(&song) {
564                    error!("Failed to append song to player: {e}");
565                }
566                self.play();
567            }
568        }
569    }
570
571    #[instrument(skip(self))]
572    fn add_songs_to_queue(&self, songs: Vec<Song>) {
573        self.queue.lock().unwrap().add_songs(songs);
574
575        // if the player is empty, start playback
576        if self.player.empty() {
577            let current_index = self.queue.lock().unwrap().current_index();
578
579            if let Some(song) =
580                current_index.map_or_else(|| self.get_next_song(), |_| self.get_current_song())
581            {
582                if let Err(e) = self.append_song_to_player(&song) {
583                    error!("Failed to append song to player: {e}");
584                }
585                self.play();
586            }
587        }
588    }
589
590    #[instrument(skip(self))]
591    fn remove_range_from_queue(&self, range: Range<usize>) {
592        // if the current song is not being removed, we don't need to do anything special to the player
593        let current_to_be_removed = self
594            .queue
595            .lock()
596            .unwrap()
597            .current_index()
598            .is_some_and(|current_index| range.contains(&current_index));
599
600        self.queue.lock().unwrap().remove_range(range);
601
602        // if the current song was removed, clear the player and restart playback
603        if current_to_be_removed {
604            self.clear_player();
605            if let Some(song) = self.get_current_song() {
606                if let Err(e) = self.append_song_to_player(&song) {
607                    error!("Failed to append song to player: {e}");
608                }
609            }
610        }
611    }
612
613    #[instrument(skip(self))]
614    fn get_current_song(&self) -> Option<Song> {
615        self.queue.lock().unwrap().current_song().cloned()
616    }
617
618    #[instrument(skip(self))]
619    fn get_next_song(&self) -> Option<Song> {
620        self.queue.lock().unwrap().next_song().cloned()
621    }
622
623    #[instrument(skip(self, source))]
624    fn append_to_player<T>(&self, source: T)
625    where
626        T: Source<Item = f32> + Send + 'static,
627    {
628        if let Some(duration) = source.total_duration() {
629            *self.duration_info.lock().unwrap() = DurationInfo {
630                time_played: Duration::from_secs(0),
631                current_duration: duration,
632            };
633        }
634        self.player.append(source);
635    }
636
637    #[instrument(skip(self))]
638    fn append_song_to_player(&self, song: &Song) -> Result<(), LibraryError> {
639        let source = Decoder::new(BufReader::new(File::open(&song.path)?))?.convert_samples();
640        *self.duration_info.lock().unwrap() = DurationInfo {
641            time_played: Duration::from_secs(0),
642            current_duration: song.runtime,
643        };
644        self.append_to_player(source);
645
646        Ok(())
647    }
648
649    #[instrument(skip(self))]
650    fn volume_control(&self, command: VolumeCommand) {
651        match command {
652            VolumeCommand::Up(percent) => {
653                let mut volume = self.volume.lock().unwrap();
654                let percent = (*volume + percent).clamp(MIN_VOLUME, MAX_VOLUME);
655                if (*volume - percent).abs() > 0.0001 {
656                    *volume = percent;
657                    let _ = self.event_tx.send(StateChange::VolumeChanged(*volume));
658                }
659            }
660            VolumeCommand::Down(percent) => {
661                let mut volume = self.volume.lock().unwrap();
662                let percent = (*volume - percent).clamp(MIN_VOLUME, MAX_VOLUME);
663                if (*volume - percent).abs() > 0.0001 {
664                    *volume = percent;
665                    let _ = self.event_tx.send(StateChange::VolumeChanged(*volume));
666                }
667            }
668            VolumeCommand::Set(percent) => {
669                let mut volume = self.volume.lock().unwrap();
670                let percent = percent.clamp(MIN_VOLUME, MAX_VOLUME);
671                if (*volume - percent).abs() > 0.0001 {
672                    *volume = percent;
673                    let _ = self.event_tx.send(StateChange::VolumeChanged(*volume));
674                }
675            }
676            VolumeCommand::Mute => {
677                self.muted.store(true, std::sync::atomic::Ordering::Relaxed);
678                let _ = self.event_tx.send(StateChange::Muted);
679            }
680            VolumeCommand::Unmute => {
681                self.muted
682                    .store(false, std::sync::atomic::Ordering::Relaxed);
683                let _ = self.event_tx.send(StateChange::Unmuted);
684            }
685            VolumeCommand::ToggleMute => {
686                self.muted.store(
687                    !self.muted.load(std::sync::atomic::Ordering::Relaxed),
688                    std::sync::atomic::Ordering::Relaxed,
689                );
690                if self.muted.load(std::sync::atomic::Ordering::Relaxed) {
691                    let _ = self.event_tx.send(StateChange::Muted);
692                } else {
693                    let _ = self.event_tx.send(StateChange::Unmuted);
694                }
695            }
696        }
697
698        if self.muted.load(std::sync::atomic::Ordering::Relaxed) {
699            self.player.set_volume(0.0);
700        } else {
701            self.player
702                .set_volume(self.volume.lock().unwrap().to_owned());
703        }
704    }
705
706    #[instrument(skip(self))]
707    fn seek(&self, seek: SeekType, duration: Duration) {
708        // get a lock on the current song duration and time played
709        let mut duration_info = self.duration_info.lock().unwrap();
710        // calculate the new time based on the seek type
711        let new_time = match seek {
712            SeekType::Absolute => duration,
713            SeekType::RelativeForwards => duration_info.time_played.saturating_add(duration),
714            SeekType::RelativeBackwards => duration_info.time_played.saturating_sub(duration),
715        };
716        let new_time = new_time.min(duration_info.current_duration);
717
718        // try to seek to the new time.
719        // if the seek fails, log the error and continue
720        // if the seek succeeds, update the time_played to the new time
721        match self.player.try_seek(new_time) {
722            Ok(()) => {
723                debug!("Seek to {} successful", format_duration(&new_time));
724                duration_info.time_played = new_time;
725                drop(duration_info);
726                let mut status = self.status.lock().unwrap();
727                if new_time > Duration::from_secs(0) && *status == Status::Stopped {
728                    *status = Status::Paused;
729                    drop(status);
730                }
731            }
732            Err(SeekError::NotSupported { underlying_source }) => {
733                error!("Seek not supported by source: {underlying_source}");
734            }
735            Err(err) => {
736                error!("Seeking failed with error: {err}");
737            }
738        }
739    }
740}
741
742impl Default for AudioKernel {
743    /// Create a new `AudioKernel` with default values, and a dummy event transmitter
744    fn default() -> Self {
745        let (tx, _) = std::sync::mpsc::channel();
746        Self::new(tx)
747    }
748}
749
750#[cfg(test)]
751mod tests {
752    use pretty_assertions::assert_eq;
753    use rstest::{fixture, rstest};
754
755    use crate::test_utils::init;
756
757    use super::*;
758    use std::sync::mpsc;
759    use std::time::Duration;
760
761    #[fixture]
762    fn audio_kernel() -> AudioKernel {
763        AudioKernel::default()
764    }
765
766    #[fixture]
767    fn audio_kernel_sender() -> Arc<AudioKernelSender> {
768        let (tx, _) = mpsc::channel();
769        AudioKernelSender::start(tx)
770    }
771
772    async fn get_state(sender: Arc<AudioKernelSender>) -> StateAudio {
773        let (tx, rx) = tokio::sync::oneshot::channel::<StateAudio>();
774        sender.send(AudioCommand::ReportStatus(tx));
775        rx.await.unwrap()
776    }
777
778    #[fixture]
779    fn sound() -> impl Source<Item = f32> + Send + 'static {
780        rodio::source::SineWave::new(440.0)
781    }
782
783    #[test]
784    fn test_audio_kernel_sender_send() {
785        let (tx, rx) = mpsc::channel();
786        let sender = AudioKernelSender::new(tx);
787        sender.send(AudioCommand::Play);
788        let (recv, _) = rx.recv().unwrap();
789        assert_eq!(recv, AudioCommand::Play);
790    }
791
792    #[test]
793    #[should_panic = "Failed to send command to audio kernel: sending on a closed channel"]
794    fn test_audio_kernel_send_closed_channel() {
795        let (tx, _) = mpsc::channel();
796        let sender = AudioKernelSender::new(tx);
797        sender.send(AudioCommand::Play);
798    }
799
800    #[rstest]
801    #[timeout(Duration::from_secs(3))] // if the test takes longer than 3 seconds, this is a failure
802    fn test_audio_player_kernel_spawn_and_exit(
803        #[from(audio_kernel_sender)] sender: Arc<AudioKernelSender>,
804    ) {
805        init();
806
807        sender.send(AudioCommand::Exit);
808    }
809
810    #[rstest]
811    fn test_volume_control(audio_kernel: AudioKernel) {
812        audio_kernel.volume_control(VolumeCommand::Up(0.1));
813        let volume = *audio_kernel.volume.lock().unwrap();
814        assert!(f32::EPSILON > (volume - 1.1).abs(), "{volume} != 1.1");
815
816        audio_kernel.volume_control(VolumeCommand::Down(0.1));
817        let volume = *audio_kernel.volume.lock().unwrap();
818        assert!(f32::EPSILON > (volume - 1.0).abs(), "{volume} != 1.0");
819
820        audio_kernel.volume_control(VolumeCommand::Set(0.5));
821        let volume = *audio_kernel.volume.lock().unwrap();
822        assert!(f32::EPSILON > (volume - 0.5).abs(), "{volume} != 0.5");
823
824        audio_kernel.volume_control(VolumeCommand::Mute);
825        assert_eq!(
826            audio_kernel
827                .muted
828                .load(std::sync::atomic::Ordering::Relaxed),
829            true
830        );
831
832        audio_kernel.volume_control(VolumeCommand::Unmute);
833        assert_eq!(
834            audio_kernel
835                .muted
836                .load(std::sync::atomic::Ordering::Relaxed),
837            false
838        );
839
840        audio_kernel.volume_control(VolumeCommand::ToggleMute);
841        assert_eq!(
842            audio_kernel
843                .muted
844                .load(std::sync::atomic::Ordering::Relaxed),
845            true
846        );
847
848        audio_kernel.volume_control(VolumeCommand::ToggleMute);
849        assert_eq!(
850            audio_kernel
851                .muted
852                .load(std::sync::atomic::Ordering::Relaxed),
853            false
854        );
855    }
856
857    mod playback_tests {
858        //! These are tests that require the audio kernel to be able to play audio
859        //! As such, they cannot be run on CI.
860        //! Therefore, they are in a separate module so that they can be skipped when running tests on CI.
861
862        use mecomp_storage::test_utils::{arb_song_case, create_song_metadata, init_test_database};
863        use pretty_assertions::assert_eq;
864        use rstest::rstest;
865
866        use crate::test_utils::init;
867
868        use super::{super::*, audio_kernel, audio_kernel_sender, get_state, sound};
869
870        #[rstest]
871        fn test_audio_kernel_play_pause(
872            audio_kernel: AudioKernel,
873            sound: impl Source<Item = f32> + Send + 'static,
874        ) {
875            audio_kernel.player.append(sound);
876            audio_kernel.play();
877            assert!(!audio_kernel.player.is_paused());
878            audio_kernel.pause();
879            assert!(audio_kernel.player.is_paused());
880        }
881
882        #[rstest]
883        fn test_audio_kernel_toggle_playback(
884            audio_kernel: AudioKernel,
885            sound: impl Source<Item = f32> + Send + 'static,
886        ) {
887            audio_kernel.player.append(sound);
888            audio_kernel.play();
889            assert!(!audio_kernel.player.is_paused());
890            audio_kernel.toggle_playback();
891            assert!(audio_kernel.player.is_paused());
892            audio_kernel.toggle_playback();
893            assert!(!audio_kernel.player.is_paused());
894        }
895
896        #[rstest]
897        #[timeout(Duration::from_secs(5))] // if the test takes longer than this, the test can be considered a failure
898        #[tokio::test]
899        async fn test_play_pause_toggle_restart(
900            #[from(audio_kernel_sender)] sender: Arc<AudioKernelSender>,
901        ) {
902            init();
903            let db = init_test_database().await.unwrap();
904            let tempdir = tempfile::tempdir().unwrap();
905
906            let song = Song::try_load_into_db(
907                &db,
908                create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
909            )
910            .await
911            .unwrap();
912
913            sender.send(AudioCommand::Queue(QueueCommand::AddToQueue(Box::new(
914                OneOrMany::One(song.clone()),
915            ))));
916
917            let state = get_state(sender.clone()).await;
918            assert_eq!(state.queue_position, Some(0));
919            assert_eq!(state.status, Status::Playing);
920
921            sender.send(AudioCommand::Pause);
922            let state = get_state(sender.clone()).await;
923            assert_eq!(state.status, Status::Paused);
924
925            sender.send(AudioCommand::Play);
926            let state = get_state(sender.clone()).await;
927            assert_eq!(state.status, Status::Playing);
928
929            sender.send(AudioCommand::RestartSong);
930            let state = get_state(sender.clone()).await;
931            assert_eq!(state.status, Status::Playing); // Note, unlike adding a song to the queue, RestartSong does not affect whether the player is paused
932
933            sender.send(AudioCommand::TogglePlayback);
934            let state = get_state(sender.clone()).await;
935            assert_eq!(state.status, Status::Paused);
936
937            sender.send(AudioCommand::RestartSong);
938            let state = get_state(sender.clone()).await;
939            assert_eq!(state.status, Status::Paused); // Note, unlike adding a song to the queue, RestartSong does not affect whether the player is paused
940
941            sender.send(AudioCommand::Exit);
942        }
943
944        #[rstest]
945        fn test_audio_kernel_stop(audio_kernel: AudioKernel) {
946            init();
947            audio_kernel.player.append(sound());
948            audio_kernel.play();
949            assert!(!audio_kernel.player.is_paused());
950            audio_kernel.stop();
951            assert!(audio_kernel.player.is_paused());
952            assert_eq!(
953                audio_kernel.duration_info.lock().unwrap().time_played,
954                Duration::from_secs(0)
955            );
956            assert_eq!(*audio_kernel.status.lock().unwrap(), Status::Stopped);
957        }
958
959        #[rstest]
960        #[timeout(Duration::from_secs(5))] // if the test takes longer than this, the test can be considered a failure
961        #[tokio::test]
962        async fn test_audio_kernel_skip_forward(audio_kernel: AudioKernel) {
963            init();
964            let db = init_test_database().await.unwrap();
965            let tempdir = tempfile::tempdir().unwrap();
966
967            let state = audio_kernel.state();
968            assert_eq!(state.queue_position, None);
969            assert!(state.paused());
970            assert_eq!(state.status, Status::Stopped);
971
972            audio_kernel.queue_control(QueueCommand::AddToQueue(Box::new(OneOrMany::Many(vec![
973                Song::try_load_into_db(
974                    &db,
975                    create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
976                )
977                .await
978                .unwrap(),
979                Song::try_load_into_db(
980                    &db,
981                    create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
982                )
983                .await
984                .unwrap(),
985                Song::try_load_into_db(
986                    &db,
987                    create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
988                )
989                .await
990                .unwrap(),
991            ]))));
992
993            // songs were added to an empty queue, so the first song should start playing
994            let state = audio_kernel.state();
995            assert_eq!(state.queue_position, Some(0));
996            assert!(!state.paused());
997            assert_eq!(state.status, Status::Playing);
998
999            audio_kernel.queue_control(QueueCommand::SkipForward(1));
1000
1001            // the second song should start playing
1002            let state = audio_kernel.state();
1003            assert_eq!(state.queue_position, Some(1));
1004            assert!(!state.paused());
1005            assert_eq!(state.status, Status::Playing);
1006
1007            audio_kernel.queue_control(QueueCommand::SkipForward(1));
1008
1009            // the third song should start playing
1010            let state = audio_kernel.state();
1011            assert_eq!(state.queue_position, Some(2));
1012            assert!(!state.paused());
1013            assert_eq!(state.status, Status::Playing);
1014
1015            audio_kernel.queue_control(QueueCommand::SkipForward(1));
1016
1017            // we were at the end of the queue and tried to skip forward with repeatmode not being Continuous, so the player should be paused and the queue position should be None
1018            let state = audio_kernel.state();
1019            assert_eq!(state.queue_position, None);
1020            assert!(state.paused());
1021            assert_eq!(state.status, Status::Stopped);
1022        }
1023
1024        #[rstest]
1025        #[timeout(Duration::from_secs(6))] // if the test takes longer than this, the test can be considered a failure
1026        #[tokio::test]
1027        async fn test_audio_kernel_skip_forward_sender(
1028            #[from(audio_kernel_sender)] sender: Arc<AudioKernelSender>,
1029        ) {
1030            // set up tracing
1031            init();
1032
1033            let db = init_test_database().await.unwrap();
1034            let tempdir = tempfile::tempdir().unwrap();
1035
1036            let state = get_state(sender.clone()).await;
1037            assert_eq!(state.queue_position, None);
1038            assert!(state.paused());
1039            assert_eq!(state.status, Status::Stopped);
1040
1041            sender.send(AudioCommand::Queue(QueueCommand::AddToQueue(Box::new(
1042                OneOrMany::Many(vec![
1043                    Song::try_load_into_db(
1044                        &db,
1045                        create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1046                    )
1047                    .await
1048                    .unwrap(),
1049                    Song::try_load_into_db(
1050                        &db,
1051                        create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1052                    )
1053                    .await
1054                    .unwrap(),
1055                    Song::try_load_into_db(
1056                        &db,
1057                        create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1058                    )
1059                    .await
1060                    .unwrap(),
1061                ]),
1062            ))));
1063            // songs were added to an empty queue, so the first song should start playing
1064            let state = get_state(sender.clone()).await;
1065            assert_eq!(state.queue_position, Some(0));
1066            assert!(!state.paused());
1067            assert_eq!(state.status, Status::Playing);
1068
1069            sender.send(AudioCommand::Queue(QueueCommand::SkipForward(1)));
1070            // the second song should start playing
1071            let state = get_state(sender.clone()).await;
1072            assert_eq!(state.queue_position, Some(1));
1073            assert!(!state.paused());
1074            assert_eq!(state.status, Status::Playing);
1075
1076            sender.send(AudioCommand::Queue(QueueCommand::SkipForward(1)));
1077            // the third song should start playing
1078            let state = get_state(sender.clone()).await;
1079            assert_eq!(state.queue_position, Some(2));
1080            assert!(!state.paused());
1081            assert_eq!(state.status, Status::Playing);
1082
1083            sender.send(AudioCommand::Queue(QueueCommand::SkipForward(1)));
1084            // we were at the end of the queue and tried to skip forward, so the player should be paused and the queue position should be None
1085            let state = get_state(sender.clone()).await;
1086            assert_eq!(state.queue_position, None);
1087            assert!(state.paused());
1088            assert_eq!(state.status, Status::Stopped);
1089
1090            sender.send(AudioCommand::Exit);
1091        }
1092
1093        #[rstest]
1094        #[timeout(Duration::from_secs(5))] // if the test takes longer than this, the test can be considered a failure
1095        #[tokio::test]
1096        async fn test_remove_range_from_queue(
1097            #[from(audio_kernel_sender)] sender: Arc<AudioKernelSender>,
1098        ) {
1099            init();
1100            let db = init_test_database().await.unwrap();
1101            let tempdir = tempfile::tempdir().unwrap();
1102            let song1 = Song::try_load_into_db(
1103                &db,
1104                create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1105            )
1106            .await
1107            .unwrap();
1108            let song2 = Song::try_load_into_db(
1109                &db,
1110                create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1111            )
1112            .await
1113            .unwrap();
1114
1115            // add songs to the queue, starts playback
1116            sender.send(AudioCommand::Queue(QueueCommand::AddToQueue(Box::new(
1117                OneOrMany::Many(vec![song1.clone(), song2.clone()]),
1118            ))));
1119            let state = get_state(sender.clone()).await;
1120            assert_eq!(state.queue_position, Some(0));
1121            assert!(!state.paused());
1122            assert_eq!(state.status, Status::Playing);
1123
1124            // pause the player
1125            sender.send(AudioCommand::Pause);
1126
1127            // remove the current song from the queue, the player should still be paused(), but also stopped
1128            sender.send(AudioCommand::Queue(QueueCommand::RemoveRange(0..1)));
1129            let state = get_state(sender.clone()).await;
1130            assert_eq!(state.queue_position, Some(0));
1131            assert!(state.paused());
1132            assert_eq!(state.status, Status::Stopped);
1133            assert_eq!(state.queue.len(), 1);
1134            assert_eq!(state.queue[0], song2);
1135
1136            // unpause the player
1137            sender.send(AudioCommand::Play);
1138
1139            // add the song back to the queue, should be playing
1140            sender.send(AudioCommand::Queue(QueueCommand::AddToQueue(Box::new(
1141                OneOrMany::One(song1.clone()),
1142            ))));
1143            let state = get_state(sender.clone()).await;
1144            assert_eq!(state.queue_position, Some(0));
1145            assert!(!state.paused());
1146            assert_eq!(state.status, Status::Playing);
1147            assert_eq!(state.queue.len(), 2);
1148            assert_eq!(state.queue[0], song2);
1149            assert_eq!(state.queue[1], song1);
1150
1151            // remove the next song from the queue, player should still be playing
1152            sender.send(AudioCommand::Queue(QueueCommand::RemoveRange(1..2)));
1153            let state = get_state(sender.clone()).await;
1154            assert_eq!(state.queue_position, Some(0));
1155            assert!(!state.paused());
1156            assert_eq!(state.status, Status::Playing);
1157            assert_eq!(state.queue.len(), 1);
1158            assert_eq!(state.queue[0], song2);
1159
1160            sender.send(AudioCommand::Exit);
1161        }
1162
1163        #[rstest]
1164        #[timeout(Duration::from_secs(10))] // if the test takes longer than this, the test can be considered a failure
1165        #[tokio::test]
1166        async fn test_audio_kernel_skip_backward(
1167            #[from(audio_kernel_sender)] sender: Arc<AudioKernelSender>,
1168        ) {
1169            init();
1170            let db = init_test_database().await.unwrap();
1171            let tempdir = tempfile::tempdir().unwrap();
1172
1173            let state = get_state(sender.clone()).await;
1174            assert_eq!(state.queue_position, None);
1175            assert!(state.paused());
1176            assert_eq!(state.status, Status::Stopped);
1177
1178            sender.send(AudioCommand::Queue(QueueCommand::AddToQueue(Box::new(
1179                OneOrMany::Many(vec![
1180                    Song::try_load_into_db(
1181                        &db,
1182                        create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1183                    )
1184                    .await
1185                    .unwrap(),
1186                    Song::try_load_into_db(
1187                        &db,
1188                        create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1189                    )
1190                    .await
1191                    .unwrap(),
1192                    Song::try_load_into_db(
1193                        &db,
1194                        create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1195                    )
1196                    .await
1197                    .unwrap(),
1198                ]),
1199            ))));
1200
1201            // songs were added to an empty queue, so the first song should start playing
1202            let state = get_state(sender.clone()).await;
1203            assert_eq!(state.queue_position, Some(0));
1204            assert!(!state.paused());
1205            assert_eq!(state.status, Status::Playing);
1206
1207            sender.send(AudioCommand::Queue(QueueCommand::SkipForward(2)));
1208
1209            // the third song should start playing
1210            let state = get_state(sender.clone()).await;
1211            assert_eq!(state.queue_position, Some(2));
1212            assert!(!state.paused());
1213            assert_eq!(state.status, Status::Playing);
1214
1215            sender.send(AudioCommand::Queue(QueueCommand::SkipBackward(1)));
1216
1217            // the second song should start playing
1218            let state = get_state(sender.clone()).await;
1219            assert_eq!(state.queue_position, Some(1));
1220            assert!(!state.paused());
1221            assert_eq!(state.status, Status::Playing);
1222
1223            sender.send(AudioCommand::Queue(QueueCommand::SkipBackward(1)));
1224
1225            // the first song should start playing
1226            let state = get_state(sender.clone()).await;
1227            assert_eq!(state.queue_position, Some(0));
1228            assert!(!state.paused());
1229
1230            sender.send(AudioCommand::Queue(QueueCommand::SkipBackward(1)));
1231
1232            // we were at the start of the queue and tried to skip backward, so the player should be paused and the queue position should be None
1233            let state = get_state(sender.clone()).await;
1234            assert_eq!(state.queue_position, None);
1235            assert!(state.paused());
1236            assert_eq!(state.status, Status::Stopped);
1237
1238            sender.send(AudioCommand::Exit);
1239        }
1240
1241        #[rstest]
1242        #[timeout(Duration::from_secs(10))] // if the test takes longer than this, the test can be considered a failure
1243        #[tokio::test]
1244        async fn test_audio_kernel_set_position(
1245            #[from(audio_kernel_sender)] sender: Arc<AudioKernelSender>,
1246        ) {
1247            init();
1248            let db = init_test_database().await.unwrap();
1249            let tempdir = tempfile::tempdir().unwrap();
1250
1251            let state = get_state(sender.clone()).await;
1252            assert_eq!(state.queue_position, None);
1253            assert!(state.paused());
1254            assert_eq!(state.status, Status::Stopped);
1255
1256            sender.send(AudioCommand::Queue(QueueCommand::AddToQueue(Box::new(
1257                OneOrMany::Many(vec![
1258                    Song::try_load_into_db(
1259                        &db,
1260                        create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1261                    )
1262                    .await
1263                    .unwrap(),
1264                    Song::try_load_into_db(
1265                        &db,
1266                        create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1267                    )
1268                    .await
1269                    .unwrap(),
1270                    Song::try_load_into_db(
1271                        &db,
1272                        create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1273                    )
1274                    .await
1275                    .unwrap(),
1276                ]),
1277            ))));
1278            // songs were added to an empty queue, so the first song should start playing
1279            let state = get_state(sender.clone()).await;
1280            assert_eq!(state.queue_position, Some(0));
1281            assert!(!state.paused());
1282            assert_eq!(state.status, Status::Playing);
1283
1284            sender.send(AudioCommand::Queue(QueueCommand::SetPosition(1)));
1285            // the second song should start playing
1286            let state = get_state(sender.clone()).await;
1287            assert_eq!(state.queue_position, Some(1));
1288            assert!(!state.paused());
1289            assert_eq!(state.status, Status::Playing);
1290
1291            sender.send(AudioCommand::Queue(QueueCommand::SetPosition(2)));
1292            // the third song should start playing
1293            let state = get_state(sender.clone()).await;
1294            assert_eq!(state.queue_position, Some(2));
1295            assert!(!state.paused());
1296            assert_eq!(state.status, Status::Playing);
1297
1298            sender.send(AudioCommand::Queue(QueueCommand::SetPosition(0)));
1299            // the first song should start playing
1300            let state = get_state(sender.clone()).await;
1301            assert_eq!(state.queue_position, Some(0));
1302            assert!(!state.paused());
1303            assert_eq!(state.status, Status::Playing);
1304
1305            sender.send(AudioCommand::Queue(QueueCommand::SetPosition(3)));
1306            // we tried to set the position to an index that's out of pounds, so the player should be at the nearest valid index
1307            let state = get_state(sender.clone()).await;
1308            assert_eq!(state.queue_position, Some(2));
1309            assert!(!state.paused());
1310            assert_eq!(state.status, Status::Playing);
1311
1312            sender.send(AudioCommand::Exit);
1313        }
1314
1315        #[rstest]
1316        #[timeout(Duration::from_secs(5))] // if the test takes longer than this, the test can be considered a failure
1317        #[tokio::test]
1318        async fn test_audio_kernel_clear(
1319            #[from(audio_kernel_sender)] sender: Arc<AudioKernelSender>,
1320        ) {
1321            init();
1322            let db = init_test_database().await.unwrap();
1323            let tempdir = tempfile::tempdir().unwrap();
1324
1325            let state = get_state(sender.clone()).await;
1326            assert_eq!(state.queue_position, None);
1327            assert!(state.paused());
1328            assert_eq!(state.status, Status::Stopped);
1329
1330            sender.send(AudioCommand::Queue(QueueCommand::AddToQueue(Box::new(
1331                OneOrMany::Many(vec![
1332                    Song::try_load_into_db(
1333                        &db,
1334                        create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1335                    )
1336                    .await
1337                    .unwrap(),
1338                    Song::try_load_into_db(
1339                        &db,
1340                        create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1341                    )
1342                    .await
1343                    .unwrap(),
1344                    Song::try_load_into_db(
1345                        &db,
1346                        create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1347                    )
1348                    .await
1349                    .unwrap(),
1350                ]),
1351            ))));
1352            // songs were added to an empty queue, so the first song should start playing
1353            let state = get_state(sender.clone()).await;
1354            assert_eq!(state.queue_position, Some(0));
1355            assert_eq!(state.queue.len(), 3);
1356            assert!(!state.paused());
1357            assert_eq!(state.status, Status::Playing);
1358
1359            sender.send(AudioCommand::ClearPlayer);
1360            // we only cleared the audio player, so the queue should still have the songs
1361            let state = get_state(sender.clone()).await;
1362            assert_eq!(state.queue_position, Some(0));
1363            assert_eq!(state.queue.len(), 3);
1364            assert!(state.paused());
1365            assert_eq!(state.status, Status::Stopped);
1366
1367            sender.send(AudioCommand::Queue(QueueCommand::Clear));
1368            // we cleared the queue, so the player should be paused and the queue should be empty
1369            let state = get_state(sender.clone()).await;
1370            assert_eq!(state.queue_position, None);
1371            assert_eq!(state.queue.len(), 0);
1372            assert!(state.paused());
1373            assert_eq!(state.status, Status::Stopped);
1374
1375            sender.send(AudioCommand::Exit);
1376        }
1377
1378        #[rstest]
1379        #[timeout(Duration::from_secs(5))] // if the test takes longer than this, the test can be considered a failure
1380        #[tokio::test]
1381        async fn test_audio_kernel_shuffle(
1382            #[from(audio_kernel_sender)] sender: Arc<AudioKernelSender>,
1383        ) {
1384            init();
1385            let db = init_test_database().await.unwrap();
1386            let tempdir = tempfile::tempdir().unwrap();
1387
1388            let state = get_state(sender.clone()).await;
1389            assert_eq!(state.queue_position, None);
1390            assert!(state.paused());
1391            assert_eq!(state.status, Status::Stopped);
1392
1393            sender.send(AudioCommand::Queue(QueueCommand::AddToQueue(Box::new(
1394                OneOrMany::Many(vec![
1395                    Song::try_load_into_db(
1396                        &db,
1397                        create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1398                    )
1399                    .await
1400                    .unwrap(),
1401                    Song::try_load_into_db(
1402                        &db,
1403                        create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1404                    )
1405                    .await
1406                    .unwrap(),
1407                    Song::try_load_into_db(
1408                        &db,
1409                        create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1410                    )
1411                    .await
1412                    .unwrap(),
1413                ]),
1414            ))));
1415            // songs were added to an empty queue, so the first song should start playing
1416            let state = get_state(sender.clone()).await;
1417            assert_eq!(state.queue_position, Some(0));
1418            assert_eq!(state.queue.len(), 3);
1419            assert!(!state.paused());
1420            assert_eq!(state.status, Status::Playing);
1421
1422            // lets go to the second song
1423            sender.send(AudioCommand::Queue(QueueCommand::SkipForward(1)));
1424            let state = get_state(sender.clone()).await;
1425            assert_eq!(state.queue_position, Some(1));
1426            assert_eq!(state.queue.len(), 3);
1427            assert!(!state.paused());
1428            assert_eq!(state.status, Status::Playing);
1429
1430            // lets shuffle the queue
1431            sender.send(AudioCommand::Queue(QueueCommand::Shuffle));
1432            // we shuffled the queue, so the player should still be playing and the queue should still have 3 songs, and the previous current song should be the now first song
1433            let state = get_state(sender.clone()).await;
1434            assert_eq!(state.queue_position, Some(0));
1435            assert_eq!(state.queue.len(), 3);
1436            assert!(!state.paused());
1437            assert_eq!(state.status, Status::Playing);
1438
1439            sender.send(AudioCommand::Exit);
1440        }
1441
1442        #[rstest]
1443        #[timeout(Duration::from_secs(5))] // if the test takes longer than this, the test can be considered a failure
1444        #[tokio::test]
1445        async fn test_volume_commands(#[from(audio_kernel_sender)] sender: Arc<AudioKernelSender>) {
1446            init();
1447
1448            let state = get_state(sender.clone()).await;
1449            assert!(
1450                f32::EPSILON > (state.volume - 1.0).abs(),
1451                "{} != 1.0",
1452                state.volume
1453            );
1454            assert!(!state.muted);
1455
1456            sender.send(AudioCommand::Volume(VolumeCommand::Up(0.1)));
1457            let state = get_state(sender.clone()).await;
1458            assert!(
1459                f32::EPSILON > (state.volume - 1.1).abs(),
1460                "{} != 1.1",
1461                state.volume
1462            );
1463            assert!(!state.muted);
1464
1465            sender.send(AudioCommand::Volume(VolumeCommand::Down(0.1)));
1466            let state = get_state(sender.clone()).await;
1467            assert!(
1468                f32::EPSILON > (state.volume - 1.0).abs(),
1469                "{} != 1.0",
1470                state.volume
1471            );
1472            assert!(!state.muted);
1473
1474            sender.send(AudioCommand::Volume(VolumeCommand::Set(0.5)));
1475            let state = get_state(sender.clone()).await;
1476            assert!(
1477                f32::EPSILON > (state.volume - 0.5).abs(),
1478                "{} != 0.5",
1479                state.volume
1480            );
1481            assert!(!state.muted);
1482
1483            sender.send(AudioCommand::Volume(VolumeCommand::Mute));
1484            let state = get_state(sender.clone()).await;
1485            assert!(
1486                f32::EPSILON > (state.volume - 0.5).abs(),
1487                "{} != 0.5",
1488                state.volume
1489            ); // although underlying volume is 0 (for the rodio player), the stored volume is still 0.5
1490            assert!(state.muted);
1491
1492            sender.send(AudioCommand::Volume(VolumeCommand::Unmute));
1493            let state = get_state(sender.clone()).await;
1494            assert!(
1495                f32::EPSILON > (state.volume - 0.5).abs(),
1496                "{} != 0.5",
1497                state.volume
1498            );
1499            assert!(!state.muted);
1500
1501            sender.send(AudioCommand::Volume(VolumeCommand::ToggleMute));
1502            let state = get_state(sender.clone()).await;
1503            assert!(
1504                f32::EPSILON > (state.volume - 0.5).abs(),
1505                "{} != 0.5",
1506                state.volume
1507            );
1508            assert!(state.muted);
1509
1510            sender.send(AudioCommand::Volume(VolumeCommand::ToggleMute));
1511            let state = get_state(sender.clone()).await;
1512            assert!(
1513                f32::EPSILON > (state.volume - 0.5).abs(),
1514                "{} != 0.5",
1515                state.volume
1516            );
1517            assert!(!state.muted);
1518
1519            sender.send(AudioCommand::Exit);
1520        }
1521
1522        #[rstest]
1523        #[timeout(Duration::from_secs(5))] // if the test takes longer than this, the test can be considered a failure
1524        #[tokio::test]
1525        async fn test_volume_out_of_bounds(
1526            #[from(audio_kernel_sender)] sender: Arc<AudioKernelSender>,
1527        ) {
1528            init();
1529
1530            // try moving volume above/below the maximum/minimum
1531            sender.send(AudioCommand::Volume(VolumeCommand::Up(MAX_VOLUME + 0.5)));
1532            let state = get_state(sender.clone()).await;
1533            assert!(
1534                f32::EPSILON > (state.volume - MAX_VOLUME).abs(),
1535                "{} != {}",
1536                state.volume,
1537                MAX_VOLUME
1538            );
1539            assert!(!state.muted);
1540            sender.send(AudioCommand::Volume(VolumeCommand::Down(
1541                MAX_VOLUME + 0.5 - MIN_VOLUME,
1542            )));
1543            let state = get_state(sender.clone()).await;
1544            assert!(
1545                f32::EPSILON > (state.volume - MIN_VOLUME).abs(),
1546                "{} != {}",
1547                state.volume,
1548                MIN_VOLUME
1549            );
1550            assert!(!state.muted);
1551
1552            // try setting volume above/below the maximum/minimum
1553            sender.send(AudioCommand::Volume(VolumeCommand::Set(MAX_VOLUME + 0.5)));
1554            let state = get_state(sender.clone()).await;
1555            assert!(
1556                f32::EPSILON > (state.volume - MAX_VOLUME).abs(),
1557                "{} != {}",
1558                state.volume,
1559                MAX_VOLUME
1560            );
1561            assert!(!state.muted);
1562            sender.send(AudioCommand::Volume(VolumeCommand::Set(MIN_VOLUME - 0.5)));
1563            let state = get_state(sender.clone()).await;
1564            assert!(
1565                f32::EPSILON > (state.volume - MIN_VOLUME).abs(),
1566                "{} != {}",
1567                state.volume,
1568                MIN_VOLUME
1569            );
1570            assert!(!state.muted);
1571
1572            sender.send(AudioCommand::Exit);
1573        }
1574
1575        #[rstest]
1576        #[timeout(Duration::from_secs(9))] // if the test takes longer than this, the test can be considered a failure
1577        #[tokio::test]
1578        async fn test_seek_commands(#[from(audio_kernel_sender)] sender: Arc<AudioKernelSender>) {
1579            init();
1580            let db = init_test_database().await.unwrap();
1581            let tempdir = tempfile::tempdir().unwrap();
1582
1583            let song = Song::try_load_into_db(
1584                &db,
1585                create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1586            )
1587            .await
1588            .unwrap();
1589
1590            // add a song to the queue
1591            // NOTE: this song has a duration of 10 seconds
1592            sender.send(AudioCommand::Queue(QueueCommand::AddToQueue(Box::new(
1593                OneOrMany::One(song.clone()),
1594            ))));
1595            sender.send(AudioCommand::Stop);
1596            sender.send(AudioCommand::Seek(
1597                SeekType::Absolute,
1598                Duration::from_secs(0),
1599            ));
1600            let state: StateAudio = get_state(sender.clone()).await;
1601            assert_eq!(state.queue_position, Some(0));
1602            assert_eq!(state.status, Status::Stopped);
1603            assert_eq!(
1604                state.runtime.unwrap().duration,
1605                Duration::from_secs(10) + Duration::from_nanos(6)
1606            );
1607            assert_eq!(state.runtime.unwrap().seek_position, Duration::from_secs(0));
1608
1609            // skip ahead a bit
1610            sender.send(AudioCommand::Seek(
1611                SeekType::RelativeForwards,
1612                Duration::from_secs(2),
1613            ));
1614            let state = get_state(sender.clone()).await;
1615            assert_eq!(state.runtime.unwrap().seek_position, Duration::from_secs(2));
1616            assert_eq!(state.current_song, Some(song.clone()));
1617            assert_eq!(state.status, Status::Paused);
1618
1619            // skip back a bit
1620            sender.send(AudioCommand::Seek(
1621                SeekType::RelativeBackwards,
1622                Duration::from_secs(1),
1623            ));
1624            let state = get_state(sender.clone()).await;
1625            assert_eq!(state.runtime.unwrap().seek_position, Duration::from_secs(1));
1626            assert_eq!(state.current_song, Some(song.clone()));
1627            assert_eq!(state.status, Status::Paused);
1628
1629            // skip to 9 seconds
1630            sender.send(AudioCommand::Seek(
1631                SeekType::Absolute,
1632                Duration::from_secs(9),
1633            ));
1634            let state = get_state(sender.clone()).await;
1635            assert_eq!(state.runtime.unwrap().seek_position, Duration::from_secs(9));
1636            assert_eq!(state.current_song, Some(song.clone()));
1637            assert_eq!(state.status, Status::Paused);
1638
1639            // now we unpause, wait a bit, and check that the song has ended
1640            sender.send(AudioCommand::Play);
1641            tokio::time::sleep(Duration::from_millis(1001)).await;
1642            let state = get_state(sender.clone()).await;
1643            assert_eq!(state.queue_position, None);
1644            assert_eq!(state.status, Status::Stopped);
1645
1646            sender.send(AudioCommand::Exit);
1647        }
1648    }
1649}