mecomp_core/audio/
mod.rs

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