1use std::{path::PathBuf, str::FromStr, time::Duration};
6
7use mecomp_core::state::{RepeatMode, SeekType, Status};
8use mpris_server::{
9 zbus::{fdo, Error as ZbusError},
10 LoopStatus, Metadata, PlaybackRate, PlaybackStatus, PlayerInterface, Time, TrackId, Volume,
11};
12use tarpc::context::Context;
13
14use crate::{interfaces::root::SUPPORTED_MIME_TYPES, metadata_from_opt_song, Mpris};
15
16impl PlayerInterface for Mpris {
17 async fn next(&self) -> fdo::Result<()> {
18 let context = Context::current();
19 let daemon_read_lock = self.daemon().await;
20 if let Some(daemon) = daemon_read_lock.as_ref() {
21 daemon
22 .playback_skip_forward(context, 1)
23 .await
24 .map_err(|e| fdo::Error::Failed(e.to_string()))?;
25 }
26 drop(daemon_read_lock);
27 Ok(())
28 }
29
30 async fn previous(&self) -> fdo::Result<()> {
31 let context = Context::current();
32 let daemon_read_lock = self.daemon().await;
33 if let Some(daemon) = daemon_read_lock.as_ref() {
34 daemon
35 .playback_skip_backward(context, 1)
36 .await
37 .map_err(|e| fdo::Error::Failed(e.to_string()))?;
38 }
39 drop(daemon_read_lock);
40 Ok(())
41 }
42
43 async fn pause(&self) -> fdo::Result<()> {
44 let context = Context::current();
45 let daemon_read_lock = self.daemon().await;
46 if let Some(daemon) = daemon_read_lock.as_ref() {
47 daemon
48 .playback_pause(context)
49 .await
50 .map_err(|e| fdo::Error::Failed(e.to_string()))?;
51 }
52 drop(daemon_read_lock);
53 Ok(())
54 }
55
56 async fn play_pause(&self) -> fdo::Result<()> {
57 let context = Context::current();
58 let daemon_read_lock = self.daemon().await;
59 if let Some(daemon) = daemon_read_lock.as_ref() {
60 daemon
61 .playback_toggle(context)
62 .await
63 .map_err(|e| fdo::Error::Failed(e.to_string()))?;
64 }
65 drop(daemon_read_lock);
66 Ok(())
67 }
68
69 async fn stop(&self) -> fdo::Result<()> {
70 let daemon_read_lock = self.daemon().await;
71 if let Some(daemon) = daemon_read_lock.as_ref() {
72 let context = Context::current();
73 daemon
74 .playback_stop(context)
75 .await
76 .map_err(|e| fdo::Error::Failed(e.to_string()))?;
77 }
78 drop(daemon_read_lock);
79
80 Ok(())
81 }
82
83 async fn play(&self) -> fdo::Result<()> {
84 let context = Context::current();
85 let daemon_read_lock = self.daemon().await;
86 if let Some(daemon) = daemon_read_lock.as_ref() {
87 daemon
88 .playback_play(context)
89 .await
90 .map_err(|e| fdo::Error::Failed(e.to_string()))?;
91 }
92 drop(daemon_read_lock);
93 Ok(())
94 }
95
96 async fn open_uri(&self, uri: String) -> fdo::Result<()> {
97 log::info!("Opening URI: {uri}");
101
102 if !uri.starts_with("file://") {
104 return Err(fdo::Error::InvalidArgs(
105 "Only file:// URIs are supported".to_string(),
106 ));
107 }
108
109 let path = uri.strip_prefix("file://").unwrap();
111 let mut path = PathBuf::from_str(path)
112 .map_err(|_| fdo::Error::InvalidArgs("Invalid file path".to_string()))?;
113
114 path = percent_encoding::percent_decode_str(&path.to_string_lossy())
116 .decode_utf8()
117 .map_err(|_| fdo::Error::InvalidArgs("Invalid file path".to_string()))?
118 .into_owned()
119 .into();
120
121 if !SUPPORTED_MIME_TYPES
123 .iter()
124 .filter_map(|s| s.split('/').last())
125 .any(|ext| path.extension().is_some_and(|e| e == ext))
126 {
127 return Err(fdo::Error::InvalidArgs(
128 "File type not supported".to_string(),
129 ));
130 }
131
132 if path.starts_with("~") {
134 path = shellexpand::tilde(&path.to_string_lossy())
135 .into_owned()
136 .into();
137 }
138
139 log::debug!("Locating file: {}", path.display());
140
141 if !path.exists() {
143 return Err(fdo::Error::InvalidArgs("File does not exist".to_string()));
144 }
145
146 if !path.is_file() {
148 return Err(fdo::Error::InvalidArgs("Path is not a file".to_string()));
149 }
150
151 path = path.canonicalize().unwrap_or(path);
153
154 let daemon_read_lock = self.daemon().await;
156 if let Some(daemon) = daemon_read_lock.as_ref() {
157 let context = Context::current();
158 if let Some(song) = daemon
159 .library_song_get_by_path(context, path)
160 .await
161 .map_err(|e| fdo::Error::Failed(e.to_string()))?
162 {
163 let context = Context::current();
164 daemon
165 .queue_add(context, song.id.clone().into())
166 .await
167 .map_err(|e| fdo::Error::Failed(e.to_string()))?
168 .map_err(|e| fdo::Error::Failed(e.to_string()))?;
169 } else {
170 return Err(fdo::Error::Failed(
171 "Failed to find song in database".to_string(),
172 ));
173 }
174 }
175 drop(daemon_read_lock);
176
177 Ok(())
178 }
179
180 async fn playback_status(&self) -> fdo::Result<PlaybackStatus> {
181 let status = self.state.read().await.status;
182 match status {
183 Status::Stopped => Ok(PlaybackStatus::Stopped),
184 Status::Paused => Ok(PlaybackStatus::Paused),
185 Status::Playing => Ok(PlaybackStatus::Playing),
186 }
187 }
188
189 async fn loop_status(&self) -> fdo::Result<LoopStatus> {
190 let repeat_mode = self.state.read().await.repeat_mode;
191 match repeat_mode {
192 RepeatMode::None => Ok(LoopStatus::None),
193 RepeatMode::One => Ok(LoopStatus::Track),
194 RepeatMode::All => Ok(LoopStatus::Playlist),
195 }
196 }
197
198 async fn set_loop_status(&self, loop_status: LoopStatus) -> Result<(), ZbusError> {
199 let repeat_mode = match loop_status {
200 LoopStatus::None => RepeatMode::None,
201 LoopStatus::Track => RepeatMode::One,
202 LoopStatus::Playlist => RepeatMode::All,
203 };
204
205 let context = Context::current();
206
207 let daemon_read_lock = self.daemon().await;
208 if let Some(daemon) = daemon_read_lock.as_ref() {
209 daemon
210 .playback_repeat(context, repeat_mode)
211 .await
212 .map_err(|e| fdo::Error::Failed(e.to_string()))?;
213 }
214 drop(daemon_read_lock);
215
216 Ok(())
217 }
218
219 async fn rate(&self) -> fdo::Result<PlaybackRate> {
220 Ok(1.0)
221 }
222
223 async fn set_rate(&self, _: PlaybackRate) -> Result<(), ZbusError> {
224 Ok(())
225 }
226
227 async fn shuffle(&self) -> fdo::Result<bool> {
228 Ok(true)
232 }
233
234 async fn set_shuffle(&self, shuffle: bool) -> Result<(), ZbusError> {
235 if shuffle {
237 let context = Context::current();
238 let daemon_read_lock = self.daemon().await;
239 if let Some(daemon) = daemon_read_lock.as_ref() {
240 daemon
241 .playback_shuffle(context)
242 .await
243 .map_err(|e| fdo::Error::Failed(e.to_string()))?;
244 }
245 drop(daemon_read_lock);
246 }
247
248 Ok(())
249 }
250
251 async fn metadata(&self) -> fdo::Result<Metadata> {
252 let state = self.state.read().await;
253
254 Ok(metadata_from_opt_song(state.current_song.as_ref()))
255 }
256
257 async fn volume(&self) -> fdo::Result<Volume> {
258 let state = self.state.read().await;
259 if state.muted {
260 Ok(0.0)
261 } else {
262 Ok(f64::from(state.volume))
263 }
264 }
265
266 async fn set_volume(&self, volume: Volume) -> Result<(), ZbusError> {
267 let context = Context::current();
268 let daemon_read_lock = self.daemon().await;
269 if let Some(daemon) = daemon_read_lock.as_ref() {
270 #[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)]
271 daemon
272 .playback_volume(context, volume as f32)
273 .await
274 .map_err(|e| fdo::Error::Failed(e.to_string()))?;
275 }
276 drop(daemon_read_lock);
277 Ok(())
278 }
279
280 async fn position(&self) -> fdo::Result<Time> {
281 self.state.read().await.runtime.map_or_else(
282 || Ok(Time::from_micros(0)),
283 |runtime| {
284 Ok(Time::from_micros(
285 i64::try_from(runtime.seek_position.as_micros()).unwrap_or(i64::MAX),
286 ))
287 },
288 )
289 }
290
291 async fn seek(&self, offset: Time) -> fdo::Result<()> {
292 let context = Context::current();
294 let daemon_read_lock = self.daemon().await;
295 if let Some(daemon) = daemon_read_lock.as_ref() {
296 let seek_type = if offset.as_micros() > 0 {
297 SeekType::RelativeForwards
298 } else {
299 SeekType::RelativeBackwards
300 };
301
302 let offset = Duration::from_micros(offset.as_micros().unsigned_abs());
303
304 daemon
305 .playback_seek(context, seek_type, offset)
306 .await
307 .map_err(|e| fdo::Error::Failed(e.to_string()))?;
308 }
309 drop(daemon_read_lock);
310 Ok(())
311 }
312
313 async fn set_position(&self, track_id: TrackId, position: Time) -> fdo::Result<()> {
314 if Some(track_id) != self.metadata().await?.trackid() {
316 return Ok(());
317 }
318
319 let daemon_read_lock = self.daemon().await;
320 if let Some(daemon) = daemon_read_lock.as_ref() {
321 let maybe_state = self.state.read().await;
322 if let Some(song) = maybe_state.current_song.as_ref() {
323 let position = position.as_micros();
325 if position < 0 || u128::from(position.unsigned_abs()) > song.runtime.as_micros() {
326 return Ok(());
327 }
328
329 let context = Context::current();
330
331 daemon
332 .playback_seek(
333 context,
334 SeekType::Absolute,
335 Duration::from_micros(u64::try_from(position).unwrap_or_default()),
336 )
337 .await
338 .map_err(|e| fdo::Error::Failed(e.to_string()))?;
339 }
340 drop(maybe_state);
341 }
342 drop(daemon_read_lock);
343
344 Ok(())
345 }
346
347 async fn minimum_rate(&self) -> fdo::Result<PlaybackRate> {
348 Ok(1.0)
349 }
350
351 async fn maximum_rate(&self) -> fdo::Result<PlaybackRate> {
352 Ok(1.0)
353 }
354
355 async fn can_go_next(&self) -> fdo::Result<bool> {
356 Ok(true)
357 }
358
359 async fn can_go_previous(&self) -> fdo::Result<bool> {
360 Ok(true)
361 }
362
363 async fn can_play(&self) -> fdo::Result<bool> {
364 Ok(true)
365 }
366
367 async fn can_pause(&self) -> fdo::Result<bool> {
368 Ok(true)
369 }
370
371 async fn can_seek(&self) -> fdo::Result<bool> {
372 Ok(true)
373 }
374
375 async fn can_control(&self) -> fdo::Result<bool> {
376 Ok(true)
377 }
378}
379
380#[cfg(test)]
384mod tests {
385 use std::sync::{mpsc::Receiver, Arc};
386
387 use mecomp_core::{
388 audio::{commands::AudioCommand, AudioKernelSender},
389 test_utils::init,
390 udp::StateChange,
391 };
392
393 use mecomp_storage::db::schemas::song::Song;
394 use pretty_assertions::{assert_eq, assert_ne};
395 use rstest::rstest;
396 use tempfile::TempDir;
397
398 use super::*;
399 use crate::test_utils::fixtures;
400
401 #[rstest]
412 #[timeout(Duration::from_secs(10))]
413 #[tokio::test(flavor = "multi_thread", worker_threads = 3)]
414 async fn test_next(
415 #[future] fixtures: (
416 Mpris,
417 Receiver<StateChange>,
418 TempDir,
419 Arc<AudioKernelSender>,
420 ),
421 ) {
422 init();
423 let (mpris, event_rx, tempdir, audio_kernel) = fixtures.await;
424
425 assert_eq!(mpris.can_go_next().await.unwrap(), true);
426
427 let context = Context::current();
429 let songs: Vec<Song> = mpris
430 .daemon
431 .read()
432 .await
433 .as_ref()
434 .unwrap()
435 .library_songs_full(context)
436 .await
437 .unwrap()
438 .unwrap()
439 .to_vec();
440 assert_eq!(songs.len(), 4);
441 audio_kernel.send(AudioCommand::Queue(
443 mecomp_core::audio::commands::QueueCommand::AddToQueue(Box::new(songs.into())),
444 ));
445
446 let Ok(StateChange::TrackChanged(Some(first_song))) = event_rx.recv() else {
447 panic!("Expected a TrackChanged event, but got something else");
448 };
449 assert_eq!(
450 event_rx.recv(),
451 Ok(StateChange::StatusChanged(Status::Playing))
452 );
453
454 mpris.next().await.unwrap();
456
457 let Ok(StateChange::TrackChanged(Some(second_song))) = event_rx.recv() else {
458 panic!("Expected a TrackChanged event, but got something else");
459 };
460
461 assert_ne!(first_song, second_song);
463
464 drop(tempdir);
465 }
466
467 #[rstest]
468 #[timeout(Duration::from_secs(10))]
469 #[tokio::test]
470 async fn test_next_maintains_status(
471 #[future] fixtures: (
472 Mpris,
473 Receiver<StateChange>,
474 TempDir,
475 Arc<AudioKernelSender>,
476 ),
477 ) {
478 init();
479 let (mpris, event_rx, tempdir, audio_kernel) = fixtures.await;
480
481 assert_eq!(mpris.can_go_next().await.unwrap(), true);
482
483 let context = Context::current();
485 let songs: Vec<Song> = mpris
486 .daemon
487 .read()
488 .await
489 .as_ref()
490 .unwrap()
491 .library_songs_full(context)
492 .await
493 .unwrap()
494 .unwrap()
495 .to_vec();
496 assert_eq!(songs.len(), 4);
497 audio_kernel.send(AudioCommand::Queue(
499 mecomp_core::audio::commands::QueueCommand::AddToQueue(Box::new(songs.into())),
500 ));
501 let Ok(StateChange::TrackChanged(Some(first_song))) = event_rx.recv() else {
502 panic!("Expected a TrackChanged event, but got something else");
503 };
504 assert_eq!(
505 event_rx.recv(),
506 Ok(StateChange::StatusChanged(Status::Playing))
507 );
508
509 mpris.stop().await.unwrap();
513 assert_eq!(
514 event_rx.recv(),
515 Ok(StateChange::Seeked(Duration::from_secs(0)))
516 );
517 assert_eq!(
518 event_rx.recv(),
519 Ok(StateChange::StatusChanged(Status::Stopped))
520 );
521
522 mpris.next().await.unwrap();
524 let Ok(StateChange::TrackChanged(Some(second_song))) = event_rx.recv() else {
525 panic!("Expected a TrackChanged event, but got something else");
526 };
527 assert!(event_rx.try_recv().is_err());
529
530 assert_ne!(first_song, second_song);
532
533 mpris.pause().await.unwrap();
535 assert_eq!(
536 event_rx.recv(),
537 Ok(StateChange::StatusChanged(Status::Paused))
538 );
539
540 mpris.next().await.unwrap();
542 let Ok(StateChange::TrackChanged(Some(third_song))) = event_rx.recv() else {
543 panic!("Expected a TrackChanged event, but got something else");
544 };
545 assert!(event_rx.try_recv().is_err());
547
548 assert_ne!(second_song, third_song);
550
551 drop(tempdir);
552 }
553
554 #[rstest]
555 #[timeout(Duration::from_secs(10))]
556 #[tokio::test]
557 async fn test_next_no_next_track(
558 #[future] fixtures: (
559 Mpris,
560 Receiver<StateChange>,
561 TempDir,
562 Arc<AudioKernelSender>,
563 ),
564 ) {
565 init();
566 let (mpris, event_rx, tempdir, audio_kernel) = fixtures.await;
567
568 assert_eq!(mpris.can_go_next().await.unwrap(), true);
569
570 let context = Context::current();
572 let songs: Vec<Song> = mpris
573 .daemon
574 .read()
575 .await
576 .as_ref()
577 .unwrap()
578 .library_songs_full(context)
579 .await
580 .unwrap()
581 .unwrap()
582 .to_vec();
583 assert_eq!(songs.len(), 4);
584 audio_kernel.send(AudioCommand::Queue(
586 mecomp_core::audio::commands::QueueCommand::AddToQueue(Box::new(
587 songs[0].clone().into(),
588 )),
589 ));
590 let _ = event_rx.recv();
591 let _ = event_rx.recv();
592
593 mpris.next().await.unwrap();
596 assert_eq!(event_rx.recv(), Ok(StateChange::TrackChanged(None)));
597 assert_eq!(
598 event_rx.recv(),
599 Ok(StateChange::StatusChanged(Status::Stopped))
600 );
601 drop(tempdir);
602 }
603
604 #[rstest]
615 #[timeout(Duration::from_secs(10))]
616 #[tokio::test]
617 async fn test_prev(
618 #[future] fixtures: (
619 Mpris,
620 Receiver<StateChange>,
621 TempDir,
622 Arc<AudioKernelSender>,
623 ),
624 ) {
625 init();
626 let (mpris, event_rx, tempdir, audio_kernel) = fixtures.await;
627
628 assert_eq!(mpris.can_go_previous().await.unwrap(), true);
629
630 let context = Context::current();
632 let songs: Vec<Song> = mpris
633 .daemon
634 .read()
635 .await
636 .as_ref()
637 .unwrap()
638 .library_songs_full(context)
639 .await
640 .unwrap()
641 .unwrap()
642 .to_vec();
643 assert_eq!(songs.len(), 4);
644 let third_song: mecomp_storage::db::schemas::Thing = songs[2].id.clone().into();
645 let fourth_song: mecomp_storage::db::schemas::Thing = songs[3].id.clone().into();
646
647 audio_kernel.send(AudioCommand::Queue(
649 mecomp_core::audio::commands::QueueCommand::AddToQueue(Box::new(songs.into())),
650 ));
651 let _ = event_rx.recv();
652 let _ = event_rx.recv();
653
654 audio_kernel.send(AudioCommand::Queue(
656 mecomp_core::audio::commands::QueueCommand::SetPosition(3),
657 ));
658 assert_eq!(
659 event_rx.recv(),
660 Ok(StateChange::TrackChanged(Some(fourth_song.clone())))
661 );
662
663 mpris.previous().await.unwrap();
665 assert_eq!(
667 event_rx.recv(),
668 Ok(StateChange::TrackChanged(Some(third_song))),
669 );
670
671 drop(tempdir);
672 }
673 #[rstest]
674 #[timeout(Duration::from_secs(10))]
675 #[tokio::test]
676 async fn test_prev_maintains_state(
677 #[future] fixtures: (
678 Mpris,
679 Receiver<StateChange>,
680 TempDir,
681 Arc<AudioKernelSender>,
682 ),
683 ) {
684 init();
685 let (mpris, event_rx, tempdir, audio_kernel) = fixtures.await;
686
687 assert_eq!(mpris.can_go_previous().await.unwrap(), true);
688
689 let context = Context::current();
691 let songs: Vec<Song> = mpris
692 .daemon
693 .read()
694 .await
695 .as_ref()
696 .unwrap()
697 .library_songs_full(context)
698 .await
699 .unwrap()
700 .unwrap()
701 .to_vec();
702 assert_eq!(songs.len(), 4);
703 let second_song: mecomp_storage::db::schemas::Thing = songs[1].id.clone().into();
704 let third_song: mecomp_storage::db::schemas::Thing = songs[2].id.clone().into();
705 let fourth_song: mecomp_storage::db::schemas::Thing = songs[3].id.clone().into();
706
707 audio_kernel.send(AudioCommand::Queue(
709 mecomp_core::audio::commands::QueueCommand::AddToQueue(Box::new(songs.into())),
710 ));
711 let _ = event_rx.recv();
712 let _ = event_rx.recv();
713
714 audio_kernel.send(AudioCommand::Queue(
716 mecomp_core::audio::commands::QueueCommand::SetPosition(3),
717 ));
718 assert_eq!(
719 event_rx.recv(),
720 Ok(StateChange::TrackChanged(Some(fourth_song.clone())))
721 );
722
723 mpris.stop().await.unwrap();
727 assert_eq!(
728 event_rx.recv(),
729 Ok(StateChange::Seeked(Duration::from_secs(0)))
730 );
731 assert_eq!(
732 event_rx.recv(),
733 Ok(StateChange::StatusChanged(Status::Stopped))
734 );
735 mpris.previous().await.unwrap();
737 assert_eq!(
739 event_rx.recv(),
740 Ok(StateChange::TrackChanged(Some(third_song))),
741 );
742 assert!(event_rx.try_recv().is_err());
744
745 mpris.pause().await.unwrap();
747 assert!(matches!(
748 event_rx.recv(),
749 Ok(StateChange::StatusChanged(Status::Paused))
750 ));
751
752 mpris.previous().await.unwrap();
754 assert_eq!(
756 event_rx.recv(),
757 Ok(StateChange::TrackChanged(Some(second_song))),
758 );
759 assert!(event_rx.try_recv().is_err());
761
762 drop(tempdir);
763 }
764
765 #[rstest]
766 #[timeout(Duration::from_secs(10))]
767 #[tokio::test]
768 async fn test_prev_no_prev_track(
769 #[future] fixtures: (
770 Mpris,
771 Receiver<StateChange>,
772 TempDir,
773 Arc<AudioKernelSender>,
774 ),
775 ) {
776 init();
777 let (mpris, event_rx, tempdir, audio_kernel) = fixtures.await;
778
779 assert_eq!(mpris.can_go_previous().await.unwrap(), true);
780
781 let context = Context::current();
783 let songs: Vec<Song> = mpris
784 .daemon
785 .read()
786 .await
787 .as_ref()
788 .unwrap()
789 .library_songs_full(context)
790 .await
791 .unwrap()
792 .unwrap()
793 .to_vec();
794 assert_eq!(songs.len(), 4);
795
796 audio_kernel.send(AudioCommand::Queue(
798 mecomp_core::audio::commands::QueueCommand::AddToQueue(Box::new(
799 songs[0].clone().into(),
800 )),
801 ));
802 let _ = event_rx.recv();
803 let _ = event_rx.recv();
804
805 mpris.previous().await.unwrap();
808 assert_eq!(event_rx.recv(), Ok(StateChange::TrackChanged(None)),);
810 assert!(matches!(
812 event_rx.recv(),
813 Ok(StateChange::StatusChanged(Status::Stopped))
814 ));
815
816 drop(tempdir);
817 }
818
819 #[rstest]
862 #[timeout(Duration::from_secs(10))]
863 #[tokio::test]
864 async fn test_play_pause_stop(
865 #[future] fixtures: (
866 Mpris,
867 Receiver<StateChange>,
868 TempDir,
869 Arc<AudioKernelSender>,
870 ),
871 ) {
872 init();
873 let (mpris, event_rx, tempdir, audio_kernel) = fixtures.await;
874
875 assert_eq!(mpris.can_pause().await.unwrap(), true);
876 assert_eq!(mpris.can_play().await.unwrap(), true);
877 assert_eq!(mpris.can_control().await.unwrap(), true);
878
879 let context = Context::current();
881 let songs: Vec<Song> = mpris
882 .daemon
883 .read()
884 .await
885 .as_ref()
886 .unwrap()
887 .library_songs_full(context)
888 .await
889 .unwrap()
890 .unwrap()
891 .to_vec();
892 assert_eq!(songs.len(), 4);
893 let first_song = songs[0].clone();
894
895 mpris.play().await.unwrap();
897 let event = event_rx.try_recv();
898 assert!(
899 event.is_err(),
900 "Expected not to receive an event, but got {:?}",
901 event
902 );
903
904 audio_kernel.send(AudioCommand::Queue(
906 mecomp_core::audio::commands::QueueCommand::AddToQueue(Box::new(
907 first_song.clone().into(),
908 )),
909 ));
910
911 assert_eq!(
912 event_rx.recv(),
913 Ok(StateChange::TrackChanged(Some(
914 first_song.id.clone().into()
915 )))
916 );
917 assert_eq!(
918 event_rx.recv(),
919 Ok(StateChange::StatusChanged(Status::Playing))
920 );
921
922 mpris.pause().await.unwrap();
924 assert_eq!(
925 event_rx.recv(),
926 Ok(StateChange::StatusChanged(Status::Paused))
927 );
928
929 mpris.pause().await.unwrap();
931 let event = event_rx.try_recv();
932 assert!(
933 event.is_err(),
934 "Expected not to receive an event, but got {:?}",
935 event
936 );
937
938 mpris.play().await.unwrap();
943 assert_eq!(
944 event_rx.recv(),
945 Ok(StateChange::StatusChanged(Status::Playing))
946 );
947
948 mpris.play().await.unwrap();
950 let event = event_rx.try_recv();
951 assert!(
952 event.is_err(),
953 "Expected not to receive an event, but got {:?}",
954 event
955 );
956
957 mpris.play_pause().await.unwrap();
965 assert_eq!(
966 event_rx.recv(),
967 Ok(StateChange::StatusChanged(Status::Paused))
968 );
969
970 mpris.play_pause().await.unwrap();
972 assert_eq!(
973 event_rx.recv(),
974 Ok(StateChange::StatusChanged(Status::Playing))
975 );
976
977 mpris.stop().await.unwrap();
979 assert_eq!(
980 event_rx.recv(),
981 Ok(StateChange::Seeked(Duration::from_secs(0)))
982 );
983 assert_eq!(
984 event_rx.recv(),
985 Ok(StateChange::StatusChanged(Status::Stopped))
986 );
987 mpris.play_pause().await.unwrap();
988 assert_eq!(
989 event_rx.recv(),
990 Ok(StateChange::StatusChanged(Status::Playing))
991 );
992
993 mpris.stop().await.unwrap();
995 assert_eq!(
996 event_rx.recv(),
997 Ok(StateChange::Seeked(Duration::from_secs(0)))
998 );
999 assert_eq!(
1000 event_rx.recv(),
1001 Ok(StateChange::StatusChanged(Status::Stopped))
1002 );
1003
1004 mpris.stop().await.unwrap();
1006 let event = event_rx.try_recv();
1007 assert!(
1008 event.is_err(),
1009 "Expected not to receive an event, but got {:?}",
1010 event
1011 );
1012
1013 drop(tempdir);
1017 }
1018
1019 #[rstest]
1035 #[timeout(Duration::from_secs(10))]
1036 #[tokio::test]
1037 async fn test_open_uri(
1038 #[future] fixtures: (
1039 Mpris,
1040 Receiver<StateChange>,
1041 TempDir,
1042 Arc<AudioKernelSender>,
1043 ),
1044 ) {
1045 init();
1046 let (mpris, event_rx, tempdir, _) = fixtures.await;
1047
1048 let context = Context::current();
1050 let songs: Vec<Song> = mpris
1051 .daemon
1052 .read()
1053 .await
1054 .as_ref()
1055 .unwrap()
1056 .library_songs_full(context)
1057 .await
1058 .unwrap()
1059 .unwrap()
1060 .to_vec();
1061 assert_eq!(songs.len(), 4);
1062 let first_song = songs[0].clone();
1063
1064 let file_uri = format!("file://{}", first_song.path.display());
1068 mpris.open_uri(file_uri).await.unwrap();
1069 assert_eq!(
1070 event_rx.recv(),
1071 Ok(StateChange::TrackChanged(Some(
1072 first_song.id.clone().into()
1073 )))
1074 );
1075 assert_eq!(
1077 event_rx.recv(),
1078 Ok(StateChange::StatusChanged(Status::Playing))
1079 );
1080
1081 let file_uri = "http://example.com/song.mp3".to_string();
1086 let result = mpris.open_uri(file_uri).await;
1087 assert!(result.is_err());
1088 let file_uri = "file://".to_string();
1090 let result = mpris.open_uri(file_uri).await;
1091 assert!(result.is_err());
1092 let file_uri = format!("file://{}", tempdir.path().display());
1094 let result = mpris.open_uri(file_uri).await;
1095 assert!(result.is_err());
1096 let file_uri = "file:///nonexistent.mp3".to_string();
1098 let result = mpris.open_uri(file_uri).await;
1099 assert!(result.is_err());
1100 std::fs::write(tempdir.path().join("unsupported.txt"), "unsupported")
1102 .expect("Failed to write file");
1103 let file_uri = format!(
1104 "file:///{}",
1105 tempdir.path().join("unsupported.txt").display()
1106 );
1107 let result = mpris.open_uri(file_uri).await;
1108 assert!(result.is_err());
1109
1110 drop(tempdir);
1114 }
1115
1116 #[rstest]
1121 #[timeout(Duration::from_secs(10))]
1122 #[tokio::test]
1123 async fn test_playback_status(
1124 #[future] fixtures: (
1125 Mpris,
1126 Receiver<StateChange>,
1127 TempDir,
1128 Arc<AudioKernelSender>,
1129 ),
1130 ) {
1131 init();
1132 let (mpris, event_rx, tempdir, audio_kernel) = fixtures.await;
1133
1134 let context = Context::current();
1136 let songs: Vec<Song> = mpris
1137 .daemon
1138 .read()
1139 .await
1140 .as_ref()
1141 .unwrap()
1142 .library_songs_full(context)
1143 .await
1144 .unwrap()
1145 .unwrap()
1146 .to_vec();
1147 assert_eq!(songs.len(), 4);
1148 let first_song = songs[0].clone();
1149
1150 assert_eq!(
1153 mpris.playback_status().await.unwrap(),
1154 PlaybackStatus::Stopped
1155 );
1156
1157 audio_kernel.send(AudioCommand::Queue(
1159 mecomp_core::audio::commands::QueueCommand::AddToQueue(Box::new(
1160 first_song.clone().into(),
1161 )),
1162 ));
1163
1164 mpris.pause().await.unwrap();
1166
1167 let mut events = [false; 3];
1169 for _ in 0..3 {
1170 let event = event_rx.recv().unwrap();
1171
1172 match event {
1173 StateChange::TrackChanged(Some(_)) => {
1174 mpris.state.write().await.current_song = Some(first_song.clone());
1175 events[0] = true;
1176 }
1177 StateChange::StatusChanged(Status::Playing) => {
1178 mpris.state.write().await.status = Status::Playing;
1179 assert_eq!(
1180 mpris.playback_status().await.unwrap(),
1181 PlaybackStatus::Playing
1182 );
1183 events[1] = true;
1184 }
1185 StateChange::StatusChanged(Status::Paused) => {
1186 mpris.state.write().await.status = Status::Paused;
1187 assert_eq!(
1188 mpris.playback_status().await.unwrap(),
1189 PlaybackStatus::Paused
1190 );
1191 events[2] = true;
1192 }
1193 _ => panic!("Unexpected event: {:?}", event),
1194 }
1195 }
1196
1197 assert!(events.iter().all(|&e| e));
1198
1199 drop(tempdir);
1200 }
1201
1202 #[rstest]
1214 #[timeout(Duration::from_secs(10))]
1215 #[tokio::test]
1216 async fn test_loop_status(
1217 #[future] fixtures: (
1218 Mpris,
1219 Receiver<StateChange>,
1220 TempDir,
1221 Arc<AudioKernelSender>,
1222 ),
1223 ) {
1224 init();
1225 let (mpris, event_rx, _, _) = fixtures.await;
1226
1227 assert_eq!(mpris.loop_status().await.unwrap(), LoopStatus::None);
1230
1231 mpris.set_loop_status(LoopStatus::Track).await.unwrap();
1233 if let Ok(StateChange::RepeatModeChanged(RepeatMode::One)) = event_rx.recv() {
1234 mpris.state.write().await.repeat_mode = RepeatMode::One;
1235 } else {
1236 panic!("Expected a RepeatModeChanged event, but got something else");
1237 };
1238 assert_eq!(mpris.loop_status().await.unwrap(), LoopStatus::Track);
1239
1240 mpris.set_loop_status(LoopStatus::Playlist).await.unwrap();
1242 if let Ok(StateChange::RepeatModeChanged(RepeatMode::All)) = event_rx.recv() {
1243 mpris.state.write().await.repeat_mode = RepeatMode::All;
1244 } else {
1245 panic!("Expected a RepeatModeChanged event, but got something else");
1246 };
1247 assert_eq!(mpris.loop_status().await.unwrap(), LoopStatus::Playlist);
1248
1249 mpris.set_loop_status(LoopStatus::None).await.unwrap();
1251 if let Ok(StateChange::RepeatModeChanged(RepeatMode::None)) = event_rx.recv() {
1252 mpris.state.write().await.repeat_mode = RepeatMode::None;
1253 } else {
1254 panic!("Expected a RepeatModeChanged event, but got something else");
1255 };
1256 assert_eq!(mpris.loop_status().await.unwrap(), LoopStatus::None);
1257 }
1258
1259 #[rstest]
1275 #[timeout(Duration::from_secs(10))]
1276 #[tokio::test]
1277 async fn test_rate(
1278 #[future] fixtures: (
1279 Mpris,
1280 Receiver<StateChange>,
1281 TempDir,
1282 Arc<AudioKernelSender>,
1283 ),
1284 ) {
1285 init();
1286 let (mpris, event_rx, _, _) = fixtures.await;
1287
1288 assert_eq!(mpris.rate().await.unwrap(), 1.0);
1290
1291 assert_eq!(mpris.minimum_rate().await.unwrap(), 1.0);
1293
1294 assert_eq!(mpris.maximum_rate().await.unwrap(), 1.0);
1296
1297 let result = mpris.set_rate(1.0).await;
1300 assert!(result.is_ok());
1301 assert!(event_rx.try_recv().is_err());
1302 }
1303
1304 #[rstest]
1320 #[timeout(Duration::from_secs(10))]
1321 #[tokio::test]
1322 async fn test_shuffle(
1323 #[future] fixtures: (
1324 Mpris,
1325 Receiver<StateChange>,
1326 TempDir,
1327 Arc<AudioKernelSender>,
1328 ),
1329 ) {
1330 init();
1331 let (mpris, event_rx, _, _) = fixtures.await;
1332
1333 assert_eq!(mpris.shuffle().await.unwrap(), true);
1335
1336 mpris.set_shuffle(true).await.unwrap();
1339 assert_eq!(mpris.shuffle().await.unwrap(), true);
1340 assert!(event_rx.try_recv().is_err());
1341
1342 mpris.set_shuffle(false).await.unwrap();
1344 assert_eq!(mpris.shuffle().await.unwrap(), true);
1345 assert!(event_rx.try_recv().is_err());
1346 }
1347
1348 #[rstest]
1356 #[timeout(Duration::from_secs(10))]
1357 #[tokio::test]
1358 async fn test_metadata(
1359 #[future] fixtures: (
1360 Mpris,
1361 Receiver<StateChange>,
1362 TempDir,
1363 Arc<AudioKernelSender>,
1364 ),
1365 ) {
1366 init();
1367 let (mpris, event_rx, tempdir, audio_kernel) = fixtures.await;
1368
1369 let context = Context::current();
1371 let songs: Vec<Song> = mpris
1372 .daemon
1373 .read()
1374 .await
1375 .as_ref()
1376 .unwrap()
1377 .library_songs_full(context)
1378 .await
1379 .unwrap()
1380 .unwrap()
1381 .to_vec();
1382 assert_eq!(songs.len(), 4);
1383 let first_song = songs[0].clone();
1384
1385 assert_eq!(
1388 mpris.metadata().await.unwrap(),
1389 Metadata::builder().trackid(TrackId::NO_TRACK).build()
1390 );
1391
1392 audio_kernel.send(AudioCommand::Queue(
1394 mecomp_core::audio::commands::QueueCommand::AddToQueue(Box::new(
1395 first_song.clone().into(),
1396 )),
1397 ));
1398
1399 assert_eq!(
1400 event_rx.recv(),
1401 Ok(StateChange::TrackChanged(Some(
1402 first_song.id.clone().into()
1403 )))
1404 );
1405
1406 *mpris.state.write().await = mpris
1407 .daemon
1408 .read()
1409 .await
1410 .as_ref()
1411 .unwrap()
1412 .state_audio(Context::current())
1413 .await
1414 .unwrap()
1415 .unwrap();
1416
1417 let metadata = mpris.metadata().await.unwrap();
1419 assert_eq!(metadata, metadata_from_opt_song(Some(&first_song)));
1420 assert_ne!(
1421 metadata,
1422 Metadata::builder().trackid(TrackId::NO_TRACK).build()
1423 );
1424
1425 drop(tempdir);
1426 }
1427
1428 #[rstest]
1433 #[timeout(Duration::from_secs(10))]
1434 #[tokio::test]
1435 async fn test_volume(
1436 #[future] fixtures: (
1437 Mpris,
1438 Receiver<StateChange>,
1439 TempDir,
1440 Arc<AudioKernelSender>,
1441 ),
1442 ) {
1443 let (mpris, event_rx, _, _) = fixtures.await;
1444
1445 assert_eq!(mpris.volume().await.unwrap(), 1.0);
1447
1448 mpris.set_volume(-1.0).await.unwrap();
1450 if let Ok(StateChange::VolumeChanged(0.0)) = event_rx.recv() {
1451 mpris.state.write().await.volume = 0.0;
1452 assert_eq!(mpris.volume().await.unwrap(), 0.0);
1453 } else {
1454 panic!("Expected a VolumeChanged event, but got something else");
1455 }
1456
1457 mpris.set_volume(1.0).await.unwrap();
1459 if let Ok(StateChange::VolumeChanged(1.0)) = event_rx.recv() {
1460 mpris.state.write().await.volume = 1.0;
1461 assert_eq!(mpris.volume().await.unwrap(), 1.0);
1462 } else {
1463 panic!("Expected a VolumeChanged event, but got something else");
1464 }
1465
1466 mpris.set_volume(1.0).await.unwrap();
1468 assert!(event_rx.try_recv().is_err());
1469 }
1470
1471 #[rstest]
1484 #[timeout(Duration::from_secs(10))]
1485 #[tokio::test]
1486 async fn test_position(
1487 #[future] fixtures: (
1488 Mpris,
1489 Receiver<StateChange>,
1490 TempDir,
1491 Arc<AudioKernelSender>,
1492 ),
1493 ) {
1494 init();
1495 let (mpris, event_rx, tempdir, audio_kernel) = fixtures.await;
1496
1497 assert!(mpris.can_seek().await.unwrap());
1498
1499 let context = Context::current();
1501 let songs: Vec<Song> = mpris
1502 .daemon
1503 .read()
1504 .await
1505 .as_ref()
1506 .unwrap()
1507 .library_songs_full(context)
1508 .await
1509 .unwrap()
1510 .unwrap()
1511 .to_vec();
1512 assert_eq!(songs.len(), 4);
1513 let first_song = songs[0].clone();
1514
1515 assert_eq!(mpris.position().await.unwrap(), Time::from_micros(0));
1518
1519 audio_kernel.send(AudioCommand::Queue(
1521 mecomp_core::audio::commands::QueueCommand::AddToQueue(Box::new(
1522 first_song.clone().into(),
1523 )),
1524 ));
1525 audio_kernel.send(AudioCommand::Pause);
1526 let _ = event_rx.recv().unwrap();
1527 let _ = event_rx.recv().unwrap();
1528 let _ = event_rx.recv().unwrap();
1529 *mpris.state.write().await = mpris
1531 .daemon
1532 .read()
1533 .await
1534 .as_ref()
1535 .unwrap()
1536 .state_audio(Context::current())
1537 .await
1538 .unwrap()
1539 .unwrap();
1540
1541 let first_song_track_id = mpris.metadata().await.unwrap().trackid().unwrap();
1542
1543 mpris
1545 .set_position(first_song_track_id.clone(), Time::from_secs(2))
1546 .await
1547 .unwrap();
1548 assert_eq!(
1549 event_rx.recv(),
1550 Ok(StateChange::Seeked(Duration::from_secs(2)))
1551 );
1552
1553 mpris
1555 .set_position(first_song_track_id.clone(), Time::from_secs(-1))
1556 .await
1557 .unwrap();
1558 assert!(event_rx.try_recv().is_err());
1559
1560 mpris
1562 .set_position(first_song_track_id.clone(), Time::from_secs(100))
1563 .await
1564 .unwrap();
1565 assert!(event_rx.try_recv().is_err());
1566
1567 assert!(mpris.can_seek().await.unwrap());
1570
1571 mpris.seek(Time::from_secs(1)).await.unwrap();
1572 assert_eq!(
1573 event_rx.recv(),
1574 Ok(StateChange::Seeked(Duration::from_secs(3)))
1575 );
1576
1577 mpris.seek(Time::from_secs(-1)).await.unwrap();
1578 assert_eq!(
1579 event_rx.recv(),
1580 Ok(StateChange::Seeked(Duration::from_secs(2)))
1581 );
1582
1583 drop(tempdir);
1584 }
1585}