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