1use std::{path::PathBuf, str::FromStr, time::Duration};
6
7use mecomp_core::state::{RepeatMode, SeekType, Status};
8use mpris_server::{
9 LoopStatus, Metadata, PlaybackRate, PlaybackStatus, PlayerInterface, Time, TrackId, Volume,
10 zbus::{Error as ZbusError, fdo},
11};
12use tarpc::context::Context;
13
14use crate::{Mpris, interfaces::root::SUPPORTED_MIME_TYPES, metadata_from_opt_song};
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('/').next_back())
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::{Arc, mpsc::Receiver};
386
387 use mecomp_core::{
388 audio::{
389 AudioKernelSender,
390 commands::{AudioCommand, QueueCommand},
391 },
392 test_utils::init,
393 udp::StateChange,
394 };
395
396 use mecomp_storage::db::schemas::song::SongBrief;
397 use pretty_assertions::{assert_eq, assert_ne};
398 use rstest::rstest;
399 use tempfile::TempDir;
400
401 use super::*;
402 use crate::test_utils::fixtures;
403
404 #[rstest]
415 #[timeout(Duration::from_secs(10))]
416 #[tokio::test(flavor = "multi_thread", worker_threads = 3)]
417 async fn test_next(
418 #[future] fixtures: (
419 Mpris,
420 Receiver<StateChange>,
421 TempDir,
422 Arc<AudioKernelSender>,
423 ),
424 ) {
425 init();
426 let (mpris, event_rx, tempdir, audio_kernel) = fixtures.await;
427
428 assert_eq!(mpris.can_go_next().await.unwrap(), true);
429
430 let context = Context::current();
432 let songs: Vec<SongBrief> = mpris
433 .daemon
434 .read()
435 .await
436 .as_ref()
437 .unwrap()
438 .library_songs_brief(context)
439 .await
440 .unwrap()
441 .unwrap()
442 .to_vec();
443 assert_eq!(songs.len(), 4);
444 audio_kernel.send(AudioCommand::Queue(QueueCommand::AddToQueue(songs.into())));
446
447 let Ok(StateChange::TrackChanged(Some(first_song))) = event_rx.recv() else {
448 panic!("Expected a TrackChanged event, but got something else");
449 };
450 assert_eq!(
451 event_rx.recv(),
452 Ok(StateChange::StatusChanged(Status::Playing))
453 );
454
455 mpris.next().await.unwrap();
457
458 let Ok(StateChange::TrackChanged(Some(second_song))) = event_rx.recv() else {
459 panic!("Expected a TrackChanged event, but got something else");
460 };
461
462 assert_ne!(first_song, second_song);
464
465 drop(tempdir);
466 }
467
468 #[rstest]
469 #[timeout(Duration::from_secs(10))]
470 #[tokio::test]
471 async fn test_next_maintains_status(
472 #[future] fixtures: (
473 Mpris,
474 Receiver<StateChange>,
475 TempDir,
476 Arc<AudioKernelSender>,
477 ),
478 ) {
479 init();
480 let (mpris, event_rx, tempdir, audio_kernel) = fixtures.await;
481
482 assert_eq!(mpris.can_go_next().await.unwrap(), true);
483
484 let context = Context::current();
486 let songs: Vec<SongBrief> = mpris
487 .daemon
488 .read()
489 .await
490 .as_ref()
491 .unwrap()
492 .library_songs_brief(context)
493 .await
494 .unwrap()
495 .unwrap()
496 .to_vec();
497 assert_eq!(songs.len(), 4);
498 audio_kernel.send(AudioCommand::Queue(QueueCommand::AddToQueue(songs.into())));
500 let Ok(StateChange::TrackChanged(Some(first_song))) = event_rx.recv() else {
501 panic!("Expected a TrackChanged event, but got something else");
502 };
503 assert_eq!(
504 event_rx.recv(),
505 Ok(StateChange::StatusChanged(Status::Playing))
506 );
507
508 mpris.stop().await.unwrap();
512 assert_eq!(
513 event_rx.recv(),
514 Ok(StateChange::Seeked(Duration::from_secs(0)))
515 );
516 assert_eq!(
517 event_rx.recv(),
518 Ok(StateChange::StatusChanged(Status::Stopped))
519 );
520
521 mpris.next().await.unwrap();
523 let Ok(StateChange::TrackChanged(Some(second_song))) = event_rx.recv() else {
524 panic!("Expected a TrackChanged event, but got something else");
525 };
526 assert!(event_rx.try_recv().is_err());
528
529 assert_ne!(first_song, second_song);
531
532 mpris.pause().await.unwrap();
534 assert_eq!(
535 event_rx.recv(),
536 Ok(StateChange::StatusChanged(Status::Paused))
537 );
538
539 mpris.next().await.unwrap();
541 let Ok(StateChange::TrackChanged(Some(third_song))) = event_rx.recv() else {
542 panic!("Expected a TrackChanged event, but got something else");
543 };
544 assert!(event_rx.try_recv().is_err());
546
547 assert_ne!(second_song, third_song);
549
550 drop(tempdir);
551 }
552
553 #[rstest]
554 #[timeout(Duration::from_secs(10))]
555 #[tokio::test]
556 async fn test_next_no_next_track(
557 #[future] fixtures: (
558 Mpris,
559 Receiver<StateChange>,
560 TempDir,
561 Arc<AudioKernelSender>,
562 ),
563 ) {
564 init();
565 let (mpris, event_rx, tempdir, audio_kernel) = fixtures.await;
566
567 assert_eq!(mpris.can_go_next().await.unwrap(), true);
568
569 let context = Context::current();
571 let songs: Vec<SongBrief> = mpris
572 .daemon
573 .read()
574 .await
575 .as_ref()
576 .unwrap()
577 .library_songs_brief(context)
578 .await
579 .unwrap()
580 .unwrap()
581 .to_vec();
582 assert_eq!(songs.len(), 4);
583 audio_kernel.send(AudioCommand::Queue(QueueCommand::AddToQueue(
585 songs[0].clone().into(),
586 )));
587 let _ = event_rx.recv();
588 let _ = event_rx.recv();
589
590 mpris.next().await.unwrap();
593 assert_eq!(event_rx.recv(), Ok(StateChange::TrackChanged(None)));
594 assert_eq!(
595 event_rx.recv(),
596 Ok(StateChange::StatusChanged(Status::Stopped))
597 );
598 drop(tempdir);
599 }
600
601 #[rstest]
612 #[timeout(Duration::from_secs(10))]
613 #[tokio::test]
614 async fn test_prev(
615 #[future] fixtures: (
616 Mpris,
617 Receiver<StateChange>,
618 TempDir,
619 Arc<AudioKernelSender>,
620 ),
621 ) {
622 init();
623 let (mpris, event_rx, tempdir, audio_kernel) = fixtures.await;
624
625 assert_eq!(mpris.can_go_previous().await.unwrap(), true);
626
627 let context = Context::current();
629 let songs: Vec<SongBrief> = mpris
630 .daemon
631 .read()
632 .await
633 .as_ref()
634 .unwrap()
635 .library_songs_brief(context)
636 .await
637 .unwrap()
638 .unwrap()
639 .to_vec();
640 assert_eq!(songs.len(), 4);
641 let third_song: mecomp_storage::db::schemas::RecordId = songs[2].id.clone().into();
642 let fourth_song: mecomp_storage::db::schemas::RecordId = songs[3].id.clone().into();
643
644 audio_kernel.send(AudioCommand::Queue(QueueCommand::AddToQueue(songs.into())));
646 let _ = event_rx.recv();
647 let _ = event_rx.recv();
648
649 audio_kernel.send(AudioCommand::Queue(QueueCommand::SetPosition(3)));
651 assert_eq!(
652 event_rx.recv(),
653 Ok(StateChange::TrackChanged(Some(fourth_song.clone())))
654 );
655
656 mpris.previous().await.unwrap();
658 assert_eq!(
660 event_rx.recv(),
661 Ok(StateChange::TrackChanged(Some(third_song))),
662 );
663
664 drop(tempdir);
665 }
666 #[rstest]
667 #[timeout(Duration::from_secs(10))]
668 #[tokio::test]
669 async fn test_prev_maintains_state(
670 #[future] fixtures: (
671 Mpris,
672 Receiver<StateChange>,
673 TempDir,
674 Arc<AudioKernelSender>,
675 ),
676 ) {
677 init();
678 let (mpris, event_rx, tempdir, audio_kernel) = fixtures.await;
679
680 assert_eq!(mpris.can_go_previous().await.unwrap(), true);
681
682 let context = Context::current();
684 let songs: Vec<SongBrief> = mpris
685 .daemon
686 .read()
687 .await
688 .as_ref()
689 .unwrap()
690 .library_songs_brief(context)
691 .await
692 .unwrap()
693 .unwrap()
694 .to_vec();
695 assert_eq!(songs.len(), 4);
696 let second_song: mecomp_storage::db::schemas::RecordId = songs[1].id.clone().into();
697 let third_song: mecomp_storage::db::schemas::RecordId = songs[2].id.clone().into();
698 let fourth_song: mecomp_storage::db::schemas::RecordId = songs[3].id.clone().into();
699
700 audio_kernel.send(AudioCommand::Queue(QueueCommand::AddToQueue(songs.into())));
702 let _ = event_rx.recv();
703 let _ = event_rx.recv();
704
705 audio_kernel.send(AudioCommand::Queue(QueueCommand::SetPosition(3)));
707 assert_eq!(
708 event_rx.recv(),
709 Ok(StateChange::TrackChanged(Some(fourth_song.clone())))
710 );
711
712 mpris.stop().await.unwrap();
716 assert_eq!(
717 event_rx.recv(),
718 Ok(StateChange::Seeked(Duration::from_secs(0)))
719 );
720 assert_eq!(
721 event_rx.recv(),
722 Ok(StateChange::StatusChanged(Status::Stopped))
723 );
724 mpris.previous().await.unwrap();
726 assert_eq!(
728 event_rx.recv(),
729 Ok(StateChange::TrackChanged(Some(third_song))),
730 );
731 assert!(event_rx.try_recv().is_err());
733
734 mpris.pause().await.unwrap();
736 assert!(matches!(
737 event_rx.recv(),
738 Ok(StateChange::StatusChanged(Status::Paused))
739 ));
740
741 mpris.previous().await.unwrap();
743 assert_eq!(
745 event_rx.recv(),
746 Ok(StateChange::TrackChanged(Some(second_song))),
747 );
748 assert!(event_rx.try_recv().is_err());
750
751 drop(tempdir);
752 }
753
754 #[rstest]
755 #[timeout(Duration::from_secs(10))]
756 #[tokio::test]
757 async fn test_prev_no_prev_track(
758 #[future] fixtures: (
759 Mpris,
760 Receiver<StateChange>,
761 TempDir,
762 Arc<AudioKernelSender>,
763 ),
764 ) {
765 init();
766 let (mpris, event_rx, tempdir, audio_kernel) = fixtures.await;
767
768 assert_eq!(mpris.can_go_previous().await.unwrap(), true);
769
770 let context = Context::current();
772 let songs: Vec<SongBrief> = mpris
773 .daemon
774 .read()
775 .await
776 .as_ref()
777 .unwrap()
778 .library_songs_brief(context)
779 .await
780 .unwrap()
781 .unwrap()
782 .to_vec();
783 assert_eq!(songs.len(), 4);
784
785 audio_kernel.send(AudioCommand::Queue(QueueCommand::AddToQueue(
787 songs[0].clone().into(),
788 )));
789 let _ = event_rx.recv();
790 let _ = event_rx.recv();
791
792 mpris.previous().await.unwrap();
795 assert_eq!(event_rx.recv(), Ok(StateChange::TrackChanged(None)),);
797 assert!(matches!(
799 event_rx.recv(),
800 Ok(StateChange::StatusChanged(Status::Stopped))
801 ));
802
803 drop(tempdir);
804 }
805
806 #[rstest]
849 #[timeout(Duration::from_secs(10))]
850 #[tokio::test]
851 async fn test_play_pause_stop(
852 #[future] fixtures: (
853 Mpris,
854 Receiver<StateChange>,
855 TempDir,
856 Arc<AudioKernelSender>,
857 ),
858 ) {
859 init();
860 let (mpris, event_rx, tempdir, audio_kernel) = fixtures.await;
861
862 assert_eq!(mpris.can_pause().await.unwrap(), true);
863 assert_eq!(mpris.can_play().await.unwrap(), true);
864 assert_eq!(mpris.can_control().await.unwrap(), true);
865
866 let context = Context::current();
868 let songs: Vec<SongBrief> = mpris
869 .daemon
870 .read()
871 .await
872 .as_ref()
873 .unwrap()
874 .library_songs_brief(context)
875 .await
876 .unwrap()
877 .unwrap()
878 .to_vec();
879 assert_eq!(songs.len(), 4);
880 let first_song = songs[0].clone();
881
882 mpris.play().await.unwrap();
884 let event = event_rx.try_recv();
885 assert!(
886 event.is_err(),
887 "Expected not to receive an event, but got {event:?}"
888 );
889
890 audio_kernel.send(AudioCommand::Queue(QueueCommand::AddToQueue(
892 first_song.clone().into(),
893 )));
894
895 assert_eq!(
896 event_rx.recv(),
897 Ok(StateChange::TrackChanged(Some(
898 first_song.id.clone().into()
899 )))
900 );
901 assert_eq!(
902 event_rx.recv(),
903 Ok(StateChange::StatusChanged(Status::Playing))
904 );
905
906 mpris.pause().await.unwrap();
908 assert_eq!(
909 event_rx.recv(),
910 Ok(StateChange::StatusChanged(Status::Paused))
911 );
912
913 mpris.pause().await.unwrap();
915 let event = event_rx.try_recv();
916 assert!(
917 event.is_err(),
918 "Expected not to receive an event, but got {event:?}"
919 );
920
921 mpris.play().await.unwrap();
926 assert_eq!(
927 event_rx.recv(),
928 Ok(StateChange::StatusChanged(Status::Playing))
929 );
930
931 mpris.play().await.unwrap();
933 let event = event_rx.try_recv();
934 assert!(
935 event.is_err(),
936 "Expected not to receive an event, but got {event:?}"
937 );
938
939 mpris.play_pause().await.unwrap();
947 assert_eq!(
948 event_rx.recv(),
949 Ok(StateChange::StatusChanged(Status::Paused))
950 );
951
952 mpris.play_pause().await.unwrap();
954 assert_eq!(
955 event_rx.recv(),
956 Ok(StateChange::StatusChanged(Status::Playing))
957 );
958
959 mpris.stop().await.unwrap();
961 assert_eq!(
962 event_rx.recv(),
963 Ok(StateChange::Seeked(Duration::from_secs(0)))
964 );
965 assert_eq!(
966 event_rx.recv(),
967 Ok(StateChange::StatusChanged(Status::Stopped))
968 );
969 mpris.play_pause().await.unwrap();
970 assert_eq!(
971 event_rx.recv(),
972 Ok(StateChange::StatusChanged(Status::Playing))
973 );
974
975 mpris.stop().await.unwrap();
977 assert_eq!(
978 event_rx.recv(),
979 Ok(StateChange::Seeked(Duration::from_secs(0)))
980 );
981 assert_eq!(
982 event_rx.recv(),
983 Ok(StateChange::StatusChanged(Status::Stopped))
984 );
985
986 mpris.stop().await.unwrap();
988 let event = event_rx.try_recv();
989 assert!(
990 event.is_err(),
991 "Expected not to receive an event, but got {event:?}"
992 );
993
994 drop(tempdir);
998 }
999
1000 #[rstest]
1016 #[timeout(Duration::from_secs(10))]
1017 #[tokio::test]
1018 async fn test_open_uri(
1019 #[future] fixtures: (
1020 Mpris,
1021 Receiver<StateChange>,
1022 TempDir,
1023 Arc<AudioKernelSender>,
1024 ),
1025 ) {
1026 init();
1027 let (mpris, event_rx, tempdir, _) = fixtures.await;
1028
1029 let context = Context::current();
1031 let songs: Vec<SongBrief> = mpris
1032 .daemon
1033 .read()
1034 .await
1035 .as_ref()
1036 .unwrap()
1037 .library_songs_brief(context)
1038 .await
1039 .unwrap()
1040 .unwrap()
1041 .to_vec();
1042 assert_eq!(songs.len(), 4);
1043 let first_song = songs[0].clone();
1044
1045 let file_uri = format!("file://{}", first_song.path.display());
1049 mpris.open_uri(file_uri).await.unwrap();
1050 assert_eq!(
1051 event_rx.recv(),
1052 Ok(StateChange::TrackChanged(Some(
1053 first_song.id.clone().into()
1054 )))
1055 );
1056 assert_eq!(
1058 event_rx.recv(),
1059 Ok(StateChange::StatusChanged(Status::Playing))
1060 );
1061
1062 let file_uri = "http://example.com/song.mp3".to_string();
1067 let result = mpris.open_uri(file_uri).await;
1068 assert!(result.is_err());
1069 let file_uri = "file://".to_string();
1071 let result = mpris.open_uri(file_uri).await;
1072 assert!(result.is_err());
1073 let file_uri = format!("file://{}", tempdir.path().display());
1075 let result = mpris.open_uri(file_uri).await;
1076 assert!(result.is_err());
1077 let file_uri = "file:///nonexistent.mp3".to_string();
1079 let result = mpris.open_uri(file_uri).await;
1080 assert!(result.is_err());
1081 std::fs::write(tempdir.path().join("unsupported.txt"), "unsupported")
1083 .expect("Failed to write file");
1084 let file_uri = format!(
1085 "file:///{}",
1086 tempdir.path().join("unsupported.txt").display()
1087 );
1088 let result = mpris.open_uri(file_uri).await;
1089 assert!(result.is_err());
1090
1091 drop(tempdir);
1095 }
1096
1097 #[rstest]
1102 #[timeout(Duration::from_secs(10))]
1103 #[tokio::test]
1104 async fn test_playback_status(
1105 #[future] fixtures: (
1106 Mpris,
1107 Receiver<StateChange>,
1108 TempDir,
1109 Arc<AudioKernelSender>,
1110 ),
1111 ) {
1112 init();
1113 let (mpris, event_rx, tempdir, audio_kernel) = fixtures.await;
1114
1115 let context = Context::current();
1117 let songs: Vec<SongBrief> = mpris
1118 .daemon
1119 .read()
1120 .await
1121 .as_ref()
1122 .unwrap()
1123 .library_songs_brief(context)
1124 .await
1125 .unwrap()
1126 .unwrap()
1127 .to_vec();
1128 assert_eq!(songs.len(), 4);
1129 let first_song: SongBrief = songs[0].clone();
1130
1131 assert_eq!(
1134 mpris.playback_status().await.unwrap(),
1135 PlaybackStatus::Stopped
1136 );
1137
1138 audio_kernel.send(AudioCommand::Queue(QueueCommand::AddToQueue(
1140 first_song.clone().into(),
1141 )));
1142
1143 mpris.pause().await.unwrap();
1145
1146 let mut events = [false; 3];
1148 for _ in 0..3 {
1149 let event = event_rx.recv().unwrap();
1150
1151 match event {
1152 StateChange::TrackChanged(Some(_)) => {
1153 mpris.state.write().await.current_song = Some(first_song.clone());
1154 events[0] = true;
1155 }
1156 StateChange::StatusChanged(Status::Playing) => {
1157 mpris.state.write().await.status = Status::Playing;
1158 assert_eq!(
1159 mpris.playback_status().await.unwrap(),
1160 PlaybackStatus::Playing
1161 );
1162 events[1] = true;
1163 }
1164 StateChange::StatusChanged(Status::Paused) => {
1165 mpris.state.write().await.status = Status::Paused;
1166 assert_eq!(
1167 mpris.playback_status().await.unwrap(),
1168 PlaybackStatus::Paused
1169 );
1170 events[2] = true;
1171 }
1172 _ => panic!("Unexpected event: {event:?}"),
1173 }
1174 }
1175
1176 assert!(events.iter().all(|&e| e));
1177
1178 drop(tempdir);
1179 }
1180
1181 #[rstest]
1193 #[timeout(Duration::from_secs(10))]
1194 #[tokio::test]
1195 async fn test_loop_status(
1196 #[future] fixtures: (
1197 Mpris,
1198 Receiver<StateChange>,
1199 TempDir,
1200 Arc<AudioKernelSender>,
1201 ),
1202 ) {
1203 init();
1204 let (mpris, event_rx, _, _) = fixtures.await;
1205
1206 assert_eq!(mpris.loop_status().await.unwrap(), LoopStatus::None);
1209
1210 mpris.set_loop_status(LoopStatus::Track).await.unwrap();
1212 if event_rx.recv() == Ok(StateChange::RepeatModeChanged(RepeatMode::One)) {
1213 mpris.state.write().await.repeat_mode = RepeatMode::One;
1214 } else {
1215 panic!("Expected a RepeatModeChanged event, but got something else");
1216 }
1217 assert_eq!(mpris.loop_status().await.unwrap(), LoopStatus::Track);
1218
1219 mpris.set_loop_status(LoopStatus::Playlist).await.unwrap();
1221 if event_rx.recv() == Ok(StateChange::RepeatModeChanged(RepeatMode::All)) {
1222 mpris.state.write().await.repeat_mode = RepeatMode::All;
1223 } else {
1224 panic!("Expected a RepeatModeChanged event, but got something else");
1225 }
1226 assert_eq!(mpris.loop_status().await.unwrap(), LoopStatus::Playlist);
1227
1228 mpris.set_loop_status(LoopStatus::None).await.unwrap();
1230 if event_rx.recv() == Ok(StateChange::RepeatModeChanged(RepeatMode::None)) {
1231 mpris.state.write().await.repeat_mode = RepeatMode::None;
1232 } else {
1233 panic!("Expected a RepeatModeChanged event, but got something else");
1234 }
1235 assert_eq!(mpris.loop_status().await.unwrap(), LoopStatus::None);
1236 }
1237
1238 #[rstest]
1254 #[timeout(Duration::from_secs(10))]
1255 #[tokio::test]
1256 async fn test_rate(
1257 #[future] fixtures: (
1258 Mpris,
1259 Receiver<StateChange>,
1260 TempDir,
1261 Arc<AudioKernelSender>,
1262 ),
1263 ) {
1264 init();
1265 let (mpris, event_rx, _, _) = fixtures.await;
1266
1267 let rate = mpris.rate().await.unwrap();
1269 assert!(f64::EPSILON > (rate - 1.0).abs(), "{rate} != 1.0");
1270
1271 let min_rate = mpris.minimum_rate().await.unwrap();
1273 assert!(f64::EPSILON > (min_rate - 1.0).abs(), "{min_rate} != 1.0");
1274
1275 let max_rate = mpris.maximum_rate().await.unwrap();
1277 assert!(f64::EPSILON > (max_rate - 1.0).abs(), "{max_rate} != 1.0");
1278
1279 let result = mpris.set_rate(1.0).await;
1282 assert!(result.is_ok());
1283 assert!(event_rx.try_recv().is_err());
1284 }
1285
1286 #[rstest]
1302 #[timeout(Duration::from_secs(10))]
1303 #[tokio::test]
1304 async fn test_shuffle(
1305 #[future] fixtures: (
1306 Mpris,
1307 Receiver<StateChange>,
1308 TempDir,
1309 Arc<AudioKernelSender>,
1310 ),
1311 ) {
1312 init();
1313 let (mpris, event_rx, _, _) = fixtures.await;
1314
1315 assert_eq!(mpris.shuffle().await.unwrap(), true);
1317
1318 mpris.set_shuffle(true).await.unwrap();
1321 assert_eq!(mpris.shuffle().await.unwrap(), true);
1322 assert!(event_rx.try_recv().is_err());
1323
1324 mpris.set_shuffle(false).await.unwrap();
1326 assert_eq!(mpris.shuffle().await.unwrap(), true);
1327 assert!(event_rx.try_recv().is_err());
1328 }
1329
1330 #[rstest]
1338 #[timeout(Duration::from_secs(10))]
1339 #[tokio::test]
1340 async fn test_metadata(
1341 #[future] fixtures: (
1342 Mpris,
1343 Receiver<StateChange>,
1344 TempDir,
1345 Arc<AudioKernelSender>,
1346 ),
1347 ) {
1348 init();
1349 let (mpris, event_rx, tempdir, audio_kernel) = fixtures.await;
1350
1351 let context = Context::current();
1353 let songs: Vec<SongBrief> = mpris
1354 .daemon
1355 .read()
1356 .await
1357 .as_ref()
1358 .unwrap()
1359 .library_songs_brief(context)
1360 .await
1361 .unwrap()
1362 .unwrap()
1363 .to_vec();
1364 assert_eq!(songs.len(), 4);
1365 let first_song = songs[0].clone();
1366
1367 assert_eq!(
1370 mpris.metadata().await.unwrap(),
1371 Metadata::builder().trackid(TrackId::NO_TRACK).build()
1372 );
1373
1374 audio_kernel.send(AudioCommand::Queue(QueueCommand::AddToQueue(
1376 first_song.clone().into(),
1377 )));
1378
1379 assert_eq!(
1380 event_rx.recv(),
1381 Ok(StateChange::TrackChanged(Some(
1382 first_song.id.clone().into()
1383 )))
1384 );
1385
1386 *mpris.state.write().await = mpris
1387 .daemon
1388 .read()
1389 .await
1390 .as_ref()
1391 .unwrap()
1392 .state_audio(Context::current())
1393 .await
1394 .unwrap()
1395 .unwrap();
1396
1397 let metadata = mpris.metadata().await.unwrap();
1399 assert_eq!(metadata, metadata_from_opt_song(Some(&first_song)));
1400 assert_ne!(
1401 metadata,
1402 Metadata::builder().trackid(TrackId::NO_TRACK).build()
1403 );
1404
1405 drop(tempdir);
1406 }
1407
1408 #[rstest]
1413 #[timeout(Duration::from_secs(10))]
1414 #[tokio::test]
1415 async fn test_volume(
1416 #[future] fixtures: (
1417 Mpris,
1418 Receiver<StateChange>,
1419 TempDir,
1420 Arc<AudioKernelSender>,
1421 ),
1422 ) {
1423 let (mpris, event_rx, _, _) = fixtures.await;
1424
1425 let volume = mpris.volume().await.unwrap();
1427 assert!(f64::EPSILON > (volume - 1.0).abs(), "{volume} != 1.0");
1428
1429 mpris.set_volume(-1.0).await.unwrap();
1431 if event_rx.recv() == Ok(StateChange::VolumeChanged(0.0)) {
1432 mpris.state.write().await.volume = 0.0;
1433 let volume = mpris.volume().await.unwrap();
1434 assert!(f64::EPSILON > volume.abs(), "{volume} != 0.0");
1435 } else {
1436 panic!("Expected a VolumeChanged event, but got something else");
1437 }
1438
1439 mpris.set_volume(1.0).await.unwrap();
1441 if event_rx.recv() == Ok(StateChange::VolumeChanged(1.0)) {
1442 mpris.state.write().await.volume = 1.0;
1443 let volume = mpris.volume().await.unwrap();
1444 assert!(f64::EPSILON > (volume - 1.0).abs(), "{volume} != 1.0");
1445 } else {
1446 panic!("Expected a VolumeChanged event, but got something else");
1447 }
1448
1449 mpris.set_volume(1.0).await.unwrap();
1451 assert!(event_rx.try_recv().is_err());
1452 }
1453
1454 #[rstest]
1467 #[timeout(Duration::from_secs(10))]
1468 #[tokio::test]
1469 async fn test_position(
1470 #[future] fixtures: (
1471 Mpris,
1472 Receiver<StateChange>,
1473 TempDir,
1474 Arc<AudioKernelSender>,
1475 ),
1476 ) {
1477 init();
1478 let (mpris, event_rx, tempdir, audio_kernel) = fixtures.await;
1479
1480 assert!(mpris.can_seek().await.unwrap());
1481
1482 let context = Context::current();
1484 let songs: Vec<SongBrief> = mpris
1485 .daemon
1486 .read()
1487 .await
1488 .as_ref()
1489 .unwrap()
1490 .library_songs_brief(context)
1491 .await
1492 .unwrap()
1493 .unwrap()
1494 .to_vec();
1495 assert_eq!(songs.len(), 4);
1496 let first_song = songs[0].clone();
1497
1498 assert_eq!(mpris.position().await.unwrap(), Time::from_micros(0));
1501
1502 audio_kernel.send(AudioCommand::Queue(QueueCommand::AddToQueue(
1504 first_song.clone().into(),
1505 )));
1506 audio_kernel.send(AudioCommand::Pause);
1507 let _ = event_rx.recv().unwrap();
1508 let _ = event_rx.recv().unwrap();
1509 let _ = event_rx.recv().unwrap();
1510 *mpris.state.write().await = mpris
1512 .daemon
1513 .read()
1514 .await
1515 .as_ref()
1516 .unwrap()
1517 .state_audio(Context::current())
1518 .await
1519 .unwrap()
1520 .unwrap();
1521
1522 let first_song_track_id = mpris.metadata().await.unwrap().trackid().unwrap();
1523
1524 mpris
1526 .set_position(first_song_track_id.clone(), Time::from_secs(2))
1527 .await
1528 .unwrap();
1529 assert_eq!(
1530 event_rx.recv(),
1531 Ok(StateChange::Seeked(Duration::from_secs(2)))
1532 );
1533
1534 mpris
1536 .set_position(first_song_track_id.clone(), Time::from_secs(-1))
1537 .await
1538 .unwrap();
1539 assert!(event_rx.try_recv().is_err());
1540
1541 mpris
1543 .set_position(first_song_track_id.clone(), Time::from_secs(100))
1544 .await
1545 .unwrap();
1546 assert!(event_rx.try_recv().is_err());
1547
1548 assert!(mpris.can_seek().await.unwrap());
1551
1552 mpris.seek(Time::from_secs(1)).await.unwrap();
1553 assert_eq!(
1554 event_rx.recv(),
1555 Ok(StateChange::Seeked(Duration::from_secs(3)))
1556 );
1557
1558 mpris.seek(Time::from_secs(-1)).await.unwrap();
1559 assert_eq!(
1560 event_rx.recv(),
1561 Ok(StateChange::Seeked(Duration::from_secs(2)))
1562 );
1563
1564 drop(tempdir);
1565 }
1566}