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