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(mpris::get_meta(track, 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: impl Into<Queue>, index: Option<usize>) -> Result<(), Error> {
474    let queue = queue.into();
475    let mut state_guard = PLAYER_STATE.write().unwrap();
476    let state = unwrap_state_mut(state_guard.as_mut())?;
477    state.play_new_queue(queue, index);
478
479    let player_guard = PLAYER_HANDLE.read().unwrap();
480    let player = unwrap_player(player_guard.as_ref())?;
481    player.stop();
482    if let Some(track) = state.current() {
483        match track.open_source() {
484            Ok(source) => {
485                player.append(source);
486                player.play();
487                state.set_playback_state(PlaybackState::Playing);
488            }
489            Err(e) => {
490                error!("Could not open audio source: {e}");
491                state.set_playback_state(PlaybackState::Stopped);
492                player.stop();
493                return Err(Error::SourceError);
494            }
495        }
496    }
497    background_save_state(state.clone());
498    Ok(())
499}
500
501/// Set the queue to a list of track.
502///
503/// Note that if shuffle is enabled, the queue will be shuffled immediately after setting it. For
504/// that reason, please use the `[play_new_queue]` function for setting the queue and then playing a
505/// track at a specific index in that queue.
506pub fn set_queue(queue: impl Into<Queue>) -> Result<(), Error> {
507    trace!("Setting a new queue");
508    let queue = queue.into();
509    let mut state_guard = PLAYER_STATE.write().unwrap();
510    let state = unwrap_state_mut(state_guard.as_mut())?;
511    state.set_queue(queue);
512    let player_guard = PLAYER_HANDLE.read().unwrap();
513    let player = unwrap_player(player_guard.as_ref())?;
514    player.stop();
515    if let Some(track) = state.current() {
516        match track.open_source() {
517            Ok(source) => {
518                player.append(source);
519                player.pause();
520            }
521            Err(e) => {
522                error!("Could not open audio source: {e}");
523                player.stop();
524                state.set_playback_state(PlaybackState::Stopped);
525                return Err(Error::SourceError);
526            }
527        }
528    }
529    background_save_state(state.clone());
530    Ok(())
531}
532
533pub fn append_to_queue(queue: &mut Vec<Arc<Track>>) -> Result<(), Error> {
534    trace!("Appending tracks to queue");
535    let mut state_guard = PLAYER_STATE.write().unwrap();
536    let state = unwrap_state_mut(state_guard.as_mut())?;
537    state.append_tracks(queue);
538    background_save_state(state.clone());
539    Ok(())
540}
541
542pub fn remove_from_queue(indices: Vec<usize>) -> Result<(), Error> {
543    trace!("Removing {} tracks from queue", indices.len());
544    let mut state_guard = PLAYER_STATE.write().unwrap();
545    let state = unwrap_state_mut(state_guard.as_mut())?;
546    let previous = state.current().cloned();
547    state.remove_tracks(indices)?;
548    let player_guard = PLAYER_HANDLE.read().unwrap();
549    let player = unwrap_player(player_guard.as_ref())?;
550    if previous.as_ref() != state.current() {
551        if let Some(track) = state.current() {
552            match track.open_source() {
553                Ok(source) => {
554                    player.stop();
555                    player.append(source);
556                    state.set_playback_state(PlaybackState::Playing);
557                }
558                Err(e) => {
559                    error!("Could not open audio source: {e}");
560                    player.stop();
561                    state.set_playback_state(PlaybackState::Stopped);
562                    return Err(Error::SourceError);
563                }
564            }
565        } else {
566            player.stop();
567            state.set_playback_state(PlaybackState::Stopped);
568        }
569    }
570    background_save_state(state.clone());
571    Ok(())
572}
573
574pub fn skip_next() -> Result<(), Error> {
575    trace!("Skipping to the next track");
576    let mut state_guard = PLAYER_STATE.write().unwrap();
577    let state = unwrap_state_mut(state_guard.as_mut())?;
578    if let Some(track) = state.next_track() {
579        let player_guard = PLAYER_HANDLE.read().unwrap();
580        let player = unwrap_player(player_guard.as_ref())?;
581        match track.open_source() {
582            Ok(source) => {
583                state.set_player_position(Duration::default());
584                state.set_playback_state(PlaybackState::Playing);
585                player.clear();
586                player.append(source);
587                player.play();
588            }
589            Err(e) => {
590                error!("Could not open audio source: {e}");
591                state.set_playback_state(PlaybackState::Stopped);
592                player.stop();
593                return Err(Error::SourceError);
594            }
595        }
596        background_save_state(state.clone());
597        Ok(())
598    } else if state.queue_empty() {
599        info!("Cannot skip to the next track, queue is empty");
600        Err(Error::QueueEmpty)
601    } else {
602        info!("Cannot skip to the next track");
603        Err(Error::CannotGoNext)
604    }
605}
606
607pub fn skip_previous() -> Result<(), Error> {
608    trace!("Skipping to the previous track");
609    let mut state_guard = PLAYER_STATE.write().unwrap();
610    let state = unwrap_state_mut(state_guard.as_mut())?;
611
612    let guard = crate::CONFIG.read().unwrap();
613    let config = guard.as_ref().unwrap();
614    if let Some(threshold) = config.playback.skip_previous_threshold
615        && state.player_position > threshold
616    {
617        trace!("Player position above skip threshold, resetting player position instead");
618        drop(state_guard);
619        return set_player_position(Duration::default());
620    }
621
622    if let Some(track) = state.previous_track() {
623        let player_guard = PLAYER_HANDLE.read().unwrap();
624        let player = unwrap_player(player_guard.as_ref())?;
625        match track.open_source() {
626            Ok(source) => {
627                state.set_player_position(Duration::default());
628                state.set_playback_state(PlaybackState::Playing);
629                player.clear();
630                player.append(source);
631                player.play();
632            }
633            Err(e) => {
634                error!("Could not open audio source: {e}");
635                player.stop();
636                state.set_playback_state(PlaybackState::Stopped);
637                return Err(Error::SourceError);
638            }
639        };
640        background_save_state(state.clone());
641        Ok(())
642    } else if state.queue_empty() {
643        info!("Cannot go to the previous track, queue is empty");
644        Err(Error::QueueEmpty)
645    } else {
646        info!("Cannot go to the previous track");
647        Err(Error::CannotGoPrevious)
648    }
649}
650
651pub fn set_loop_state(loop_state: LoopState) -> Result<(), Error> {
652    trace!("Setting loop state to {loop_state:?}");
653    let mut state_guard = PLAYER_STATE.write().unwrap();
654    let state = unwrap_state_mut(state_guard.as_mut())?;
655    state.set_loop_state(loop_state);
656    background_save_state(state.clone());
657    Ok(())
658}
659
660pub fn cycle_loop_state() -> Result<(), Error> {
661    trace!("Cycling loop state");
662    let mut state_guard = PLAYER_STATE.write().unwrap();
663    let state = unwrap_state_mut(state_guard.as_mut())?;
664    state.set_loop_state(state.loop_state.cycle());
665    background_save_state(state.clone());
666    Ok(())
667}
668
669pub fn set_shuffle_state(shuffle_state: ShuffleState) -> Result<(), Error> {
670    trace!("Setting shuffle state to {shuffle_state:?}");
671    let mut state_guard = PLAYER_STATE.write().unwrap();
672    let state = unwrap_state_mut(state_guard.as_mut())?;
673    state.set_shuffle_state(shuffle_state);
674    background_save_state(state.clone());
675    Ok(())
676}
677
678pub fn toggle_shuffle_state() -> Result<(), Error> {
679    trace!("Toggling shuffle state");
680    let mut state_guard = PLAYER_STATE.write().unwrap();
681    let state = unwrap_state_mut(state_guard.as_mut())?;
682    let shuffle_state = state.shuffle_state.toggle();
683    trace!("Setting shuffle state to {shuffle_state:?}");
684    state.set_shuffle_state(shuffle_state);
685    background_save_state(state.clone());
686    Ok(())
687}
688
689pub fn set_player_position(position: Duration) -> Result<(), Error> {
690    trace!("Setting player position to {:?}", position.as_secs());
691    let player_guard = PLAYER_HANDLE.read().unwrap();
692    let mut state_guard = PLAYER_STATE.write().unwrap();
693    let state = unwrap_state_mut(state_guard.as_mut())?;
694    if state.playback_state == PlaybackState::Stopped {
695        return Err(Error::PlayerStopped);
696    }
697    let player = match player_guard.as_ref() {
698        Some(player) => player,
699        None => {
700            warn!("Cannot set player position, player is not connected");
701            return Err(Error::PlayerNotConnected);
702        }
703    };
704    if player.empty() {
705        Err(Error::QueueEmpty)
706    } else if let Some(track) = state.current()
707        && position > track.duration
708    {
709        drop(state_guard);
710        skip_next()
711    } else if let Err(e) = player.try_seek(position) {
712        error!("Could not set player position: {e}");
713        // Prevent MPRIS from showing incorrect data if seek is not supported
714        #[cfg(feature = "mpris")]
715        mpris::set_position(state.player_position);
716        Err(Error::SeekNotSupported)
717    } else {
718        state.set_player_position(position);
719        Ok(())
720    }
721}
722
723/// When seeking, if the resulting position would result in a value less than this threshold, set
724/// the position to 0s.
725///
726/// Similarly, if the difference between the duration of the current track and the resulting
727/// position would be smaller than this value, skip to the next track.
728static SEEK_ROUND_THRESHOLD: LazyLock<Duration> = LazyLock::new(|| Duration::from_secs(1));
729
730// TODO: Add mime type(s) for M3U8 files (if it makes sense)
731/// Mime types supported by the music player.
732///
733/// Chilen uses the `rodio` crate for audio playback, which itself uses
734/// [Symphonia](https://github.com/pdeljanov/Symphonia) for decoding audio files. Chilen supports
735/// all audio formats Symphonia does.
736#[cfg(feature = "mpris")]
737static SUPPORTED_MIME_TYPES: LazyLock<Vec<String>> = LazyLock::new(|| {
738    let arr = [
739        "audio/aac",
740        "audio/32kadpcm",
741        "audio/aiff",
742        "audio/x-aiff",
743        "audio/x-caf",
744        "audio/flac",
745        "audio/matroska",
746        "audio/mpeg",
747        "audio/mp4",
748        "audio/MPA",
749        "audio/mpa-robust",
750        "audio/ogg",
751        "audio/vorbis",
752        "audio/vorbis-config",
753        "audio/vnd.wave",
754        "audio/wav",
755        "audio/wave",
756        "audio/x-wav",
757        "audio/webm",
758    ];
759    arr.into_iter().map(|t| t.to_string()).collect()
760});
761
762pub fn seek(delta: SignedDuration) -> Result<(), Error> {
763    let player_guard = PLAYER_HANDLE.read().unwrap();
764    let player = match player_guard.as_ref() {
765        Some(player) => player,
766        None => {
767            warn!("Cannot seek, the player is not connected");
768            return Err(Error::PlayerNotConnected);
769        }
770    };
771    let mut state_guard = PLAYER_STATE.write().unwrap();
772    let state = unwrap_state_mut(state_guard.as_mut())?;
773    let pos = match delta {
774        SignedDuration::Positive(positive_dur) => {
775            if positive_dur == Duration::default() {
776                info!("Refusing to seek by 0s");
777                return Err(Error::InvalidDuration);
778            }
779            let sum = match positive_dur.checked_add(player.get_pos()) {
780                Some(sum) => sum,
781                None => {
782                    error!("Overflow detected while seeking, aborting");
783                    return Err(Error::DurationOverflow);
784                }
785            };
786            if let Some(track) = state.current()
787                && sum > track.duration - *SEEK_ROUND_THRESHOLD
788            {
789                drop(state_guard);
790                return skip_next();
791            } else {
792                trace!("Seeking player by {positive_dur:?}");
793                sum
794            }
795        }
796        SignedDuration::Negative(negative_dur) => {
797            if negative_dur == Duration::default() {
798                info!("Refusing to seek by 0s");
799                return Err(Error::InvalidDuration);
800            }
801            trace!("Seeking player by -{negative_dur:?}");
802            let sub = match player.get_pos().checked_sub(negative_dur) {
803                Some(sub) => sub,
804                None => {
805                    error!("Overflow detected while seeking");
806                    return Err(Error::DurationOverflow);
807                }
808            };
809            if sub < *SEEK_ROUND_THRESHOLD {
810                Duration::default()
811            } else {
812                sub
813            }
814        }
815    };
816    if let Err(e) = player.try_seek(pos) {
817        error!("Could not set player position: {e}");
818        Err(Error::SeekNotSupported)
819    } else {
820        state.set_player_position(pos);
821        Ok(())
822    }
823}
824
825pub fn set_player_volume(volume: PlayerVolume) -> Result<(), Error> {
826    trace!("Setting player volume to {:?}", volume);
827    let player_guard = PLAYER_HANDLE.read().unwrap();
828    let player = unwrap_player(player_guard.as_ref())?;
829    player.set_volume(volume.get());
830    let mut state_guard = PLAYER_STATE.write().unwrap();
831    let state = unwrap_state_mut(state_guard.as_mut())?;
832    state.set_player_volume(volume);
833    background_save_state(state.clone());
834    Ok(())
835}
836
837pub(crate) fn init(
838    #[cfg(feature = "mpris")] identity: String,
839    #[cfg(feature = "mpris")] identifier: String,
840) {
841    trace!("Initializing the playback module");
842
843    let state = match restore_state_from_cache() {
844        Ok(state) => {
845            debug!("Restored player state from cache");
846            state
847        }
848        Err(e) => {
849            // FIX: This breaks the state in the player somehow
850            error!("Could not restore player state from cache: {e}");
851            let state = PlayerState::default();
852            debug!("Creating a new state and attempting to save it in cache");
853            background_save_state(state.clone());
854            state
855        }
856    };
857    crate::send_event(crate::Event::Playback(Event::StateInitialized(
858        state.clone(),
859    )));
860    trace!("Player state ready!");
861
862    // TODO: This doesn't seem to default to the sample rate of the audio device?
863    // TODO: Allow setting custom sinks
864    let handle = match rodio::DeviceSinkBuilder::open_default_sink() {
865        Ok(sink) => sink,
866        Err(e) => {
867            error!("Could not open the default sink, audio playback will not work: {e}");
868            return;
869        }
870    };
871    let player = Player::connect_new(handle.mixer());
872    player.set_volume(state.player_volume.get());
873    trace!("Player handle initialized, audio playback should work fine!");
874
875    *PLAYER_STATE.write().unwrap() = Some(state);
876    *PLAYER_HANDLE.write().unwrap() = Some(player);
877
878    let mut state_guard = PLAYER_STATE.write().unwrap();
879    let state = state_guard.as_mut().unwrap();
880    if let Some(track) = state.current() {
881        match track.open_source() {
882            Ok(source) => {
883                let player_guard = PLAYER_HANDLE.read().unwrap();
884                let player = player_guard.as_ref().unwrap();
885                player.append(source);
886                player.pause();
887                if let Err(e) = player.try_seek(state.player_position) {
888                    error!("Couldn't set initial player position: {e}");
889                    state.set_player_position(Duration::default());
890                }
891                drop(player_guard);
892            }
893            Err(e) => {
894                error!("Couldn't open the current track: {e}");
895                state.set_playback_state(PlaybackState::Stopped);
896            }
897        }
898    }
899    drop(state_guard);
900
901    #[cfg(feature = "mpris")]
902    mpris::launch_server(identity, identifier);
903
904    trace!("Playback module initialized");
905
906    let sleep_duration = Duration::from_millis(100);
907    loop {
908        thread::sleep(sleep_duration);
909        let mut state_guard = PLAYER_STATE.write().unwrap();
910        let state = state_guard.as_mut().unwrap();
911        let player_guard = PLAYER_HANDLE.read().unwrap();
912        let player = player_guard.as_ref().unwrap();
913        if !player.is_paused() && !player.empty() {
914            state.increment_player_position(sleep_duration);
915        } else if player.empty() && state.is_playing() {
916            state.set_player_position(Duration::default());
917            let track = match state.next_track() {
918                Some(track) => track,
919                None => {
920                    state.set_playback_state(PlaybackState::Stopped);
921                    continue;
922                }
923            };
924            match track.open_source() {
925                Ok(source) => {
926                    player.append(source);
927                    state.set_playback_state(PlaybackState::Playing);
928                }
929                Err(e) => {
930                    error!("Could not open audio source: {e}");
931                    player.stop();
932                    state.set_playback_state(PlaybackState::Stopped);
933                    continue;
934                }
935            }
936        }
937        drop(state_guard);
938        drop(player_guard);
939    }
940}