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#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
30pub enum PlaybackState {
31 Playing,
33 Paused,
35 #[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#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
54pub enum LoopState {
55 #[default]
57 Off,
58 Track,
60 Playlist,
62}
63
64#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
66pub enum ShuffleState {
67 #[default]
71 Off,
72 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#[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#[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 pub fn new(volume: f64) -> Self {
156 Self {
157 volume: volume.clamp(0.0, 1.0),
158 }
159 }
160
161 pub fn set(&mut self, volume: f64) {
165 self.volume = volume.clamp(0.0, 1.0);
166 }
167
168 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
184pub(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 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
303pub(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 #[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
536static SEEK_ROUND_THRESHOLD: LazyLock<Duration> = LazyLock::new(|| Duration::from_secs(1));
542
543#[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
575pub 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
663pub(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 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}