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