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.into(),
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            self.clear_player();
596            if let Some(song) = self.get_current_song() {
597                if let Err(e) = self.append_song_to_player(&song) {
598                    error!("Failed to append song to player: {e}");
599                }
600            }
601        }
602    }
603
604    #[instrument(skip(self))]
605    fn get_current_song(&self) -> Option<SongBrief> {
606        self.queue.current_song().cloned()
607    }
608
609    #[instrument(skip(self))]
610    fn get_next_song(&mut self) -> Option<SongBrief> {
611        self.queue.next_song().cloned()
612    }
613
614    fn get_time_played(&self) -> Duration {
615        self.player.get_pos()
616    }
617
618    #[instrument(skip(self, source))]
619    fn append_to_player<T>(&self, source: T)
620    where
621        T: Source<Item = f32> + Send + 'static,
622    {
623        self.player.append(source);
624
625        // establish a callback for starting the next song once the current one finishes
626        let command_tx = self.command_tx.clone();
627        self.player.append(EmptyCallback::new(Box::new(move || {
628            debug!("Song finished");
629            if let Err(e) = command_tx.send((
630                AudioCommand::Queue(QueueCommand::PlayNextSong),
631                tracing::Span::current(),
632            )) {
633                error!("Failed to send command to audio kernel: {e}");
634            } else {
635                debug!("Sent PlayNextSong command to audio kernel");
636            }
637        })));
638    }
639
640    #[instrument(skip(self))]
641    fn append_song_to_player(&self, song: &SongBrief) -> Result<(), LibraryError> {
642        let file = File::open(&song.path)?;
643        let byte_len = file.metadata()?.len();
644        let decoder = DecoderBuilder::new()
645            .with_data(BufReader::new(file))
646            .with_byte_len(byte_len)
647            .with_seekable(true)
648            .with_coarse_seek(true)
649            .with_gapless(true)
650            .build()?;
651        self.append_to_player(decoder);
652
653        Ok(())
654    }
655
656    #[instrument(skip(self))]
657    fn volume_control(&mut self, command: VolumeCommand) {
658        match command {
659            VolumeCommand::Up(change) => {
660                let volume = self.volume;
661                let updated = (volume + change).clamp(MIN_VOLUME, MAX_VOLUME);
662                // only update volume if it has changed
663                if (volume - updated).abs() > 0.0001 {
664                    self.volume = updated;
665                    let _ = self.event_tx.send(StateChange::VolumeChanged(self.volume));
666                }
667            }
668            VolumeCommand::Down(change) => {
669                let volume = self.volume;
670                let updated = (volume - change).clamp(MIN_VOLUME, MAX_VOLUME);
671                if (volume - updated).abs() > 0.0001 {
672                    self.volume = updated;
673                    let _ = self.event_tx.send(StateChange::VolumeChanged(self.volume));
674                }
675            }
676            VolumeCommand::Set(updated) => {
677                let volume = self.volume;
678                let updated = updated.clamp(MIN_VOLUME, MAX_VOLUME);
679                if (volume - updated).abs() > 0.0001 {
680                    self.volume = updated;
681                    let _ = self.event_tx.send(StateChange::VolumeChanged(self.volume));
682                }
683            }
684            VolumeCommand::Mute => {
685                self.muted = true;
686                let _ = self.event_tx.send(StateChange::Muted);
687            }
688            VolumeCommand::Unmute => {
689                self.muted = false;
690                let _ = self.event_tx.send(StateChange::Unmuted);
691            }
692            VolumeCommand::ToggleMute => {
693                self.muted = !self.muted;
694                if self.muted {
695                    let _ = self.event_tx.send(StateChange::Muted);
696                } else {
697                    let _ = self.event_tx.send(StateChange::Unmuted);
698                }
699            }
700        }
701
702        if self.muted {
703            self.player.set_volume(0.0);
704        } else {
705            self.player.set_volume(self.volume.to_owned());
706        }
707    }
708
709    #[instrument(skip(self))]
710    fn seek(&mut self, seek: SeekType, duration: Duration) {
711        // calculate the new time based on the seek type
712        let new_time = match seek {
713            SeekType::Absolute => duration,
714            SeekType::RelativeForwards => self.get_time_played().saturating_add(duration),
715            SeekType::RelativeBackwards => self.get_time_played().saturating_sub(duration),
716        };
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                if new_time > Duration::from_secs(0) && self.status == Status::Stopped {
725                    self.status = Status::Paused;
726                }
727            }
728            Err(SeekError::NotSupported { underlying_source }) => {
729                error!("Seek not supported by source: {underlying_source}");
730            }
731            Err(err) => {
732                error!("Seeking failed with error: {err}");
733            }
734        }
735    }
736}
737
738#[cfg(test)]
739mod tests {
740    use pretty_assertions::assert_eq;
741    use rstest::{fixture, rstest};
742
743    use crate::test_utils::init;
744
745    use super::*;
746    use std::sync::mpsc;
747    use std::time::Duration;
748
749    #[fixture]
750    fn audio_kernel() -> AudioKernel {
751        // channel for commands
752        let (tx, _) = mpsc::channel();
753        // channel for events
754        let (event_tx, _) = mpsc::channel();
755        AudioKernel::new(tx, event_tx)
756    }
757
758    #[fixture]
759    fn audio_kernel_sender() -> Arc<AudioKernelSender> {
760        // channel for events
761        let (tx, _) = mpsc::channel();
762        AudioKernelSender::start(tx)
763    }
764
765    async fn get_state(sender: Arc<AudioKernelSender>) -> StateAudio {
766        let (tx, rx) = tokio::sync::oneshot::channel::<StateAudio>();
767        sender.send(AudioCommand::ReportStatus(tx));
768        rx.await.unwrap()
769    }
770
771    #[fixture]
772    fn sound() -> impl Source<Item = f32> + Send + 'static {
773        rodio::source::SineWave::new(440.0)
774    }
775
776    #[test]
777    fn test_audio_kernel_sender_send() {
778        let (tx, rx) = mpsc::channel();
779        let sender = AudioKernelSender::new(tx);
780        sender.send(AudioCommand::Play);
781        let (recv, _) = rx.recv().unwrap();
782        assert_eq!(recv, AudioCommand::Play);
783    }
784
785    #[test]
786    #[should_panic = "Failed to send command to audio kernel: sending on a closed channel"]
787    fn test_audio_kernel_send_closed_channel() {
788        let (tx, _) = mpsc::channel();
789        let sender = AudioKernelSender::new(tx);
790        sender.send(AudioCommand::Play);
791    }
792
793    #[test]
794    fn test_audio_kernel_try_send_closed_channel() {
795        let (tx, _) = mpsc::channel();
796        let sender = AudioKernelSender::new(tx);
797        assert!(sender.try_send(AudioCommand::Play).is_err());
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(mut audio_kernel: AudioKernel) {
812        audio_kernel.volume_control(VolumeCommand::Up(0.1));
813        let volume = audio_kernel.volume;
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;
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;
822        assert!(f32::EPSILON > (volume - 0.5).abs(), "{volume} != 0.5");
823
824        audio_kernel.volume_control(VolumeCommand::Mute);
825        assert_eq!(audio_kernel.muted, true);
826
827        audio_kernel.volume_control(VolumeCommand::Unmute);
828        assert_eq!(audio_kernel.muted, false);
829
830        audio_kernel.volume_control(VolumeCommand::ToggleMute);
831        assert_eq!(audio_kernel.muted, true);
832
833        audio_kernel.volume_control(VolumeCommand::ToggleMute);
834        assert_eq!(audio_kernel.muted, false);
835    }
836
837    mod playback_tests {
838        //! These are tests that require the audio kernel to be able to play audio
839        //! As such, they cannot be run on CI.
840        //! Therefore, they are in a separate module so that they can be skipped when running tests on CI.
841
842        use mecomp_storage::{
843            db::schemas::song::Song,
844            test_utils::{arb_song_case, create_song_metadata, init_test_database},
845        };
846        use pretty_assertions::assert_eq;
847        use rstest::rstest;
848
849        use crate::test_utils::init;
850
851        use super::{super::*, audio_kernel, audio_kernel_sender, get_state, sound};
852
853        #[rstest]
854        fn test_audio_kernel_play_pause(
855            mut audio_kernel: AudioKernel,
856            sound: impl Source<Item = f32> + Send + 'static,
857        ) {
858            init();
859            audio_kernel.player.append(sound);
860            audio_kernel.play();
861            assert!(!audio_kernel.player.is_paused());
862            audio_kernel.pause();
863            assert!(audio_kernel.player.is_paused());
864        }
865
866        #[rstest]
867        fn test_audio_kernel_toggle_playback(
868            mut audio_kernel: AudioKernel,
869            sound: impl Source<Item = f32> + Send + 'static,
870        ) {
871            init();
872            audio_kernel.player.append(sound);
873            audio_kernel.play();
874            assert!(!audio_kernel.player.is_paused());
875            audio_kernel.toggle_playback();
876            assert!(audio_kernel.player.is_paused());
877            audio_kernel.toggle_playback();
878            assert!(!audio_kernel.player.is_paused());
879        }
880
881        #[rstest]
882        #[timeout(Duration::from_secs(10))] // if the test takes longer than this, the test can be considered a failure
883        #[tokio::test]
884        async fn test_play_pause_toggle_restart(
885            #[from(audio_kernel_sender)] sender: Arc<AudioKernelSender>,
886        ) {
887            init();
888            let db = init_test_database().await.unwrap();
889            let tempdir = tempfile::tempdir().unwrap();
890
891            let song = Song::try_load_into_db(
892                &db,
893                create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
894            )
895            .await
896            .unwrap();
897
898            sender.send(AudioCommand::Queue(QueueCommand::AddToQueue(
899                song.brief().into(),
900            )));
901
902            let state = get_state(sender.clone()).await;
903            assert_eq!(state.queue_position, Some(0));
904            assert_eq!(state.status, Status::Playing);
905
906            sender.send(AudioCommand::Pause);
907            let state = get_state(sender.clone()).await;
908            assert_eq!(state.status, Status::Paused);
909
910            sender.send(AudioCommand::Play);
911            let state = get_state(sender.clone()).await;
912            assert_eq!(state.status, Status::Playing);
913
914            sender.send(AudioCommand::RestartSong);
915            let state = get_state(sender.clone()).await;
916            assert_eq!(state.status, Status::Playing); // Note, unlike adding a song to the queue, RestartSong does not affect whether the player is paused
917
918            sender.send(AudioCommand::TogglePlayback);
919            let state = get_state(sender.clone()).await;
920            assert_eq!(state.status, Status::Paused);
921
922            sender.send(AudioCommand::RestartSong);
923            let state = get_state(sender.clone()).await;
924            assert_eq!(state.status, Status::Paused); // Note, unlike adding a song to the queue, RestartSong does not affect whether the player is paused
925
926            sender.send(AudioCommand::Exit);
927        }
928
929        #[rstest]
930        fn test_audio_kernel_stop(mut audio_kernel: AudioKernel) {
931            init();
932            audio_kernel.player.append(sound());
933            audio_kernel.play();
934            assert!(!audio_kernel.player.is_paused());
935            audio_kernel.stop();
936            assert!(audio_kernel.player.is_paused());
937            assert_eq!(audio_kernel.player.get_pos(), Duration::from_secs(0));
938            assert_eq!(audio_kernel.status, Status::Stopped);
939        }
940
941        #[rstest]
942        #[timeout(Duration::from_secs(10))] // if the test takes longer than this, the test can be considered a failure
943        #[tokio::test]
944        async fn test_audio_kernel_skip_forward(mut audio_kernel: AudioKernel) {
945            init();
946            let db = init_test_database().await.unwrap();
947            let tempdir = tempfile::tempdir().unwrap();
948
949            let state = audio_kernel.state();
950            assert_eq!(state.queue_position, None);
951            assert!(state.paused());
952            assert_eq!(state.status, Status::Stopped);
953
954            audio_kernel.queue_control(QueueCommand::AddToQueue(OneOrMany::Many(vec![
955                Song::try_load_into_db(
956                    &db,
957                    create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
958                )
959                .await
960                .unwrap()
961                .into(),
962                Song::try_load_into_db(
963                    &db,
964                    create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
965                )
966                .await
967                .unwrap()
968                .into(),
969                Song::try_load_into_db(
970                    &db,
971                    create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
972                )
973                .await
974                .unwrap()
975                .into(),
976            ])));
977
978            // songs were added to an empty queue, so the first song should start playing
979            let state = audio_kernel.state();
980            assert_eq!(state.queue_position, Some(0));
981            assert!(!state.paused());
982            assert_eq!(state.status, Status::Playing);
983
984            audio_kernel.queue_control(QueueCommand::SkipForward(1));
985
986            // the second song should start playing
987            let state = audio_kernel.state();
988            assert_eq!(state.queue_position, Some(1));
989            assert!(!state.paused());
990            assert_eq!(state.status, Status::Playing);
991
992            audio_kernel.queue_control(QueueCommand::SkipForward(1));
993
994            // the third song should start playing
995            let state = audio_kernel.state();
996            assert_eq!(state.queue_position, Some(2));
997            assert!(!state.paused());
998            assert_eq!(state.status, Status::Playing);
999
1000            audio_kernel.queue_control(QueueCommand::SkipForward(1));
1001
1002            // we were at the end of the queue and tried to skip forward with repeatmode not being Continuous,
1003            // so the player should be paused and the queue position should be None
1004            let state = audio_kernel.state();
1005            assert_eq!(state.queue_position, None);
1006            assert!(state.paused());
1007            assert_eq!(state.status, Status::Stopped);
1008        }
1009
1010        #[rstest]
1011        #[timeout(Duration::from_secs(6))] // if the test takes longer than this, the test can be considered a failure
1012        #[tokio::test]
1013        async fn test_audio_kernel_skip_forward_sender(
1014            #[from(audio_kernel_sender)] sender: Arc<AudioKernelSender>,
1015        ) {
1016            // set up tracing
1017            init();
1018
1019            let db = init_test_database().await.unwrap();
1020            let tempdir = tempfile::tempdir().unwrap();
1021
1022            let state = get_state(sender.clone()).await;
1023            assert_eq!(state.queue_position, None);
1024            assert!(state.paused());
1025            assert_eq!(state.status, Status::Stopped);
1026
1027            sender.send(AudioCommand::Queue(QueueCommand::AddToQueue(
1028                OneOrMany::Many(vec![
1029                    Song::try_load_into_db(
1030                        &db,
1031                        create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1032                    )
1033                    .await
1034                    .unwrap()
1035                    .into(),
1036                    Song::try_load_into_db(
1037                        &db,
1038                        create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1039                    )
1040                    .await
1041                    .unwrap()
1042                    .into(),
1043                    Song::try_load_into_db(
1044                        &db,
1045                        create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1046                    )
1047                    .await
1048                    .unwrap()
1049                    .into(),
1050                ]),
1051            )));
1052            // songs were added to an empty queue, so the first song should start playing
1053            let state = get_state(sender.clone()).await;
1054            assert_eq!(state.queue_position, Some(0));
1055            assert!(!state.paused());
1056            assert_eq!(state.status, Status::Playing);
1057
1058            sender.send(AudioCommand::Queue(QueueCommand::SkipForward(1)));
1059            // the second song should start playing
1060            let state = get_state(sender.clone()).await;
1061            assert_eq!(state.queue_position, Some(1));
1062            assert!(!state.paused());
1063            assert_eq!(state.status, Status::Playing);
1064
1065            sender.send(AudioCommand::Queue(QueueCommand::SkipForward(1)));
1066            // the third song should start playing
1067            let state = get_state(sender.clone()).await;
1068            assert_eq!(state.queue_position, Some(2));
1069            assert!(!state.paused());
1070            assert_eq!(state.status, Status::Playing);
1071
1072            sender.send(AudioCommand::Queue(QueueCommand::SkipForward(1)));
1073            // 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
1074            let state = get_state(sender.clone()).await;
1075            assert_eq!(state.queue_position, None);
1076            assert!(state.paused());
1077            assert_eq!(state.status, Status::Stopped);
1078
1079            sender.send(AudioCommand::Exit);
1080        }
1081
1082        #[rstest]
1083        #[timeout(Duration::from_secs(6))] // if the test takes longer than this, the test can be considered a failure
1084        #[tokio::test]
1085        async fn test_remove_range_from_queue(
1086            #[from(audio_kernel_sender)] sender: Arc<AudioKernelSender>,
1087        ) {
1088            init();
1089            let db = init_test_database().await.unwrap();
1090            let tempdir = tempfile::tempdir().unwrap();
1091            let song1 = Song::try_load_into_db(
1092                &db,
1093                create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1094            )
1095            .await
1096            .unwrap();
1097            let song2 = Song::try_load_into_db(
1098                &db,
1099                create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1100            )
1101            .await
1102            .unwrap();
1103
1104            // add songs to the queue, starts playback
1105            sender.send(AudioCommand::Queue(QueueCommand::AddToQueue(
1106                OneOrMany::Many(vec![song1.clone().into(), song2.clone().into()]),
1107            )));
1108            let state = get_state(sender.clone()).await;
1109            assert_eq!(state.queue_position, Some(0));
1110            assert!(!state.paused());
1111            assert_eq!(state.status, Status::Playing);
1112
1113            // pause the player
1114            sender.send(AudioCommand::Pause);
1115
1116            // remove the current song from the queue, the player should still be paused(), but also stopped
1117            sender.send(AudioCommand::Queue(QueueCommand::RemoveRange(0..1)));
1118            let state = get_state(sender.clone()).await;
1119            assert_eq!(state.queue_position, Some(0));
1120            assert!(state.paused());
1121            assert_eq!(state.status, Status::Stopped);
1122            assert_eq!(state.queue.len(), 1);
1123            assert_eq!(state.queue[0], song2.clone().into());
1124
1125            // unpause the player
1126            sender.send(AudioCommand::Play);
1127
1128            // add the song back to the queue, should be playing
1129            sender.send(AudioCommand::Queue(QueueCommand::AddToQueue(
1130                song1.clone().brief().into(),
1131            )));
1132            let state = get_state(sender.clone()).await;
1133            assert_eq!(state.queue_position, Some(0));
1134            assert!(!state.paused());
1135            assert_eq!(state.status, Status::Playing);
1136            assert_eq!(state.queue.len(), 2);
1137            assert_eq!(state.queue[0], song2.clone().into());
1138            assert_eq!(state.queue[1], song1.into());
1139
1140            // remove the next song from the queue, player should still be playing
1141            sender.send(AudioCommand::Queue(QueueCommand::RemoveRange(1..2)));
1142            let state = get_state(sender.clone()).await;
1143            assert_eq!(state.queue_position, Some(0));
1144            assert!(!state.paused());
1145            assert_eq!(state.status, Status::Playing);
1146            assert_eq!(state.queue.len(), 1);
1147            assert_eq!(state.queue[0], song2.into());
1148
1149            sender.send(AudioCommand::Exit);
1150        }
1151
1152        #[rstest]
1153        #[timeout(Duration::from_secs(10))] // if the test takes longer than this, the test can be considered a failure
1154        #[tokio::test]
1155        async fn test_audio_kernel_skip_backward(
1156            #[from(audio_kernel_sender)] sender: Arc<AudioKernelSender>,
1157        ) {
1158            init();
1159            let db = init_test_database().await.unwrap();
1160            let tempdir = tempfile::tempdir().unwrap();
1161
1162            let state = get_state(sender.clone()).await;
1163            assert_eq!(state.queue_position, None);
1164            assert!(state.paused());
1165            assert_eq!(state.status, Status::Stopped);
1166
1167            sender.send(AudioCommand::Queue(QueueCommand::AddToQueue(
1168                OneOrMany::Many(vec![
1169                    Song::try_load_into_db(
1170                        &db,
1171                        create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1172                    )
1173                    .await
1174                    .unwrap()
1175                    .into(),
1176                    Song::try_load_into_db(
1177                        &db,
1178                        create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1179                    )
1180                    .await
1181                    .unwrap()
1182                    .into(),
1183                    Song::try_load_into_db(
1184                        &db,
1185                        create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1186                    )
1187                    .await
1188                    .unwrap()
1189                    .into(),
1190                ]),
1191            )));
1192
1193            // songs were added to an empty queue, so the first song should start playing
1194            let state = get_state(sender.clone()).await;
1195            assert_eq!(state.queue_position, Some(0));
1196            assert!(!state.paused());
1197            assert_eq!(state.status, Status::Playing);
1198
1199            sender.send(AudioCommand::Queue(QueueCommand::SkipForward(2)));
1200
1201            // the third song should start playing
1202            let state = get_state(sender.clone()).await;
1203            assert_eq!(state.queue_position, Some(2));
1204            assert!(!state.paused());
1205            assert_eq!(state.status, Status::Playing);
1206
1207            sender.send(AudioCommand::Queue(QueueCommand::SkipBackward(1)));
1208
1209            // the second song should start playing
1210            let state = get_state(sender.clone()).await;
1211            assert_eq!(state.queue_position, Some(1));
1212            assert!(!state.paused());
1213            assert_eq!(state.status, Status::Playing);
1214
1215            sender.send(AudioCommand::Queue(QueueCommand::SkipBackward(1)));
1216
1217            // the first song should start playing
1218            let state = get_state(sender.clone()).await;
1219            assert_eq!(state.queue_position, Some(0));
1220            assert!(!state.paused());
1221
1222            sender.send(AudioCommand::Queue(QueueCommand::SkipBackward(1)));
1223
1224            // 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
1225            let state = get_state(sender.clone()).await;
1226            assert_eq!(state.queue_position, None);
1227            assert!(state.paused());
1228            assert_eq!(state.status, Status::Stopped);
1229
1230            sender.send(AudioCommand::Exit);
1231        }
1232
1233        #[rstest]
1234        #[timeout(Duration::from_secs(10))] // if the test takes longer than this, the test can be considered a failure
1235        #[tokio::test]
1236        async fn test_audio_kernel_set_position(
1237            #[from(audio_kernel_sender)] sender: Arc<AudioKernelSender>,
1238        ) {
1239            init();
1240            let db = init_test_database().await.unwrap();
1241            let tempdir = tempfile::tempdir().unwrap();
1242
1243            let state = get_state(sender.clone()).await;
1244            assert_eq!(state.queue_position, None);
1245            assert!(state.paused());
1246            assert_eq!(state.status, Status::Stopped);
1247
1248            sender.send(AudioCommand::Queue(QueueCommand::AddToQueue(
1249                OneOrMany::Many(vec![
1250                    Song::try_load_into_db(
1251                        &db,
1252                        create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1253                    )
1254                    .await
1255                    .unwrap()
1256                    .into(),
1257                    Song::try_load_into_db(
1258                        &db,
1259                        create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1260                    )
1261                    .await
1262                    .unwrap()
1263                    .into(),
1264                    Song::try_load_into_db(
1265                        &db,
1266                        create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1267                    )
1268                    .await
1269                    .unwrap()
1270                    .into(),
1271                ]),
1272            )));
1273            // songs were added to an empty queue, so the first song should start playing
1274            let state = get_state(sender.clone()).await;
1275            assert_eq!(state.queue_position, Some(0));
1276            assert!(!state.paused());
1277            assert_eq!(state.status, Status::Playing);
1278
1279            sender.send(AudioCommand::Queue(QueueCommand::SetPosition(1)));
1280            // the second song should start playing
1281            let state = get_state(sender.clone()).await;
1282            assert_eq!(state.queue_position, Some(1));
1283            assert!(!state.paused());
1284            assert_eq!(state.status, Status::Playing);
1285
1286            sender.send(AudioCommand::Queue(QueueCommand::SetPosition(2)));
1287            // the third song should start playing
1288            let state = get_state(sender.clone()).await;
1289            assert_eq!(state.queue_position, Some(2));
1290            assert!(!state.paused());
1291            assert_eq!(state.status, Status::Playing);
1292
1293            sender.send(AudioCommand::Queue(QueueCommand::SetPosition(0)));
1294            // the first song should start playing
1295            let state = get_state(sender.clone()).await;
1296            assert_eq!(state.queue_position, Some(0));
1297            assert!(!state.paused());
1298            assert_eq!(state.status, Status::Playing);
1299
1300            sender.send(AudioCommand::Queue(QueueCommand::SetPosition(3)));
1301            // we tried to set the position to an index that's out of pounds, so the player should be at the nearest valid index
1302            let state = get_state(sender.clone()).await;
1303            assert_eq!(state.queue_position, Some(2));
1304            assert!(!state.paused());
1305            assert_eq!(state.status, Status::Playing);
1306
1307            sender.send(AudioCommand::Exit);
1308        }
1309
1310        #[rstest]
1311        #[timeout(Duration::from_secs(6))] // if the test takes longer than this, the test can be considered a failure
1312        #[tokio::test]
1313        async fn test_audio_kernel_clear(
1314            #[from(audio_kernel_sender)] sender: Arc<AudioKernelSender>,
1315        ) {
1316            init();
1317            let db = init_test_database().await.unwrap();
1318            let tempdir = tempfile::tempdir().unwrap();
1319
1320            let state = get_state(sender.clone()).await;
1321            assert_eq!(state.queue_position, None);
1322            assert!(state.paused());
1323            assert_eq!(state.status, Status::Stopped);
1324
1325            sender.send(AudioCommand::Queue(QueueCommand::AddToQueue(
1326                OneOrMany::Many(vec![
1327                    Song::try_load_into_db(
1328                        &db,
1329                        create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1330                    )
1331                    .await
1332                    .unwrap()
1333                    .into(),
1334                    Song::try_load_into_db(
1335                        &db,
1336                        create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1337                    )
1338                    .await
1339                    .unwrap()
1340                    .into(),
1341                    Song::try_load_into_db(
1342                        &db,
1343                        create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1344                    )
1345                    .await
1346                    .unwrap()
1347                    .into(),
1348                ]),
1349            )));
1350            // songs were added to an empty queue, so the first song should start playing
1351            let state = get_state(sender.clone()).await;
1352            assert_eq!(state.queue_position, Some(0));
1353            assert_eq!(state.queue.len(), 3);
1354            assert!(!state.paused());
1355            assert_eq!(state.status, Status::Playing);
1356
1357            sender.send(AudioCommand::ClearPlayer);
1358            // we only cleared the audio player, so the queue should still have the songs
1359            let state = get_state(sender.clone()).await;
1360            assert_eq!(state.queue_position, Some(0));
1361            assert_eq!(state.queue.len(), 3);
1362            assert!(state.paused());
1363            assert_eq!(state.status, Status::Stopped);
1364
1365            sender.send(AudioCommand::Queue(QueueCommand::Clear));
1366            // we cleared the queue, so the player should be paused and the queue should be empty
1367            let state = get_state(sender.clone()).await;
1368            assert_eq!(state.queue_position, None);
1369            assert_eq!(state.queue.len(), 0);
1370            assert!(state.paused());
1371            assert_eq!(state.status, Status::Stopped);
1372
1373            sender.send(AudioCommand::Exit);
1374        }
1375
1376        #[rstest]
1377        #[timeout(Duration::from_secs(6))] // if the test takes longer than this, the test can be considered a failure
1378        #[tokio::test]
1379        async fn test_audio_kernel_shuffle(
1380            #[from(audio_kernel_sender)] sender: Arc<AudioKernelSender>,
1381        ) {
1382            init();
1383            let db = init_test_database().await.unwrap();
1384            let tempdir = tempfile::tempdir().unwrap();
1385
1386            let state = get_state(sender.clone()).await;
1387            assert_eq!(state.queue_position, None);
1388            assert!(state.paused());
1389            assert_eq!(state.status, Status::Stopped);
1390
1391            sender.send(AudioCommand::Queue(QueueCommand::AddToQueue(
1392                OneOrMany::Many(vec![
1393                    Song::try_load_into_db(
1394                        &db,
1395                        create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1396                    )
1397                    .await
1398                    .unwrap()
1399                    .into(),
1400                    Song::try_load_into_db(
1401                        &db,
1402                        create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1403                    )
1404                    .await
1405                    .unwrap()
1406                    .into(),
1407                    Song::try_load_into_db(
1408                        &db,
1409                        create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1410                    )
1411                    .await
1412                    .unwrap()
1413                    .into(),
1414                ]),
1415            )));
1416            // songs were added to an empty queue, so the first song should start playing
1417            let state = get_state(sender.clone()).await;
1418            assert_eq!(state.queue_position, Some(0));
1419            assert_eq!(state.queue.len(), 3);
1420            assert!(!state.paused());
1421            assert_eq!(state.status, Status::Playing);
1422
1423            // lets go to the second song
1424            sender.send(AudioCommand::Queue(QueueCommand::SkipForward(1)));
1425            let state = get_state(sender.clone()).await;
1426            assert_eq!(state.queue_position, Some(1));
1427            assert_eq!(state.queue.len(), 3);
1428            assert!(!state.paused());
1429            assert_eq!(state.status, Status::Playing);
1430
1431            // lets shuffle the queue
1432            sender.send(AudioCommand::Queue(QueueCommand::Shuffle));
1433            // 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
1434            let state = get_state(sender.clone()).await;
1435            assert_eq!(state.queue_position, Some(0));
1436            assert_eq!(state.queue.len(), 3);
1437            assert!(!state.paused());
1438            assert_eq!(state.status, Status::Playing);
1439
1440            sender.send(AudioCommand::Exit);
1441        }
1442
1443        #[rstest]
1444        #[timeout(Duration::from_secs(5))] // if the test takes longer than this, the test can be considered a failure
1445        #[tokio::test]
1446        async fn test_volume_commands(#[from(audio_kernel_sender)] sender: Arc<AudioKernelSender>) {
1447            init();
1448
1449            let state = get_state(sender.clone()).await;
1450            assert!(
1451                f32::EPSILON > (state.volume - 1.0).abs(),
1452                "{} != 1.0",
1453                state.volume
1454            );
1455            assert!(!state.muted);
1456
1457            sender.send(AudioCommand::Volume(VolumeCommand::Up(0.1)));
1458            let state = get_state(sender.clone()).await;
1459            assert!(
1460                f32::EPSILON > (state.volume - 1.1).abs(),
1461                "{} != 1.1",
1462                state.volume
1463            );
1464            assert!(!state.muted);
1465
1466            sender.send(AudioCommand::Volume(VolumeCommand::Down(0.1)));
1467            let state = get_state(sender.clone()).await;
1468            assert!(
1469                f32::EPSILON > (state.volume - 1.0).abs(),
1470                "{} != 1.0",
1471                state.volume
1472            );
1473            assert!(!state.muted);
1474
1475            sender.send(AudioCommand::Volume(VolumeCommand::Set(0.5)));
1476            let state = get_state(sender.clone()).await;
1477            assert!(
1478                f32::EPSILON > (state.volume - 0.5).abs(),
1479                "{} != 0.5",
1480                state.volume
1481            );
1482            assert!(!state.muted);
1483
1484            sender.send(AudioCommand::Volume(VolumeCommand::Mute));
1485            let state = get_state(sender.clone()).await;
1486            assert!(
1487                f32::EPSILON > (state.volume - 0.5).abs(),
1488                "{} != 0.5",
1489                state.volume
1490            ); // although underlying volume is 0 (for the rodio player), the stored volume is still 0.5
1491            assert!(state.muted);
1492
1493            sender.send(AudioCommand::Volume(VolumeCommand::Unmute));
1494            let state = get_state(sender.clone()).await;
1495            assert!(
1496                f32::EPSILON > (state.volume - 0.5).abs(),
1497                "{} != 0.5",
1498                state.volume
1499            );
1500            assert!(!state.muted);
1501
1502            sender.send(AudioCommand::Volume(VolumeCommand::ToggleMute));
1503            let state = get_state(sender.clone()).await;
1504            assert!(
1505                f32::EPSILON > (state.volume - 0.5).abs(),
1506                "{} != 0.5",
1507                state.volume
1508            );
1509            assert!(state.muted);
1510
1511            sender.send(AudioCommand::Volume(VolumeCommand::ToggleMute));
1512            let state = get_state(sender.clone()).await;
1513            assert!(
1514                f32::EPSILON > (state.volume - 0.5).abs(),
1515                "{} != 0.5",
1516                state.volume
1517            );
1518            assert!(!state.muted);
1519
1520            sender.send(AudioCommand::Exit);
1521        }
1522
1523        #[rstest]
1524        #[timeout(Duration::from_secs(5))] // if the test takes longer than this, the test can be considered a failure
1525        #[tokio::test]
1526        async fn test_volume_out_of_bounds(
1527            #[from(audio_kernel_sender)] sender: Arc<AudioKernelSender>,
1528        ) {
1529            init();
1530
1531            // try moving volume above/below the maximum/minimum
1532            sender.send(AudioCommand::Volume(VolumeCommand::Up(MAX_VOLUME + 0.5)));
1533            let state = get_state(sender.clone()).await;
1534            assert!(
1535                f32::EPSILON > (state.volume - MAX_VOLUME).abs(),
1536                "{} != {}",
1537                state.volume,
1538                MAX_VOLUME
1539            );
1540            assert!(!state.muted);
1541            sender.send(AudioCommand::Volume(VolumeCommand::Down(
1542                MAX_VOLUME + 0.5 - MIN_VOLUME,
1543            )));
1544            let state = get_state(sender.clone()).await;
1545            assert!(
1546                f32::EPSILON > (state.volume - MIN_VOLUME).abs(),
1547                "{} != {}",
1548                state.volume,
1549                MIN_VOLUME
1550            );
1551            assert!(!state.muted);
1552
1553            // try setting volume above/below the maximum/minimum
1554            sender.send(AudioCommand::Volume(VolumeCommand::Set(MAX_VOLUME + 0.5)));
1555            let state = get_state(sender.clone()).await;
1556            assert!(
1557                f32::EPSILON > (state.volume - MAX_VOLUME).abs(),
1558                "{} != {}",
1559                state.volume,
1560                MAX_VOLUME
1561            );
1562            assert!(!state.muted);
1563            sender.send(AudioCommand::Volume(VolumeCommand::Set(MIN_VOLUME - 0.5)));
1564            let state = get_state(sender.clone()).await;
1565            assert!(
1566                f32::EPSILON > (state.volume - MIN_VOLUME).abs(),
1567                "{} != {}",
1568                state.volume,
1569                MIN_VOLUME
1570            );
1571            assert!(!state.muted);
1572
1573            sender.send(AudioCommand::Exit);
1574        }
1575
1576        #[rstest]
1577        #[timeout(Duration::from_secs(9))] // if the test takes longer than this, the test can be considered a failure
1578        #[tokio::test]
1579        async fn test_seek_commands(#[from(audio_kernel_sender)] sender: Arc<AudioKernelSender>) {
1580            init();
1581            let db = init_test_database().await.unwrap();
1582            let tempdir = tempfile::tempdir().unwrap();
1583
1584            let song = Song::try_load_into_db(
1585                &db,
1586                create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1587            )
1588            .await
1589            .unwrap();
1590
1591            // add a song to the queue
1592            // NOTE: this song has a duration of 10 seconds
1593            sender.send(AudioCommand::Queue(QueueCommand::AddToQueue(
1594                song.clone().brief().into(),
1595            )));
1596            sender.send(AudioCommand::Stop);
1597            sender.send(AudioCommand::Seek(
1598                SeekType::Absolute,
1599                Duration::from_secs(0),
1600            ));
1601            let state: StateAudio = get_state(sender.clone()).await;
1602            assert_eq!(state.queue_position, Some(0));
1603            assert_eq!(state.status, Status::Stopped);
1604            assert_eq!(
1605                state.runtime.unwrap().duration,
1606                Duration::from_secs(10) + Duration::from_millis(188)
1607            );
1608            assert_eq!(state.runtime.unwrap().seek_position, Duration::from_secs(0));
1609
1610            // skip ahead a bit
1611            sender.send(AudioCommand::Seek(
1612                SeekType::RelativeForwards,
1613                Duration::from_secs(2),
1614            ));
1615            let state = get_state(sender.clone()).await;
1616            assert_eq!(state.runtime.unwrap().seek_position, Duration::from_secs(2));
1617            assert_eq!(state.current_song, Some(song.clone().into()));
1618            assert_eq!(state.status, Status::Paused);
1619
1620            // skip back a bit
1621            sender.send(AudioCommand::Seek(
1622                SeekType::RelativeBackwards,
1623                Duration::from_secs(1),
1624            ));
1625            let state = get_state(sender.clone()).await;
1626            assert_eq!(state.runtime.unwrap().seek_position, Duration::from_secs(1));
1627            assert_eq!(state.current_song, Some(song.clone().into()));
1628            assert_eq!(state.status, Status::Paused);
1629
1630            // skip to 10 seconds
1631            sender.send(AudioCommand::Seek(
1632                SeekType::Absolute,
1633                Duration::from_secs(10),
1634            ));
1635            let state = get_state(sender.clone()).await;
1636            assert_eq!(
1637                state.runtime.unwrap().seek_position,
1638                Duration::from_secs(10)
1639            );
1640            assert_eq!(state.current_song, Some(song.clone().into()));
1641            assert_eq!(state.status, Status::Paused);
1642
1643            // now we unpause, wait a bit, and check that the song has ended
1644            sender.send(AudioCommand::Play);
1645            sender.send(AudioCommand::Seek(
1646                SeekType::RelativeForwards,
1647                Duration::from_secs(1),
1648            ));
1649            tokio::time::sleep(Duration::from_millis(500)).await;
1650            let state = get_state(sender.clone()).await;
1651            assert_eq!(state.queue_position, None);
1652            assert_eq!(state.status, Status::Stopped);
1653
1654            sender.send(AudioCommand::Exit);
1655        }
1656    }
1657}