Skip to main content

chilen_backend/playback/
mod.rs

1#[cfg(feature = "mpris")]
2mod mpris;
3pub(crate) mod state;
4#[cfg(test)]
5mod tests;
6
7use std::{
8    path::PathBuf,
9    sync::{Arc, LazyLock, RwLock},
10    thread,
11    time::Duration,
12};
13
14use log::{debug, error, info, trace, warn};
15use rodio::Player;
16use serde::{Deserialize, Serialize};
17use walkdir::WalkDir;
18
19use crate::{
20    Error,
21    music_lib::{Track, tracks_from_m3u8, tracks_from_paths},
22    playback::state::{
23        PLAYER_STATE, background_save_state, restore_state_from_cache, unwrap_state_mut,
24        unwrap_state_ref,
25    },
26};
27
28pub use state::{Event, PlayerState, Queue, QueueSource};
29
30/// Playback state of the player.
31#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
32pub enum PlaybackState {
33    /// The player is playing.
34    Playing,
35    /// The player is paused.
36    Paused,
37    /// The player is stopped.
38    ///
39    /// Playing will play the current track from the beginning.
40    #[default]
41    Stopped,
42}
43
44impl std::fmt::Display for PlaybackState {
45    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46        match self {
47            Self::Playing => write!(f, "Playing"),
48            Self::Paused => write!(f, "Paused"),
49            Self::Stopped => write!(f, "Stopped"),
50        }
51    }
52}
53
54/// Specifies how the player loops playback.
55#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
56pub enum LoopState {
57    /// The playback will stop when there are no more tracks to play.
58    #[default]
59    Off,
60    /// The playback will loop through the entire queue.
61    Playlist,
62    /// The current track will start again from the beginning once it has finished playing.
63    Track,
64}
65
66impl LoopState {
67    pub fn cycle(&self) -> Self {
68        match self {
69            Self::Off => Self::Playlist,
70            Self::Playlist => Self::Track,
71            Self::Track => Self::Off,
72        }
73    }
74}
75/// Shuffle state of the player.
76#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
77pub enum ShuffleState {
78    /// Play tracks in their original order.
79    ///
80    /// For example tracks from a playlist will be played in the order they appear in the playlist.
81    #[default]
82    Off,
83    /// Shuffle the tracks in the queue.
84    On,
85}
86
87impl ShuffleState {
88    pub fn enabled(&self) -> bool {
89        self == &Self::On
90    }
91
92    pub fn toggle(self) -> Self {
93        match self {
94            Self::Off => Self::On,
95            Self::On => Self::Off,
96        }
97    }
98}
99
100impl std::fmt::Display for ShuffleState {
101    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102        match self {
103            Self::Off => write!(f, "Off"),
104            Self::On => write!(f, "On"),
105        }
106    }
107}
108
109impl std::fmt::Display for LoopState {
110    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111        match self {
112            Self::Off => write!(f, "Off"),
113            Self::Track => write!(f, "Track"),
114            Self::Playlist => write!(f, "Playlist"),
115        }
116    }
117}
118
119/// Signed duration type used for seeking.
120///
121/// This is just a bare bones type that shouldn't be used as a [`Duration`] replacement in most
122/// cases.
123#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
124pub enum SignedDuration {
125    Positive(Duration),
126    Negative(Duration),
127}
128
129impl SignedDuration {
130    pub fn from_secs<T: Into<i64> + Copy>(secs: T) -> SignedDuration {
131        if secs.into() < 0 {
132            SignedDuration::Negative(Duration::from_secs(secs.into().abs().try_into().unwrap()))
133        } else {
134            SignedDuration::Positive(Duration::from_secs(secs.into().abs().try_into().unwrap()))
135        }
136    }
137}
138
139/// Player volume.
140///
141/// Values passed to this struct will be clamped between `0.0` (no sound at all) and `1.0` (regular
142/// volume).
143#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
144pub struct PlayerVolume {
145    volume: f64,
146}
147
148impl Default for PlayerVolume {
149    fn default() -> Self {
150        Self { volume: 1.0 }
151    }
152}
153
154impl std::fmt::Display for PlayerVolume {
155    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
156        write!(f, "Player volume: {}", self.volume)
157    }
158}
159
160impl PlayerVolume {
161    /// Create a new [`PlayerVolume`] struct with the specified volume.
162    ///
163    /// The passed `volume` parameter will be clamped between `0.0` and `1.0`.
164    pub fn new(volume: f64) -> Self {
165        Self {
166            volume: volume.clamp(0.0, 1.0),
167        }
168    }
169
170    /// Set the volume.
171    ///
172    /// The passed `volume` parameter will be clamped between 0.0 and 1.0.
173    pub fn set(&mut self, volume: f64) {
174        self.volume = volume.clamp(0.0, 1.0);
175    }
176
177    /// Get the wrapped value.
178    pub fn get(&self) -> f64 {
179        self.volume
180    }
181}
182
183#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
184pub struct Config {
185    /// Minimum allowed player position where skipping to the previous track doesn't reset player
186    /// position instead.
187    ///
188    /// Setting this to [`None`] disables the behavior completely.
189    pub skip_previous_threshold: Option<Duration>,
190}
191
192pub(crate) static PLAYER_HANDLE: LazyLock<Arc<RwLock<Option<rodio::Player>>>> =
193    LazyLock::new(|| Arc::new(RwLock::new(None)));
194
195pub(crate) fn unwrap_player(maybe_player: Option<&rodio::Player>) -> Result<&rodio::Player, Error> {
196    match maybe_player {
197        Some(player) => Ok(player),
198        None => Err(Error::StateNotInitialized),
199    }
200}
201
202/// Play the current track or a track at a specified index in the queue.
203pub fn play(index: Option<usize>) -> Result<(), Error> {
204    let player_guard = PLAYER_HANDLE.read().unwrap();
205    let player = unwrap_player(player_guard.as_ref())?;
206    let mut state_guard = PLAYER_STATE.write().unwrap();
207    let state = unwrap_state_mut(state_guard.as_mut())?;
208    if let Some(id) = index {
209        trace!("Playing a track at index {id}");
210        let track = match state.play_track(id) {
211            Some(track) => track,
212            None => {
213                error!("No track at index {id}");
214                return Err(Error::NoTrackAtIndex(id));
215            }
216        };
217        match track.open_source() {
218            Ok(source) => {
219                player.clear();
220                player.append(source);
221                player.play();
222                state.set_playback_state(PlaybackState::Playing);
223            }
224            Err(e) => {
225                error!("Could not open audio source: {e}");
226                player.stop();
227                state.set_playback_state(PlaybackState::Stopped);
228                return Err(Error::SourceError);
229            }
230        }
231        background_save_state(state.clone());
232        Ok(())
233    } else {
234        trace!("Playing the current media");
235        if !player.is_paused() && !player.empty() {
236            Err(Error::PlayerPlaying)
237        } else if player.empty() {
238            if let Some(track) = state.current() {
239                match track.open_source() {
240                    Ok(source) => {
241                        player.append(source);
242                        state.set_playback_state(PlaybackState::Playing);
243                    }
244                    Err(e) => {
245                        error!("Could not open audio source: {e}");
246                        player.stop();
247                        state.set_playback_state(PlaybackState::Stopped);
248                        return Err(Error::SourceError);
249                    }
250                }
251                background_save_state(state.clone());
252                Ok(())
253            } else {
254                Err(Error::QueueEmpty)
255            }
256        } else {
257            player.play();
258            state.set_playback_state(PlaybackState::Playing);
259            Ok(())
260        }
261    }
262}
263
264pub fn pause() -> Result<(), Error> {
265    trace!("Pausing the current media");
266    let player_guard = PLAYER_HANDLE.read().unwrap();
267    let player = player_guard.as_ref().unwrap();
268    if player.is_paused() {
269        Err(Error::PlayerPaused)
270    } else {
271        // FIX: Audio popping by adding a 10ms fade out effect here
272        // NOTE: This is a missing feature in Rodio, see https://github.com/RustAudio/rodio/issues/889
273        player.pause();
274        let mut state_guard = PLAYER_STATE.write().unwrap();
275        let state = unwrap_state_mut(state_guard.as_mut())?;
276        state.set_playback_state(PlaybackState::Paused);
277        Ok(())
278    }
279}
280
281pub fn stop() -> Result<(), Error> {
282    trace!("Stopping the playback");
283    let player_guard = PLAYER_HANDLE.read().unwrap();
284    let player = player_guard.as_ref().unwrap();
285    if player.empty() {
286        Err(Error::PlayerStopped)
287    } else {
288        player.stop();
289        let mut state_guard = PLAYER_STATE.write().unwrap();
290        let state = unwrap_state_mut(state_guard.as_mut())?;
291        state.set_playback_state(PlaybackState::Stopped);
292        Ok(())
293    }
294}
295
296pub fn toggle_playing() -> Result<(), Error> {
297    trace!("Toggling playback state");
298    let state_guard = PLAYER_STATE.read().unwrap();
299    let state = unwrap_state_ref(state_guard.as_ref())?;
300    let playback_state = state.playback_state;
301    match playback_state {
302        PlaybackState::Paused => {
303            drop(state_guard);
304            play(None)
305        }
306        PlaybackState::Playing => {
307            drop(state_guard);
308            pause()
309        }
310        PlaybackState::Stopped => {
311            if state.current().is_some() {
312                let pos = state.position;
313                drop(state_guard);
314                play(Some(pos))
315            } else {
316                Err(Error::QueueEmpty)
317            }
318        }
319    }
320}
321
322#[cfg(feature = "mpris")]
323pub(crate) fn get_playback_state() -> Result<PlaybackState, Error> {
324    let state_guard = PLAYER_STATE.read().unwrap();
325    let state = unwrap_state_ref(state_guard.as_ref())?;
326    Ok(state.playback_state)
327}
328
329#[cfg(feature = "mpris")]
330pub(crate) fn get_current_meta() -> Result<Option<mpris_server::Metadata>, Error> {
331    let state_guard = PLAYER_STATE.read().unwrap();
332    let state = unwrap_state_ref(state_guard.as_ref())?;
333    match state.current() {
334        Some(track) => Ok(Some(track.get_meta(state.position))),
335        None => Ok(None),
336    }
337}
338
339#[cfg(feature = "mpris")]
340pub(crate) fn get_loop_state() -> Result<LoopState, Error> {
341    let state_guard = PLAYER_STATE.read().unwrap();
342    let state = unwrap_state_ref(state_guard.as_ref())?;
343    Ok(state.loop_state)
344}
345
346#[cfg(feature = "mpris")]
347pub(crate) fn get_shuffle_state() -> Result<ShuffleState, Error> {
348    let state_guard = PLAYER_STATE.read().unwrap();
349    let state = unwrap_state_ref(state_guard.as_ref())?;
350    Ok(state.shuffle_state)
351}
352
353#[cfg(feature = "mpris")]
354pub(crate) fn get_player_position() -> Result<Duration, Error> {
355    let state_guard = PLAYER_STATE.read().unwrap();
356    let state = unwrap_state_ref(state_guard.as_ref())?;
357    Ok(state.player_position)
358}
359
360#[cfg(feature = "mpris")]
361pub(crate) fn get_player_volume() -> Result<PlayerVolume, Error> {
362    let player_guard = PLAYER_HANDLE.read().unwrap();
363    let player = unwrap_player(player_guard.as_ref())?;
364    Ok(PlayerVolume::new(player.volume()))
365}
366
367#[cfg(feature = "mpris")]
368pub(crate) fn can_play() -> Result<bool, Error> {
369    let state_guard = PLAYER_STATE.read().unwrap();
370    let state = unwrap_state_ref(state_guard.as_ref())?;
371    Ok(state.can_play())
372}
373
374#[cfg(feature = "mpris")]
375pub(crate) fn can_pause() -> Result<bool, Error> {
376    let state_guard = PLAYER_STATE.read().unwrap();
377    let state = unwrap_state_ref(state_guard.as_ref())?;
378    Ok(state.playback_state == PlaybackState::Playing)
379}
380
381#[cfg(feature = "mpris")]
382pub(crate) fn can_go_next() -> Result<bool, Error> {
383    let state_guard = PLAYER_STATE.read().unwrap();
384    let state = unwrap_state_ref(state_guard.as_ref())?;
385    Ok(state.can_go_next())
386}
387
388#[cfg(feature = "mpris")]
389pub(crate) fn can_go_previous() -> Result<bool, Error> {
390    let state_guard = PLAYER_STATE.read().unwrap();
391    let state = unwrap_state_ref(state_guard.as_ref())?;
392    Ok(state.can_go_previous())
393}
394
395// TEST: Add tests for this
396/// Opens a URI that can either be a track, a directory with tracks, or an M3U8 playlist file.
397pub fn open_uri(uri: PathBuf) -> Result<(), Error> {
398    trace!("Opening URI {uri:?}");
399    match uri.try_exists() {
400        Ok(exists) => {
401            if !exists {
402                return Err(Error::PathDoesNotExist);
403            }
404        }
405        Err(e) => {
406            error!("Could not check if the URI {uri:?} exists: {e}");
407            return Err(Error::PathInaccessible(uri));
408        }
409    }
410    if uri.is_dir() {
411        trace!("The provided URI {uri:?} is a directory, indexing it");
412
413        let mut files = Vec::new();
414        let label = uri.to_string_lossy().to_string();
415        for result in WalkDir::new(uri).into_iter() {
416            let entry = match result {
417                Ok(entry) => entry,
418                Err(e) => {
419                    warn!("Error while trying to access the file: {e}");
420                    continue;
421                }
422            };
423
424            match entry.metadata() {
425                Ok(meta) => {
426                    if meta.is_file() {
427                        files.push(PathBuf::from(entry.path()));
428                    }
429                }
430                Err(e) => {
431                    warn!("Could not get path metadata: {e}");
432                    continue;
433                }
434            };
435        }
436        let tracks = tracks_from_paths(&files, true)?;
437        if tracks.is_empty() {
438            Err(Error::DirectoryWithNoTracks)
439        } else {
440            set_queue(Queue::Custom { label, tracks })
441        }
442    } else {
443        trace!(
444            "The provided URI {uri:?} is a file, so we're either opening an audio file or a playlist"
445        );
446        if let Ok(paths) = tracks_from_m3u8(&uri) {
447            trace!("Loaded M3U8 playlist {uri:?}");
448            let tracks = tracks_from_paths(&paths, true)?;
449            return set_queue(Queue::Custom {
450                label: uri.to_string_lossy().to_string(),
451                tracks,
452            });
453        } else {
454            trace!("The file does not appear to be an M3U8 playlist");
455        }
456        let label = uri.to_string_lossy().to_string();
457        let track = tracks_from_paths(&[uri], false)?;
458        set_queue(Queue::Custom {
459            label,
460            tracks: track,
461        })
462    }
463}
464
465/// Set a new queue and play a track at a specific index in that queue.
466///
467/// If the index is unspecified, then it will be reset to 0. Additionally, if shuffle is enabled,
468/// the queue will be shuffled fully, meaning that setting a new queue without specifying the index
469/// while shuffle is enabled will start the queue from a random track.
470///
471/// If the index is specified, then playback will start from the track at index in the queue, even
472/// if shuffle is enabled.
473pub fn play_new_queue(queue: Queue, index: Option<usize>) -> Result<(), Error> {
474    let mut state_guard = PLAYER_STATE.write().unwrap();
475    let state = unwrap_state_mut(state_guard.as_mut())?;
476    state.play_new_queue(queue, index);
477
478    let player_guard = PLAYER_HANDLE.read().unwrap();
479    let player = unwrap_player(player_guard.as_ref())?;
480    player.stop();
481    if let Some(track) = state.current() {
482        match track.open_source() {
483            Ok(source) => {
484                player.append(source);
485                player.play();
486                state.set_playback_state(PlaybackState::Playing);
487            }
488            Err(e) => {
489                error!("Could not open audio source: {e}");
490                state.set_playback_state(PlaybackState::Stopped);
491                player.stop();
492                return Err(Error::SourceError);
493            }
494        }
495    }
496    background_save_state(state.clone());
497    Ok(())
498}
499
500/// Set the queue to a list of track.
501///
502/// Note that if shuffle is enabled, the queue will be shuffled immediately after setting it. For
503/// that reason, please use the `[play_new_queue]` function for setting the queue and then playing a
504/// track at a specific index in that queue.
505pub fn set_queue(queue: Queue) -> Result<(), Error> {
506    trace!("Setting a new queue");
507    let mut state_guard = PLAYER_STATE.write().unwrap();
508    let state = unwrap_state_mut(state_guard.as_mut())?;
509    state.set_queue(queue);
510    let player_guard = PLAYER_HANDLE.read().unwrap();
511    let player = unwrap_player(player_guard.as_ref())?;
512    player.stop();
513    if let Some(track) = state.current() {
514        match track.open_source() {
515            Ok(source) => {
516                player.append(source);
517                player.pause();
518            }
519            Err(e) => {
520                error!("Could not open audio source: {e}");
521                player.stop();
522                state.set_playback_state(PlaybackState::Stopped);
523                return Err(Error::SourceError);
524            }
525        }
526    }
527    background_save_state(state.clone());
528    Ok(())
529}
530
531pub fn append_to_queue(queue: &mut Vec<Arc<Track>>) -> Result<(), Error> {
532    trace!("Appending tracks to queue");
533    let mut state_guard = PLAYER_STATE.write().unwrap();
534    let state = unwrap_state_mut(state_guard.as_mut())?;
535    state.append_tracks(queue);
536    background_save_state(state.clone());
537    Ok(())
538}
539
540pub fn remove_from_queue(indices: Vec<usize>) -> Result<(), Error> {
541    trace!("Removing {} tracks from queue", indices.len());
542    let mut state_guard = PLAYER_STATE.write().unwrap();
543    let state = unwrap_state_mut(state_guard.as_mut())?;
544    let previous = state.current().cloned();
545    state.remove_tracks(indices)?;
546    let player_guard = PLAYER_HANDLE.read().unwrap();
547    let player = unwrap_player(player_guard.as_ref())?;
548    if previous.as_ref() != state.current() {
549        if let Some(track) = state.current() {
550            match track.open_source() {
551                Ok(source) => {
552                    player.stop();
553                    player.append(source);
554                    state.set_playback_state(PlaybackState::Playing);
555                }
556                Err(e) => {
557                    error!("Could not open audio source: {e}");
558                    player.stop();
559                    state.set_playback_state(PlaybackState::Stopped);
560                    return Err(Error::SourceError);
561                }
562            }
563        } else {
564            player.stop();
565            state.set_playback_state(PlaybackState::Stopped);
566        }
567    }
568    background_save_state(state.clone());
569    Ok(())
570}
571
572pub fn skip_next() -> Result<(), Error> {
573    trace!("Skipping to the next track");
574    let mut state_guard = PLAYER_STATE.write().unwrap();
575    let state = unwrap_state_mut(state_guard.as_mut())?;
576    if let Some(track) = state.next_track() {
577        let player_guard = PLAYER_HANDLE.read().unwrap();
578        let player = unwrap_player(player_guard.as_ref())?;
579        match track.open_source() {
580            Ok(source) => {
581                state.set_player_position(Duration::default());
582                state.set_playback_state(PlaybackState::Playing);
583                player.clear();
584                player.append(source);
585                player.play();
586            }
587            Err(e) => {
588                error!("Could not open audio source: {e}");
589                state.set_playback_state(PlaybackState::Stopped);
590                player.stop();
591                return Err(Error::SourceError);
592            }
593        }
594        background_save_state(state.clone());
595        Ok(())
596    } else if state.queue_empty() {
597        info!("Cannot skip to the next track, queue is empty");
598        Err(Error::QueueEmpty)
599    } else {
600        info!("Cannot skip to the next track");
601        Err(Error::CannotGoNext)
602    }
603}
604
605pub fn skip_previous() -> Result<(), Error> {
606    trace!("Skipping to the previous track");
607    let mut state_guard = PLAYER_STATE.write().unwrap();
608    let state = unwrap_state_mut(state_guard.as_mut())?;
609
610    let guard = crate::CONFIG.read().unwrap();
611    let config = guard.as_ref().unwrap();
612    if let Some(threshold) = config.playback.skip_previous_threshold
613        && state.player_position > threshold
614    {
615        trace!("Player position above skip threshold, resetting player position instead");
616        drop(state_guard);
617        return set_player_position(Duration::default());
618    }
619
620    if let Some(track) = state.previous_track() {
621        let player_guard = PLAYER_HANDLE.read().unwrap();
622        let player = unwrap_player(player_guard.as_ref())?;
623        match track.open_source() {
624            Ok(source) => {
625                state.set_player_position(Duration::default());
626                state.set_playback_state(PlaybackState::Playing);
627                player.clear();
628                player.append(source);
629                player.play();
630            }
631            Err(e) => {
632                error!("Could not open audio source: {e}");
633                player.stop();
634                state.set_playback_state(PlaybackState::Stopped);
635                return Err(Error::SourceError);
636            }
637        };
638        background_save_state(state.clone());
639        Ok(())
640    } else if state.queue_empty() {
641        info!("Cannot go to the previous track, queue is empty");
642        Err(Error::QueueEmpty)
643    } else {
644        info!("Cannot go to the previous track");
645        Err(Error::CannotGoPrevious)
646    }
647}
648
649pub fn set_loop_state(loop_state: LoopState) -> Result<(), Error> {
650    trace!("Setting loop state to {loop_state:?}");
651    let mut state_guard = PLAYER_STATE.write().unwrap();
652    let state = unwrap_state_mut(state_guard.as_mut())?;
653    state.set_loop_state(loop_state);
654    background_save_state(state.clone());
655    Ok(())
656}
657
658pub fn cycle_loop_state() -> Result<(), Error> {
659    trace!("Cycling loop state");
660    let mut state_guard = PLAYER_STATE.write().unwrap();
661    let state = unwrap_state_mut(state_guard.as_mut())?;
662    state.set_loop_state(state.loop_state.cycle());
663    background_save_state(state.clone());
664    Ok(())
665}
666
667pub fn set_shuffle_state(shuffle_state: ShuffleState) -> Result<(), Error> {
668    trace!("Setting shuffle state to {shuffle_state:?}");
669    let mut state_guard = PLAYER_STATE.write().unwrap();
670    let state = unwrap_state_mut(state_guard.as_mut())?;
671    state.set_shuffle_state(shuffle_state);
672    background_save_state(state.clone());
673    Ok(())
674}
675
676pub fn toggle_shuffle_state() -> Result<(), Error> {
677    trace!("Toggling shuffle state");
678    let mut state_guard = PLAYER_STATE.write().unwrap();
679    let state = unwrap_state_mut(state_guard.as_mut())?;
680    let shuffle_state = state.shuffle_state.toggle();
681    trace!("Setting shuffle state to {shuffle_state:?}");
682    state.set_shuffle_state(shuffle_state);
683    background_save_state(state.clone());
684    Ok(())
685}
686
687pub fn set_player_position(position: Duration) -> Result<(), Error> {
688    trace!("Setting player position to {:?}", position.as_secs());
689    let player_guard = PLAYER_HANDLE.read().unwrap();
690    let mut state_guard = PLAYER_STATE.write().unwrap();
691    let state = unwrap_state_mut(state_guard.as_mut())?;
692    if state.playback_state == PlaybackState::Stopped {
693        return Err(Error::PlayerStopped);
694    }
695    let player = match player_guard.as_ref() {
696        Some(player) => player,
697        None => {
698            warn!("Cannot set player position, player is not connected");
699            return Err(Error::PlayerNotConnected);
700        }
701    };
702    if player.empty() {
703        Err(Error::QueueEmpty)
704    } else if let Some(track) = state.current()
705        && position > track.duration
706    {
707        drop(state_guard);
708        skip_next()
709    } else if let Err(e) = player.try_seek(position) {
710        error!("Could not set player position: {e}");
711        // Prevent MPRIS from showing incorrect data if seek is not supported
712        #[cfg(feature = "mpris")]
713        mpris::set_position(state.player_position);
714        Err(Error::SeekNotSupported)
715    } else {
716        state.set_player_position(position);
717        Ok(())
718    }
719}
720
721/// When seeking, if the resulting position would result in a value less than this threshold, set
722/// the position to 0s.
723///
724/// Similarly, if the difference between the duration of the current track and the resulting
725/// position would be smaller than this value, skip to the next track.
726static SEEK_ROUND_THRESHOLD: LazyLock<Duration> = LazyLock::new(|| Duration::from_secs(1));
727
728// TODO: Add mime type(s) for M3U8 files (if it makes sense)
729/// Mime types supported by the music player.
730///
731/// Chilen uses the `rodio` crate for audio playback, which itself uses
732/// [Symphonia](https://github.com/pdeljanov/Symphonia) for decoding audio files. Chilen supports
733/// all audio formats Symphonia does.
734#[cfg(feature = "mpris")]
735static SUPPORTED_MIME_TYPES: LazyLock<Vec<String>> = LazyLock::new(|| {
736    let arr = [
737        "audio/aac",
738        "audio/32kadpcm",
739        "audio/aiff",
740        "audio/x-aiff",
741        "audio/x-caf",
742        "audio/flac",
743        "audio/matroska",
744        "audio/mpeg",
745        "audio/mp4",
746        "audio/MPA",
747        "audio/mpa-robust",
748        "audio/ogg",
749        "audio/vorbis",
750        "audio/vorbis-config",
751        "audio/vnd.wave",
752        "audio/wav",
753        "audio/wave",
754        "audio/x-wav",
755        "audio/webm",
756    ];
757    arr.into_iter().map(|t| t.to_string()).collect()
758});
759
760pub fn seek(delta: SignedDuration) -> Result<(), Error> {
761    let player_guard = PLAYER_HANDLE.read().unwrap();
762    let player = match player_guard.as_ref() {
763        Some(player) => player,
764        None => {
765            warn!("Cannot seek, the player is not connected");
766            return Err(Error::PlayerNotConnected);
767        }
768    };
769    let mut state_guard = PLAYER_STATE.write().unwrap();
770    let state = unwrap_state_mut(state_guard.as_mut())?;
771    let pos = match delta {
772        SignedDuration::Positive(positive_dur) => {
773            if positive_dur == Duration::default() {
774                info!("Refusing to seek by 0s");
775                return Err(Error::InvalidDuration);
776            }
777            let sum = match positive_dur.checked_add(player.get_pos()) {
778                Some(sum) => sum,
779                None => {
780                    error!("Overflow detected while seeking, aborting");
781                    return Err(Error::DurationOverflow);
782                }
783            };
784            if let Some(track) = state.current()
785                && sum > track.duration - *SEEK_ROUND_THRESHOLD
786            {
787                drop(state_guard);
788                return skip_next();
789            } else {
790                trace!("Seeking player by {positive_dur:?}");
791                sum
792            }
793        }
794        SignedDuration::Negative(negative_dur) => {
795            if negative_dur == Duration::default() {
796                info!("Refusing to seek by 0s");
797                return Err(Error::InvalidDuration);
798            }
799            trace!("Seeking player by -{negative_dur:?}");
800            let sub = match player.get_pos().checked_sub(negative_dur) {
801                Some(sub) => sub,
802                None => {
803                    error!("Overflow detected while seeking");
804                    return Err(Error::DurationOverflow);
805                }
806            };
807            if sub < *SEEK_ROUND_THRESHOLD {
808                Duration::default()
809            } else {
810                sub
811            }
812        }
813    };
814    if let Err(e) = player.try_seek(pos) {
815        error!("Could not set player position: {e}");
816        Err(Error::SeekNotSupported)
817    } else {
818        state.set_player_position(pos);
819        Ok(())
820    }
821}
822
823pub fn set_player_volume(volume: PlayerVolume) -> Result<(), Error> {
824    trace!("Setting player volume to {:?}", volume);
825    let player_guard = PLAYER_HANDLE.read().unwrap();
826    let player = unwrap_player(player_guard.as_ref())?;
827    player.set_volume(volume.get());
828    let mut state_guard = PLAYER_STATE.write().unwrap();
829    let state = unwrap_state_mut(state_guard.as_mut())?;
830    state.set_player_volume(volume);
831    background_save_state(state.clone());
832    Ok(())
833}
834
835pub(crate) fn init(
836    #[cfg(feature = "mpris")] identity: String,
837    #[cfg(feature = "mpris")] identifier: String,
838) {
839    trace!("Initializing the playback module");
840
841    let state = match restore_state_from_cache() {
842        Ok(state) => {
843            debug!("Restored player state from cache");
844            state
845        }
846        Err(e) => {
847            // FIX: This breaks the state in the player somehow
848            error!("Could not restore player state from cache: {e}");
849            let state = PlayerState::default();
850            debug!("Creating a new state and attempting to save it in cache");
851            background_save_state(state.clone());
852            state
853        }
854    };
855    crate::send_event(crate::Event::Playback(Event::StateInitialized(
856        state.clone(),
857    )));
858    trace!("Player state ready!");
859
860    // TODO: This doesn't seem to default to the sample rate of the audio device?
861    // TODO: Allow setting custom sinks
862    let handle = match rodio::DeviceSinkBuilder::open_default_sink() {
863        Ok(sink) => sink,
864        Err(e) => {
865            error!("Could not open the default sink, audio playback will not work! Error: {e}");
866            return;
867        }
868    };
869    let player = Player::connect_new(handle.mixer());
870    player.set_volume(state.player_volume.get());
871
872    *PLAYER_STATE.write().unwrap() = Some(state);
873    *PLAYER_HANDLE.write().unwrap() = Some(player);
874
875    let mut state_guard = PLAYER_STATE.write().unwrap();
876    let state = state_guard.as_mut().unwrap();
877    if let Some(track) = state.current() {
878        match track.open_source() {
879            Ok(source) => {
880                let player_guard = PLAYER_HANDLE.read().unwrap();
881                let player = player_guard.as_ref().unwrap();
882                player.append(source);
883                player.pause();
884                if let Err(e) = player.try_seek(state.player_position) {
885                    error!("Couldn't set initial player position: {e}");
886                    state.set_player_position(Duration::default());
887                }
888                drop(player_guard);
889            }
890            Err(e) => {
891                error!("Couldn't open the current track: {e}");
892                state.set_playback_state(PlaybackState::Stopped);
893            }
894        }
895    }
896    drop(state_guard);
897
898    #[cfg(feature = "mpris")]
899    mpris::launch_server(identity, identifier);
900
901    trace!("Playback module initialized");
902
903    let sleep_duration = Duration::from_millis(100);
904    loop {
905        thread::sleep(sleep_duration);
906        let mut state_guard = PLAYER_STATE.write().unwrap();
907        let state = state_guard.as_mut().unwrap();
908        let player_guard = PLAYER_HANDLE.read().unwrap();
909        let player = player_guard.as_ref().unwrap();
910        if !player.is_paused() && !player.empty() {
911            state.increment_player_position(sleep_duration);
912        } else if player.empty() && state.is_playing() {
913            state.set_player_position(Duration::default());
914            let track = match state.next_track() {
915                Some(track) => track,
916                None => {
917                    state.set_playback_state(PlaybackState::Stopped);
918                    continue;
919                }
920            };
921            match track.open_source() {
922                Ok(source) => {
923                    player.append(source);
924                    state.set_playback_state(PlaybackState::Playing);
925                }
926                Err(e) => {
927                    error!("Could not open audio source: {e}");
928                    player.stop();
929                    state.set_playback_state(PlaybackState::Stopped);
930                    continue;
931                }
932            }
933        }
934        drop(state_guard);
935        drop(player_guard);
936    }
937}