mecomp_core/audio/
mod.rs

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