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 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        // the uri should be in the format:
98        // file:///path/to/file
99        // TODO: Support loading a playlist file when we've implemented importing/exporting external playlists
100        log::info!("Opening URI: {uri}");
101
102        // ensure the URI is a supported URI
103        if !uri.starts_with("file://") {
104            return Err(fdo::Error::InvalidArgs(
105                "Only file:// URIs are supported".to_string(),
106            ));
107        }
108
109        // extract the path from the URI, and ensure it's a valid file path
110        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        // parse out escaped characters (e.g. %20 for space)
115        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        // ensure the file type is supported
122        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        // expand the tilde if present
133        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        // ensure the path exists
142        if !path.exists() {
143            return Err(fdo::Error::InvalidArgs("File does not exist".to_string()));
144        }
145
146        // ensure the path is a file
147        if !path.is_file() {
148            return Err(fdo::Error::InvalidArgs("Path is not a file".to_string()));
149        }
150
151        // canonicalize the path
152        path = path.canonicalize().unwrap_or(path);
153
154        // add the song to the queue
155        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        // Mecomp has no distinction between shuffle and non-shuffle playback
229        // shuffling is done by actually shuffling the queue and is not reversible
230        // therefore, we always return true
231        Ok(true)
232    }
233
234    async fn set_shuffle(&self, shuffle: bool) -> Result<(), ZbusError> {
235        // if called with false, does nothing, if called with true, shuffles the queue
236        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        //TODO: if the value passed in would mean seeking beyond the end of the track, act like a call to Next
293        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        // "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'"
315        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                // if the position not in the range of the song, ignore the call
324                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// NOTE: these tests do not run the event `Subscriber` main loop so events from the audio kernel are not
381// actually going to be applied to the state, so we have to manually set the state when testing methods
382// that simply report the state.
383#[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    /// """
405    /// Skips to the next track in the tracklist.
406    /// If there is no next track (and endless playback and track repeat are both off), stop playback.
407    /// If playback is paused or stopped, it remains that way.
408    /// If [CanGoNext] is false, attempting to call this method should have no effect.
409    /// """
410    ///
411    /// Mecomp supports skipping to the next track in the queue.
412    ///
413    /// the last case is irrelevant here, as we always return true for [CanGoNext]
414    #[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        // setup
431        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        // send all the songs to the audio kernel (adding them to the queue and starting playback)
445        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        // it skips to the next track //
456        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        // the current song should be different from the previous song
463        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        // setup
485        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        // send all the songs to the audio kernel (adding them to the queue and starting playback)
499        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        // if playback is paused or stopped, it remains that way //
509
510        // stop playback
511        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        // skip to the next track
522        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        // playback should remain stopped
527        assert!(event_rx.try_recv().is_err());
528
529        // the current song should be different from the previous song
530        assert_ne!(first_song, second_song);
531
532        // pause playback
533        mpris.pause().await.unwrap();
534        assert_eq!(
535            event_rx.recv(),
536            Ok(StateChange::StatusChanged(Status::Paused))
537        );
538
539        // skip to the next track
540        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        // playback should remain paused
545        assert!(event_rx.try_recv().is_err());
546
547        // the current song should be different from the previous song
548        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        // setup
570        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        // send one song to the audio kernel (adding them to the queue and starting playback)
584        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        // if there is no next track (and endless playback and track repeat are both off), stop playback. //
591        // skip to the next track (which should be nothing)
592        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    /// """
602    /// Skips to the previous track in the tracklist.
603    /// If there is no previous track (and endless playback and track repeat are both off), stop playback.
604    /// If playback is paused or stopped, it remains that way.
605    /// If [CanGoPrevious] is false, attempting to call this method should have no effect.
606    /// """
607    ///
608    /// Mecomp supports skipping to the previous track in the queue.
609    ///
610    /// the last case is irrelevant here, as we always return true for [CanGoPrevious]
611    #[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        // setup
628        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        // send all the songs to the audio kernel (adding them to the queue and starting playback)
645        audio_kernel.send(AudioCommand::Queue(QueueCommand::AddToQueue(songs.into())));
646        let _ = event_rx.recv();
647        let _ = event_rx.recv();
648
649        // skip to the last song in the queue
650        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        // it skips to the previous track //
657        mpris.previous().await.unwrap();
658        // should go back to the fourth song
659        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        // setup
683        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        // send all the songs to the audio kernel (adding them to the queue and starting playback)
701        audio_kernel.send(AudioCommand::Queue(QueueCommand::AddToQueue(songs.into())));
702        let _ = event_rx.recv();
703        let _ = event_rx.recv();
704
705        // skip to the last song in the queue
706        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        // if playback is paused or stopped, it remains that way //
713
714        // stop playback
715        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        // skip to the previous track
725        mpris.previous().await.unwrap();
726        // should go back to the third
727        assert_eq!(
728            event_rx.recv(),
729            Ok(StateChange::TrackChanged(Some(third_song))),
730        );
731        // playback should remain stopped
732        assert!(event_rx.try_recv().is_err());
733
734        // pause playback
735        mpris.pause().await.unwrap();
736        assert!(matches!(
737            event_rx.recv(),
738            Ok(StateChange::StatusChanged(Status::Paused))
739        ));
740
741        // skip to the previous track
742        mpris.previous().await.unwrap();
743        // should go back to the second song
744        assert_eq!(
745            event_rx.recv(),
746            Ok(StateChange::TrackChanged(Some(second_song))),
747        );
748        // playback should remain paused
749        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        // setup
771        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        // send all the songs to the audio kernel (adding them to the queue and starting playback)
786        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        // if there is no previous track (and endless playback and track repeat are both off), stop playback. //
793        // skip to the previous track
794        mpris.previous().await.unwrap();
795        // should go back to nothing
796        assert_eq!(event_rx.recv(), Ok(StateChange::TrackChanged(None)),);
797        // playback should be stopped
798        assert!(matches!(
799            event_rx.recv(),
800            Ok(StateChange::StatusChanged(Status::Stopped))
801        ));
802
803        drop(tempdir);
804    }
805
806    /// """
807    /// Pauses playback.
808    /// If playback is already paused, this has no effect.
809    /// Calling [Play] after this should cause playback to start again from the same position.
810    /// If [CanPause] is false, attempting to call this method should have no effect.
811    /// """
812    ///
813    /// Mecomp supports pausing playback.
814    ///
815    /// the last case is irrelevant here, as we always return true for [CanPause]
816    ///
817    /// """
818    /// Starts or resumes playback.
819    /// If already playing, this has no effect.
820    /// If paused, playback resumes from the current position.
821    /// If there is no track to play, this has no effect.
822    /// If [CanPlay] is false, attempting to call this method should have no effect.
823    /// """
824    ///
825    /// Mecomp supports starting or resuming playback.
826    ///
827    /// the last case is irrelevant here, as we always return true for [CanPlay]
828    ///
829    /// """
830    /// Pauses playback.
831    /// If playback is already paused, resumes playback.
832    /// If playback is stopped, starts playback.
833    /// If [CanPause] is false, attempting to call this method should have no effect and raise an error.
834    /// """
835    ///
836    /// Mecomp supports toggling between playing and pausing playback.
837    ///
838    /// """
839    /// Stops playback.
840    /// If playback is already stopped, this has no effect.
841    /// Calling Play after this should cause playback to start again from the beginning of the track.
842    /// If [CanControl] is false, attempting to call this method should have no effect and raise an error.
843    /// """
844    ///
845    /// Mecomp supports stopping playback.
846    ///
847    /// the last case is irrelevant here, as we always return true for [CanControl]
848    #[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        // setup
867        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        // Play: if there is no track to play, this has no effect //
883        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        // send all the songs to the audio kernel (adding them to the queue and starting playback)
891        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        // Pause: pauses playback //
907        mpris.pause().await.unwrap();
908        assert_eq!(
909            event_rx.recv(),
910            Ok(StateChange::StatusChanged(Status::Paused))
911        );
912
913        // Pause: if playback is already paused, this has no effect //
914        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        // Pause: calling [Play] after this should cause playback to start again from the same position. //
922        // not easily testable
923
924        // Play: Starts or resumes playback. //
925        mpris.play().await.unwrap();
926        assert_eq!(
927            event_rx.recv(),
928            Ok(StateChange::StatusChanged(Status::Playing))
929        );
930
931        // Play if already playing, this has no effect //
932        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        // Play: If paused, playback resumes from the current position. //
940        // not easily testable
941
942        // Play: If there is no track to play, this has no effect. //
943        // tested above before sending the songs to the audio kernel
944
945        // Play-Pause: Pauses playback. //
946        mpris.play_pause().await.unwrap();
947        assert_eq!(
948            event_rx.recv(),
949            Ok(StateChange::StatusChanged(Status::Paused))
950        );
951
952        // Play-Pause: If playback is already paused, resumes playback. //
953        mpris.play_pause().await.unwrap();
954        assert_eq!(
955            event_rx.recv(),
956            Ok(StateChange::StatusChanged(Status::Playing))
957        );
958
959        // Play-Pause: If playback is stopped, starts playback. //
960        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        // Stop: Stops playback. //
976        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        // Stop: If playback is already stopped, this has no effect. //
987        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        // Stop: Calling Play after this should cause playback to start again from the beginning of the track. //
995        // not easily testable
996
997        drop(tempdir);
998    }
999
1000    /// """
1001    /// Opens the uri given as an argument
1002    /// If the playback is stopped, starts playing
1003    /// If the uri scheme or the mime-type of the uri to open is not supported,
1004    ///  this method does nothing and may raise an error.
1005    ///  In particular, if the list of available uri schemes is empty,
1006    ///  this method may not be implemented.
1007    /// If the media player implements the [TrackList interface], then the opened track should be made part of the tracklist,
1008    ///  the [TrackAdded] or [TrackListReplaced] signal should be fired, as well as the
1009    ///  org.freedesktop.DBus.Properties.PropertiesChanged signal on the [TrackList interface]
1010    /// """
1011    ///
1012    /// Mecomp supports opening file URIs, and returns errors for unsupported or invalid URIs.
1013    ///
1014    /// Mecomp does not currently implement the [TrackList interface], so the last case is irrelevant here.
1015    #[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        // setup
1030        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        // Opens the uri given as an argument //
1046
1047        // open a valid file uri
1048        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        // If the playback is stopped, starts playing //
1057        assert_eq!(
1058            event_rx.recv(),
1059            Ok(StateChange::StatusChanged(Status::Playing))
1060        );
1061
1062        // 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. //
1063        // In particular, if the list of available uri schemes is empty, this method may not be implemented. //
1064
1065        // open a uri with an unsupported scheme
1066        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        // open a file uri with an invalid path (is not a file path)
1070        let file_uri = "file://".to_string();
1071        let result = mpris.open_uri(file_uri).await;
1072        assert!(result.is_err());
1073        // open a file uri with an invalid path (a directory)
1074        let file_uri = format!("file://{}", tempdir.path().display());
1075        let result = mpris.open_uri(file_uri).await;
1076        assert!(result.is_err());
1077        // open a file uri that doesn't exist
1078        let file_uri = "file:///nonexistent.mp3".to_string();
1079        let result = mpris.open_uri(file_uri).await;
1080        assert!(result.is_err());
1081        // open a file uri with an unsupported mime type
1082        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        // 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] //
1092        // TODO: test this when we implement the [TrackList interface]
1093
1094        drop(tempdir);
1095    }
1096
1097    /// """
1098    /// Returns the playback status.
1099    /// """
1100    /// Mecomp supports returning the playback status.
1101    #[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        // setup
1116        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        // Returns the playback status. //
1132        // playback is stopped
1133        assert_eq!(
1134            mpris.playback_status().await.unwrap(),
1135            PlaybackStatus::Stopped
1136        );
1137
1138        // send all the songs to the audio kernel (adding them to the queue and starting playback)
1139        audio_kernel.send(AudioCommand::Queue(QueueCommand::AddToQueue(
1140            first_song.clone().into(),
1141        )));
1142
1143        // pause playback
1144        mpris.pause().await.unwrap();
1145
1146        // we expect there to be 3 events
1147        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    /// """
1182    /// Returns the loop status.
1183    /// """
1184    ///
1185    /// Mecomp supports returning the loop status.
1186    ///
1187    /// """
1188    /// Sets the loop status.
1189    /// """
1190    ///
1191    /// Mecomp supports setting the loop status.
1192    #[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        // Returns the loop status. //
1207        // loop status is none
1208        assert_eq!(mpris.loop_status().await.unwrap(), LoopStatus::None);
1209
1210        // set loop status to track
1211        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        // set loop status to playlist
1220        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        // set loop status to none
1229        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    /// """
1239    /// Returns the rate.
1240    /// """
1241    /// """
1242    /// The minimum value which the [Rate] property can take. Clients should not attempt to set the [Rate] property below this value.
1243    /// """
1244    /// """
1245    /// """
1246    /// The maximum value which the [Rate] property can take. Clients should not attempt to set the [Rate] property above this value.
1247    /// """
1248    /// """
1249    /// Sets the playback rate.
1250    /// """
1251    ///
1252    /// Mecomp supports returning the playback rate, but does not support changing it.
1253    #[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        // Returns the playback rate. //
1268        let rate = mpris.rate().await.unwrap();
1269        assert!(f64::EPSILON > (rate - 1.0).abs(), "{rate} != 1.0");
1270
1271        // The minimum value which the [Rate] property can take. Clients should not attempt to set the [Rate] property below this value. //
1272        let min_rate = mpris.minimum_rate().await.unwrap();
1273        assert!(f64::EPSILON > (min_rate - 1.0).abs(), "{min_rate} != 1.0");
1274
1275        // The maximum value which the [Rate] property can take. Clients should not attempt to set the [Rate] property above this value. //
1276        let max_rate = mpris.maximum_rate().await.unwrap();
1277        assert!(f64::EPSILON > (max_rate - 1.0).abs(), "{max_rate} != 1.0");
1278
1279        // Sets the playback rate. //
1280        // not supported, but the spec doesn't specify that an error should be reported so we just return Ok
1281        let result = mpris.set_rate(1.0).await;
1282        assert!(result.is_ok());
1283        assert!(event_rx.try_recv().is_err());
1284    }
1285
1286    /// """
1287    /// Returns whether playback is shuffled.
1288    /// """
1289    ///
1290    /// Mecomp supports returning whether playback is shuffled.
1291    ///
1292    /// """
1293    /// Sets whether playback is shuffled.
1294    /// """
1295    ///
1296    /// Mecomp supports setting whether playback is shuffled.
1297    ///
1298    /// NOTE: Mecomp does not actually implement this properly,
1299    /// as setting shuffle to false will not restore the original order of the queue
1300    /// and is instead a no-op.
1301    #[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        // Returns whether playback is shuffled. //
1316        assert_eq!(mpris.shuffle().await.unwrap(), true);
1317
1318        // Sets whether playback is shuffled. //
1319        // set shuffle to true
1320        mpris.set_shuffle(true).await.unwrap();
1321        assert_eq!(mpris.shuffle().await.unwrap(), true);
1322        assert!(event_rx.try_recv().is_err());
1323
1324        // set shuffle to false
1325        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    /// """
1331    /// The metadata of the current element.
1332    /// When this property changes, the org.freedesktop.DBus.Properties.PropertiesChanged
1333    ///     signal via [properties_changed] must be emitted with the new value.
1334    /// If there is a current track, this must have a [mpris:trackid] entry at the very least,
1335    ///     which contains a D-Bus path that uniquely identifies this track.
1336    /// """
1337    #[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        // setup
1352        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        // The metadata of the current element. //
1368        // when there is no current song
1369        assert_eq!(
1370            mpris.metadata().await.unwrap(),
1371            Metadata::builder().trackid(TrackId::NO_TRACK).build()
1372        );
1373
1374        // send all the songs to the audio kernel (adding them to the queue and starting playback)
1375        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        // when there is a current song
1398        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    /// """
1409    /// The volume level.
1410    /// When setting, if a negative value is passed, the volume should be set to 0.0.
1411    /// """
1412    #[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        // The volume level. //
1426        let volume = mpris.volume().await.unwrap();
1427        assert!(f64::EPSILON > (volume - 1.0).abs(), "{volume} != 1.0");
1428
1429        // When setting, if a negative value is passed, the volume should be set to 0.0. //
1430        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        // set the volume back to 1.0
1440        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        // set the volume to the same value
1450        mpris.set_volume(1.0).await.unwrap();
1451        assert!(event_rx.try_recv().is_err());
1452    }
1453
1454    /// """
1455    /// The current track position, between 0 and the [mpris:length] metadata entry.
1456    /// If the media player allows it, the current playback position can be changed either the [SetPosition] method or the [Seek] method on this interface.
1457    /// If this is not the case, the [CanSeek] property is false, and setting this property has no effect and can raise an error.
1458    /// """
1459    ///
1460    /// for set_position:
1461    /// """
1462    /// If the Position argument is less than 0, do nothing.
1463    /// If the Position argument is greater than the track length, do nothing.
1464    /// If the given `track_id` this does not match the id of the currently-playing track, the call is ignored as "stale"
1465    /// """
1466    #[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        // setup
1483        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        // The current track position, between 0 and the [mpris:length] metadata entry. //
1499        // when there is no current song
1500        assert_eq!(mpris.position().await.unwrap(), Time::from_micros(0));
1501
1502        // send all the songs to the audio kernel (adding them to the queue and starting playback)
1503        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        // update internal state
1511        *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        // normal:
1525        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        // If the Position argument is less than 0, do nothing. //
1535        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        // If the Position argument is greater than the track length, do nothing. //
1542        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        // If the media player allows it, the current playback position can be changed either the [SetPosition] method or the [Seek] method on this interface. //
1549        // If this is not the case, the [CanSeek] property is false, and setting this property has no effect and can raise an error. //
1550        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}