Skip to main content

mecomp_mpris/interfaces/
player.rs

1//! Implements the player interface of the MPRIS specification.
2//!
3//! [org.mpris.MediaPlayer2.Player](https://specifications.freedesktop.org/mpris-spec/latest/Player_Interface.html)
4
5use std::{path::PathBuf, str::FromStr, time::Duration};
6
7use futures::executor::block_on;
8use mecomp_core::state::{RepeatMode, SeekType, Status};
9use mecomp_prost::{PlaybackSeekRequest, PlaybackSkipRequest, PlaybackVolumeSetRequest};
10use mpris_server::{
11    LoopStatus, Metadata, PlaybackRate, PlaybackStatus, PlayerInterface, Time, TrackId, Volume,
12    zbus::{Error as ZbusError, fdo},
13};
14
15use crate::{Mpris, interfaces::root::SUPPORTED_MIME_TYPES, metadata_from_opt_song};
16
17impl PlayerInterface for Mpris {
18    async fn next(&self) -> fdo::Result<()> {
19        block_on(
20            self.daemon
21                .clone()
22                .playback_skip_forward(PlaybackSkipRequest::new(1)),
23        )
24        .map_err(|e| fdo::Error::Failed(e.to_string()))?;
25        Ok(())
26    }
27
28    async fn previous(&self) -> fdo::Result<()> {
29        block_on(
30            self.daemon
31                .clone()
32                .playback_skip_backward(PlaybackSkipRequest::new(1)),
33        )
34        .map_err(|e| fdo::Error::Failed(e.to_string()))?;
35        Ok(())
36    }
37
38    async fn pause(&self) -> fdo::Result<()> {
39        block_on(self.daemon.clone().playback_pause(()))
40            .map_err(|e| fdo::Error::Failed(e.to_string()))?;
41
42        Ok(())
43    }
44
45    async fn play_pause(&self) -> fdo::Result<()> {
46        block_on(self.daemon.clone().playback_toggle(()))
47            .map_err(|e| fdo::Error::Failed(e.to_string()))?;
48        Ok(())
49    }
50
51    async fn stop(&self) -> fdo::Result<()> {
52        block_on(self.daemon.clone().playback_stop(()))
53            .map_err(|e| fdo::Error::Failed(e.to_string()))?;
54        Ok(())
55    }
56
57    async fn play(&self) -> fdo::Result<()> {
58        block_on(self.daemon.clone().playback_play(()))
59            .map_err(|e| fdo::Error::Failed(e.to_string()))?;
60        Ok(())
61    }
62
63    async fn open_uri(&self, uri: String) -> fdo::Result<()> {
64        // the uri should be in the format:
65        // file:///path/to/file
66        // TODO: Support loading a playlist file when we've implemented importing/exporting external playlists
67        log::info!("Opening URI: {uri}");
68
69        // ensure the URI is a supported URI
70        if !uri.starts_with("file://") {
71            return Err(fdo::Error::InvalidArgs(
72                "Only file:// URIs are supported".to_string(),
73            ));
74        }
75
76        // extract the path from the URI, and ensure it's a valid file path
77        let path = uri.strip_prefix("file://").unwrap();
78        let mut path = PathBuf::from_str(path)
79            .map_err(|_| fdo::Error::InvalidArgs("Invalid file path".to_string()))?;
80
81        // parse out escaped characters (e.g. %20 for space)
82        path = percent_encoding::percent_decode_str(&path.to_string_lossy())
83            .decode_utf8()
84            .map_err(|_| fdo::Error::InvalidArgs("Invalid file path".to_string()))?
85            .into_owned()
86            .into();
87
88        // ensure the file type is supported
89        if !SUPPORTED_MIME_TYPES
90            .iter()
91            .filter_map(|s| s.split('/').next_back())
92            .any(|ext| path.extension().is_some_and(|e| e == ext))
93        {
94            return Err(fdo::Error::InvalidArgs(
95                "File type not supported".to_string(),
96            ));
97        }
98
99        // expand the tilde if present
100        if path.starts_with("~") {
101            path = shellexpand::tilde(&path.to_string_lossy())
102                .into_owned()
103                .into();
104        }
105
106        log::debug!("Locating file: {}", path.display());
107
108        // ensure the path exists
109        if !path.exists() {
110            return Err(fdo::Error::InvalidArgs("File does not exist".to_string()));
111        }
112
113        // ensure the path is a file
114        if !path.is_file() {
115            return Err(fdo::Error::InvalidArgs("Path is not a file".to_string()));
116        }
117
118        // canonicalize the path
119        path = path.canonicalize().unwrap_or(path);
120
121        // add the song to the queue
122        let mut daemon = self.daemon.clone();
123        if let Some(song) = block_on(daemon.library_song_get_by_path(mecomp_prost::Path::new(path)))
124            .map_err(|e| fdo::Error::Failed(e.to_string()))?
125            .into_inner()
126            .song
127        {
128            block_on(daemon.queue_add(song.id)).map_err(|e| fdo::Error::Failed(e.to_string()))?;
129        } else {
130            return Err(fdo::Error::Failed(
131                "Failed to find song in database".to_string(),
132            ));
133        }
134
135        Ok(())
136    }
137
138    async fn playback_status(&self) -> fdo::Result<PlaybackStatus> {
139        let status = self.state.read().await.status;
140        match status {
141            Status::Stopped => Ok(PlaybackStatus::Stopped),
142            Status::Paused => Ok(PlaybackStatus::Paused),
143            Status::Playing => Ok(PlaybackStatus::Playing),
144        }
145    }
146
147    async fn loop_status(&self) -> fdo::Result<LoopStatus> {
148        let repeat_mode = self.state.read().await.repeat_mode;
149        match repeat_mode {
150            RepeatMode::None => Ok(LoopStatus::None),
151            RepeatMode::One => Ok(LoopStatus::Track),
152            RepeatMode::All => Ok(LoopStatus::Playlist),
153        }
154    }
155
156    async fn set_loop_status(&self, loop_status: LoopStatus) -> Result<(), ZbusError> {
157        let repeat_mode = match loop_status {
158            LoopStatus::None => RepeatMode::None,
159            LoopStatus::Track => RepeatMode::One,
160            LoopStatus::Playlist => RepeatMode::All,
161        };
162
163        let mut daemon = self.daemon.clone();
164        block_on(daemon.playback_repeat(mecomp_prost::PlaybackRepeatRequest::new(repeat_mode)))
165            .map_err(|e| fdo::Error::Failed(e.to_string()))?;
166
167        Ok(())
168    }
169
170    async fn rate(&self) -> fdo::Result<PlaybackRate> {
171        Ok(1.0)
172    }
173
174    async fn set_rate(&self, _: PlaybackRate) -> Result<(), ZbusError> {
175        Ok(())
176    }
177
178    async fn shuffle(&self) -> fdo::Result<bool> {
179        // Mecomp has no distinction between shuffle and non-shuffle playback
180        // shuffling is done by actually shuffling the queue and is not reversible
181        // therefore, we always return true
182        Ok(true)
183    }
184
185    async fn set_shuffle(&self, shuffle: bool) -> Result<(), ZbusError> {
186        // if called with false, does nothing, if called with true, shuffles the queue
187        if shuffle {
188            let mut daemon = self.daemon.clone();
189
190            block_on(daemon.playback_shuffle(())).map_err(|e| fdo::Error::Failed(e.to_string()))?;
191        }
192
193        Ok(())
194    }
195
196    async fn metadata(&self) -> fdo::Result<Metadata> {
197        let state = self.state.read().await;
198
199        Ok(metadata_from_opt_song(state.current_song.as_ref()))
200    }
201
202    async fn volume(&self) -> fdo::Result<Volume> {
203        let state = self.state.read().await;
204        if state.muted {
205            Ok(0.0)
206        } else {
207            Ok(f64::from(state.volume))
208        }
209    }
210
211    async fn set_volume(&self, volume: Volume) -> Result<(), ZbusError> {
212        let mut daemon = self.daemon.clone();
213        #[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)]
214        block_on(daemon.playback_volume(PlaybackVolumeSetRequest::new(volume as f32)))
215            .map_err(|e| fdo::Error::Failed(e.to_string()))?;
216
217        Ok(())
218    }
219
220    async fn position(&self) -> fdo::Result<Time> {
221        self.state.read().await.runtime.map_or_else(
222            || Ok(Time::from_micros(0)),
223            |runtime| {
224                Ok(Time::from_micros(
225                    i64::try_from(runtime.seek_position.as_micros()).unwrap_or(i64::MAX),
226                ))
227            },
228        )
229    }
230
231    async fn seek(&self, offset: Time) -> fdo::Result<()> {
232        //TODO: if the value passed in would mean seeking beyond the end of the track, act like a call to Next
233        let seek_type = if offset.as_micros() > 0 {
234            SeekType::RelativeForwards
235        } else {
236            SeekType::RelativeBackwards
237        };
238
239        let offset = Duration::from_micros(offset.as_micros().unsigned_abs());
240
241        let mut daemon = self.daemon.clone();
242
243        block_on(daemon.playback_seek(PlaybackSeekRequest::new(seek_type, offset)))
244            .map_err(|e| fdo::Error::Failed(e.to_string()))?;
245        Ok(())
246    }
247
248    async fn set_position(&self, track_id: TrackId, position: Time) -> fdo::Result<()> {
249        // "track_id - The currently playing track's identifier. If this does not match the id of the currently-playing track, the call is ignored as 'stale'"
250        if Some(track_id) != self.metadata().await?.trackid() {
251            return Ok(());
252        }
253
254        if let Some(song) = self.state.read().await.current_song.as_ref() {
255            // if the position not in the range of the song, ignore the call
256            let position = position.as_micros();
257            if position < 0 || u128::from(position.unsigned_abs()) > song.runtime.as_micros() {
258                return Ok(());
259            }
260
261            block_on(self.daemon.clone().playback_seek(PlaybackSeekRequest::new(
262                SeekType::Absolute,
263                Duration::from_micros(u64::try_from(position).unwrap_or_default()),
264            )))
265            .map_err(|e| fdo::Error::Failed(e.to_string()))?;
266        }
267
268        Ok(())
269    }
270
271    async fn minimum_rate(&self) -> fdo::Result<PlaybackRate> {
272        Ok(1.0)
273    }
274
275    async fn maximum_rate(&self) -> fdo::Result<PlaybackRate> {
276        Ok(1.0)
277    }
278
279    async fn can_go_next(&self) -> fdo::Result<bool> {
280        Ok(true)
281    }
282
283    async fn can_go_previous(&self) -> fdo::Result<bool> {
284        Ok(true)
285    }
286
287    async fn can_play(&self) -> fdo::Result<bool> {
288        Ok(true)
289    }
290
291    async fn can_pause(&self) -> fdo::Result<bool> {
292        Ok(true)
293    }
294
295    async fn can_seek(&self) -> fdo::Result<bool> {
296        Ok(true)
297    }
298
299    async fn can_control(&self) -> fdo::Result<bool> {
300        Ok(true)
301    }
302}
303
304// NOTE: these tests do not run the event `Subscriber` main loop so events from the audio kernel are not
305// actually going to be applied to the state, so we have to manually set the state when testing methods
306// that simply report the state.
307#[cfg(test)]
308mod tests {
309    use std::sync::{Arc, mpsc::Receiver};
310
311    use mecomp_core::{
312        audio::{
313            AudioKernelSender,
314            commands::{AudioCommand, QueueCommand},
315        },
316        test_utils::init,
317        udp::StateChange,
318    };
319
320    use mecomp_storage::db::schemas::song::SongBrief;
321    use one_or_many::OneOrMany;
322    use pretty_assertions::{assert_eq, assert_ne};
323    use rstest::rstest;
324    use tempfile::TempDir;
325
326    use super::*;
327    use crate::test_utils::fixtures;
328
329    /// """
330    /// Skips to the next track in the tracklist.
331    /// If there is no next track (and endless playback and track repeat are both off), stop playback.
332    /// If playback is paused or stopped, it remains that way.
333    /// If [CanGoNext] is false, attempting to call this method should have no effect.
334    /// """
335    ///
336    /// Mecomp supports skipping to the next track in the queue.
337    ///
338    /// the last case is irrelevant here, as we always return true for [CanGoNext]
339    #[rstest]
340    #[timeout(Duration::from_secs(20))]
341    #[tokio::test(flavor = "multi_thread", worker_threads = 3)]
342    async fn test_next(
343        #[future] fixtures: (
344            Mpris,
345            Receiver<StateChange>,
346            TempDir,
347            Arc<AudioKernelSender>,
348        ),
349    ) {
350        init();
351        let (mpris, event_rx, tempdir, audio_kernel) = fixtures.await;
352
353        assert_eq!(mpris.can_go_next().await.unwrap(), true);
354
355        // setup
356        let songs = mpris
357            .daemon
358            .clone()
359            .library_songs(())
360            .await
361            .unwrap()
362            .into_inner()
363            .songs;
364        assert_eq!(songs.len(), 4);
365        // send all the songs to the audio kernel (adding them to the queue and starting playback)
366        audio_kernel.send(AudioCommand::Queue(QueueCommand::AddToQueue(
367            songs.into_iter().map(Into::into).collect(),
368        )));
369        assert_eq!(event_rx.recv(), Ok(StateChange::QueueChanged));
370        let Ok(StateChange::TrackChanged(Some(first_song))) = event_rx.recv() else {
371            panic!("Expected a TrackChanged event, but got something else");
372        };
373        assert_eq!(
374            event_rx.recv(),
375            Ok(StateChange::StatusChanged(Status::Playing))
376        );
377
378        // it skips to the next track //
379        mpris.next().await.unwrap();
380
381        let Ok(StateChange::TrackChanged(Some(second_song))) = event_rx.recv() else {
382            panic!("Expected a TrackChanged event, but got something else");
383        };
384
385        // the current song should be different from the previous song
386        assert_ne!(first_song, second_song);
387
388        drop(tempdir);
389    }
390
391    #[rstest]
392    #[timeout(Duration::from_secs(20))]
393    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
394    async fn test_next_maintains_status(
395        #[future] fixtures: (
396            Mpris,
397            Receiver<StateChange>,
398            TempDir,
399            Arc<AudioKernelSender>,
400        ),
401    ) {
402        init();
403        let (mpris, event_rx, tempdir, audio_kernel) = fixtures.await;
404
405        assert_eq!(mpris.can_go_next().await.unwrap(), true);
406
407        // setup
408        let songs = mpris
409            .daemon
410            .clone()
411            .library_songs(())
412            .await
413            .unwrap()
414            .into_inner()
415            .songs;
416        assert_eq!(songs.len(), 4);
417        // send all the songs to the audio kernel (adding them to the queue and starting playback)
418        audio_kernel.send(AudioCommand::Queue(QueueCommand::AddToQueue(
419            songs.into_iter().map(Into::into).collect(),
420        )));
421        assert_eq!(event_rx.recv(), Ok(StateChange::QueueChanged));
422        let Ok(StateChange::TrackChanged(Some(first_song))) = event_rx.recv() else {
423            panic!("Expected a TrackChanged event, but got something else");
424        };
425        assert_eq!(
426            event_rx.recv(),
427            Ok(StateChange::StatusChanged(Status::Playing))
428        );
429
430        // if playback is paused or stopped, it remains that way //
431
432        // stop playback
433        mpris.stop().await.unwrap();
434        assert_eq!(
435            event_rx.recv(),
436            Ok(StateChange::Seeked(Duration::from_secs(0)))
437        );
438        assert_eq!(
439            event_rx.recv(),
440            Ok(StateChange::StatusChanged(Status::Stopped))
441        );
442
443        // skip to the next track
444        mpris.next().await.unwrap();
445        let Ok(StateChange::TrackChanged(Some(second_song))) = event_rx.recv() else {
446            panic!("Expected a TrackChanged event, but got something else");
447        };
448        // playback should remain stopped
449        assert!(event_rx.try_recv().is_err());
450
451        // the current song should be different from the previous song
452        assert_ne!(first_song, second_song);
453
454        // pause playback
455        mpris.pause().await.unwrap();
456        assert_eq!(
457            event_rx.recv(),
458            Ok(StateChange::StatusChanged(Status::Paused))
459        );
460
461        // skip to the next track
462        mpris.next().await.unwrap();
463        let Ok(StateChange::TrackChanged(Some(third_song))) = event_rx.recv() else {
464            panic!("Expected a TrackChanged event, but got something else");
465        };
466        // playback should remain paused
467        assert!(event_rx.try_recv().is_err());
468
469        // the current song should be different from the previous song
470        assert_ne!(second_song, third_song);
471
472        drop(tempdir);
473    }
474
475    #[rstest]
476    #[timeout(Duration::from_secs(20))]
477    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
478    async fn test_next_no_next_track(
479        #[future] fixtures: (
480            Mpris,
481            Receiver<StateChange>,
482            TempDir,
483            Arc<AudioKernelSender>,
484        ),
485    ) {
486        init();
487        let (mpris, event_rx, tempdir, audio_kernel) = fixtures.await;
488
489        assert_eq!(mpris.can_go_next().await.unwrap(), true);
490
491        // setup
492        let songs = mpris
493            .daemon
494            .clone()
495            .library_songs(())
496            .await
497            .unwrap()
498            .into_inner()
499            .songs;
500        assert_eq!(songs.len(), 4);
501        // send one song to the audio kernel (adding them to the queue and starting playback)
502        audio_kernel.send(AudioCommand::Queue(QueueCommand::AddToQueue(
503            OneOrMany::One(Box::new(songs[0].clone().into())),
504        )));
505        let _ = event_rx.recv();
506        let _ = event_rx.recv();
507        let _ = event_rx.recv();
508
509        // if there is no next track (and endless playback and track repeat are both off), stop playback. //
510        // skip to the next track (which should be nothing)
511        mpris.next().await.unwrap();
512        assert_eq!(
513            event_rx.recv(),
514            Ok(StateChange::QueueChanged),
515            "since it was the last song, the queue clears"
516        );
517        assert_eq!(event_rx.recv(), Ok(StateChange::TrackChanged(None)));
518        assert_eq!(
519            event_rx.recv(),
520            Ok(StateChange::StatusChanged(Status::Stopped))
521        );
522        drop(tempdir);
523    }
524
525    /// """
526    /// Skips to the previous track in the tracklist.
527    /// If there is no previous track (and endless playback and track repeat are both off), stop playback.
528    /// If playback is paused or stopped, it remains that way.
529    /// If [CanGoPrevious] is false, attempting to call this method should have no effect.
530    /// """
531    ///
532    /// Mecomp supports skipping to the previous track in the queue.
533    ///
534    /// the last case is irrelevant here, as we always return true for [CanGoPrevious]
535    #[rstest]
536    #[timeout(Duration::from_secs(20))]
537    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
538    async fn test_prev(
539        #[future] fixtures: (
540            Mpris,
541            Receiver<StateChange>,
542            TempDir,
543            Arc<AudioKernelSender>,
544        ),
545    ) {
546        init();
547        let (mpris, event_rx, tempdir, audio_kernel) = fixtures.await;
548
549        assert_eq!(mpris.can_go_previous().await.unwrap(), true);
550
551        // setup
552        let songs = mpris
553            .daemon
554            .clone()
555            .library_songs(())
556            .await
557            .unwrap()
558            .into_inner()
559            .songs;
560        assert_eq!(songs.len(), 4);
561        let first_song: mecomp_storage::db::schemas::RecordId = songs[0].id.clone().into();
562        let third_song: mecomp_storage::db::schemas::RecordId = songs[2].id.clone().into();
563        let fourth_song: mecomp_storage::db::schemas::RecordId = songs[3].id.clone().into();
564
565        // send all the songs to the audio kernel (adding them to the queue and starting playback)
566        audio_kernel.send(AudioCommand::Queue(QueueCommand::AddToQueue(
567            songs.into_iter().map(Into::into).collect(),
568        )));
569        assert_eq!(event_rx.recv(), Ok(StateChange::QueueChanged));
570        assert_eq!(
571            event_rx.recv(),
572            Ok(StateChange::TrackChanged(Some(first_song)))
573        );
574        assert_eq!(
575            event_rx.recv(),
576            Ok(StateChange::StatusChanged(Status::Playing))
577        );
578
579        // skip to the last song in the queue
580        audio_kernel.send(AudioCommand::Queue(QueueCommand::SetPosition(3)));
581        assert_eq!(
582            event_rx.recv(),
583            Ok(StateChange::TrackChanged(Some(fourth_song.clone())))
584        );
585
586        // it skips to the previous track //
587        mpris.previous().await.unwrap();
588        // should go back to the fourth song
589        assert_eq!(
590            event_rx.recv(),
591            Ok(StateChange::TrackChanged(Some(third_song))),
592        );
593
594        drop(tempdir);
595    }
596    #[rstest]
597    #[timeout(Duration::from_secs(20))]
598    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
599    async fn test_prev_maintains_state(
600        #[future] fixtures: (
601            Mpris,
602            Receiver<StateChange>,
603            TempDir,
604            Arc<AudioKernelSender>,
605        ),
606    ) {
607        init();
608        let (mpris, event_rx, tempdir, audio_kernel) = fixtures.await;
609
610        assert_eq!(mpris.can_go_previous().await.unwrap(), true);
611
612        // setup
613        let songs = mpris
614            .daemon
615            .clone()
616            .library_songs(())
617            .await
618            .unwrap()
619            .into_inner()
620            .songs;
621        assert_eq!(songs.len(), 4);
622        let first_song: mecomp_storage::db::schemas::RecordId = songs[0].id.clone().into();
623        let second_song: mecomp_storage::db::schemas::RecordId = songs[1].id.clone().into();
624        let third_song: mecomp_storage::db::schemas::RecordId = songs[2].id.clone().into();
625        let fourth_song: mecomp_storage::db::schemas::RecordId = songs[3].id.clone().into();
626
627        // send all the songs to the audio kernel (adding them to the queue and starting playback)
628        audio_kernel.send(AudioCommand::Queue(QueueCommand::AddToQueue(
629            songs.into_iter().map(Into::into).collect(),
630        )));
631        assert_eq!(event_rx.recv(), Ok(StateChange::QueueChanged));
632        assert_eq!(
633            event_rx.recv(),
634            Ok(StateChange::TrackChanged(Some(first_song)))
635        );
636        assert_eq!(
637            event_rx.recv(),
638            Ok(StateChange::StatusChanged(Status::Playing))
639        );
640
641        // skip to the last song in the queue
642        audio_kernel.send(AudioCommand::Queue(QueueCommand::SetPosition(3)));
643        assert_eq!(
644            event_rx.recv(),
645            Ok(StateChange::TrackChanged(Some(fourth_song.clone())))
646        );
647
648        // if playback is paused or stopped, it remains that way //
649
650        // stop playback
651        mpris.stop().await.unwrap();
652        assert_eq!(
653            event_rx.recv(),
654            Ok(StateChange::Seeked(Duration::from_secs(0)))
655        );
656        assert_eq!(
657            event_rx.recv(),
658            Ok(StateChange::StatusChanged(Status::Stopped))
659        );
660        // skip to the previous track
661        mpris.previous().await.unwrap();
662        // should go back to the third
663        assert_eq!(
664            event_rx.recv(),
665            Ok(StateChange::TrackChanged(Some(third_song))),
666        );
667        // playback should remain stopped
668        assert!(event_rx.try_recv().is_err());
669
670        // pause playback
671        mpris.pause().await.unwrap();
672        assert!(matches!(
673            event_rx.recv(),
674            Ok(StateChange::StatusChanged(Status::Paused))
675        ));
676
677        // skip to the previous track
678        mpris.previous().await.unwrap();
679        // should go back to the second song
680        assert_eq!(
681            event_rx.recv(),
682            Ok(StateChange::TrackChanged(Some(second_song))),
683        );
684        // playback should remain paused
685        assert!(event_rx.try_recv().is_err());
686
687        drop(tempdir);
688    }
689
690    #[rstest]
691    #[timeout(Duration::from_secs(20))]
692    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
693    async fn test_prev_no_prev_track(
694        #[future] fixtures: (
695            Mpris,
696            Receiver<StateChange>,
697            TempDir,
698            Arc<AudioKernelSender>,
699        ),
700    ) {
701        init();
702        let (mpris, event_rx, tempdir, audio_kernel) = fixtures.await;
703
704        assert_eq!(mpris.can_go_previous().await.unwrap(), true);
705
706        // setup
707        let songs = mpris
708            .daemon
709            .clone()
710            .library_songs(())
711            .await
712            .unwrap()
713            .into_inner()
714            .songs;
715        assert_eq!(songs.len(), 4);
716
717        // send all the songs to the audio kernel (adding them to the queue and starting playback)
718        audio_kernel.send(AudioCommand::Queue(QueueCommand::AddToQueue(
719            OneOrMany::One(Box::new(songs[0].clone().into())),
720        )));
721        assert_eq!(event_rx.recv(), Ok(StateChange::QueueChanged));
722        let _ = event_rx.recv();
723        let _ = event_rx.recv();
724
725        // if there is no previous track (and endless playback and track repeat are both off), stop playback. //
726        // skip to the previous track
727        mpris.previous().await.unwrap();
728        // should go back to nothing
729        assert_eq!(event_rx.recv(), Ok(StateChange::TrackChanged(None)),);
730        // playback should be stopped
731        assert!(matches!(
732            event_rx.recv(),
733            Ok(StateChange::StatusChanged(Status::Stopped))
734        ));
735
736        drop(tempdir);
737    }
738
739    /// """
740    /// Pauses playback.
741    /// If playback is already paused, this has no effect.
742    /// Calling [Play] after this should cause playback to start again from the same position.
743    /// If [CanPause] is false, attempting to call this method should have no effect.
744    /// """
745    ///
746    /// Mecomp supports pausing playback.
747    ///
748    /// the last case is irrelevant here, as we always return true for [CanPause]
749    ///
750    /// """
751    /// Starts or resumes playback.
752    /// If already playing, this has no effect.
753    /// If paused, playback resumes from the current position.
754    /// If there is no track to play, this has no effect.
755    /// If [CanPlay] is false, attempting to call this method should have no effect.
756    /// """
757    ///
758    /// Mecomp supports starting or resuming playback.
759    ///
760    /// the last case is irrelevant here, as we always return true for [CanPlay]
761    ///
762    /// """
763    /// Pauses playback.
764    /// If playback is already paused, resumes playback.
765    /// If playback is stopped, starts playback.
766    /// If [CanPause] is false, attempting to call this method should have no effect and raise an error.
767    /// """
768    ///
769    /// Mecomp supports toggling between playing and pausing playback.
770    ///
771    /// """
772    /// Stops playback.
773    /// If playback is already stopped, this has no effect.
774    /// Calling Play after this should cause playback to start again from the beginning of the track.
775    /// If [CanControl] is false, attempting to call this method should have no effect and raise an error.
776    /// """
777    ///
778    /// Mecomp supports stopping playback.
779    ///
780    /// the last case is irrelevant here, as we always return true for [CanControl]
781    #[rstest]
782    #[timeout(Duration::from_secs(20))]
783    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
784    async fn test_play_pause_stop(
785        #[future] fixtures: (
786            Mpris,
787            Receiver<StateChange>,
788            TempDir,
789            Arc<AudioKernelSender>,
790        ),
791    ) {
792        init();
793        let (mpris, event_rx, tempdir, audio_kernel) = fixtures.await;
794
795        assert_eq!(mpris.can_pause().await.unwrap(), true);
796        assert_eq!(mpris.can_play().await.unwrap(), true);
797        assert_eq!(mpris.can_control().await.unwrap(), true);
798
799        // setup
800        let songs = mpris
801            .daemon
802            .clone()
803            .library_songs(())
804            .await
805            .unwrap()
806            .into_inner()
807            .songs;
808        assert_eq!(songs.len(), 4);
809        let first_song = songs[0].clone();
810
811        // Play: if there is no track to play, this has no effect //
812        mpris.play().await.unwrap();
813        let event = event_rx.try_recv();
814        assert!(
815            event.is_err(),
816            "Expected not to receive an event, but got {event:?}"
817        );
818
819        // send all the songs to the audio kernel (adding them to the queue and starting playback)
820        audio_kernel.send(AudioCommand::Queue(QueueCommand::AddToQueue(
821            OneOrMany::One(Box::new(first_song.clone().into())),
822        )));
823        assert_eq!(event_rx.recv(), Ok(StateChange::QueueChanged));
824        assert_eq!(
825            event_rx.recv(),
826            Ok(StateChange::TrackChanged(Some(
827                first_song.id.clone().into()
828            )))
829        );
830        assert_eq!(
831            event_rx.recv(),
832            Ok(StateChange::StatusChanged(Status::Playing))
833        );
834
835        // Pause: pauses playback //
836        mpris.pause().await.unwrap();
837        assert_eq!(
838            event_rx.recv(),
839            Ok(StateChange::StatusChanged(Status::Paused))
840        );
841
842        // Pause: if playback is already paused, this has no effect //
843        mpris.pause().await.unwrap();
844        let event = event_rx.try_recv();
845        assert!(
846            event.is_err(),
847            "Expected not to receive an event, but got {event:?}"
848        );
849
850        // Pause: calling [Play] after this should cause playback to start again from the same position. //
851        // not easily testable
852
853        // Play: Starts or resumes playback. //
854        mpris.play().await.unwrap();
855        assert_eq!(
856            event_rx.recv(),
857            Ok(StateChange::StatusChanged(Status::Playing))
858        );
859
860        // Play if already playing, this has no effect //
861        mpris.play().await.unwrap();
862        let event = event_rx.try_recv();
863        assert!(
864            event.is_err(),
865            "Expected not to receive an event, but got {event:?}"
866        );
867
868        // Play: If paused, playback resumes from the current position. //
869        // not easily testable
870
871        // Play: If there is no track to play, this has no effect. //
872        // tested above before sending the songs to the audio kernel
873
874        // Play-Pause: Pauses playback. //
875        mpris.play_pause().await.unwrap();
876        assert_eq!(
877            event_rx.recv(),
878            Ok(StateChange::StatusChanged(Status::Paused))
879        );
880
881        // Play-Pause: If playback is already paused, resumes playback. //
882        mpris.play_pause().await.unwrap();
883        assert_eq!(
884            event_rx.recv(),
885            Ok(StateChange::StatusChanged(Status::Playing))
886        );
887
888        // Play-Pause: If playback is stopped, starts playback. //
889        mpris.stop().await.unwrap();
890        assert_eq!(
891            event_rx.recv(),
892            Ok(StateChange::Seeked(Duration::from_secs(0)))
893        );
894        assert_eq!(
895            event_rx.recv(),
896            Ok(StateChange::StatusChanged(Status::Stopped))
897        );
898        mpris.play_pause().await.unwrap();
899        assert_eq!(
900            event_rx.recv(),
901            Ok(StateChange::StatusChanged(Status::Playing))
902        );
903
904        // Stop: Stops playback. //
905        mpris.stop().await.unwrap();
906        assert_eq!(
907            event_rx.recv(),
908            Ok(StateChange::Seeked(Duration::from_secs(0)))
909        );
910        assert_eq!(
911            event_rx.recv(),
912            Ok(StateChange::StatusChanged(Status::Stopped))
913        );
914
915        // Stop: If playback is already stopped, this has no effect. //
916        mpris.stop().await.unwrap();
917        let event = event_rx.try_recv();
918        assert!(
919            event.is_err(),
920            "Expected not to receive an event, but got {event:?}"
921        );
922
923        // Stop: Calling Play after this should cause playback to start again from the beginning of the track. //
924        // not easily testable
925
926        drop(tempdir);
927    }
928
929    /// """
930    /// Opens the uri given as an argument
931    /// If the playback is stopped, starts playing
932    /// If the uri scheme or the mime-type of the uri to open is not supported,
933    ///  this method does nothing and may raise an error.
934    ///  In particular, if the list of available uri schemes is empty,
935    ///  this method may not be implemented.
936    /// If the media player implements the [TrackList interface], then the opened track should be made part of the tracklist,
937    ///  the [TrackAdded] or [TrackListReplaced] signal should be fired, as well as the
938    ///  org.freedesktop.DBus.Properties.PropertiesChanged signal on the [TrackList interface]
939    /// """
940    ///
941    /// Mecomp supports opening file URIs, and returns errors for unsupported or invalid URIs.
942    ///
943    /// Mecomp does not currently implement the [TrackList interface], so the last case is irrelevant here.
944    #[rstest]
945    #[timeout(Duration::from_secs(20))]
946    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
947    async fn test_open_uri(
948        #[future] fixtures: (
949            Mpris,
950            Receiver<StateChange>,
951            TempDir,
952            Arc<AudioKernelSender>,
953        ),
954    ) {
955        init();
956        let (mpris, event_rx, tempdir, _) = fixtures.await;
957
958        // setup
959        let songs = mpris
960            .daemon
961            .clone()
962            .library_songs(())
963            .await
964            .unwrap()
965            .into_inner()
966            .songs;
967        assert_eq!(songs.len(), 4);
968        let first_song = songs[0].clone();
969
970        // Opens the uri given as an argument //
971
972        // open a valid file uri
973        let file_uri = format!("file://{}", first_song.path);
974        mpris.open_uri(file_uri).await.unwrap();
975        assert_eq!(event_rx.recv(), Ok(StateChange::QueueChanged));
976        assert_eq!(
977            event_rx.recv(),
978            Ok(StateChange::TrackChanged(Some(
979                first_song.id.clone().into()
980            )))
981        );
982        // If the playback is stopped, starts playing //
983        assert_eq!(
984            event_rx.recv(),
985            Ok(StateChange::StatusChanged(Status::Playing))
986        );
987
988        // If the uri scheme or the mime-type of the uri to open is not supported, this method does nothing and may raise an error. //
989        // In particular, if the list of available uri schemes is empty, this method may not be implemented. //
990
991        // open a uri with an unsupported scheme
992        let file_uri = "http://example.com/song.mp3".to_string();
993        let result = mpris.open_uri(file_uri).await;
994        assert!(result.is_err());
995        // open a file uri with an invalid path (is not a file path)
996        let file_uri = "file://".to_string();
997        let result = mpris.open_uri(file_uri).await;
998        assert!(result.is_err());
999        // open a file uri with an invalid path (a directory)
1000        let file_uri = format!("file://{}", tempdir.path().display());
1001        let result = mpris.open_uri(file_uri).await;
1002        assert!(result.is_err());
1003        // open a file uri that doesn't exist
1004        let file_uri = "file:///nonexistent.mp3".to_string();
1005        let result = mpris.open_uri(file_uri).await;
1006        assert!(result.is_err());
1007        // open a file uri with an unsupported mime type
1008        std::fs::write(tempdir.path().join("unsupported.txt"), "unsupported")
1009            .expect("Failed to write file");
1010        let file_uri = format!(
1011            "file:///{}",
1012            tempdir.path().join("unsupported.txt").display()
1013        );
1014        let result = mpris.open_uri(file_uri).await;
1015        assert!(result.is_err());
1016
1017        // If the media player implements the [TrackList interface], then the opened track should be made part of the tracklist, the [TrackAdded] or [TrackListReplaced] signal should be fired, as well as the org.freedesktop.DBus.Properties.PropertiesChanged signal on the [TrackList interface] //
1018        // TODO: test this when we implement the [TrackList interface]
1019
1020        drop(tempdir);
1021    }
1022
1023    /// """
1024    /// Returns the playback status.
1025    /// """
1026    /// Mecomp supports returning the playback status.
1027    #[rstest]
1028    #[timeout(Duration::from_secs(20))]
1029    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1030    async fn test_playback_status(
1031        #[future] fixtures: (
1032            Mpris,
1033            Receiver<StateChange>,
1034            TempDir,
1035            Arc<AudioKernelSender>,
1036        ),
1037    ) {
1038        init();
1039        let (mpris, event_rx, tempdir, audio_kernel) = fixtures.await;
1040
1041        // setup
1042        let songs = mpris
1043            .daemon
1044            .clone()
1045            .library_songs(())
1046            .await
1047            .unwrap()
1048            .into_inner()
1049            .songs;
1050        assert_eq!(songs.len(), 4);
1051        let first_song = songs[0].clone();
1052
1053        // Returns the playback status. //
1054        // playback is stopped
1055        assert_eq!(
1056            mpris.playback_status().await.unwrap(),
1057            PlaybackStatus::Stopped
1058        );
1059
1060        // send all the songs to the audio kernel (adding them to the queue and starting playback)
1061        audio_kernel.send(AudioCommand::Queue(QueueCommand::AddToQueue(
1062            OneOrMany::One(Box::new(first_song.clone().into())),
1063        )));
1064
1065        // pause playback
1066        mpris.pause().await.unwrap();
1067
1068        // we expect there to be 4 events
1069        let mut events = [false; 4];
1070        for _ in 0..4 {
1071            let event = event_rx.recv().unwrap();
1072
1073            match event {
1074                StateChange::QueueChanged => {
1075                    events[0] = true;
1076                }
1077                StateChange::TrackChanged(Some(_)) => {
1078                    mpris.state.write().await.current_song = Some(first_song.clone().into());
1079                    events[1] = true;
1080                }
1081                StateChange::StatusChanged(Status::Playing) => {
1082                    mpris.state.write().await.status = Status::Playing;
1083                    assert_eq!(
1084                        mpris.playback_status().await.unwrap(),
1085                        PlaybackStatus::Playing
1086                    );
1087                    events[2] = true;
1088                }
1089                StateChange::StatusChanged(Status::Paused) => {
1090                    mpris.state.write().await.status = Status::Paused;
1091                    assert_eq!(
1092                        mpris.playback_status().await.unwrap(),
1093                        PlaybackStatus::Paused
1094                    );
1095                    events[3] = true;
1096                }
1097                _ => panic!("Unexpected event: {event:?}"),
1098            }
1099        }
1100
1101        assert!(events.iter().all(|&e| e));
1102
1103        drop(tempdir);
1104    }
1105
1106    /// """
1107    /// Returns the loop status.
1108    /// """
1109    ///
1110    /// Mecomp supports returning the loop status.
1111    ///
1112    /// """
1113    /// Sets the loop status.
1114    /// """
1115    ///
1116    /// Mecomp supports setting the loop status.
1117    #[rstest]
1118    #[timeout(Duration::from_secs(20))]
1119    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1120    async fn test_loop_status(
1121        #[future] fixtures: (
1122            Mpris,
1123            Receiver<StateChange>,
1124            TempDir,
1125            Arc<AudioKernelSender>,
1126        ),
1127    ) {
1128        init();
1129        let (mpris, event_rx, _, _) = fixtures.await;
1130
1131        // Returns the loop status. //
1132        // loop status is none
1133        assert_eq!(mpris.loop_status().await.unwrap(), LoopStatus::None);
1134
1135        // set loop status to track
1136        mpris.set_loop_status(LoopStatus::Track).await.unwrap();
1137        if event_rx.recv() == Ok(StateChange::RepeatModeChanged(RepeatMode::One)) {
1138            mpris.state.write().await.repeat_mode = RepeatMode::One;
1139        } else {
1140            panic!("Expected a RepeatModeChanged event, but got something else");
1141        }
1142        assert_eq!(mpris.loop_status().await.unwrap(), LoopStatus::Track);
1143
1144        // set loop status to playlist
1145        mpris.set_loop_status(LoopStatus::Playlist).await.unwrap();
1146        if event_rx.recv() == Ok(StateChange::RepeatModeChanged(RepeatMode::All)) {
1147            mpris.state.write().await.repeat_mode = RepeatMode::All;
1148        } else {
1149            panic!("Expected a RepeatModeChanged event, but got something else");
1150        }
1151        assert_eq!(mpris.loop_status().await.unwrap(), LoopStatus::Playlist);
1152
1153        // set loop status to none
1154        mpris.set_loop_status(LoopStatus::None).await.unwrap();
1155        if event_rx.recv() == Ok(StateChange::RepeatModeChanged(RepeatMode::None)) {
1156            mpris.state.write().await.repeat_mode = RepeatMode::None;
1157        } else {
1158            panic!("Expected a RepeatModeChanged event, but got something else");
1159        }
1160        assert_eq!(mpris.loop_status().await.unwrap(), LoopStatus::None);
1161    }
1162
1163    /// """
1164    /// Returns the rate.
1165    /// """
1166    /// """
1167    /// The minimum value which the [Rate] property can take. Clients should not attempt to set the [Rate] property below this value.
1168    /// """
1169    /// """
1170    /// """
1171    /// The maximum value which the [Rate] property can take. Clients should not attempt to set the [Rate] property above this value.
1172    /// """
1173    /// """
1174    /// Sets the playback rate.
1175    /// """
1176    ///
1177    /// Mecomp supports returning the playback rate, but does not support changing it.
1178    #[rstest]
1179    #[timeout(Duration::from_secs(20))]
1180    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1181    async fn test_rate(
1182        #[future] fixtures: (
1183            Mpris,
1184            Receiver<StateChange>,
1185            TempDir,
1186            Arc<AudioKernelSender>,
1187        ),
1188    ) {
1189        init();
1190        let (mpris, event_rx, _, _) = fixtures.await;
1191
1192        // Returns the playback rate. //
1193        let rate = mpris.rate().await.unwrap();
1194        assert!(f64::EPSILON > (rate - 1.0).abs(), "{rate} != 1.0");
1195
1196        // The minimum value which the [Rate] property can take. Clients should not attempt to set the [Rate] property below this value. //
1197        let min_rate = mpris.minimum_rate().await.unwrap();
1198        assert!(f64::EPSILON > (min_rate - 1.0).abs(), "{min_rate} != 1.0");
1199
1200        // The maximum value which the [Rate] property can take. Clients should not attempt to set the [Rate] property above this value. //
1201        let max_rate = mpris.maximum_rate().await.unwrap();
1202        assert!(f64::EPSILON > (max_rate - 1.0).abs(), "{max_rate} != 1.0");
1203
1204        // Sets the playback rate. //
1205        // not supported, but the spec doesn't specify that an error should be reported so we just return Ok
1206        let result = mpris.set_rate(1.0).await;
1207        assert!(result.is_ok());
1208        assert!(event_rx.try_recv().is_err());
1209    }
1210
1211    /// """
1212    /// Returns whether playback is shuffled.
1213    /// """
1214    ///
1215    /// Mecomp supports returning whether playback is shuffled.
1216    ///
1217    /// """
1218    /// Sets whether playback is shuffled.
1219    /// """
1220    ///
1221    /// Mecomp supports setting whether playback is shuffled.
1222    ///
1223    /// NOTE: Mecomp does not actually implement this properly,
1224    /// as setting shuffle to false will not restore the original order of the queue
1225    /// and is instead a no-op.
1226    #[rstest]
1227    #[timeout(Duration::from_secs(20))]
1228    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1229    async fn test_shuffle(
1230        #[future] fixtures: (
1231            Mpris,
1232            Receiver<StateChange>,
1233            TempDir,
1234            Arc<AudioKernelSender>,
1235        ),
1236    ) {
1237        init();
1238        let (mpris, event_rx, _, _) = fixtures.await;
1239
1240        // Returns whether playback is shuffled. //
1241        assert_eq!(mpris.shuffle().await.unwrap(), true);
1242
1243        // Sets whether playback is shuffled. //
1244        // set shuffle to true
1245        mpris.set_shuffle(true).await.unwrap();
1246        assert_eq!(mpris.shuffle().await.unwrap(), true);
1247        assert_eq!(event_rx.recv(), Ok(StateChange::QueueChanged));
1248
1249        // set shuffle to false
1250        mpris.set_shuffle(false).await.unwrap();
1251        assert_eq!(mpris.shuffle().await.unwrap(), true);
1252        assert!(event_rx.recv_timeout(Duration::from_millis(100)).is_err());
1253    }
1254
1255    /// """
1256    /// The metadata of the current element.
1257    /// When this property changes, the org.freedesktop.DBus.Properties.PropertiesChanged
1258    ///     signal via [properties_changed] must be emitted with the new value.
1259    /// If there is a current track, this must have a [mpris:trackid] entry at the very least,
1260    ///     which contains a D-Bus path that uniquely identifies this track.
1261    /// """
1262    #[rstest]
1263    #[timeout(Duration::from_secs(20))]
1264    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1265    async fn test_metadata(
1266        #[future] fixtures: (
1267            Mpris,
1268            Receiver<StateChange>,
1269            TempDir,
1270            Arc<AudioKernelSender>,
1271        ),
1272    ) {
1273        init();
1274        let (mpris, event_rx, tempdir, audio_kernel) = fixtures.await;
1275
1276        // setup
1277        let songs = mpris
1278            .daemon
1279            .clone()
1280            .library_songs(())
1281            .await
1282            .unwrap()
1283            .into_inner()
1284            .songs;
1285        assert_eq!(songs.len(), 4);
1286        let first_song: SongBrief = songs[0].clone().into();
1287
1288        // The metadata of the current element. //
1289        // when there is no current song
1290        assert_eq!(
1291            mpris.metadata().await.unwrap(),
1292            Metadata::builder().trackid(TrackId::NO_TRACK).build()
1293        );
1294
1295        // send all the songs to the audio kernel (adding them to the queue and starting playback)
1296        audio_kernel.send(AudioCommand::Queue(QueueCommand::AddToQueue(
1297            first_song.clone().into(),
1298        )));
1299
1300        assert_eq!(event_rx.recv(), Ok(StateChange::QueueChanged));
1301        assert_eq!(
1302            event_rx.recv(),
1303            Ok(StateChange::TrackChanged(Some(
1304                first_song.id.clone().into()
1305            )))
1306        );
1307
1308        *mpris.state.write().await = mpris
1309            .daemon
1310            .clone()
1311            .state_audio(())
1312            .await
1313            .unwrap()
1314            .into_inner()
1315            .state
1316            .unwrap()
1317            .into();
1318
1319        // when there is a current song
1320        let metadata = mpris.metadata().await.unwrap();
1321        assert_eq!(metadata, metadata_from_opt_song(Some(&first_song)));
1322        assert_ne!(
1323            metadata,
1324            Metadata::builder().trackid(TrackId::NO_TRACK).build()
1325        );
1326
1327        drop(tempdir);
1328    }
1329
1330    /// """
1331    /// The volume level.
1332    /// When setting, if a negative value is passed, the volume should be set to 0.0.
1333    /// """
1334    #[rstest]
1335    #[timeout(Duration::from_secs(20))]
1336    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1337    async fn test_volume(
1338        #[future] fixtures: (
1339            Mpris,
1340            Receiver<StateChange>,
1341            TempDir,
1342            Arc<AudioKernelSender>,
1343        ),
1344    ) {
1345        let (mpris, event_rx, _, _) = fixtures.await;
1346
1347        // The volume level. //
1348        let volume = mpris.volume().await.unwrap();
1349        assert!(f64::EPSILON > (volume - 1.0).abs(), "{volume} != 1.0");
1350
1351        // When setting, if a negative value is passed, the volume should be set to 0.0. //
1352        mpris.set_volume(-1.0).await.unwrap();
1353        if event_rx.recv() == Ok(StateChange::VolumeChanged(0.0)) {
1354            mpris.state.write().await.volume = 0.0;
1355            let volume = mpris.volume().await.unwrap();
1356            assert!(f64::EPSILON > volume.abs(), "{volume} != 0.0");
1357        } else {
1358            panic!("Expected a VolumeChanged event, but got something else");
1359        }
1360
1361        // set the volume back to 1.0
1362        mpris.set_volume(1.0).await.unwrap();
1363        if event_rx.recv() == Ok(StateChange::VolumeChanged(1.0)) {
1364            mpris.state.write().await.volume = 1.0;
1365            let volume = mpris.volume().await.unwrap();
1366            assert!(f64::EPSILON > (volume - 1.0).abs(), "{volume} != 1.0");
1367        } else {
1368            panic!("Expected a VolumeChanged event, but got something else");
1369        }
1370
1371        // set the volume to the same value
1372        mpris.set_volume(1.0).await.unwrap();
1373        assert!(event_rx.try_recv().is_err());
1374    }
1375
1376    /// """
1377    /// The current track position, between 0 and the [mpris:length] metadata entry.
1378    /// If the media player allows it, the current playback position can be changed either the [SetPosition] method or the [Seek] method on this interface.
1379    /// If this is not the case, the [CanSeek] property is false, and setting this property has no effect and can raise an error.
1380    /// """
1381    ///
1382    /// for set_position:
1383    /// """
1384    /// If the Position argument is less than 0, do nothing.
1385    /// If the Position argument is greater than the track length, do nothing.
1386    /// If the given `track_id` this does not match the id of the currently-playing track, the call is ignored as "stale"
1387    /// """
1388    #[rstest]
1389    #[timeout(Duration::from_secs(20))]
1390    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1391    async fn test_position(
1392        #[future] fixtures: (
1393            Mpris,
1394            Receiver<StateChange>,
1395            TempDir,
1396            Arc<AudioKernelSender>,
1397        ),
1398    ) {
1399        init();
1400        let (mpris, event_rx, tempdir, audio_kernel) = fixtures.await;
1401
1402        assert!(mpris.can_seek().await.unwrap());
1403
1404        // setup
1405        let songs = mpris
1406            .daemon
1407            .clone()
1408            .library_songs(())
1409            .await
1410            .unwrap()
1411            .into_inner()
1412            .songs;
1413        assert_eq!(songs.len(), 4);
1414        let first_song: SongBrief = songs[0].clone().into();
1415
1416        // The current track position, between 0 and the [mpris:length] metadata entry. //
1417        // when there is no current song
1418        assert_eq!(mpris.position().await.unwrap(), Time::from_micros(0));
1419
1420        // send all the songs to the audio kernel (adding them to the queue and starting playback)
1421        audio_kernel.send(AudioCommand::Queue(QueueCommand::AddToQueue(
1422            first_song.clone().into(),
1423        )));
1424        audio_kernel.send(AudioCommand::Pause);
1425        let _ = event_rx.recv().unwrap();
1426        let _ = event_rx.recv().unwrap();
1427        let _ = event_rx.recv().unwrap();
1428        let _ = event_rx.recv().unwrap();
1429        // update internal state
1430        *mpris.state.write().await = mpris
1431            .daemon
1432            .clone()
1433            .state_audio(())
1434            .await
1435            .unwrap()
1436            .into_inner()
1437            .state
1438            .unwrap()
1439            .into();
1440
1441        let first_song_track_id = mpris.metadata().await.unwrap().trackid().unwrap();
1442
1443        // normal:
1444        mpris
1445            .set_position(first_song_track_id.clone(), Time::from_secs(2))
1446            .await
1447            .unwrap();
1448        assert_eq!(
1449            event_rx.recv(),
1450            Ok(StateChange::Seeked(Duration::from_secs(2)))
1451        );
1452
1453        // If the Position argument is less than 0, do nothing. //
1454        mpris
1455            .set_position(first_song_track_id.clone(), Time::from_secs(-1))
1456            .await
1457            .unwrap();
1458        assert!(event_rx.try_recv().is_err());
1459
1460        // If the Position argument is greater than the track length, do nothing. //
1461        mpris
1462            .set_position(first_song_track_id.clone(), Time::from_secs(100))
1463            .await
1464            .unwrap();
1465        assert!(event_rx.try_recv().is_err());
1466
1467        // If the media player allows it, the current playback position can be changed either the [SetPosition] method or the [Seek] method on this interface. //
1468        // If this is not the case, the [CanSeek] property is false, and setting this property has no effect and can raise an error. //
1469        assert!(mpris.can_seek().await.unwrap());
1470
1471        mpris.seek(Time::from_secs(1)).await.unwrap();
1472        assert_eq!(
1473            event_rx.recv(),
1474            Ok(StateChange::Seeked(Duration::from_secs(3)))
1475        );
1476
1477        mpris.seek(Time::from_secs(-1)).await.unwrap();
1478        assert_eq!(
1479            event_rx.recv(),
1480            Ok(StateChange::Seeked(Duration::from_secs(2)))
1481        );
1482
1483        drop(tempdir);
1484    }
1485}