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