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::SongBrief;
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: SongBrief) {
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<SongBrief>) {
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<SongBrief> {
599        self.queue.current_song().cloned()
600    }
601
602    #[instrument(skip(self))]
603    fn get_next_song(&mut self) -> Option<SongBrief> {
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: &SongBrief) -> 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::{
829            db::schemas::song::Song,
830            test_utils::{arb_song_case, create_song_metadata, init_test_database},
831        };
832        use pretty_assertions::assert_eq;
833        use rstest::rstest;
834
835        use crate::test_utils::init;
836
837        use super::{super::*, audio_kernel, audio_kernel_sender, get_state, sound};
838
839        #[rstest]
840        fn test_audio_kernel_play_pause(
841            mut audio_kernel: AudioKernel,
842            sound: impl Source<Item = f32> + Send + 'static,
843        ) {
844            init();
845            audio_kernel.player.append(sound);
846            audio_kernel.play();
847            assert!(!audio_kernel.player.is_paused());
848            audio_kernel.pause();
849            assert!(audio_kernel.player.is_paused());
850        }
851
852        #[rstest]
853        fn test_audio_kernel_toggle_playback(
854            mut audio_kernel: AudioKernel,
855            sound: impl Source<Item = f32> + Send + 'static,
856        ) {
857            init();
858            audio_kernel.player.append(sound);
859            audio_kernel.play();
860            assert!(!audio_kernel.player.is_paused());
861            audio_kernel.toggle_playback();
862            assert!(audio_kernel.player.is_paused());
863            audio_kernel.toggle_playback();
864            assert!(!audio_kernel.player.is_paused());
865        }
866
867        #[rstest]
868        #[timeout(Duration::from_secs(10))] // if the test takes longer than this, the test can be considered a failure
869        #[tokio::test]
870        async fn test_play_pause_toggle_restart(
871            #[from(audio_kernel_sender)] sender: Arc<AudioKernelSender>,
872        ) {
873            init();
874            let db = init_test_database().await.unwrap();
875            let tempdir = tempfile::tempdir().unwrap();
876
877            let song = Song::try_load_into_db(
878                &db,
879                create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
880            )
881            .await
882            .unwrap();
883
884            sender.send(AudioCommand::Queue(QueueCommand::AddToQueue(
885                OneOrMany::One(song.into()),
886            )));
887
888            let state = get_state(sender.clone()).await;
889            assert_eq!(state.queue_position, Some(0));
890            assert_eq!(state.status, Status::Playing);
891
892            sender.send(AudioCommand::Pause);
893            let state = get_state(sender.clone()).await;
894            assert_eq!(state.status, Status::Paused);
895
896            sender.send(AudioCommand::Play);
897            let state = get_state(sender.clone()).await;
898            assert_eq!(state.status, Status::Playing);
899
900            sender.send(AudioCommand::RestartSong);
901            let state = get_state(sender.clone()).await;
902            assert_eq!(state.status, Status::Playing); // Note, unlike adding a song to the queue, RestartSong does not affect whether the player is paused
903
904            sender.send(AudioCommand::TogglePlayback);
905            let state = get_state(sender.clone()).await;
906            assert_eq!(state.status, Status::Paused);
907
908            sender.send(AudioCommand::RestartSong);
909            let state = get_state(sender.clone()).await;
910            assert_eq!(state.status, Status::Paused); // Note, unlike adding a song to the queue, RestartSong does not affect whether the player is paused
911
912            sender.send(AudioCommand::Exit);
913        }
914
915        #[rstest]
916        fn test_audio_kernel_stop(mut audio_kernel: AudioKernel) {
917            init();
918            audio_kernel.player.append(sound());
919            audio_kernel.play();
920            assert!(!audio_kernel.player.is_paused());
921            audio_kernel.stop();
922            assert!(audio_kernel.player.is_paused());
923            assert_eq!(audio_kernel.player.get_pos(), Duration::from_secs(0));
924            assert_eq!(audio_kernel.status, Status::Stopped);
925        }
926
927        #[rstest]
928        #[timeout(Duration::from_secs(10))] // if the test takes longer than this, the test can be considered a failure
929        #[tokio::test]
930        async fn test_audio_kernel_skip_forward(mut audio_kernel: AudioKernel) {
931            init();
932            let db = init_test_database().await.unwrap();
933            let tempdir = tempfile::tempdir().unwrap();
934
935            let state = audio_kernel.state();
936            assert_eq!(state.queue_position, None);
937            assert!(state.paused());
938            assert_eq!(state.status, Status::Stopped);
939
940            audio_kernel.queue_control(QueueCommand::AddToQueue(OneOrMany::Many(vec![
941                Song::try_load_into_db(
942                    &db,
943                    create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
944                )
945                .await
946                .unwrap()
947                .into(),
948                Song::try_load_into_db(
949                    &db,
950                    create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
951                )
952                .await
953                .unwrap()
954                .into(),
955                Song::try_load_into_db(
956                    &db,
957                    create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
958                )
959                .await
960                .unwrap()
961                .into(),
962            ])));
963
964            // songs were added to an empty queue, so the first song should start playing
965            let state = audio_kernel.state();
966            assert_eq!(state.queue_position, Some(0));
967            assert!(!state.paused());
968            assert_eq!(state.status, Status::Playing);
969
970            audio_kernel.queue_control(QueueCommand::SkipForward(1));
971
972            // the second song should start playing
973            let state = audio_kernel.state();
974            assert_eq!(state.queue_position, Some(1));
975            assert!(!state.paused());
976            assert_eq!(state.status, Status::Playing);
977
978            audio_kernel.queue_control(QueueCommand::SkipForward(1));
979
980            // the third song should start playing
981            let state = audio_kernel.state();
982            assert_eq!(state.queue_position, Some(2));
983            assert!(!state.paused());
984            assert_eq!(state.status, Status::Playing);
985
986            audio_kernel.queue_control(QueueCommand::SkipForward(1));
987
988            // we were at the end of the queue and tried to skip forward with repeatmode not being Continuous,
989            // so the player should be paused and the queue position should be None
990            let state = audio_kernel.state();
991            assert_eq!(state.queue_position, None);
992            assert!(state.paused());
993            assert_eq!(state.status, Status::Stopped);
994        }
995
996        #[rstest]
997        #[timeout(Duration::from_secs(6))] // if the test takes longer than this, the test can be considered a failure
998        #[tokio::test]
999        async fn test_audio_kernel_skip_forward_sender(
1000            #[from(audio_kernel_sender)] sender: Arc<AudioKernelSender>,
1001        ) {
1002            // set up tracing
1003            init();
1004
1005            let db = init_test_database().await.unwrap();
1006            let tempdir = tempfile::tempdir().unwrap();
1007
1008            let state = get_state(sender.clone()).await;
1009            assert_eq!(state.queue_position, None);
1010            assert!(state.paused());
1011            assert_eq!(state.status, Status::Stopped);
1012
1013            sender.send(AudioCommand::Queue(QueueCommand::AddToQueue(
1014                OneOrMany::Many(vec![
1015                    Song::try_load_into_db(
1016                        &db,
1017                        create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1018                    )
1019                    .await
1020                    .unwrap()
1021                    .into(),
1022                    Song::try_load_into_db(
1023                        &db,
1024                        create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1025                    )
1026                    .await
1027                    .unwrap()
1028                    .into(),
1029                    Song::try_load_into_db(
1030                        &db,
1031                        create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1032                    )
1033                    .await
1034                    .unwrap()
1035                    .into(),
1036                ]),
1037            )));
1038            // songs were added to an empty queue, so the first song should start playing
1039            let state = get_state(sender.clone()).await;
1040            assert_eq!(state.queue_position, Some(0));
1041            assert!(!state.paused());
1042            assert_eq!(state.status, Status::Playing);
1043
1044            sender.send(AudioCommand::Queue(QueueCommand::SkipForward(1)));
1045            // the second song should start playing
1046            let state = get_state(sender.clone()).await;
1047            assert_eq!(state.queue_position, Some(1));
1048            assert!(!state.paused());
1049            assert_eq!(state.status, Status::Playing);
1050
1051            sender.send(AudioCommand::Queue(QueueCommand::SkipForward(1)));
1052            // the third song should start playing
1053            let state = get_state(sender.clone()).await;
1054            assert_eq!(state.queue_position, Some(2));
1055            assert!(!state.paused());
1056            assert_eq!(state.status, Status::Playing);
1057
1058            sender.send(AudioCommand::Queue(QueueCommand::SkipForward(1)));
1059            // 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
1060            let state = get_state(sender.clone()).await;
1061            assert_eq!(state.queue_position, None);
1062            assert!(state.paused());
1063            assert_eq!(state.status, Status::Stopped);
1064
1065            sender.send(AudioCommand::Exit);
1066        }
1067
1068        #[rstest]
1069        #[timeout(Duration::from_secs(6))] // if the test takes longer than this, the test can be considered a failure
1070        #[tokio::test]
1071        async fn test_remove_range_from_queue(
1072            #[from(audio_kernel_sender)] sender: Arc<AudioKernelSender>,
1073        ) {
1074            init();
1075            let db = init_test_database().await.unwrap();
1076            let tempdir = tempfile::tempdir().unwrap();
1077            let song1 = Song::try_load_into_db(
1078                &db,
1079                create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1080            )
1081            .await
1082            .unwrap();
1083            let song2 = Song::try_load_into_db(
1084                &db,
1085                create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1086            )
1087            .await
1088            .unwrap();
1089
1090            // add songs to the queue, starts playback
1091            sender.send(AudioCommand::Queue(QueueCommand::AddToQueue(
1092                OneOrMany::Many(vec![song1.clone().into(), song2.clone().into()]),
1093            )));
1094            let state = get_state(sender.clone()).await;
1095            assert_eq!(state.queue_position, Some(0));
1096            assert!(!state.paused());
1097            assert_eq!(state.status, Status::Playing);
1098
1099            // pause the player
1100            sender.send(AudioCommand::Pause);
1101
1102            // remove the current song from the queue, the player should still be paused(), but also stopped
1103            sender.send(AudioCommand::Queue(QueueCommand::RemoveRange(0..1)));
1104            let state = get_state(sender.clone()).await;
1105            assert_eq!(state.queue_position, Some(0));
1106            assert!(state.paused());
1107            assert_eq!(state.status, Status::Stopped);
1108            assert_eq!(state.queue.len(), 1);
1109            assert_eq!(state.queue[0], song2.clone().into());
1110
1111            // unpause the player
1112            sender.send(AudioCommand::Play);
1113
1114            // add the song back to the queue, should be playing
1115            sender.send(AudioCommand::Queue(QueueCommand::AddToQueue(
1116                OneOrMany::One(song1.clone().into()),
1117            )));
1118            let state = get_state(sender.clone()).await;
1119            assert_eq!(state.queue_position, Some(0));
1120            assert!(!state.paused());
1121            assert_eq!(state.status, Status::Playing);
1122            assert_eq!(state.queue.len(), 2);
1123            assert_eq!(state.queue[0], song2.clone().into());
1124            assert_eq!(state.queue[1], song1.into());
1125
1126            // remove the next song from the queue, player should still be playing
1127            sender.send(AudioCommand::Queue(QueueCommand::RemoveRange(1..2)));
1128            let state = get_state(sender.clone()).await;
1129            assert_eq!(state.queue_position, Some(0));
1130            assert!(!state.paused());
1131            assert_eq!(state.status, Status::Playing);
1132            assert_eq!(state.queue.len(), 1);
1133            assert_eq!(state.queue[0], song2.into());
1134
1135            sender.send(AudioCommand::Exit);
1136        }
1137
1138        #[rstest]
1139        #[timeout(Duration::from_secs(10))] // if the test takes longer than this, the test can be considered a failure
1140        #[tokio::test]
1141        async fn test_audio_kernel_skip_backward(
1142            #[from(audio_kernel_sender)] sender: Arc<AudioKernelSender>,
1143        ) {
1144            init();
1145            let db = init_test_database().await.unwrap();
1146            let tempdir = tempfile::tempdir().unwrap();
1147
1148            let state = get_state(sender.clone()).await;
1149            assert_eq!(state.queue_position, None);
1150            assert!(state.paused());
1151            assert_eq!(state.status, Status::Stopped);
1152
1153            sender.send(AudioCommand::Queue(QueueCommand::AddToQueue(
1154                OneOrMany::Many(vec![
1155                    Song::try_load_into_db(
1156                        &db,
1157                        create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1158                    )
1159                    .await
1160                    .unwrap()
1161                    .into(),
1162                    Song::try_load_into_db(
1163                        &db,
1164                        create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1165                    )
1166                    .await
1167                    .unwrap()
1168                    .into(),
1169                    Song::try_load_into_db(
1170                        &db,
1171                        create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1172                    )
1173                    .await
1174                    .unwrap()
1175                    .into(),
1176                ]),
1177            )));
1178
1179            // songs were added to an empty queue, so the first song should start playing
1180            let state = get_state(sender.clone()).await;
1181            assert_eq!(state.queue_position, Some(0));
1182            assert!(!state.paused());
1183            assert_eq!(state.status, Status::Playing);
1184
1185            sender.send(AudioCommand::Queue(QueueCommand::SkipForward(2)));
1186
1187            // the third song should start playing
1188            let state = get_state(sender.clone()).await;
1189            assert_eq!(state.queue_position, Some(2));
1190            assert!(!state.paused());
1191            assert_eq!(state.status, Status::Playing);
1192
1193            sender.send(AudioCommand::Queue(QueueCommand::SkipBackward(1)));
1194
1195            // the second song should start playing
1196            let state = get_state(sender.clone()).await;
1197            assert_eq!(state.queue_position, Some(1));
1198            assert!(!state.paused());
1199            assert_eq!(state.status, Status::Playing);
1200
1201            sender.send(AudioCommand::Queue(QueueCommand::SkipBackward(1)));
1202
1203            // the first song should start playing
1204            let state = get_state(sender.clone()).await;
1205            assert_eq!(state.queue_position, Some(0));
1206            assert!(!state.paused());
1207
1208            sender.send(AudioCommand::Queue(QueueCommand::SkipBackward(1)));
1209
1210            // 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
1211            let state = get_state(sender.clone()).await;
1212            assert_eq!(state.queue_position, None);
1213            assert!(state.paused());
1214            assert_eq!(state.status, Status::Stopped);
1215
1216            sender.send(AudioCommand::Exit);
1217        }
1218
1219        #[rstest]
1220        #[timeout(Duration::from_secs(10))] // if the test takes longer than this, the test can be considered a failure
1221        #[tokio::test]
1222        async fn test_audio_kernel_set_position(
1223            #[from(audio_kernel_sender)] sender: Arc<AudioKernelSender>,
1224        ) {
1225            init();
1226            let db = init_test_database().await.unwrap();
1227            let tempdir = tempfile::tempdir().unwrap();
1228
1229            let state = get_state(sender.clone()).await;
1230            assert_eq!(state.queue_position, None);
1231            assert!(state.paused());
1232            assert_eq!(state.status, Status::Stopped);
1233
1234            sender.send(AudioCommand::Queue(QueueCommand::AddToQueue(
1235                OneOrMany::Many(vec![
1236                    Song::try_load_into_db(
1237                        &db,
1238                        create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1239                    )
1240                    .await
1241                    .unwrap()
1242                    .into(),
1243                    Song::try_load_into_db(
1244                        &db,
1245                        create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1246                    )
1247                    .await
1248                    .unwrap()
1249                    .into(),
1250                    Song::try_load_into_db(
1251                        &db,
1252                        create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1253                    )
1254                    .await
1255                    .unwrap()
1256                    .into(),
1257                ]),
1258            )));
1259            // songs were added to an empty queue, so the first song should start playing
1260            let state = get_state(sender.clone()).await;
1261            assert_eq!(state.queue_position, Some(0));
1262            assert!(!state.paused());
1263            assert_eq!(state.status, Status::Playing);
1264
1265            sender.send(AudioCommand::Queue(QueueCommand::SetPosition(1)));
1266            // the second song should start playing
1267            let state = get_state(sender.clone()).await;
1268            assert_eq!(state.queue_position, Some(1));
1269            assert!(!state.paused());
1270            assert_eq!(state.status, Status::Playing);
1271
1272            sender.send(AudioCommand::Queue(QueueCommand::SetPosition(2)));
1273            // the third song should start playing
1274            let state = get_state(sender.clone()).await;
1275            assert_eq!(state.queue_position, Some(2));
1276            assert!(!state.paused());
1277            assert_eq!(state.status, Status::Playing);
1278
1279            sender.send(AudioCommand::Queue(QueueCommand::SetPosition(0)));
1280            // the first song should start playing
1281            let state = get_state(sender.clone()).await;
1282            assert_eq!(state.queue_position, Some(0));
1283            assert!(!state.paused());
1284            assert_eq!(state.status, Status::Playing);
1285
1286            sender.send(AudioCommand::Queue(QueueCommand::SetPosition(3)));
1287            // we tried to set the position to an index that's out of pounds, so the player should be at the nearest valid index
1288            let state = get_state(sender.clone()).await;
1289            assert_eq!(state.queue_position, Some(2));
1290            assert!(!state.paused());
1291            assert_eq!(state.status, Status::Playing);
1292
1293            sender.send(AudioCommand::Exit);
1294        }
1295
1296        #[rstest]
1297        #[timeout(Duration::from_secs(6))] // if the test takes longer than this, the test can be considered a failure
1298        #[tokio::test]
1299        async fn test_audio_kernel_clear(
1300            #[from(audio_kernel_sender)] sender: Arc<AudioKernelSender>,
1301        ) {
1302            init();
1303            let db = init_test_database().await.unwrap();
1304            let tempdir = tempfile::tempdir().unwrap();
1305
1306            let state = get_state(sender.clone()).await;
1307            assert_eq!(state.queue_position, None);
1308            assert!(state.paused());
1309            assert_eq!(state.status, Status::Stopped);
1310
1311            sender.send(AudioCommand::Queue(QueueCommand::AddToQueue(
1312                OneOrMany::Many(vec![
1313                    Song::try_load_into_db(
1314                        &db,
1315                        create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1316                    )
1317                    .await
1318                    .unwrap()
1319                    .into(),
1320                    Song::try_load_into_db(
1321                        &db,
1322                        create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1323                    )
1324                    .await
1325                    .unwrap()
1326                    .into(),
1327                    Song::try_load_into_db(
1328                        &db,
1329                        create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1330                    )
1331                    .await
1332                    .unwrap()
1333                    .into(),
1334                ]),
1335            )));
1336            // songs were added to an empty queue, so the first song should start playing
1337            let state = get_state(sender.clone()).await;
1338            assert_eq!(state.queue_position, Some(0));
1339            assert_eq!(state.queue.len(), 3);
1340            assert!(!state.paused());
1341            assert_eq!(state.status, Status::Playing);
1342
1343            sender.send(AudioCommand::ClearPlayer);
1344            // we only cleared the audio player, so the queue should still have the songs
1345            let state = get_state(sender.clone()).await;
1346            assert_eq!(state.queue_position, Some(0));
1347            assert_eq!(state.queue.len(), 3);
1348            assert!(state.paused());
1349            assert_eq!(state.status, Status::Stopped);
1350
1351            sender.send(AudioCommand::Queue(QueueCommand::Clear));
1352            // we cleared the queue, so the player should be paused and the queue should be empty
1353            let state = get_state(sender.clone()).await;
1354            assert_eq!(state.queue_position, None);
1355            assert_eq!(state.queue.len(), 0);
1356            assert!(state.paused());
1357            assert_eq!(state.status, Status::Stopped);
1358
1359            sender.send(AudioCommand::Exit);
1360        }
1361
1362        #[rstest]
1363        #[timeout(Duration::from_secs(6))] // if the test takes longer than this, the test can be considered a failure
1364        #[tokio::test]
1365        async fn test_audio_kernel_shuffle(
1366            #[from(audio_kernel_sender)] sender: Arc<AudioKernelSender>,
1367        ) {
1368            init();
1369            let db = init_test_database().await.unwrap();
1370            let tempdir = tempfile::tempdir().unwrap();
1371
1372            let state = get_state(sender.clone()).await;
1373            assert_eq!(state.queue_position, None);
1374            assert!(state.paused());
1375            assert_eq!(state.status, Status::Stopped);
1376
1377            sender.send(AudioCommand::Queue(QueueCommand::AddToQueue(
1378                OneOrMany::Many(vec![
1379                    Song::try_load_into_db(
1380                        &db,
1381                        create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1382                    )
1383                    .await
1384                    .unwrap()
1385                    .into(),
1386                    Song::try_load_into_db(
1387                        &db,
1388                        create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1389                    )
1390                    .await
1391                    .unwrap()
1392                    .into(),
1393                    Song::try_load_into_db(
1394                        &db,
1395                        create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1396                    )
1397                    .await
1398                    .unwrap()
1399                    .into(),
1400                ]),
1401            )));
1402            // songs were added to an empty queue, so the first song should start playing
1403            let state = get_state(sender.clone()).await;
1404            assert_eq!(state.queue_position, Some(0));
1405            assert_eq!(state.queue.len(), 3);
1406            assert!(!state.paused());
1407            assert_eq!(state.status, Status::Playing);
1408
1409            // lets go to the second song
1410            sender.send(AudioCommand::Queue(QueueCommand::SkipForward(1)));
1411            let state = get_state(sender.clone()).await;
1412            assert_eq!(state.queue_position, Some(1));
1413            assert_eq!(state.queue.len(), 3);
1414            assert!(!state.paused());
1415            assert_eq!(state.status, Status::Playing);
1416
1417            // lets shuffle the queue
1418            sender.send(AudioCommand::Queue(QueueCommand::Shuffle));
1419            // 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
1420            let state = get_state(sender.clone()).await;
1421            assert_eq!(state.queue_position, Some(0));
1422            assert_eq!(state.queue.len(), 3);
1423            assert!(!state.paused());
1424            assert_eq!(state.status, Status::Playing);
1425
1426            sender.send(AudioCommand::Exit);
1427        }
1428
1429        #[rstest]
1430        #[timeout(Duration::from_secs(5))] // if the test takes longer than this, the test can be considered a failure
1431        #[tokio::test]
1432        async fn test_volume_commands(#[from(audio_kernel_sender)] sender: Arc<AudioKernelSender>) {
1433            init();
1434
1435            let state = get_state(sender.clone()).await;
1436            assert!(
1437                f32::EPSILON > (state.volume - 1.0).abs(),
1438                "{} != 1.0",
1439                state.volume
1440            );
1441            assert!(!state.muted);
1442
1443            sender.send(AudioCommand::Volume(VolumeCommand::Up(0.1)));
1444            let state = get_state(sender.clone()).await;
1445            assert!(
1446                f32::EPSILON > (state.volume - 1.1).abs(),
1447                "{} != 1.1",
1448                state.volume
1449            );
1450            assert!(!state.muted);
1451
1452            sender.send(AudioCommand::Volume(VolumeCommand::Down(0.1)));
1453            let state = get_state(sender.clone()).await;
1454            assert!(
1455                f32::EPSILON > (state.volume - 1.0).abs(),
1456                "{} != 1.0",
1457                state.volume
1458            );
1459            assert!(!state.muted);
1460
1461            sender.send(AudioCommand::Volume(VolumeCommand::Set(0.5)));
1462            let state = get_state(sender.clone()).await;
1463            assert!(
1464                f32::EPSILON > (state.volume - 0.5).abs(),
1465                "{} != 0.5",
1466                state.volume
1467            );
1468            assert!(!state.muted);
1469
1470            sender.send(AudioCommand::Volume(VolumeCommand::Mute));
1471            let state = get_state(sender.clone()).await;
1472            assert!(
1473                f32::EPSILON > (state.volume - 0.5).abs(),
1474                "{} != 0.5",
1475                state.volume
1476            ); // although underlying volume is 0 (for the rodio player), the stored volume is still 0.5
1477            assert!(state.muted);
1478
1479            sender.send(AudioCommand::Volume(VolumeCommand::Unmute));
1480            let state = get_state(sender.clone()).await;
1481            assert!(
1482                f32::EPSILON > (state.volume - 0.5).abs(),
1483                "{} != 0.5",
1484                state.volume
1485            );
1486            assert!(!state.muted);
1487
1488            sender.send(AudioCommand::Volume(VolumeCommand::ToggleMute));
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::ToggleMute));
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            );
1504            assert!(!state.muted);
1505
1506            sender.send(AudioCommand::Exit);
1507        }
1508
1509        #[rstest]
1510        #[timeout(Duration::from_secs(5))] // if the test takes longer than this, the test can be considered a failure
1511        #[tokio::test]
1512        async fn test_volume_out_of_bounds(
1513            #[from(audio_kernel_sender)] sender: Arc<AudioKernelSender>,
1514        ) {
1515            init();
1516
1517            // try moving volume above/below the maximum/minimum
1518            sender.send(AudioCommand::Volume(VolumeCommand::Up(MAX_VOLUME + 0.5)));
1519            let state = get_state(sender.clone()).await;
1520            assert!(
1521                f32::EPSILON > (state.volume - MAX_VOLUME).abs(),
1522                "{} != {}",
1523                state.volume,
1524                MAX_VOLUME
1525            );
1526            assert!(!state.muted);
1527            sender.send(AudioCommand::Volume(VolumeCommand::Down(
1528                MAX_VOLUME + 0.5 - MIN_VOLUME,
1529            )));
1530            let state = get_state(sender.clone()).await;
1531            assert!(
1532                f32::EPSILON > (state.volume - MIN_VOLUME).abs(),
1533                "{} != {}",
1534                state.volume,
1535                MIN_VOLUME
1536            );
1537            assert!(!state.muted);
1538
1539            // try setting volume above/below the maximum/minimum
1540            sender.send(AudioCommand::Volume(VolumeCommand::Set(MAX_VOLUME + 0.5)));
1541            let state = get_state(sender.clone()).await;
1542            assert!(
1543                f32::EPSILON > (state.volume - MAX_VOLUME).abs(),
1544                "{} != {}",
1545                state.volume,
1546                MAX_VOLUME
1547            );
1548            assert!(!state.muted);
1549            sender.send(AudioCommand::Volume(VolumeCommand::Set(MIN_VOLUME - 0.5)));
1550            let state = get_state(sender.clone()).await;
1551            assert!(
1552                f32::EPSILON > (state.volume - MIN_VOLUME).abs(),
1553                "{} != {}",
1554                state.volume,
1555                MIN_VOLUME
1556            );
1557            assert!(!state.muted);
1558
1559            sender.send(AudioCommand::Exit);
1560        }
1561
1562        #[rstest]
1563        #[timeout(Duration::from_secs(9))] // if the test takes longer than this, the test can be considered a failure
1564        #[tokio::test]
1565        async fn test_seek_commands(#[from(audio_kernel_sender)] sender: Arc<AudioKernelSender>) {
1566            init();
1567            let db = init_test_database().await.unwrap();
1568            let tempdir = tempfile::tempdir().unwrap();
1569
1570            let song = Song::try_load_into_db(
1571                &db,
1572                create_song_metadata(&tempdir, arb_song_case()()).unwrap(),
1573            )
1574            .await
1575            .unwrap();
1576
1577            // add a song to the queue
1578            // NOTE: this song has a duration of 10 seconds
1579            sender.send(AudioCommand::Queue(QueueCommand::AddToQueue(
1580                OneOrMany::One(song.clone().into()),
1581            )));
1582            sender.send(AudioCommand::Stop);
1583            sender.send(AudioCommand::Seek(
1584                SeekType::Absolute,
1585                Duration::from_secs(0),
1586            ));
1587            let state: StateAudio = get_state(sender.clone()).await;
1588            assert_eq!(state.queue_position, Some(0));
1589            assert_eq!(state.status, Status::Stopped);
1590            assert_eq!(
1591                state.runtime.unwrap().duration,
1592                Duration::from_secs(10) + Duration::from_millis(188)
1593            );
1594            assert_eq!(state.runtime.unwrap().seek_position, Duration::from_secs(0));
1595
1596            // skip ahead a bit
1597            sender.send(AudioCommand::Seek(
1598                SeekType::RelativeForwards,
1599                Duration::from_secs(2),
1600            ));
1601            let state = get_state(sender.clone()).await;
1602            assert_eq!(state.runtime.unwrap().seek_position, Duration::from_secs(2));
1603            assert_eq!(state.current_song, Some(song.clone().into()));
1604            assert_eq!(state.status, Status::Paused);
1605
1606            // skip back a bit
1607            sender.send(AudioCommand::Seek(
1608                SeekType::RelativeBackwards,
1609                Duration::from_secs(1),
1610            ));
1611            let state = get_state(sender.clone()).await;
1612            assert_eq!(state.runtime.unwrap().seek_position, Duration::from_secs(1));
1613            assert_eq!(state.current_song, Some(song.clone().into()));
1614            assert_eq!(state.status, Status::Paused);
1615
1616            // skip to 10 seconds
1617            sender.send(AudioCommand::Seek(
1618                SeekType::Absolute,
1619                Duration::from_secs(10),
1620            ));
1621            let state = get_state(sender.clone()).await;
1622            assert_eq!(
1623                state.runtime.unwrap().seek_position,
1624                Duration::from_secs(10)
1625            );
1626            assert_eq!(state.current_song, Some(song.clone().into()));
1627            assert_eq!(state.status, Status::Paused);
1628
1629            // now we unpause, wait a bit, and check that the song has ended
1630            sender.send(AudioCommand::Play);
1631            tokio::time::sleep(Duration::from_millis(500)).await;
1632            let state = get_state(sender.clone()).await;
1633            assert_eq!(state.queue_position, None);
1634            assert_eq!(state.status, Status::Stopped);
1635
1636            sender.send(AudioCommand::Exit);
1637        }
1638    }
1639}