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    zbus::{fdo, Error as ZbusError},
10    LoopStatus, Metadata, PlaybackRate, PlaybackStatus, PlayerInterface, Time, TrackId, Volume,
11};
12use tarpc::context::Context;
13
14use crate::{interfaces::root::SUPPORTED_MIME_TYPES, metadata_from_opt_song, Mpris};
15
16impl PlayerInterface for Mpris {
17    async fn next(&self) -> fdo::Result<()> {
18        let context = Context::current();
19        let daemon_read_lock = self.daemon().await;
20        if let Some(daemon) = daemon_read_lock.as_ref() {
21            daemon
22                .playback_skip_forward(context, 1)
23                .await
24                .map_err(|e| fdo::Error::Failed(e.to_string()))?;
25        }
26        drop(daemon_read_lock);
27        Ok(())
28    }
29
30    async fn previous(&self) -> fdo::Result<()> {
31        let context = Context::current();
32        let daemon_read_lock = self.daemon().await;
33        if let Some(daemon) = daemon_read_lock.as_ref() {
34            daemon
35                .playback_skip_backward(context, 1)
36                .await
37                .map_err(|e| fdo::Error::Failed(e.to_string()))?;
38        }
39        drop(daemon_read_lock);
40        Ok(())
41    }
42
43    async fn pause(&self) -> fdo::Result<()> {
44        let context = Context::current();
45        let daemon_read_lock = self.daemon().await;
46        if let Some(daemon) = daemon_read_lock.as_ref() {
47            daemon
48                .playback_pause(context)
49                .await
50                .map_err(|e| fdo::Error::Failed(e.to_string()))?;
51        }
52        drop(daemon_read_lock);
53        Ok(())
54    }
55
56    async fn play_pause(&self) -> fdo::Result<()> {
57        let context = Context::current();
58        let daemon_read_lock = self.daemon().await;
59        if let Some(daemon) = daemon_read_lock.as_ref() {
60            daemon
61                .playback_toggle(context)
62                .await
63                .map_err(|e| fdo::Error::Failed(e.to_string()))?;
64        }
65        drop(daemon_read_lock);
66        Ok(())
67    }
68
69    async fn stop(&self) -> fdo::Result<()> {
70        let daemon_read_lock = self.daemon().await;
71        if let Some(daemon) = daemon_read_lock.as_ref() {
72            let context = Context::current();
73            daemon
74                .playback_stop(context)
75                .await
76                .map_err(|e| fdo::Error::Failed(e.to_string()))?;
77        }
78        drop(daemon_read_lock);
79
80        Ok(())
81    }
82
83    async fn play(&self) -> fdo::Result<()> {
84        let context = Context::current();
85        let daemon_read_lock = self.daemon().await;
86        if let Some(daemon) = daemon_read_lock.as_ref() {
87            daemon
88                .playback_play(context)
89                .await
90                .map_err(|e| fdo::Error::Failed(e.to_string()))?;
91        }
92        drop(daemon_read_lock);
93        Ok(())
94    }
95
96    async fn open_uri(&self, uri: String) -> fdo::Result<()> {
97        // 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('/').last())
125            .any(|ext| path.extension().is_some_and(|e| e == ext))
126        {
127            return Err(fdo::Error::InvalidArgs(
128                "File type not supported".to_string(),
129            ));
130        }
131
132        // 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::{mpsc::Receiver, Arc};
386
387    use mecomp_core::{
388        audio::{commands::AudioCommand, AudioKernelSender},
389        test_utils::init,
390        udp::StateChange,
391    };
392
393    use mecomp_storage::db::schemas::song::Song;
394    use pretty_assertions::{assert_eq, assert_ne};
395    use rstest::rstest;
396    use tempfile::TempDir;
397
398    use super::*;
399    use crate::test_utils::fixtures;
400
401    /// """
402    /// Skips to the next track in the tracklist.
403    /// If there is no next track (and endless playback and track repeat are both off), stop playback.
404    /// If playback is paused or stopped, it remains that way.
405    /// If [CanGoNext] is false, attempting to call this method should have no effect.
406    /// """
407    ///
408    /// Mecomp supports skipping to the next track in the queue.
409    ///
410    /// the last case is irrelevant here, as we always return true for [CanGoNext]
411    #[rstest]
412    #[timeout(Duration::from_secs(10))]
413    #[tokio::test(flavor = "multi_thread", worker_threads = 3)]
414    async fn test_next(
415        #[future] fixtures: (
416            Mpris,
417            Receiver<StateChange>,
418            TempDir,
419            Arc<AudioKernelSender>,
420        ),
421    ) {
422        init();
423        let (mpris, event_rx, tempdir, audio_kernel) = fixtures.await;
424
425        assert_eq!(mpris.can_go_next().await.unwrap(), true);
426
427        // setup
428        let context = Context::current();
429        let songs: Vec<Song> = mpris
430            .daemon
431            .read()
432            .await
433            .as_ref()
434            .unwrap()
435            .library_songs_full(context)
436            .await
437            .unwrap()
438            .unwrap()
439            .to_vec();
440        assert_eq!(songs.len(), 4);
441        // send all the songs to the audio kernel (adding them to the queue and starting playback)
442        audio_kernel.send(AudioCommand::Queue(
443            mecomp_core::audio::commands::QueueCommand::AddToQueue(Box::new(songs.into())),
444        ));
445
446        let Ok(StateChange::TrackChanged(Some(first_song))) = event_rx.recv() else {
447            panic!("Expected a TrackChanged event, but got something else");
448        };
449        assert_eq!(
450            event_rx.recv(),
451            Ok(StateChange::StatusChanged(Status::Playing))
452        );
453
454        // it skips to the next track //
455        mpris.next().await.unwrap();
456
457        let Ok(StateChange::TrackChanged(Some(second_song))) = event_rx.recv() else {
458            panic!("Expected a TrackChanged event, but got something else");
459        };
460
461        // the current song should be different from the previous song
462        assert_ne!(first_song, second_song);
463
464        drop(tempdir);
465    }
466
467    #[rstest]
468    #[timeout(Duration::from_secs(10))]
469    #[tokio::test]
470    async fn test_next_maintains_status(
471        #[future] fixtures: (
472            Mpris,
473            Receiver<StateChange>,
474            TempDir,
475            Arc<AudioKernelSender>,
476        ),
477    ) {
478        init();
479        let (mpris, event_rx, tempdir, audio_kernel) = fixtures.await;
480
481        assert_eq!(mpris.can_go_next().await.unwrap(), true);
482
483        // setup
484        let context = Context::current();
485        let songs: Vec<Song> = mpris
486            .daemon
487            .read()
488            .await
489            .as_ref()
490            .unwrap()
491            .library_songs_full(context)
492            .await
493            .unwrap()
494            .unwrap()
495            .to_vec();
496        assert_eq!(songs.len(), 4);
497        // send all the songs to the audio kernel (adding them to the queue and starting playback)
498        audio_kernel.send(AudioCommand::Queue(
499            mecomp_core::audio::commands::QueueCommand::AddToQueue(Box::new(songs.into())),
500        ));
501        let Ok(StateChange::TrackChanged(Some(first_song))) = event_rx.recv() else {
502            panic!("Expected a TrackChanged event, but got something else");
503        };
504        assert_eq!(
505            event_rx.recv(),
506            Ok(StateChange::StatusChanged(Status::Playing))
507        );
508
509        // if playback is paused or stopped, it remains that way //
510
511        // stop playback
512        mpris.stop().await.unwrap();
513        assert_eq!(
514            event_rx.recv(),
515            Ok(StateChange::Seeked(Duration::from_secs(0)))
516        );
517        assert_eq!(
518            event_rx.recv(),
519            Ok(StateChange::StatusChanged(Status::Stopped))
520        );
521
522        // skip to the next track
523        mpris.next().await.unwrap();
524        let Ok(StateChange::TrackChanged(Some(second_song))) = event_rx.recv() else {
525            panic!("Expected a TrackChanged event, but got something else");
526        };
527        // playback should remain stopped
528        assert!(event_rx.try_recv().is_err());
529
530        // the current song should be different from the previous song
531        assert_ne!(first_song, second_song);
532
533        // pause playback
534        mpris.pause().await.unwrap();
535        assert_eq!(
536            event_rx.recv(),
537            Ok(StateChange::StatusChanged(Status::Paused))
538        );
539
540        // skip to the next track
541        mpris.next().await.unwrap();
542        let Ok(StateChange::TrackChanged(Some(third_song))) = event_rx.recv() else {
543            panic!("Expected a TrackChanged event, but got something else");
544        };
545        // playback should remain paused
546        assert!(event_rx.try_recv().is_err());
547
548        // the current song should be different from the previous song
549        assert_ne!(second_song, third_song);
550
551        drop(tempdir);
552    }
553
554    #[rstest]
555    #[timeout(Duration::from_secs(10))]
556    #[tokio::test]
557    async fn test_next_no_next_track(
558        #[future] fixtures: (
559            Mpris,
560            Receiver<StateChange>,
561            TempDir,
562            Arc<AudioKernelSender>,
563        ),
564    ) {
565        init();
566        let (mpris, event_rx, tempdir, audio_kernel) = fixtures.await;
567
568        assert_eq!(mpris.can_go_next().await.unwrap(), true);
569
570        // setup
571        let context = Context::current();
572        let songs: Vec<Song> = mpris
573            .daemon
574            .read()
575            .await
576            .as_ref()
577            .unwrap()
578            .library_songs_full(context)
579            .await
580            .unwrap()
581            .unwrap()
582            .to_vec();
583        assert_eq!(songs.len(), 4);
584        // send one song to the audio kernel (adding them to the queue and starting playback)
585        audio_kernel.send(AudioCommand::Queue(
586            mecomp_core::audio::commands::QueueCommand::AddToQueue(Box::new(
587                songs[0].clone().into(),
588            )),
589        ));
590        let _ = event_rx.recv();
591        let _ = event_rx.recv();
592
593        // if there is no next track (and endless playback and track repeat are both off), stop playback. //
594        // skip to the next track (which should be nothing)
595        mpris.next().await.unwrap();
596        assert_eq!(event_rx.recv(), Ok(StateChange::TrackChanged(None)));
597        assert_eq!(
598            event_rx.recv(),
599            Ok(StateChange::StatusChanged(Status::Stopped))
600        );
601        drop(tempdir);
602    }
603
604    /// """
605    /// Skips to the previous track in the tracklist.
606    /// If there is no previous track (and endless playback and track repeat are both off), stop playback.
607    /// If playback is paused or stopped, it remains that way.
608    /// If [CanGoPrevious] is false, attempting to call this method should have no effect.
609    /// """
610    ///
611    /// Mecomp supports skipping to the previous track in the queue.
612    ///
613    /// the last case is irrelevant here, as we always return true for [CanGoPrevious]
614    #[rstest]
615    #[timeout(Duration::from_secs(10))]
616    #[tokio::test]
617    async fn test_prev(
618        #[future] fixtures: (
619            Mpris,
620            Receiver<StateChange>,
621            TempDir,
622            Arc<AudioKernelSender>,
623        ),
624    ) {
625        init();
626        let (mpris, event_rx, tempdir, audio_kernel) = fixtures.await;
627
628        assert_eq!(mpris.can_go_previous().await.unwrap(), true);
629
630        // setup
631        let context = Context::current();
632        let songs: Vec<Song> = mpris
633            .daemon
634            .read()
635            .await
636            .as_ref()
637            .unwrap()
638            .library_songs_full(context)
639            .await
640            .unwrap()
641            .unwrap()
642            .to_vec();
643        assert_eq!(songs.len(), 4);
644        let third_song: mecomp_storage::db::schemas::Thing = songs[2].id.clone().into();
645        let fourth_song: mecomp_storage::db::schemas::Thing = songs[3].id.clone().into();
646
647        // send all the songs to the audio kernel (adding them to the queue and starting playback)
648        audio_kernel.send(AudioCommand::Queue(
649            mecomp_core::audio::commands::QueueCommand::AddToQueue(Box::new(songs.into())),
650        ));
651        let _ = event_rx.recv();
652        let _ = event_rx.recv();
653
654        // skip to the last song in the queue
655        audio_kernel.send(AudioCommand::Queue(
656            mecomp_core::audio::commands::QueueCommand::SetPosition(3),
657        ));
658        assert_eq!(
659            event_rx.recv(),
660            Ok(StateChange::TrackChanged(Some(fourth_song.clone())))
661        );
662
663        // it skips to the previous track //
664        mpris.previous().await.unwrap();
665        // should go back to the fourth song
666        assert_eq!(
667            event_rx.recv(),
668            Ok(StateChange::TrackChanged(Some(third_song))),
669        );
670
671        drop(tempdir);
672    }
673    #[rstest]
674    #[timeout(Duration::from_secs(10))]
675    #[tokio::test]
676    async fn test_prev_maintains_state(
677        #[future] fixtures: (
678            Mpris,
679            Receiver<StateChange>,
680            TempDir,
681            Arc<AudioKernelSender>,
682        ),
683    ) {
684        init();
685        let (mpris, event_rx, tempdir, audio_kernel) = fixtures.await;
686
687        assert_eq!(mpris.can_go_previous().await.unwrap(), true);
688
689        // setup
690        let context = Context::current();
691        let songs: Vec<Song> = mpris
692            .daemon
693            .read()
694            .await
695            .as_ref()
696            .unwrap()
697            .library_songs_full(context)
698            .await
699            .unwrap()
700            .unwrap()
701            .to_vec();
702        assert_eq!(songs.len(), 4);
703        let second_song: mecomp_storage::db::schemas::Thing = songs[1].id.clone().into();
704        let third_song: mecomp_storage::db::schemas::Thing = songs[2].id.clone().into();
705        let fourth_song: mecomp_storage::db::schemas::Thing = songs[3].id.clone().into();
706
707        // send all the songs to the audio kernel (adding them to the queue and starting playback)
708        audio_kernel.send(AudioCommand::Queue(
709            mecomp_core::audio::commands::QueueCommand::AddToQueue(Box::new(songs.into())),
710        ));
711        let _ = event_rx.recv();
712        let _ = event_rx.recv();
713
714        // skip to the last song in the queue
715        audio_kernel.send(AudioCommand::Queue(
716            mecomp_core::audio::commands::QueueCommand::SetPosition(3),
717        ));
718        assert_eq!(
719            event_rx.recv(),
720            Ok(StateChange::TrackChanged(Some(fourth_song.clone())))
721        );
722
723        // if playback is paused or stopped, it remains that way //
724
725        // stop playback
726        mpris.stop().await.unwrap();
727        assert_eq!(
728            event_rx.recv(),
729            Ok(StateChange::Seeked(Duration::from_secs(0)))
730        );
731        assert_eq!(
732            event_rx.recv(),
733            Ok(StateChange::StatusChanged(Status::Stopped))
734        );
735        // skip to the previous track
736        mpris.previous().await.unwrap();
737        // should go back to the third
738        assert_eq!(
739            event_rx.recv(),
740            Ok(StateChange::TrackChanged(Some(third_song))),
741        );
742        // playback should remain stopped
743        assert!(event_rx.try_recv().is_err());
744
745        // pause playback
746        mpris.pause().await.unwrap();
747        assert!(matches!(
748            event_rx.recv(),
749            Ok(StateChange::StatusChanged(Status::Paused))
750        ));
751
752        // skip to the previous track
753        mpris.previous().await.unwrap();
754        // should go back to the second song
755        assert_eq!(
756            event_rx.recv(),
757            Ok(StateChange::TrackChanged(Some(second_song))),
758        );
759        // playback should remain paused
760        assert!(event_rx.try_recv().is_err());
761
762        drop(tempdir);
763    }
764
765    #[rstest]
766    #[timeout(Duration::from_secs(10))]
767    #[tokio::test]
768    async fn test_prev_no_prev_track(
769        #[future] fixtures: (
770            Mpris,
771            Receiver<StateChange>,
772            TempDir,
773            Arc<AudioKernelSender>,
774        ),
775    ) {
776        init();
777        let (mpris, event_rx, tempdir, audio_kernel) = fixtures.await;
778
779        assert_eq!(mpris.can_go_previous().await.unwrap(), true);
780
781        // setup
782        let context = Context::current();
783        let songs: Vec<Song> = mpris
784            .daemon
785            .read()
786            .await
787            .as_ref()
788            .unwrap()
789            .library_songs_full(context)
790            .await
791            .unwrap()
792            .unwrap()
793            .to_vec();
794        assert_eq!(songs.len(), 4);
795
796        // send all the songs to the audio kernel (adding them to the queue and starting playback)
797        audio_kernel.send(AudioCommand::Queue(
798            mecomp_core::audio::commands::QueueCommand::AddToQueue(Box::new(
799                songs[0].clone().into(),
800            )),
801        ));
802        let _ = event_rx.recv();
803        let _ = event_rx.recv();
804
805        // if there is no previous track (and endless playback and track repeat are both off), stop playback. //
806        // skip to the previous track
807        mpris.previous().await.unwrap();
808        // should go back to nothing
809        assert_eq!(event_rx.recv(), Ok(StateChange::TrackChanged(None)),);
810        // playback should be stopped
811        assert!(matches!(
812            event_rx.recv(),
813            Ok(StateChange::StatusChanged(Status::Stopped))
814        ));
815
816        drop(tempdir);
817    }
818
819    /// """
820    /// Pauses playback.
821    /// If playback is already paused, this has no effect.
822    /// Calling [Play] after this should cause playback to start again from the same position.
823    /// If [CanPause] is false, attempting to call this method should have no effect.
824    /// """
825    ///
826    /// Mecomp supports pausing playback.
827    ///
828    /// the last case is irrelevant here, as we always return true for [CanPause]
829    ///
830    /// """
831    /// Starts or resumes playback.
832    /// If already playing, this has no effect.
833    /// If paused, playback resumes from the current position.
834    /// If there is no track to play, this has no effect.
835    /// If [CanPlay] is false, attempting to call this method should have no effect.
836    /// """
837    ///
838    /// Mecomp supports starting or resuming playback.
839    ///
840    /// the last case is irrelevant here, as we always return true for [CanPlay]
841    ///
842    /// """
843    /// Pauses playback.
844    /// If playback is already paused, resumes playback.
845    /// If playback is stopped, starts playback.
846    /// If [CanPause] is false, attempting to call this method should have no effect and raise an error.
847    /// """
848    ///
849    /// Mecomp supports toggling between playing and pausing playback.
850    ///
851    /// """
852    /// Stops playback.
853    /// If playback is already stopped, this has no effect.
854    /// Calling Play after this should cause playback to start again from the beginning of the track.
855    /// If [CanControl] is false, attempting to call this method should have no effect and raise an error.
856    /// """
857    ///
858    /// Mecomp supports stopping playback.
859    ///
860    /// the last case is irrelevant here, as we always return true for [CanControl]
861    #[rstest]
862    #[timeout(Duration::from_secs(10))]
863    #[tokio::test]
864    async fn test_play_pause_stop(
865        #[future] fixtures: (
866            Mpris,
867            Receiver<StateChange>,
868            TempDir,
869            Arc<AudioKernelSender>,
870        ),
871    ) {
872        init();
873        let (mpris, event_rx, tempdir, audio_kernel) = fixtures.await;
874
875        assert_eq!(mpris.can_pause().await.unwrap(), true);
876        assert_eq!(mpris.can_play().await.unwrap(), true);
877        assert_eq!(mpris.can_control().await.unwrap(), true);
878
879        // setup
880        let context = Context::current();
881        let songs: Vec<Song> = mpris
882            .daemon
883            .read()
884            .await
885            .as_ref()
886            .unwrap()
887            .library_songs_full(context)
888            .await
889            .unwrap()
890            .unwrap()
891            .to_vec();
892        assert_eq!(songs.len(), 4);
893        let first_song = songs[0].clone();
894
895        // Play: if there is no track to play, this has no effect //
896        mpris.play().await.unwrap();
897        let event = event_rx.try_recv();
898        assert!(
899            event.is_err(),
900            "Expected not to receive an event, but got {:?}",
901            event
902        );
903
904        // send all the songs to the audio kernel (adding them to the queue and starting playback)
905        audio_kernel.send(AudioCommand::Queue(
906            mecomp_core::audio::commands::QueueCommand::AddToQueue(Box::new(
907                first_song.clone().into(),
908            )),
909        ));
910
911        assert_eq!(
912            event_rx.recv(),
913            Ok(StateChange::TrackChanged(Some(
914                first_song.id.clone().into()
915            )))
916        );
917        assert_eq!(
918            event_rx.recv(),
919            Ok(StateChange::StatusChanged(Status::Playing))
920        );
921
922        // Pause: pauses playback //
923        mpris.pause().await.unwrap();
924        assert_eq!(
925            event_rx.recv(),
926            Ok(StateChange::StatusChanged(Status::Paused))
927        );
928
929        // Pause: if playback is already paused, this has no effect //
930        mpris.pause().await.unwrap();
931        let event = event_rx.try_recv();
932        assert!(
933            event.is_err(),
934            "Expected not to receive an event, but got {:?}",
935            event
936        );
937
938        // Pause: calling [Play] after this should cause playback to start again from the same position. //
939        // not easily testable
940
941        // Play: Starts or resumes playback. //
942        mpris.play().await.unwrap();
943        assert_eq!(
944            event_rx.recv(),
945            Ok(StateChange::StatusChanged(Status::Playing))
946        );
947
948        // Play if already playing, this has no effect //
949        mpris.play().await.unwrap();
950        let event = event_rx.try_recv();
951        assert!(
952            event.is_err(),
953            "Expected not to receive an event, but got {:?}",
954            event
955        );
956
957        // Play: If paused, playback resumes from the current position. //
958        // not easily testable
959
960        // Play: If there is no track to play, this has no effect. //
961        // tested above before sending the songs to the audio kernel
962
963        // Play-Pause: Pauses playback. //
964        mpris.play_pause().await.unwrap();
965        assert_eq!(
966            event_rx.recv(),
967            Ok(StateChange::StatusChanged(Status::Paused))
968        );
969
970        // Play-Pause: If playback is already paused, resumes playback. //
971        mpris.play_pause().await.unwrap();
972        assert_eq!(
973            event_rx.recv(),
974            Ok(StateChange::StatusChanged(Status::Playing))
975        );
976
977        // Play-Pause: If playback is stopped, starts playback. //
978        mpris.stop().await.unwrap();
979        assert_eq!(
980            event_rx.recv(),
981            Ok(StateChange::Seeked(Duration::from_secs(0)))
982        );
983        assert_eq!(
984            event_rx.recv(),
985            Ok(StateChange::StatusChanged(Status::Stopped))
986        );
987        mpris.play_pause().await.unwrap();
988        assert_eq!(
989            event_rx.recv(),
990            Ok(StateChange::StatusChanged(Status::Playing))
991        );
992
993        // Stop: Stops playback. //
994        mpris.stop().await.unwrap();
995        assert_eq!(
996            event_rx.recv(),
997            Ok(StateChange::Seeked(Duration::from_secs(0)))
998        );
999        assert_eq!(
1000            event_rx.recv(),
1001            Ok(StateChange::StatusChanged(Status::Stopped))
1002        );
1003
1004        // Stop: If playback is already stopped, this has no effect. //
1005        mpris.stop().await.unwrap();
1006        let event = event_rx.try_recv();
1007        assert!(
1008            event.is_err(),
1009            "Expected not to receive an event, but got {:?}",
1010            event
1011        );
1012
1013        // Stop: Calling Play after this should cause playback to start again from the beginning of the track. //
1014        // not easily testable
1015
1016        drop(tempdir);
1017    }
1018
1019    /// """
1020    /// Opens the uri given as an argument
1021    /// If the playback is stopped, starts playing
1022    /// If the uri scheme or the mime-type of the uri to open is not supported,
1023    ///  this method does nothing and may raise an error.
1024    ///  In particular, if the list of available uri schemes is empty,
1025    ///  this method may not be implemented.
1026    /// If the media player implements the [TrackList interface], then the opened track should be made part of the tracklist,
1027    ///  the [TrackAdded] or [TrackListReplaced] signal should be fired, as well as the
1028    ///  org.freedesktop.DBus.Properties.PropertiesChanged signal on the [TrackList interface]
1029    /// """
1030    ///
1031    /// Mecomp supports opening file URIs, and returns errors for unsupported or invalid URIs.
1032    ///
1033    /// Mecomp does not currently implement the [TrackList interface], so the last case is irrelevant here.
1034    #[rstest]
1035    #[timeout(Duration::from_secs(10))]
1036    #[tokio::test]
1037    async fn test_open_uri(
1038        #[future] fixtures: (
1039            Mpris,
1040            Receiver<StateChange>,
1041            TempDir,
1042            Arc<AudioKernelSender>,
1043        ),
1044    ) {
1045        init();
1046        let (mpris, event_rx, tempdir, _) = fixtures.await;
1047
1048        // setup
1049        let context = Context::current();
1050        let songs: Vec<Song> = mpris
1051            .daemon
1052            .read()
1053            .await
1054            .as_ref()
1055            .unwrap()
1056            .library_songs_full(context)
1057            .await
1058            .unwrap()
1059            .unwrap()
1060            .to_vec();
1061        assert_eq!(songs.len(), 4);
1062        let first_song = songs[0].clone();
1063
1064        // Opens the uri given as an argument //
1065
1066        // open a valid file uri
1067        let file_uri = format!("file://{}", first_song.path.display());
1068        mpris.open_uri(file_uri).await.unwrap();
1069        assert_eq!(
1070            event_rx.recv(),
1071            Ok(StateChange::TrackChanged(Some(
1072                first_song.id.clone().into()
1073            )))
1074        );
1075        // If the playback is stopped, starts playing //
1076        assert_eq!(
1077            event_rx.recv(),
1078            Ok(StateChange::StatusChanged(Status::Playing))
1079        );
1080
1081        // 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. //
1082        // In particular, if the list of available uri schemes is empty, this method may not be implemented. //
1083
1084        // open a uri with an unsupported scheme
1085        let file_uri = "http://example.com/song.mp3".to_string();
1086        let result = mpris.open_uri(file_uri).await;
1087        assert!(result.is_err());
1088        // open a file uri with an invalid path (is not a file path)
1089        let file_uri = "file://".to_string();
1090        let result = mpris.open_uri(file_uri).await;
1091        assert!(result.is_err());
1092        // open a file uri with an invalid path (a directory)
1093        let file_uri = format!("file://{}", tempdir.path().display());
1094        let result = mpris.open_uri(file_uri).await;
1095        assert!(result.is_err());
1096        // open a file uri that doesn't exist
1097        let file_uri = "file:///nonexistent.mp3".to_string();
1098        let result = mpris.open_uri(file_uri).await;
1099        assert!(result.is_err());
1100        // open a file uri with an unsupported mime type
1101        std::fs::write(tempdir.path().join("unsupported.txt"), "unsupported")
1102            .expect("Failed to write file");
1103        let file_uri = format!(
1104            "file:///{}",
1105            tempdir.path().join("unsupported.txt").display()
1106        );
1107        let result = mpris.open_uri(file_uri).await;
1108        assert!(result.is_err());
1109
1110        // 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] //
1111        // TODO: test this when we implement the [TrackList interface]
1112
1113        drop(tempdir);
1114    }
1115
1116    /// """
1117    /// Returns the playback status.
1118    /// """
1119    /// Mecomp supports returning the playback status.
1120    #[rstest]
1121    #[timeout(Duration::from_secs(10))]
1122    #[tokio::test]
1123    async fn test_playback_status(
1124        #[future] fixtures: (
1125            Mpris,
1126            Receiver<StateChange>,
1127            TempDir,
1128            Arc<AudioKernelSender>,
1129        ),
1130    ) {
1131        init();
1132        let (mpris, event_rx, tempdir, audio_kernel) = fixtures.await;
1133
1134        // setup
1135        let context = Context::current();
1136        let songs: Vec<Song> = mpris
1137            .daemon
1138            .read()
1139            .await
1140            .as_ref()
1141            .unwrap()
1142            .library_songs_full(context)
1143            .await
1144            .unwrap()
1145            .unwrap()
1146            .to_vec();
1147        assert_eq!(songs.len(), 4);
1148        let first_song = songs[0].clone();
1149
1150        // Returns the playback status. //
1151        // playback is stopped
1152        assert_eq!(
1153            mpris.playback_status().await.unwrap(),
1154            PlaybackStatus::Stopped
1155        );
1156
1157        // send all the songs to the audio kernel (adding them to the queue and starting playback)
1158        audio_kernel.send(AudioCommand::Queue(
1159            mecomp_core::audio::commands::QueueCommand::AddToQueue(Box::new(
1160                first_song.clone().into(),
1161            )),
1162        ));
1163
1164        // pause playback
1165        mpris.pause().await.unwrap();
1166
1167        // we expect there to be 3 events
1168        let mut events = [false; 3];
1169        for _ in 0..3 {
1170            let event = event_rx.recv().unwrap();
1171
1172            match event {
1173                StateChange::TrackChanged(Some(_)) => {
1174                    mpris.state.write().await.current_song = Some(first_song.clone());
1175                    events[0] = true;
1176                }
1177                StateChange::StatusChanged(Status::Playing) => {
1178                    mpris.state.write().await.status = Status::Playing;
1179                    assert_eq!(
1180                        mpris.playback_status().await.unwrap(),
1181                        PlaybackStatus::Playing
1182                    );
1183                    events[1] = true;
1184                }
1185                StateChange::StatusChanged(Status::Paused) => {
1186                    mpris.state.write().await.status = Status::Paused;
1187                    assert_eq!(
1188                        mpris.playback_status().await.unwrap(),
1189                        PlaybackStatus::Paused
1190                    );
1191                    events[2] = true;
1192                }
1193                _ => panic!("Unexpected event: {:?}", event),
1194            }
1195        }
1196
1197        assert!(events.iter().all(|&e| e));
1198
1199        drop(tempdir);
1200    }
1201
1202    /// """
1203    /// Returns the loop status.
1204    /// """
1205    ///
1206    /// Mecomp supports returning the loop status.
1207    ///
1208    /// """
1209    /// Sets the loop status.
1210    /// """
1211    ///
1212    /// Mecomp supports setting the loop status.
1213    #[rstest]
1214    #[timeout(Duration::from_secs(10))]
1215    #[tokio::test]
1216    async fn test_loop_status(
1217        #[future] fixtures: (
1218            Mpris,
1219            Receiver<StateChange>,
1220            TempDir,
1221            Arc<AudioKernelSender>,
1222        ),
1223    ) {
1224        init();
1225        let (mpris, event_rx, _, _) = fixtures.await;
1226
1227        // Returns the loop status. //
1228        // loop status is none
1229        assert_eq!(mpris.loop_status().await.unwrap(), LoopStatus::None);
1230
1231        // set loop status to track
1232        mpris.set_loop_status(LoopStatus::Track).await.unwrap();
1233        if let Ok(StateChange::RepeatModeChanged(RepeatMode::One)) = event_rx.recv() {
1234            mpris.state.write().await.repeat_mode = RepeatMode::One;
1235        } else {
1236            panic!("Expected a RepeatModeChanged event, but got something else");
1237        };
1238        assert_eq!(mpris.loop_status().await.unwrap(), LoopStatus::Track);
1239
1240        // set loop status to playlist
1241        mpris.set_loop_status(LoopStatus::Playlist).await.unwrap();
1242        if let Ok(StateChange::RepeatModeChanged(RepeatMode::All)) = event_rx.recv() {
1243            mpris.state.write().await.repeat_mode = RepeatMode::All;
1244        } else {
1245            panic!("Expected a RepeatModeChanged event, but got something else");
1246        };
1247        assert_eq!(mpris.loop_status().await.unwrap(), LoopStatus::Playlist);
1248
1249        // set loop status to none
1250        mpris.set_loop_status(LoopStatus::None).await.unwrap();
1251        if let Ok(StateChange::RepeatModeChanged(RepeatMode::None)) = event_rx.recv() {
1252            mpris.state.write().await.repeat_mode = RepeatMode::None;
1253        } else {
1254            panic!("Expected a RepeatModeChanged event, but got something else");
1255        };
1256        assert_eq!(mpris.loop_status().await.unwrap(), LoopStatus::None);
1257    }
1258
1259    /// """
1260    /// Returns the rate.
1261    /// """
1262    /// """
1263    /// The minimum value which the [Rate] property can take. Clients should not attempt to set the [Rate] property below this value.
1264    /// """
1265    /// """
1266    /// """
1267    /// The maximum value which the [Rate] property can take. Clients should not attempt to set the [Rate] property above this value.
1268    /// """
1269    /// """
1270    /// Sets the playback rate.
1271    /// """
1272    ///
1273    /// Mecomp supports returning the playback rate, but does not support changing it.
1274    #[rstest]
1275    #[timeout(Duration::from_secs(10))]
1276    #[tokio::test]
1277    async fn test_rate(
1278        #[future] fixtures: (
1279            Mpris,
1280            Receiver<StateChange>,
1281            TempDir,
1282            Arc<AudioKernelSender>,
1283        ),
1284    ) {
1285        init();
1286        let (mpris, event_rx, _, _) = fixtures.await;
1287
1288        // Returns the playback rate. //
1289        assert_eq!(mpris.rate().await.unwrap(), 1.0);
1290
1291        // The minimum value which the [Rate] property can take. Clients should not attempt to set the [Rate] property below this value. //
1292        assert_eq!(mpris.minimum_rate().await.unwrap(), 1.0);
1293
1294        // The maximum value which the [Rate] property can take. Clients should not attempt to set the [Rate] property above this value. //
1295        assert_eq!(mpris.maximum_rate().await.unwrap(), 1.0);
1296
1297        // Sets the playback rate. //
1298        // not supported, but the spec doesn't specify that an error should be reported so we just return Ok
1299        let result = mpris.set_rate(1.0).await;
1300        assert!(result.is_ok());
1301        assert!(event_rx.try_recv().is_err());
1302    }
1303
1304    /// """
1305    /// Returns whether playback is shuffled.
1306    /// """
1307    ///
1308    /// Mecomp supports returning whether playback is shuffled.
1309    ///
1310    /// """
1311    /// Sets whether playback is shuffled.
1312    /// """
1313    ///
1314    /// Mecomp supports setting whether playback is shuffled.
1315    ///
1316    /// NOTE: Mecomp does not actually implement this properly,
1317    /// as setting shuffle to false will not restore the original order of the queue
1318    /// and is instead a no-op.
1319    #[rstest]
1320    #[timeout(Duration::from_secs(10))]
1321    #[tokio::test]
1322    async fn test_shuffle(
1323        #[future] fixtures: (
1324            Mpris,
1325            Receiver<StateChange>,
1326            TempDir,
1327            Arc<AudioKernelSender>,
1328        ),
1329    ) {
1330        init();
1331        let (mpris, event_rx, _, _) = fixtures.await;
1332
1333        // Returns whether playback is shuffled. //
1334        assert_eq!(mpris.shuffle().await.unwrap(), true);
1335
1336        // Sets whether playback is shuffled. //
1337        // set shuffle to true
1338        mpris.set_shuffle(true).await.unwrap();
1339        assert_eq!(mpris.shuffle().await.unwrap(), true);
1340        assert!(event_rx.try_recv().is_err());
1341
1342        // set shuffle to false
1343        mpris.set_shuffle(false).await.unwrap();
1344        assert_eq!(mpris.shuffle().await.unwrap(), true);
1345        assert!(event_rx.try_recv().is_err());
1346    }
1347
1348    /// """
1349    /// The metadata of the current element.
1350    /// When this property changes, the org.freedesktop.DBus.Properties.PropertiesChanged
1351    ///     signal via [properties_changed] must be emitted with the new value.
1352    /// If there is a current track, this must have a [mpris:trackid] entry at the very least,
1353    ///     which contains a D-Bus path that uniquely identifies this track.
1354    /// """
1355    #[rstest]
1356    #[timeout(Duration::from_secs(10))]
1357    #[tokio::test]
1358    async fn test_metadata(
1359        #[future] fixtures: (
1360            Mpris,
1361            Receiver<StateChange>,
1362            TempDir,
1363            Arc<AudioKernelSender>,
1364        ),
1365    ) {
1366        init();
1367        let (mpris, event_rx, tempdir, audio_kernel) = fixtures.await;
1368
1369        // setup
1370        let context = Context::current();
1371        let songs: Vec<Song> = mpris
1372            .daemon
1373            .read()
1374            .await
1375            .as_ref()
1376            .unwrap()
1377            .library_songs_full(context)
1378            .await
1379            .unwrap()
1380            .unwrap()
1381            .to_vec();
1382        assert_eq!(songs.len(), 4);
1383        let first_song = songs[0].clone();
1384
1385        // The metadata of the current element. //
1386        // when there is no current song
1387        assert_eq!(
1388            mpris.metadata().await.unwrap(),
1389            Metadata::builder().trackid(TrackId::NO_TRACK).build()
1390        );
1391
1392        // send all the songs to the audio kernel (adding them to the queue and starting playback)
1393        audio_kernel.send(AudioCommand::Queue(
1394            mecomp_core::audio::commands::QueueCommand::AddToQueue(Box::new(
1395                first_song.clone().into(),
1396            )),
1397        ));
1398
1399        assert_eq!(
1400            event_rx.recv(),
1401            Ok(StateChange::TrackChanged(Some(
1402                first_song.id.clone().into()
1403            )))
1404        );
1405
1406        *mpris.state.write().await = mpris
1407            .daemon
1408            .read()
1409            .await
1410            .as_ref()
1411            .unwrap()
1412            .state_audio(Context::current())
1413            .await
1414            .unwrap()
1415            .unwrap();
1416
1417        // when there is a current song
1418        let metadata = mpris.metadata().await.unwrap();
1419        assert_eq!(metadata, metadata_from_opt_song(Some(&first_song)));
1420        assert_ne!(
1421            metadata,
1422            Metadata::builder().trackid(TrackId::NO_TRACK).build()
1423        );
1424
1425        drop(tempdir);
1426    }
1427
1428    /// """
1429    /// The volume level.
1430    /// When setting, if a negative value is passed, the volume should be set to 0.0.
1431    /// """
1432    #[rstest]
1433    #[timeout(Duration::from_secs(10))]
1434    #[tokio::test]
1435    async fn test_volume(
1436        #[future] fixtures: (
1437            Mpris,
1438            Receiver<StateChange>,
1439            TempDir,
1440            Arc<AudioKernelSender>,
1441        ),
1442    ) {
1443        let (mpris, event_rx, _, _) = fixtures.await;
1444
1445        // The volume level. //
1446        assert_eq!(mpris.volume().await.unwrap(), 1.0);
1447
1448        // When setting, if a negative value is passed, the volume should be set to 0.0. //
1449        mpris.set_volume(-1.0).await.unwrap();
1450        if let Ok(StateChange::VolumeChanged(0.0)) = event_rx.recv() {
1451            mpris.state.write().await.volume = 0.0;
1452            assert_eq!(mpris.volume().await.unwrap(), 0.0);
1453        } else {
1454            panic!("Expected a VolumeChanged event, but got something else");
1455        }
1456
1457        // set the volume back to 1.0
1458        mpris.set_volume(1.0).await.unwrap();
1459        if let Ok(StateChange::VolumeChanged(1.0)) = event_rx.recv() {
1460            mpris.state.write().await.volume = 1.0;
1461            assert_eq!(mpris.volume().await.unwrap(), 1.0);
1462        } else {
1463            panic!("Expected a VolumeChanged event, but got something else");
1464        }
1465
1466        // set the volume to the same value
1467        mpris.set_volume(1.0).await.unwrap();
1468        assert!(event_rx.try_recv().is_err());
1469    }
1470
1471    /// """
1472    /// The current track position, between 0 and the [mpris:length] metadata entry.
1473    /// If the media player allows it, the current playback position can be changed either the [SetPosition] method or the [Seek] method on this interface.
1474    /// If this is not the case, the [CanSeek] property is false, and setting this property has no effect and can raise an error.
1475    /// """
1476    ///
1477    /// for set_position:
1478    /// """
1479    /// If the Position argument is less than 0, do nothing.
1480    /// If the Position argument is greater than the track length, do nothing.
1481    /// If the given `track_id` this does not match the id of the currently-playing track, the call is ignored as "stale"
1482    /// """
1483    #[rstest]
1484    #[timeout(Duration::from_secs(10))]
1485    #[tokio::test]
1486    async fn test_position(
1487        #[future] fixtures: (
1488            Mpris,
1489            Receiver<StateChange>,
1490            TempDir,
1491            Arc<AudioKernelSender>,
1492        ),
1493    ) {
1494        init();
1495        let (mpris, event_rx, tempdir, audio_kernel) = fixtures.await;
1496
1497        assert!(mpris.can_seek().await.unwrap());
1498
1499        // setup
1500        let context = Context::current();
1501        let songs: Vec<Song> = mpris
1502            .daemon
1503            .read()
1504            .await
1505            .as_ref()
1506            .unwrap()
1507            .library_songs_full(context)
1508            .await
1509            .unwrap()
1510            .unwrap()
1511            .to_vec();
1512        assert_eq!(songs.len(), 4);
1513        let first_song = songs[0].clone();
1514
1515        // The current track position, between 0 and the [mpris:length] metadata entry. //
1516        // when there is no current song
1517        assert_eq!(mpris.position().await.unwrap(), Time::from_micros(0));
1518
1519        // send all the songs to the audio kernel (adding them to the queue and starting playback)
1520        audio_kernel.send(AudioCommand::Queue(
1521            mecomp_core::audio::commands::QueueCommand::AddToQueue(Box::new(
1522                first_song.clone().into(),
1523            )),
1524        ));
1525        audio_kernel.send(AudioCommand::Pause);
1526        let _ = event_rx.recv().unwrap();
1527        let _ = event_rx.recv().unwrap();
1528        let _ = event_rx.recv().unwrap();
1529        // update internal state
1530        *mpris.state.write().await = mpris
1531            .daemon
1532            .read()
1533            .await
1534            .as_ref()
1535            .unwrap()
1536            .state_audio(Context::current())
1537            .await
1538            .unwrap()
1539            .unwrap();
1540
1541        let first_song_track_id = mpris.metadata().await.unwrap().trackid().unwrap();
1542
1543        // normal:
1544        mpris
1545            .set_position(first_song_track_id.clone(), Time::from_secs(2))
1546            .await
1547            .unwrap();
1548        assert_eq!(
1549            event_rx.recv(),
1550            Ok(StateChange::Seeked(Duration::from_secs(2)))
1551        );
1552
1553        // If the Position argument is less than 0, do nothing. //
1554        mpris
1555            .set_position(first_song_track_id.clone(), Time::from_secs(-1))
1556            .await
1557            .unwrap();
1558        assert!(event_rx.try_recv().is_err());
1559
1560        // If the Position argument is greater than the track length, do nothing. //
1561        mpris
1562            .set_position(first_song_track_id.clone(), Time::from_secs(100))
1563            .await
1564            .unwrap();
1565        assert!(event_rx.try_recv().is_err());
1566
1567        // If the media player allows it, the current playback position can be changed either the [SetPosition] method or the [Seek] method on this interface. //
1568        // If this is not the case, the [CanSeek] property is false, and setting this property has no effect and can raise an error. //
1569        assert!(mpris.can_seek().await.unwrap());
1570
1571        mpris.seek(Time::from_secs(1)).await.unwrap();
1572        assert_eq!(
1573            event_rx.recv(),
1574            Ok(StateChange::Seeked(Duration::from_secs(3)))
1575        );
1576
1577        mpris.seek(Time::from_secs(-1)).await.unwrap();
1578        assert_eq!(
1579            event_rx.recv(),
1580            Ok(StateChange::Seeked(Duration::from_secs(2)))
1581        );
1582
1583        drop(tempdir);
1584    }
1585}