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