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 chilen_ipc::playback::{LoopState, PlaybackResponse};
15#[cfg(feature = "shuffle")]
16use chilen_ipc::{
17 Event, Response,
18 playback::{
19 PlaybackCommand, PlaybackRate, PlaybackState, PlayerVolume, ShuffleState, SignedDuration,
20 },
21};
22use log::{debug, error, info, trace, warn};
23use rodio::Player;
24use serde::{Deserialize, Serialize};
25use walkdir::WalkDir;
26
27use crate::{
28 music_lib::{
29 state::{Track, get_library},
30 tracks_from_m3u8, tracks_from_paths,
31 },
32 playback::state::{
33 PLAYER_STATE, PlayerState, background_save_state, restore_state_from_cache,
34 unwrap_state_mut, unwrap_state_ref,
35 },
36};
37
38#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
40pub struct Config {
41 #[cfg(any(feature = "mpris", doc))]
45 pub identity: String,
46 #[cfg(any(feature = "mpris", doc))]
57 pub bus_name_suffix: String,
58 pub allow_rate_modification: bool,
63}
64
65#[derive(Debug, Clone)]
66pub(crate) enum Command {
67 Play(Option<usize>),
68 Pause,
69 Stop,
70 TogglePlaying,
71 GetPlaybackState,
72 SetQueue(Vec<Track>),
73 AppendToQueue(Vec<Track>),
74 GetCurrentTrack,
75 Next,
76 Previous,
77 SetLoopState(LoopState),
78 GetLoopState,
79 SetRate(f64),
80 GetRate,
81 #[cfg(feature = "shuffle")]
82 SetShuffleState(ShuffleState),
83 GetShuffleState,
84 #[cfg(not(feature = "shuffle"))]
85 SetShuffleState,
86 SetPlayerPosition(Duration),
87 Seek(SignedDuration),
88 GetPlayerPosition,
89 SetPlayerVolume(PlayerVolume),
90 GetPlayerVolume,
91 OpenURI(String),
92}
93
94impl TryFrom<PlaybackCommand> for Command {
95 type Error = chilen_ipc::Error;
96 fn try_from(value: PlaybackCommand) -> Result<Self, Self::Error> {
97 match value {
98 PlaybackCommand::Play(maybe_index) => Ok(Self::Play(maybe_index)),
99 PlaybackCommand::Pause => Ok(Self::Pause),
100 PlaybackCommand::Stop => Ok(Self::Stop),
101 PlaybackCommand::TogglePlaying => Ok(Self::TogglePlaying),
102 PlaybackCommand::GetPlaybackState => Ok(Self::GetPlaybackState),
103 PlaybackCommand::SetQueue(track_paths) => {
104 let tracks = tracks_from_paths(&track_paths, false)?;
105 Ok(Self::SetQueue(tracks))
106 }
107 PlaybackCommand::AppendToQueue(track_paths) => {
108 let tracks = tracks_from_paths(&track_paths, false)?;
109 Ok(Self::AppendToQueue(tracks))
110 }
111 PlaybackCommand::SetPlaylist(playlist) => {
112 let lib = get_library()?;
113 let playlist = match lib.find_playlist(&playlist) {
114 Some(playlist) => playlist,
115 None => return Err(chilen_ipc::Error::UnknownPlaylist),
116 };
117 Ok(Self::SetQueue(
118 playlist.tracks.iter().map(|t| t.as_ref().clone()).collect(),
119 ))
120 }
121 PlaybackCommand::AppendPlaylist(playlist) => {
122 let lib = get_library()?;
123 let playlist = match lib.find_playlist(&playlist) {
124 Some(playlist) => playlist,
125 None => return Err(chilen_ipc::Error::UnknownPlaylist),
126 };
127 Ok(Self::AppendToQueue(
128 playlist.tracks.iter().map(|t| t.as_ref().clone()).collect(),
129 ))
130 }
131 PlaybackCommand::GetCurrentTrack => Ok(Self::GetCurrentTrack),
132 PlaybackCommand::Next => Ok(Self::Next),
133 PlaybackCommand::Previous => Ok(Self::Previous),
134 PlaybackCommand::SetLoopState(loop_state) => Ok(Self::SetLoopState(loop_state)),
135 PlaybackCommand::GetLoopState => Ok(Self::GetLoopState),
136 PlaybackCommand::SetRate(rate) => Ok(Self::SetRate(rate)),
137 PlaybackCommand::GetRate => Ok(Self::GetRate),
138 #[cfg(feature = "shuffle")]
139 PlaybackCommand::SetShuffleState(shuffle_state) => {
140 Ok(Self::SetShuffleState(shuffle_state))
141 }
142 #[cfg(not(feature = "shuffle"))]
143 PlaybackCommand::SetShuffleState(_) => Ok(Self::SetShuffleState),
144 PlaybackCommand::GetShuffleState => Ok(Self::GetShuffleState),
145 PlaybackCommand::SetPlayerPosition(position) => Ok(Self::SetPlayerPosition(position)),
146 PlaybackCommand::Seek(delta) => Ok(Self::Seek(delta)),
147 PlaybackCommand::GetPlayerPosition => Ok(Self::GetPlayerPosition),
148 PlaybackCommand::SetPlayerVolume(volume) => Ok(Self::SetPlayerVolume(volume)),
149 PlaybackCommand::GetPlayerVolume => Ok(Self::GetPlayerVolume),
150 PlaybackCommand::OpenURI(uri) => Ok(Self::OpenURI(uri)),
151 }
152 }
153}
154
155static PLAYER_HANDLE: LazyLock<Arc<RwLock<Option<rodio::Player>>>> =
156 LazyLock::new(|| Arc::new(RwLock::new(None)));
157
158pub(crate) static CONFIG: LazyLock<Arc<RwLock<Option<Config>>>> =
159 LazyLock::new(|| Arc::new(RwLock::new(None)));
160
161pub(crate) fn cleanup() {
162 *PLAYER_HANDLE.write().unwrap() = None;
163 *CONFIG.write().unwrap() = None;
164}
165
166pub(crate) fn unwrap_player(
167 maybe_player: Option<&rodio::Player>,
168) -> Result<&rodio::Player, chilen_ipc::Error> {
169 match maybe_player {
170 Some(player) => Ok(player),
171 None => Err(chilen_ipc::Error::StateNotInitialized),
172 }
173}
174
175pub fn set_allow_rate_modification(allow_rate_modification: bool) -> Result<(), super::Error> {
180 let mut conf_guard = CONFIG.write().unwrap();
181 let conf = match conf_guard.as_mut() {
182 Some(conf) => conf,
183 None => return Err(super::Error::DaemonNotRunning),
184 };
185 conf.allow_rate_modification = allow_rate_modification;
186 Ok(())
187}
188
189pub(crate) fn play(index: Option<usize>) -> Result<(), chilen_ipc::Error> {
191 let player_guard = PLAYER_HANDLE.read().unwrap();
192 let player = unwrap_player(player_guard.as_ref())?;
193 let mut state_guard = PLAYER_STATE.write().unwrap();
194 let state = unwrap_state_mut(state_guard.as_mut())?;
195 if let Some(id) = index {
196 trace!("Playing a track at index {id}");
197 let track = match state.play_track(id) {
198 Some(track) => track,
199 None => {
200 error!("No track at index {id}");
201 return Err(chilen_ipc::Error::NoTrackAtIndex(id));
202 }
203 };
204 let source = match track.open_source() {
205 Ok(source) => source,
206 Err(e) => {
207 error!("Could not open audio source: {e}");
208 return Err(chilen_ipc::Error::SourceError);
209 }
210 };
211 player.stop();
212 player.append(source);
213 player.play();
214 state.set_playback_state(PlaybackState::Playing);
215 background_save_state(state.clone());
216 Ok(())
217 } else {
218 trace!("Playing the current media");
219 if !player.is_paused() && !player.empty() {
220 Err(chilen_ipc::Error::PlayerPlaying)
221 } else if player.empty() {
222 if let Some(track) = state.current() {
223 let source = match track.open_source() {
224 Ok(source) => source,
225 Err(e) => {
226 error!("Could not open audio source: {e}");
227 return Err(chilen_ipc::Error::SourceError);
228 }
229 };
230 player.append(source);
231 state.set_playback_state(PlaybackState::Playing);
232 Ok(())
233 } else {
234 Err(chilen_ipc::Error::QueueEmpty)
235 }
236 } else {
237 player.play();
238 state.set_playback_state(PlaybackState::Playing);
239 Ok(())
240 }
241 }
242}
243
244pub(crate) fn pause() -> Result<(), chilen_ipc::Error> {
245 trace!("Pausing the current media");
246 let player_guard = PLAYER_HANDLE.read().unwrap();
247 let player = player_guard.as_ref().unwrap();
248 if player.is_paused() {
249 Err(chilen_ipc::Error::PlayerPaused)
250 } else {
251 player.pause();
254 let mut state_guard = PLAYER_STATE.write().unwrap();
255 let state = unwrap_state_mut(state_guard.as_mut())?;
256 state.set_playback_state(PlaybackState::Paused);
257 Ok(())
258 }
259}
260
261pub(crate) fn stop() -> Result<(), chilen_ipc::Error> {
262 trace!("Stopping the playback");
263 let player_guard = PLAYER_HANDLE.read().unwrap();
264 let player = player_guard.as_ref().unwrap();
265 if player.empty() {
266 Err(chilen_ipc::Error::PlayerStopped)
267 } else {
268 player.stop();
269 let mut state_guard = PLAYER_STATE.write().unwrap();
270 let state = unwrap_state_mut(state_guard.as_mut())?;
271 state.set_playback_state(PlaybackState::Stopped);
272 Ok(())
273 }
274}
275
276pub(crate) fn toggle_playing() -> Result<(), chilen_ipc::Error> {
277 trace!("Toggling playback state");
278 let state_guard = PLAYER_STATE.read().unwrap();
279 let state = unwrap_state_ref(state_guard.as_ref())?;
280 let playback_state = state.playback_state;
281 match playback_state {
282 PlaybackState::Paused => {
283 drop(state_guard);
284 play(None)
285 }
286 PlaybackState::Playing => {
287 drop(state_guard);
288 pause()
289 }
290 PlaybackState::Stopped => {
291 if state.current().is_some() {
292 let pos = state.position;
293 drop(state_guard);
294 play(Some(pos))
295 } else {
296 Err(chilen_ipc::Error::QueueEmpty)
297 }
298 }
299 }
300}
301
302pub(crate) fn get_playback_state() -> Result<PlaybackState, chilen_ipc::Error> {
303 let state_guard = PLAYER_STATE.read().unwrap();
304 let state = unwrap_state_ref(state_guard.as_ref())?;
305 Ok(state.playback_state)
306}
307
308pub(crate) fn open_uri(uri: PathBuf) -> Result<(), chilen_ipc::Error> {
311 trace!("Opening URI {uri:?}");
312 match uri.try_exists() {
313 Ok(exists) => {
314 if !exists {
315 return Err(chilen_ipc::Error::PathDoesNotExist);
316 }
317 }
318 Err(e) => {
319 error!("Could not check if the URI {uri:?} exists: {e}");
320 return Err(chilen_ipc::Error::PathExistenceUnknown);
321 }
322 }
323 if uri.is_dir() {
324 trace!("The provided URI {uri:?} is a directory, indexing it");
325
326 let mut files = Vec::new();
327 for result in WalkDir::new(uri).into_iter() {
328 let entry = match result {
329 Ok(entry) => entry,
330 Err(e) => {
331 warn!("Error while trying to access the file: {e}");
332 continue;
333 }
334 };
335
336 match entry.metadata() {
337 Ok(meta) => {
338 if meta.is_file() {
339 files.push(PathBuf::from(entry.path()));
340 }
341 }
342 Err(e) => {
343 warn!("Could not get path metadata: {e}");
344 continue;
345 }
346 };
347 }
348 let tracks = tracks_from_paths(&files, true)?;
349 if tracks.is_empty() {
350 Err(chilen_ipc::Error::DirectoryWithNoTracks)
351 } else {
352 set_queue(tracks)
353 }
354 } else {
355 trace!(
356 "The provided URI {uri:?} is a file, so we're either opening an audio file or a playlist"
357 );
358 if let Ok(paths) = tracks_from_m3u8(&uri) {
359 trace!("Loaded M3U8 playlist {uri:?}");
360 let tracks = tracks_from_paths(&paths, true)?;
361 return set_queue(tracks);
362 } else {
363 trace!("The file does not appear to be an M3U8 playlist");
364 }
365 let track = tracks_from_paths(&[uri], false)?;
366 set_queue(track)
367 }
368}
369
370pub(crate) fn set_queue(queue: Vec<Track>) -> Result<(), chilen_ipc::Error> {
371 trace!("Setting a new queue");
372 let mut state_guard = PLAYER_STATE.write().unwrap();
373 let state = unwrap_state_mut(state_guard.as_mut())?;
374 state.set_tracks(queue);
375 #[cfg(feature = "shuffle")]
376 if state.shuffle_state == ShuffleState::On {
377 state.shuffle();
378 }
379 let player_guard = PLAYER_HANDLE.read().unwrap();
380 let player = unwrap_player(player_guard.as_ref())?;
381 player.stop();
382 if let Some(track) = state.current() {
383 let source = match track.open_source() {
384 Ok(source) => source,
385 Err(e) => {
386 error!("Could not open audio source: {e}");
387 return Err(chilen_ipc::Error::SourceError);
388 }
389 };
390 player.append(source);
391 player.pause();
392 }
393 background_save_state(state.clone());
394 Ok(())
395}
396
397pub(crate) fn append_to_queue(queue: &mut Vec<Track>) -> Result<(), chilen_ipc::Error> {
398 trace!("Appending tracks to queue");
399 let mut state_guard = PLAYER_STATE.write().unwrap();
400 let state = unwrap_state_mut(state_guard.as_mut())?;
401 state.append_tracks(queue);
402 background_save_state(state.clone());
403 Ok(())
404}
405
406pub(crate) fn get_current_track() -> Result<Option<Track>, chilen_ipc::Error> {
407 let state_guard = PLAYER_STATE.read().unwrap();
408 let state = unwrap_state_ref(state_guard.as_ref())?;
409 match state.current() {
410 Some(track) => Ok(Some(track.clone())),
411 None => Ok(None),
412 }
413}
414
415#[cfg(feature = "mpris")]
416pub(crate) fn get_current_meta() -> Result<Option<mpris_server::Metadata>, chilen_ipc::Error> {
417 let state_guard = PLAYER_STATE.read().unwrap();
418 let state = unwrap_state_ref(state_guard.as_ref())?;
419 match state.current() {
420 Some(track) => Ok(Some(track.get_meta(state.position))),
421 None => Ok(None),
422 }
423}
424
425pub(crate) fn skip_next() -> Result<(), chilen_ipc::Error> {
426 trace!("Skipping to the next track");
427 let mut state_guard = PLAYER_STATE.write().unwrap();
428 let state = unwrap_state_mut(state_guard.as_mut())?;
429 if state.can_go_next() {
430 let track = state.next().unwrap();
431 let source = match track.open_source() {
432 Ok(source) => source,
433 Err(e) => {
434 error!("Could not open audio source: {e}");
435 return Err(chilen_ipc::Error::SourceError);
436 }
437 };
438 background_save_state(state.clone());
439 let player_guard = PLAYER_HANDLE.read().unwrap();
440 let player = unwrap_player(player_guard.as_ref())?;
441 state.set_player_position(Duration::default());
442 state.set_playback_state(PlaybackState::Playing);
443 player.clear();
444 player.append(source);
445 player.play();
446 Ok(())
447 } else if state.is_empty() {
448 info!("Cannot skip to the next track, queue is empty");
449 Err(chilen_ipc::Error::QueueEmpty)
450 } else {
451 info!("Cannot skip to the next track");
452 Err(chilen_ipc::Error::CannotGoNext)
453 }
454}
455
456pub(crate) fn skip_previous() -> Result<(), chilen_ipc::Error> {
457 trace!("Skipping to the previous track");
458 let mut state_guard = PLAYER_STATE.write().unwrap();
459 let state = unwrap_state_mut(state_guard.as_mut())?;
460 if state.can_go_previous() {
461 let track = state.previous().unwrap();
462 let source = match track.open_source() {
463 Ok(source) => source,
464 Err(e) => {
465 error!("Could not open audio source: {e}");
466 return Err(chilen_ipc::Error::SourceError);
467 }
468 };
469 background_save_state(state.clone());
470 let player_guard = PLAYER_HANDLE.read().unwrap();
471 let player = unwrap_player(player_guard.as_ref())?;
472 state.set_player_position(Duration::default());
473 state.set_playback_state(PlaybackState::Playing);
474 player.clear();
475 player.append(source);
476 player.play();
477 Ok(())
478 } else if state.is_empty() {
479 info!("Cannot go to the previous track, queue is empty");
480 Err(chilen_ipc::Error::QueueEmpty)
481 } else {
482 info!("Cannot go to the previous track");
483 Err(chilen_ipc::Error::CannotGoPrevious)
484 }
485}
486
487pub(crate) fn set_loop_state(loop_state: LoopState) -> Result<(), chilen_ipc::Error> {
488 trace!("Setting loop state to {loop_state:?}");
489 let mut state_guard = PLAYER_STATE.write().unwrap();
490 let state = unwrap_state_mut(state_guard.as_mut())?;
491 state.set_loop_state(loop_state);
492 background_save_state(state.clone());
493 Ok(())
494}
495
496pub(crate) fn get_loop_state() -> Result<LoopState, chilen_ipc::Error> {
497 let state_guard = PLAYER_STATE.read().unwrap();
498 let state = unwrap_state_ref(state_guard.as_ref())?;
499 Ok(state.loop_state)
500}
501
502pub(crate) fn set_rate(rate: f64) -> Result<(), chilen_ipc::Error> {
503 let conf = CONFIG.read().unwrap();
504 if !conf.as_ref().unwrap().allow_rate_modification {
505 return Err(chilen_ipc::Error::FixedRate);
506 }
507 let mut state_guard = PLAYER_STATE.write().unwrap();
508 let state = unwrap_state_mut(state_guard.as_mut())?;
509 if !state.playback_rate.is_in_range(rate) {
510 Err(chilen_ipc::Error::RateOutOfRange)
511 } else {
512 let player_guard = PLAYER_HANDLE.read().unwrap();
513 let player = unwrap_player(player_guard.as_ref())?;
514 player.set_speed(PlaybackRate::from(rate).get_value_f32());
515 state.set_rate(rate);
516 background_save_state(state.clone());
517 Ok(())
518 }
519}
520
521pub(crate) fn get_rate() -> Result<PlaybackRate, chilen_ipc::Error> {
522 let state_guard = PLAYER_STATE.read().unwrap();
523 let state = unwrap_state_ref(state_guard.as_ref())?;
524 Ok(state.playback_rate)
525}
526
527pub(crate) fn set_shuffle_state(shuffle_state: ShuffleState) -> Result<(), chilen_ipc::Error> {
528 #[cfg(feature = "shuffle")]
529 {
530 trace!("Setting shuffle state to {shuffle_state:?}");
531 let mut state_guard = PLAYER_STATE.write().unwrap();
532 let state = unwrap_state_mut(state_guard.as_mut())?;
533 state.set_shuffle_state(shuffle_state);
534 state.shuffle();
535 background_save_state(state.clone());
536 Ok(())
537 }
538 #[cfg(not(feature = "shuffle"))]
539 {
540 info!("The daemon was built without shuffle support");
541 return Err(chilen_ipc::Error::ShuffleNotSupported);
542 }
543}
544
545pub(crate) fn get_shuffle_state() -> Result<ShuffleState, chilen_ipc::Error> {
546 let state_guard = PLAYER_STATE.read().unwrap();
547 let state = unwrap_state_ref(state_guard.as_ref())?;
548 Ok(state.shuffle_state)
549}
550
551pub(crate) fn set_player_position(position: Duration) -> Result<(), chilen_ipc::Error> {
552 trace!("Setting player position to {:?}", position.as_secs());
553 let player_guard = PLAYER_HANDLE.read().unwrap();
554 let mut state_guard = PLAYER_STATE.write().unwrap();
555 let state = unwrap_state_mut(state_guard.as_mut())?;
556 if state.playback_state == PlaybackState::Stopped {
557 return Err(chilen_ipc::Error::PlayerStopped);
558 }
559 let player = match player_guard.as_ref() {
560 Some(player) => player,
561 None => {
562 warn!("Cannot set player position, player is not connected");
563 return Err(chilen_ipc::Error::PlayerNotConnected);
564 }
565 };
566 if player.empty() {
567 Err(chilen_ipc::Error::QueueEmpty)
568 } else if let Some(track) = state.current()
569 && position > track.duration
570 {
571 skip_next()
572 } else if let Err(e) = player.try_seek(position) {
573 error!("Could not set player position: {e}");
574 #[cfg(feature = "mpris")]
576 mpris::set_position(state.player_position);
577 Err(chilen_ipc::Error::SeekNotSupported)
578 } else {
579 state.set_player_position(position);
580 Ok(())
581 }
582}
583
584static SEEK_ROUND_THRESHOLD: LazyLock<Duration> = LazyLock::new(|| Duration::from_secs(1));
590
591#[cfg(feature = "mpris")]
598static SUPPORTED_MIME_TYPES: LazyLock<Vec<String>> = LazyLock::new(|| {
599 let arr = [
600 "audio/aac",
601 "audio/32kadpcm",
602 "audio/aiff",
603 "audio/x-aiff",
604 "audio/x-caf",
605 "audio/flac",
606 "audio/matroska",
607 "audio/mpeg",
608 "audio/mp4",
609 "audio/MPA",
610 "audio/mpa-robust",
611 "audio/ogg",
612 "audio/vorbis",
613 "audio/vorbis-config",
614 "audio/vnd.wave",
615 "audio/wav",
616 "audio/wave",
617 "audio/x-wav",
618 "audio/webm",
619 ];
620 arr.into_iter().map(|t| t.to_string()).collect()
621});
622
623pub(crate) fn seek(delta: SignedDuration) -> Result<(), chilen_ipc::Error> {
625 let player_guard = PLAYER_HANDLE.read().unwrap();
626 let player = match player_guard.as_ref() {
627 Some(player) => player,
628 None => {
629 warn!("Cannot seek, the player is not connected");
630 return Err(chilen_ipc::Error::PlayerNotConnected);
631 }
632 };
633 let mut state_guard = PLAYER_STATE.write().unwrap();
634 let state = unwrap_state_mut(state_guard.as_mut())?;
635 let pos = match delta {
636 SignedDuration::Positive(positive_dur) => {
637 if positive_dur == Duration::default() {
638 info!("Refusing to seek by 0s");
639 return Err(chilen_ipc::Error::InvalidDuration);
640 }
641 let sum = match positive_dur.checked_add(player.get_pos()) {
642 Some(sum) => sum,
643 None => {
644 error!("Overflow detected while seeking, aborting");
645 return Err(chilen_ipc::Error::DurationOverflow);
646 }
647 };
648 if let Some(track) = state.current()
649 && sum > track.duration - *SEEK_ROUND_THRESHOLD
650 {
651 drop(state_guard);
652 return skip_next();
653 } else {
654 trace!("Seeking player by {positive_dur:?}");
655 sum
656 }
657 }
658 SignedDuration::Negative(negative_dur) => {
659 if negative_dur == Duration::default() {
660 info!("Refusing to seek by 0s");
661 return Err(chilen_ipc::Error::InvalidDuration);
662 }
663 trace!("Seeking player by -{negative_dur:?}");
664 let sub = match player.get_pos().checked_sub(negative_dur) {
665 Some(sub) => sub,
666 None => {
667 error!("Overflow detected while seeking");
668 return Err(chilen_ipc::Error::DurationOverflow);
669 }
670 };
671 if sub < *SEEK_ROUND_THRESHOLD {
672 Duration::default()
673 } else {
674 sub
675 }
676 }
677 };
678 if let Err(e) = player.try_seek(pos) {
679 error!("Could not set player position: {e}");
680 Err(chilen_ipc::Error::SeekNotSupported)
681 } else {
682 state.set_player_position(pos);
683 Ok(())
684 }
685}
686
687pub(crate) fn get_player_position() -> Result<Duration, chilen_ipc::Error> {
688 let state_guard = PLAYER_STATE.read().unwrap();
689 let state = unwrap_state_ref(state_guard.as_ref())?;
690 Ok(state.player_position)
691}
692
693pub(crate) fn set_player_volume(volume: PlayerVolume) -> Result<(), chilen_ipc::Error> {
694 trace!("Setting player volume to {:?}", volume);
695 let player_guard = PLAYER_HANDLE.read().unwrap();
696 let player = unwrap_player(player_guard.as_ref())?;
697 player.set_volume(volume.get());
698 let mut state_guard = PLAYER_STATE.write().unwrap();
699 let state = unwrap_state_mut(state_guard.as_mut())?;
700 state.set_player_volume(volume);
701 background_save_state(state.clone());
702 Ok(())
703}
704
705pub(crate) fn get_player_volume() -> Result<PlayerVolume, chilen_ipc::Error> {
706 let player_guard = PLAYER_HANDLE.read().unwrap();
707 let player = unwrap_player(player_guard.as_ref())?;
708 Ok(PlayerVolume::new(player.volume()))
709}
710
711pub(crate) fn get_initial_events() -> Result<Vec<Event>, chilen_ipc::Error> {
712 let state_guard = PLAYER_STATE.read().unwrap();
713 let state = unwrap_state_ref(state_guard.as_ref())?;
714 Ok(state.get_initial_events())
715}
716
717pub(crate) fn init(config: Config) {
718 trace!("Initializing the playback module");
719
720 trace!("Initializing config");
721 *CONFIG.write().unwrap() = Some(config.clone());
722
723 let state = match restore_state_from_cache() {
724 Ok(state) => {
725 debug!("Restored player state from cache");
726 state
727 }
728 Err(e) => {
729 error!("Could not restore player state from cache: {e}");
730 let state = PlayerState::default();
731 debug!("Creating a new state and attempting to save it in cache");
732 background_save_state(state.clone());
733 state
734 }
735 };
736 trace!("Player state ready!");
737
738 state.send_initial_events();
739
740 let handle = match rodio::DeviceSinkBuilder::open_default_sink() {
741 Ok(sink) => sink,
742 Err(e) => {
743 error!("Could not open the default sink, audio playback will not work! Error: {e}");
744 return;
745 }
746 };
747 let player = Player::connect_new(handle.mixer());
748 player.set_volume(state.player_volume.get());
749
750 *PLAYER_STATE.write().unwrap() = Some(state);
751 *PLAYER_HANDLE.write().unwrap() = Some(player);
752
753 let mut state_guard = PLAYER_STATE.write().unwrap();
754 let state = state_guard.as_mut().unwrap();
755 if let Some(track) = state.current()
756 && let Ok(source) = track.open_source()
757 {
758 let player_guard = PLAYER_HANDLE.read().unwrap();
759 let player = player_guard.as_ref().unwrap();
760 state.player_position = Duration::default();
761 player.append(source);
762 player.pause();
763 player.set_speed(state.playback_rate.get_value_f32());
764 drop(player_guard);
765 }
766 drop(state_guard);
767
768 #[cfg(feature = "mpris")]
769 mpris::launch_server(config);
770
771 let mut initial_iter = true;
772 let sleep_duration = Duration::from_millis(100);
773 loop {
774 thread::sleep(sleep_duration);
775 let mut state_guard = PLAYER_STATE.write().unwrap();
776 let state = state_guard.as_mut().unwrap();
777 let player_guard = PLAYER_HANDLE.read().unwrap();
778 let player = player_guard.as_ref().unwrap();
779 if !player.is_paused() && !player.empty() {
780 state.increment_player_position(sleep_duration);
781 } else if player.empty() {
782 state.set_player_position(Duration::default());
783 if !state.can_go_next() {
784 state.set_playback_state(PlaybackState::Stopped);
785 continue;
786 }
787 let track = state.next().unwrap();
788 let source = match track.open_source() {
789 Ok(source) => source,
790 Err(e) => {
791 error!("Could not open audio source: {e}");
792 continue;
793 }
794 };
795 player.append(source);
796 if initial_iter {
797 player.pause();
798 initial_iter = false;
799 state.set_playback_state(PlaybackState::Stopped);
800 } else {
801 state.set_playback_state(PlaybackState::Playing);
802 }
803 }
804 drop(state_guard);
805 drop(player_guard);
806 }
807}
808
809#[cfg(feature = "mpris")]
810pub(crate) fn can_play() -> Result<bool, chilen_ipc::Error> {
811 let state_guard = PLAYER_STATE.read().unwrap();
812 let state = unwrap_state_ref(state_guard.as_ref())?;
813 Ok(state.can_play())
814}
815
816#[cfg(feature = "mpris")]
817pub(crate) fn can_pause() -> Result<bool, chilen_ipc::Error> {
818 let state_guard = PLAYER_STATE.read().unwrap();
819 let state = unwrap_state_ref(state_guard.as_ref())?;
820 Ok(state.playback_state == PlaybackState::Playing)
821}
822
823#[cfg(feature = "mpris")]
824pub(crate) fn can_go_next() -> Result<bool, chilen_ipc::Error> {
825 let state_guard = PLAYER_STATE.read().unwrap();
826 let state = unwrap_state_ref(state_guard.as_ref())?;
827 Ok(state.can_go_next())
828}
829
830#[cfg(feature = "mpris")]
831pub(crate) fn can_go_previous() -> Result<bool, chilen_ipc::Error> {
832 let state_guard = PLAYER_STATE.read().unwrap();
833 let state = unwrap_state_ref(state_guard.as_ref())?;
834 Ok(state.can_go_previous())
835}
836
837pub(crate) fn run_command(cmd: Command) -> Result<Response, chilen_ipc::Error> {
838 match cmd {
839 Command::Play(pos) => play(pos)?,
840 Command::Pause => pause()?,
841 Command::Stop => stop()?,
842 Command::TogglePlaying => toggle_playing()?,
843 Command::GetPlaybackState => {
844 return Ok(Response::Playback(PlaybackResponse::PlaybackState(
845 get_playback_state()?,
846 )));
847 }
848 Command::SetQueue(queue) => set_queue(queue)?,
849 Command::AppendToQueue(mut queue) => append_to_queue(&mut queue)?,
850 Command::GetCurrentTrack => {
851 return Ok(Response::Playback(match get_current_track()? {
852 Some(track) => PlaybackResponse::Track(Some(Box::new(track.into()))),
853 None => PlaybackResponse::Track(None),
854 }));
855 }
856 Command::Next => skip_next()?,
857 Command::Previous => skip_previous()?,
858 Command::SetLoopState(loop_state) => set_loop_state(loop_state)?,
859 Command::GetLoopState => {
860 return Ok(Response::Playback(PlaybackResponse::LoopState(
861 get_loop_state()?,
862 )));
863 }
864 Command::SetRate(rate) => set_rate(rate)?,
865 Command::GetRate => {
866 return Ok(Response::Playback(PlaybackResponse::PlaybackRate(
867 get_rate()?,
868 )));
869 }
870 Command::SetShuffleState(shuffle_state) => set_shuffle_state(shuffle_state)?,
871 #[cfg(feature = "shuffle")]
872 Command::GetShuffleState => {
873 return Ok(Response::Playback(PlaybackResponse::ShuffleState(
874 get_shuffle_state()?,
875 )));
876 }
877 Command::SetPlayerPosition(position) => set_player_position(position)?,
878 Command::Seek(delta) => seek(delta)?,
879 Command::GetPlayerPosition => {
880 return Ok(Response::Playback(PlaybackResponse::PlayerPosition(
881 get_player_position()?,
882 )));
883 }
884 Command::SetPlayerVolume(volume) => set_player_volume(volume)?,
885 Command::GetPlayerVolume => {
886 return Ok(Response::Playback(PlaybackResponse::PlayerVolume(
887 get_player_volume()?,
888 )));
889 }
890 Command::OpenURI(uri) => open_uri(uri.into())?,
891 }
892
893 Ok(Response::Ok)
894}