Skip to main content

chilen_backend/playback/
mod.rs

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