mpris_server/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![warn(rust_2018_idioms, missing_debug_implementations)]
3#![deny(rustdoc::broken_intra_doc_links)]
4#![doc = include_str!("../README.md")]
5
6// TODO:
7// * Document the rest of public interface
8// * Access Server from interfaces
9// * Replace `DateTime` and `Uri` with proper types
10// * Add sensible default method impls on `*Interface` traits
11// * Profile if inlining is worth it
12// * Add public `test` method to check if interface is implemented correctly
13// * Avoid clones in `Metadata` getters
14
15mod local_server;
16mod loop_status;
17mod metadata;
18mod playback_status;
19mod player;
20mod playlist;
21mod playlist_ordering;
22mod property;
23mod server;
24mod signal;
25mod time;
26mod track_id;
27
28/// This contains the definitions of builder-pattern structs.
29///
30/// The `builder` methods on the objects must be used instead to construct
31/// these builder-pattern structs.
32pub mod builder {
33    pub use crate::{metadata::MetadataBuilder, player::PlayerBuilder};
34}
35
36pub use zbus;
37use zbus::{fdo, zvariant::OwnedObjectPath, Result};
38
39pub use crate::{
40    local_server::{LocalServer, LocalServerRunTask},
41    loop_status::LoopStatus,
42    metadata::{DateTime, Metadata},
43    playback_status::PlaybackStatus,
44    player::Player,
45    playlist::Playlist,
46    playlist_ordering::PlaylistOrdering,
47    property::{PlaylistsProperty, Property, TrackListProperty},
48    server::Server,
49    signal::{PlaylistsSignal, Signal, TrackListSignal},
50    time::Time,
51    track_id::TrackId,
52};
53
54macro_rules! define_iface {
55    (#[$attr:meta],
56        $root_iface_ident:ident extra_docs $extra_root_docs:literal,
57        $player_iface_ident:ident extra_docs $extra_player_docs:literal,
58        $track_list_iface_ident:ident extra_docs $extra_track_list_docs:literal,
59        $playlists_iface_ident:ident extra_docs $extra_playlists_docs:literal) => {
60        #[doc = $extra_root_docs]
61        #[doc = ""]
62        /// Used to implement [org.mpris.MediaPlayer2] interface.
63        ///
64        /// [org.mpris.MediaPlayer2]: https://specifications.freedesktop.org/mpris-spec/latest/Media_Player.html
65        #[$attr]
66        pub trait $root_iface_ident {
67            /// Brings the media player's user interface to the front using any
68            /// appropriate mechanism available.
69            ///
70            /// The media player may be unable to control how its user interface is
71            /// displayed, or it may not have a graphical user interface at all. In this
72            /// case, the [`CanRaise`] property is **false** and this method does
73            /// nothing.
74            ///
75            /// [`CanRaise`]: Self::can_raise
76            #[doc(alias = "Raise")]
77            async fn raise(&self) -> fdo::Result<()>;
78
79            /// Causes the media player to stop running.
80            ///
81            /// The media player may refuse to allow clients to shut it down. In this
82            /// case, the [`CanQuit`] property is **false** and this method does
83            /// nothing.
84            ///
85            /// **Note:** Media players which can be D-Bus activated, or for which there
86            /// is no sensibly easy way to terminate a running instance (via the
87            /// main interface or a notification area icon for example) should allow
88            /// clients to use this method. Otherwise, it should not be needed.
89            ///
90            /// If the media player does not have a UI, this should be implemented.
91            ///
92            /// [`CanQuit`]: Self::can_quit
93            #[doc(alias = "Quit")]
94            async fn quit(&self) -> fdo::Result<()>;
95
96            /// Whether the player may be asked to quit.
97            ///
98            /// When this property changes, the
99            /// `org.freedesktop.DBus.Properties.PropertiesChanged` signal via
100            /// [`properties_changed`] must be emitted with the new value.
101            ///
102            /// If **false**, calling [`Quit`] will have no effect, and may raise a
103            /// [`NotSupported`] error. If **true**, calling [`Quit`] will cause the media
104            /// application to attempt to quit (although it may still be prevented from
105            /// quitting by the user, for example).
106            ///
107            /// [`properties_changed`]: Server::properties_changed
108            /// [`Quit`]: Self::quit
109            /// [`NotSupported`]: fdo::Error::NotSupported
110            #[doc(alias = "CanQuit")]
111            async fn can_quit(&self) -> fdo::Result<bool>;
112
113            /// Whether the media player is occupying the fullscreen.
114            ///
115            /// This property is *optional*. Clients should handle its absence
116            /// gracefully.
117            ///
118            /// When this property changes, the
119            /// `org.freedesktop.DBus.Properties.PropertiesChanged` signal via
120            /// [`properties_changed`] must be emitted with the new value.
121            ///
122            /// This is typically used for videos. A value of **true** indicates that
123            /// the media player is taking up the full screen.
124            ///
125            /// Media centre software may well have this value fixed to **true**
126            ///
127            /// If [`CanSetFullscreen`] is **true**, clients may set this property to
128            /// **true** to tell the media player to enter fullscreen mode, or to
129            /// **false** to return to windowed mode.
130            ///
131            /// If [`CanSetFullscreen`] is **false**, then attempting to set this
132            /// property should have no effect, and may raise an error. However,
133            /// even if it is **true**, the media player may still be unable to
134            /// fulfil the request, in which case attempting to set this property
135            /// will have no effect (but should not raise an error).
136            ///
137            /// <details><summary>Rationale</summary>
138            ///
139            /// This allows remote control interfaces, such as LIRC or mobile devices
140            /// like phones, to control whether a video is shown in fullscreen.
141            ///
142            /// </details>
143            ///
144            /// [`properties_changed`]: Server::properties_changed
145            /// [`CanSetFullscreen`]: Self::can_set_fullscreen
146            #[doc(alias = "Fullscreen")]
147            async fn fullscreen(&self) -> fdo::Result<bool>;
148
149            /// Sets whether the media player is occupying the fullscreen.
150            ///
151            /// See [`Fullscreen`] for more details.
152            ///
153            /// [`Fullscreen`]: Self::fullscreen
154            #[doc(alias = "Fullscreen")]
155            async fn set_fullscreen(&self, fullscreen: bool) -> Result<()>;
156
157            /// Whether the player may be asked to enter or leave fullscreen.
158            ///
159            /// This property is *optional*. Clients should handle its absence
160            /// gracefully.
161            ///
162            /// When this property changes, the
163            /// `org.freedesktop.DBus.Properties.PropertiesChanged` signal via
164            /// [`properties_changed`] must be emitted with the new value.
165            ///
166            /// If **false**, attempting to set [`Fullscreen`] will have no effect, and
167            /// may raise an error. If **true**, attempting to set [`Fullscreen`]
168            /// will not raise an error, and (if it is different from the current
169            /// value) will cause the media player to attempt to enter or exit
170            /// fullscreen mode.
171            ///
172            /// Note that the media player may be unable to fulfil the request. In this
173            /// case, the value will not change. If the media player knows in advance
174            /// that it will not be able to fulfil the request, however, this property
175            /// should be **false**.
176            ///
177            /// <details><summary>Rationale</summary>
178            ///
179            /// This allows clients to choose whether to display controls for entering
180            /// or exiting fullscreen mode.
181            ///
182            /// </details>
183            ///
184            /// [`properties_changed`]: Server::properties_changed
185            /// [`Fullscreen`]: Self::fullscreen
186            #[doc(alias = "CanSetFullscreen")]
187            async fn can_set_fullscreen(&self) -> fdo::Result<bool>;
188
189            /// Whether the media player may be asked to be raised.
190            ///
191            /// When this property changes, the
192            /// `org.freedesktop.DBus.Properties.PropertiesChanged` signal via
193            /// [`properties_changed`] must be emitted with the new value.
194            ///
195            /// If **false**, calling [`Raise`] will have no effect, and may raise a
196            /// [`NotSupported`] error. If **true**, calling [`Raise`] will cause the
197            /// media application to attempt to bring its user interface to the
198            /// front, although it may be prevented from doing so (by the window
199            /// manager, for example).
200            ///
201            /// [`properties_changed`]: Server::properties_changed
202            /// [`Raise`]: Self::raise
203            /// [`NotSupported`]: fdo::Error::NotSupported
204            #[doc(alias = "CanRaise")]
205            async fn can_raise(&self) -> fdo::Result<bool>;
206
207            /// Indicates whether the `/org/mpris/MediaPlayer2` object implements the
208            /// [`TrackList interface`].
209            ///
210            /// When this property changes, the
211            /// `org.freedesktop.DBus.Properties.PropertiesChanged` signal via
212            /// [`properties_changed`] must be emitted with the new value.
213            ///
214            /// [`TrackList interface`]: TrackListInterface
215            /// [`properties_changed`]: Server::properties_changed
216            #[doc(alias = "HasTrackList")]
217            async fn has_track_list(&self) -> fdo::Result<bool>;
218
219            /// A friendly name to identify the media player to users (eg: "VLC media
220            /// player").
221            ///
222            /// When this property changes, the
223            /// `org.freedesktop.DBus.Properties.PropertiesChanged` signal via
224            /// [`properties_changed`] must be emitted with the new value.
225            ///
226            /// This should usually match the name found in .desktop files
227            ///
228            /// [`properties_changed`]: Server::properties_changed
229            #[doc(alias = "Identity")]
230            async fn identity(&self) -> fdo::Result<String>;
231
232            /// The basename of an installed .desktop file which complies with the
233            /// [Desktop entry specification], with the ".desktop" extension stripped.
234            ///
235            /// This property is *optional*. Clients should handle its absence
236            /// gracefully.
237            ///
238            /// When this property changes, the
239            /// `org.freedesktop.DBus.Properties.PropertiesChanged` signal via
240            /// [`properties_changed`] must be emitted with the new value.
241            ///
242            /// Example: The desktop entry file is
243            /// "/usr/share/applications/vlc.desktop", and this property contains "vlc"
244            ///
245            /// [Desktop entry specification]: https://specifications.freedesktop.org/desktop-entry-spec/latest/
246            /// [`properties_changed`]: Server::properties_changed
247            #[doc(alias = "DesktopEntry")]
248            async fn desktop_entry(&self) -> fdo::Result<String>;
249
250            /// The URI schemes supported by the media player.
251            ///
252            /// When this property changes, the
253            /// `org.freedesktop.DBus.Properties.PropertiesChanged` signal via
254            /// [`properties_changed`] must be emitted with the new value.
255            ///
256            /// This can be viewed as protocols supported by the player in almost all
257            /// cases. Almost every media player will include support for the "file"
258            /// scheme. Other common schemes are "http" and "rtsp".
259            ///
260            /// Note that URI schemes should be lower-case.
261            ///
262            /// <details><summary>Rationale</summary>
263            ///
264            /// This is important for clients to know when using the editing
265            /// capabilities of the [`Playlists interface`], for example.
266            ///
267            /// </details>
268            ///
269            /// [`properties_changed`]: Server::properties_changed
270            /// [`Playlists interface`]: PlaylistsInterface
271            #[doc(alias = "SupportedUriSchemes")]
272            async fn supported_uri_schemes(&self) -> fdo::Result<Vec<String>>;
273
274            /// The mime-types supported by the media player.
275            ///
276            /// When this property changes, the
277            /// `org.freedesktop.DBus.Properties.PropertiesChanged` signal via
278            /// [`properties_changed`] must be emitted with the new value.
279            ///
280            /// Mime-types should be in the standard format (eg: audio/mpeg or
281            /// application/ogg).
282            ///
283            /// <details><summary>Rationale</summary>
284            ///
285            /// This is important for clients to know when using the editing
286            /// capabilities of the [`Playlists interface`], for example.
287            ///
288            /// </details>
289            ///
290            /// [`properties_changed`]: Server::properties_changed
291            /// [`Playlists interface`]: PlaylistsInterface
292            #[doc(alias = "SupportedMimeTypes")]
293            async fn supported_mime_types(&self) -> fdo::Result<Vec<String>>;
294        }
295
296        #[doc = $extra_player_docs]
297        #[doc = ""]
298        /// Used to implement [org.mpris.MediaPlayer2.Player] interface, which
299        /// implements the methods for querying and providing basic control over what is
300        /// currently playing.
301        ///
302        /// [org.mpris.MediaPlayer2.Player]: https://specifications.freedesktop.org/mpris-spec/latest/Player_Interface.html
303        #[$attr]
304        #[doc(alias = "org.mpris.MediaPlayer2.Player")]
305        pub trait $player_iface_ident: $root_iface_ident {
306            /// Skips to the next track in the tracklist.
307            ///
308            /// If there is no next track (and endless playback and track repeat are
309            /// both off), stop playback.
310            ///
311            /// If playback is paused or stopped, it remains that way.
312            ///
313            /// If [`CanGoNext`] is **false**, attempting to call this method should
314            /// have no effect.
315            ///
316            /// [`CanGoNext`]: Self::can_go_next
317            #[doc(alias = "Next")]
318            async fn next(&self) -> fdo::Result<()>;
319
320            /// Skips to the previous track in the tracklist.
321            ///
322            /// If there is no previous track (and endless playback and track repeat are
323            /// both off), stop playback.
324            ///
325            /// If playback is paused or stopped, it remains that way.
326            ///
327            /// If [`CanGoPrevious`] is **false**, attempting to call this method should
328            /// have no effect.
329            ///
330            /// [`CanGoPrevious`]: Self::can_go_previous
331            #[doc(alias = "Previous")]
332            async fn previous(&self) -> fdo::Result<()>;
333
334            /// Pauses playback.
335            ///
336            /// If playback is already paused, this has no effect.
337            ///
338            /// Calling [`Play`] after this should cause playback to start again from
339            /// the same position.
340            ///
341            /// If [`CanPause`] is **false**, attempting to call this method should have
342            /// no effect.
343            ///
344            /// [`Play`]: Self::play
345            /// [`CanPause`]: Self::can_pause
346            #[doc(alias = "Pause")]
347            async fn pause(&self) -> fdo::Result<()>;
348
349            /// Pauses playback.
350            ///
351            /// If playback is already paused, resumes playback.
352            ///
353            /// If playback is stopped, starts playback.
354            ///
355            /// If [`CanPause`] is **false**, attempting to call this method should have
356            /// no effect and raise an error.
357            ///
358            /// [`CanPause`]: Self::can_pause
359            #[doc(alias = "PlayPause")]
360            async fn play_pause(&self) -> fdo::Result<()>;
361
362            /// Stops playback.
363            ///
364            /// If playback is already stopped, this has no effect.
365            ///
366            /// Calling Play after this should cause playback to start again from the
367            /// beginning of the track.
368            ///
369            /// If [`CanControl`] is **false**, attempting to call this method should
370            /// have no effect and raise an error.
371            ///
372            /// [`CanControl`]: Self::can_control
373            #[doc(alias = "Stop")]
374            async fn stop(&self) -> fdo::Result<()>;
375
376            /// Starts or resumes playback.
377            ///
378            /// If already playing, this has no effect.
379            ///
380            /// If paused, playback resumes from the current position.
381            ///
382            /// If there is no track to play, this has no effect.
383            ///
384            /// If [`CanPlay`] is **false**, attempting to call this method should have
385            /// no effect.
386            ///
387            /// [`CanPlay`]: Self::can_play
388            #[doc(alias = "Play")]
389            async fn play(&self) -> fdo::Result<()>;
390
391            /// Seeks forward in the current track by the specified offset in time.
392            ///
393            /// # Parameters
394            ///
395            /// * `offset` - The offset in time to seek forward.
396            ///
397            /// A negative value seeks back. If this would mean seeking back further
398            /// than the start of the track, the position is set to 0.
399            ///
400            /// If the value passed in would mean seeking beyond the end of the track,
401            /// acts like a call to Next.
402            ///
403            /// If the [`CanSeek`] property is **false**, this has no effect.
404            ///
405            /// [`CanSeek`]: Self::can_seek
406            #[doc(alias = "Seek")]
407            async fn seek(&self, offset: Time) -> fdo::Result<()>;
408
409            /// Sets the current track position.
410            ///
411            /// # Parameters
412            ///
413            /// * `track_id` - The currently playing track's identifier. If this does
414            ///   not match the id of the currently-playing track, the call is ignored
415            ///   as "stale". [`/org/mpris/MediaPlayer2/TrackList/NoTrack`] is not a
416            ///   valid value for this argument.
417            /// * `position` - The track position. This must be between 0 and
418            ///   <track_length>.
419            ///
420            /// If the Position argument is less than 0, do nothing.
421            ///
422            /// If the Position argument is greater than the track length, do nothing.
423            ///
424            /// If the [`CanSeek`] property is **false**, this has no effect.
425            ///
426            /// <details><summary>Rationale</summary>
427            ///
428            /// The reason for having this method, rather than making [`Position`]
429            /// writable, is to include the `track_id` argument to avoid race
430            /// conditions where a client tries to seek to a position when the track
431            /// has already changed.
432            ///
433            /// </details>
434            ///
435            /// [`/org/mpris/MediaPlayer2/TrackList/NoTrack`]: TrackId::NO_TRACK
436            /// [`CanSeek`]: Self::can_seek
437            /// [`Position`]: Self::position
438            #[doc(alias = "SetPosition")]
439            async fn set_position(&self, track_id: TrackId, position: Time) -> fdo::Result<()>;
440
441            /// Opens the `uri` given as an argument
442            ///
443            /// # Parameters
444            ///
445            /// * `uri` - Uri of the track to load. Its uri scheme should be an element
446            ///   of the [`SupportedUriSchemes`] property and the mime-type should match
447            ///   one of the elements of the [`SupportedMimeTypes`].
448            ///
449            /// If the playback is stopped, starts playing
450            ///
451            /// If the uri scheme or the mime-type of the uri to open is not supported,
452            /// this method does nothing and may raise an error. In particular, if the
453            /// list of available uri schemes is empty, this method may not be
454            /// implemented.
455            ///
456            /// Clients should not assume that the `uri` has been opened as soon as this
457            /// method returns. They should wait until the [`mpris:trackid`] field in
458            /// the [`Metadata`] property changes.
459            ///
460            /// If the media player implements the [`TrackList interface`], then the
461            /// opened track should be made part of the tracklist, the [`TrackAdded`] or
462            /// [`TrackListReplaced`] signal should be fired, as well as the
463            /// `org.freedesktop.DBus.Properties.PropertiesChanged` signal on the
464            /// [`TrackList interface`].
465            ///
466            /// [`SupportedUriSchemes`]: RootInterface::supported_uri_schemes
467            /// [`SupportedMimeTypes`]: RootInterface::supported_mime_types
468            /// [`mpris:trackid`]: Metadata::set_trackid
469            /// [`Metadata`]: Self::metadata
470            /// [`TrackList interface`]: TrackListInterface
471            /// [`TrackAdded`]: TrackListSignal::TrackAdded
472            /// [`TrackListReplaced`]: TrackListSignal::TrackListReplaced
473            #[doc(alias = "OpenUri")]
474            async fn open_uri(&self, uri: String) -> fdo::Result<()>;
475
476            /// The current playback status.
477            ///
478            /// When this property changes, the
479            /// `org.freedesktop.DBus.Properties.PropertiesChanged` signal via
480            /// [`properties_changed`] must be emitted with the new value.
481            ///
482            /// May be [`Playing`], [`Paused`] or [`Stopped`].
483            ///
484            /// [`properties_changed`]: Server::properties_changed
485            /// [`Playing`]: PlaybackStatus::Playing
486            /// [`Paused`]: PlaybackStatus::Paused
487            /// [`Stopped`]: PlaybackStatus::Stopped
488            #[doc(alias = "PlaybackStatus")]
489            async fn playback_status(&self) -> fdo::Result<PlaybackStatus>;
490
491            /// The current loop / repeat status
492            ///
493            /// This property is *optional*. Clients should handle its absence
494            /// gracefully.
495            ///
496            /// When this property changes, the
497            /// `org.freedesktop.DBus.Properties.PropertiesChanged` signal via
498            /// [`properties_changed`] must be emitted with the new value.
499            ///
500            /// May be:
501            ///
502            /// * [`None`] if the playback will stop when there are no more tracks to
503            ///   play
504            /// * [`Track`] if the current track will start again from the beginning
505            ///   once it has finished playing
506            /// * [`Playlist`] if the playback loops through a list of tracks
507            ///
508            /// If [`CanControl`] is **false**, attempting to set this property should
509            /// have no effect and raise an error.
510            ///
511            /// [`properties_changed`]: Server::properties_changed
512            /// [`None`]: LoopStatus::None
513            /// [`Track`]: LoopStatus::Track
514            /// [`Playlist`]: LoopStatus::Playlist
515            /// [`CanControl`]: Self::can_control
516            #[doc(alias = "LoopStatus")]
517            async fn loop_status(&self) -> fdo::Result<LoopStatus>;
518
519            /// Sets the current loop / repeat status
520            ///
521            /// See [`LoopStatus`] for more details.
522            ///
523            /// [`LoopStatus`]: Self::loop_status
524            #[doc(alias = "LoopStatus")]
525            async fn set_loop_status(&self, loop_status: LoopStatus) -> Result<()>;
526
527            /// The current playback rate.
528            ///
529            /// When this property changes, the
530            /// `org.freedesktop.DBus.Properties.PropertiesChanged` signal via
531            /// [`properties_changed`] must be emitted with the new value.
532            ///
533            /// The value must fall in the range described by [`MinimumRate`] and
534            /// [`MaximumRate`], and must not be 0.0. If playback is paused, the
535            /// [`PlaybackStatus`] property should be used to indicate this. A value of
536            /// 0.0 should not be set by the client. If it is, the media player
537            /// should act as though [`Pause`] was called.
538            ///
539            /// If the media player has no ability to play at speeds other than the
540            /// normal playback rate, this must still be implemented, and must return
541            /// 1.0. The [`MinimumRate`] and [`MaximumRate`] properties must also be set
542            /// to 1.0.
543            ///
544            /// Not all values may be accepted by the media player. It is left to media
545            /// player implementations to decide how to deal with values they cannot
546            /// use; they may either ignore them or pick a "best fit" value. Clients are
547            /// recommended to only use sensible fractions or multiples of 1 (eg: 0.5,
548            /// 0.25, 1.5, 2.0, etc).
549            ///
550            /// <details><summary>Rationale</summary>
551            ///
552            /// This allows clients to display (reasonably) accurate progress bars
553            /// without having to regularly query the media player for the current
554            /// position.
555            ///
556            /// </details>
557            ///
558            /// [`properties_changed`]: Server::properties_changed
559            /// [`MinimumRate`]: Self::minimum_rate
560            /// [`MaximumRate`]: Self::maximum_rate
561            /// [`PlaybackStatus`]: Self::playback_status
562            /// [`Pause`]: Self::pause
563            #[doc(alias = "Rate")]
564            async fn rate(&self) -> fdo::Result<PlaybackRate>;
565
566            /// Sets the current playback rate.
567            ///
568            /// See [`Rate`] for more details.
569            ///
570            /// [`Rate`]: Self::rate
571            #[doc(alias = "Rate")]
572            async fn set_rate(&self, rate: PlaybackRate) -> Result<()>;
573
574            /// Whether playback is shuffled.
575            ///
576            /// This property is *optional*. Clients should handle its absence
577            /// gracefully.
578            ///
579            /// When this property changes, the
580            /// `org.freedesktop.DBus.Properties.PropertiesChanged` signal via
581            /// [`properties_changed`] must be emitted with the new value.
582            ///
583            /// A value of **false** indicates that playback is progressing linearly
584            /// through a playlist, while **true** means playback is progressing through
585            /// a playlist in some other order.
586            ///
587            /// If [`CanControl`] is **false**, attempting to set this property should
588            /// have no effect and raise an error.
589            ///
590            /// [`properties_changed`]: Server::properties_changed
591            /// [`CanControl`]: Self::can_control
592            #[doc(alias = "Shuffle")]
593            async fn shuffle(&self) -> fdo::Result<bool>;
594
595            /// Sets whether playback is shuffled.
596            ///
597            /// See [`Shuffle`] for more details.
598            ///
599            /// [`Shuffle`]: Self::shuffle
600            #[doc(alias = "Shuffle")]
601            async fn set_shuffle(&self, shuffle: bool) -> Result<()>;
602
603            /// The metadata of the current element.
604            ///
605            /// When this property changes, the
606            /// `org.freedesktop.DBus.Properties.PropertiesChanged` signal via
607            /// [`properties_changed`] must be emitted with the new value.
608            ///
609            /// If there is a current track, this must have a [`mpris:trackid`] entry at
610            /// the very least, which contains a D-Bus path that uniquely identifies
611            /// this track.
612            ///
613            /// [`properties_changed`]: Server::properties_changed
614            /// [`mpris:trackid`]: Metadata::set_trackid
615            #[doc(alias = "Metadata")]
616            async fn metadata(&self) -> fdo::Result<Metadata>;
617
618            /// The volume level.
619            ///
620            /// When this property changes, the
621            /// `org.freedesktop.DBus.Properties.PropertiesChanged` signal via
622            /// [`properties_changed`] must be emitted with the new value.
623            ///
624            /// When setting, if a negative value is passed, the volume should be set to
625            /// 0.0.
626            ///
627            /// If [`CanControl`] is **false**, attempting to set this property should
628            /// have no effect and raise an error.
629            ///
630            /// [`properties_changed`]: Server::properties_changed
631            /// [`CanControl`]: Self::can_control
632            #[doc(alias = "Volume")]
633            async fn volume(&self) -> fdo::Result<Volume>;
634
635            /// Sets the volume level.
636            ///
637            /// See [`Volume`] for more details.
638            ///
639            /// [`Volume`]: Self::volume
640            #[doc(alias = "Volume")]
641            async fn set_volume(&self, volume: Volume) -> Result<()>;
642
643            /// The current track position, between 0 and the [`mpris:length`]
644            /// metadata entry.
645            ///
646            /// When this property changes, the
647            /// `org.freedesktop.DBus.Properties.PropertiesChanged` signal via
648            /// [`properties_changed`] must *not* be emitted.
649            ///
650            /// **Note:** If the media player allows it, the current playback position
651            /// can be changed either the [`SetPosition`] method or the [`Seek`]
652            /// method on this interface. If this is not the case, the [`CanSeek`]
653            /// property is **false**, and setting this property has no effect and
654            /// can raise an error.
655            ///
656            /// If the playback progresses in a way that is inconstistent with the
657            /// [`Rate`] property, the [`Seeked`] signal is emitted.
658            ///
659            /// [`mpris:length`]: Metadata::set_length
660            /// [`properties_changed`]: Server::properties_changed
661            /// [`SetPosition`]: Self::set_position
662            /// [`Seek`]: Self::seek
663            /// [`CanSeek`]: Self::can_seek
664            /// [`Rate`]: Self::rate
665            /// [`Seeked`]: Signal::Seeked
666            #[doc(alias = "Position")]
667            async fn position(&self) -> fdo::Result<Time>;
668
669            /// The minimum value which the [`Rate`] property can take. Clients should
670            /// not attempt to set the [`Rate`] property below this value.
671            ///
672            /// When this property changes, the
673            /// `org.freedesktop.DBus.Properties.PropertiesChanged` signal via
674            /// [`properties_changed`] must be emitted with the new value.
675            ///
676            /// Note that even if this value is 0.0 or negative, clients should not
677            /// attempt to set the [`Rate`] property to 0.0.
678            ///
679            /// This value should always be 1.0 or less.
680            ///
681            /// [`Rate`]: Self::rate
682            /// [`properties_changed`]: Server::properties_changed
683            #[doc(alias = "MinimumRate")]
684            async fn minimum_rate(&self) -> fdo::Result<PlaybackRate>;
685
686            /// The maximum value which the [`Rate`] property can take. Clients should
687            /// not attempt to set the [`Rate`] property above this value.
688            ///
689            /// When this property changes, the
690            /// `org.freedesktop.DBus.Properties.PropertiesChanged` signal via
691            /// [`properties_changed`] must be emitted with the new value.
692            ///
693            /// This value should always be 1.0 or greater.
694            ///
695            /// [`Rate`]: Self::rate
696            /// [`properties_changed`]: Server::properties_changed
697            #[doc(alias = "MaximumRate")]
698            async fn maximum_rate(&self) -> fdo::Result<PlaybackRate>;
699
700            /// Whether the client can call the [`Next`] method on this interface and
701            /// expect the current track to change.
702            ///
703            /// When this property changes, the
704            /// `org.freedesktop.DBus.Properties.PropertiesChanged` signal via
705            /// [`properties_changed`] must be emitted with the new value.
706            ///
707            /// If it is unknown whether a call to [`Next`] will be successful (for
708            /// example, when streaming tracks), this property should be set to
709            /// **true**.
710            ///
711            /// If [`CanControl`] is **false**, this property should also be **false**.
712            ///
713            /// <details><summary>Rationale</summary>
714            ///
715            /// Even when playback can generally be controlled, there may not always be
716            /// a next track to move to.
717            ///
718            /// </details>
719            ///
720            /// [`Next`]: Self::next
721            /// [`properties_changed`]: Server::properties_changed
722            /// [`CanControl`]: Self::can_control
723            #[doc(alias = "CanGoNext")]
724            async fn can_go_next(&self) -> fdo::Result<bool>;
725
726            /// Whether the client can call the [`Previous`] method on this interface
727            /// and expect the current track to change.
728            ///
729            /// When this property changes, the
730            /// `org.freedesktop.DBus.Properties.PropertiesChanged` signal via
731            /// [`properties_changed`] must be emitted with the new value.
732            ///
733            /// If it is unknown whether a call to [`Previous`] will be successful (for
734            /// example, when streaming tracks), this property should be set to
735            /// **true**.
736            ///
737            /// If [`CanControl`] is **false**, this property should also be **false**.
738            ///
739            /// <details><summary>Rationale</summary>
740            ///
741            /// Even when playback can generally be controlled, there may not always be
742            /// a next previous to move to.
743            ///
744            /// </details>
745            ///
746            /// [`Previous`]: Self::previous
747            /// [`properties_changed`]: Server::properties_changed
748            /// [`CanControl`]: Self::can_control
749            #[doc(alias = "CanGoPrevious")]
750            async fn can_go_previous(&self) -> fdo::Result<bool>;
751
752            /// Whether playback can be started using [`Play`] or [`PlayPause`].
753            ///
754            /// When this property changes, the
755            /// `org.freedesktop.DBus.Properties.PropertiesChanged` signal via
756            /// [`properties_changed`] must be emitted with the new value.
757            ///
758            /// Note that this is related to whether there is a "current track": the
759            /// value should not depend on whether the track is currently paused or
760            /// playing. In fact, if a track is currently playing (and [`CanControl`] is
761            /// **true**), this should be **true**.
762            ///
763            /// If [`CanControl`] is **false**, this property should also be **false**.
764            ///
765            /// <details><summary>Rationale</summary>
766            ///
767            /// Even when playback can generally be controlled, it may not be possible
768            /// to enter a "playing" state, for example if there is no "current track".
769            ///
770            /// </details>
771            ///
772            /// [`Play`]: Self::play
773            /// [`PlayPause`]: Self::play_pause
774            /// [`properties_changed`]: Server::properties_changed
775            /// [`CanControl`]: Self::can_control
776            #[doc(alias = "CanPlay")]
777            async fn can_play(&self) -> fdo::Result<bool>;
778
779            /// Whether playback can be paused using [`Pause`] or [`PlayPause`].
780            ///
781            /// When this property changes, the
782            /// `org.freedesktop.DBus.Properties.PropertiesChanged` signal via
783            /// [`properties_changed`] must be emitted with the new value.
784            ///
785            /// Note that this is an intrinsic property of the current track: its value
786            /// should not depend on whether the track is currently paused or playing.
787            /// In fact, if playback is currently paused (and [`CanControl`] is
788            /// **true**), this should be **true**.
789            ///
790            /// If [`CanControl`] is **false**, this property should also be **false**.
791            ///
792            /// <details><summary>Rationale</summary>
793            ///
794            /// Not all media is pausable: it may not be possible to pause some streamed
795            /// media, for example.
796            ///
797            /// </details>
798            ///
799            /// [`Pause`]: Self::pause
800            /// [`PlayPause`]: Self::play_pause
801            /// [`properties_changed`]: Server::properties_changed
802            /// [`CanControl`]: Self::can_control
803            #[doc(alias = "CanPause")]
804            async fn can_pause(&self) -> fdo::Result<bool>;
805
806            /// Whether the client can control the playback position using [`Seek`] and
807            /// [`SetPosition`]. This may be different for different tracks.
808            ///
809            /// When this property changes, the
810            /// `org.freedesktop.DBus.Properties.PropertiesChanged` signal via
811            /// [`properties_changed`] must be emitted with the new value.
812            ///
813            /// If [`CanControl`] is **false**, this property should also be **false**.
814            ///
815            /// <details><summary>Rationale</summary>
816            ///
817            /// Not all media is seekable: it may not be possible to seek when playing
818            /// some streamed media, for example.
819            ///
820            /// </details>
821            ///
822            /// [`Seek`]: Self::seek
823            /// [`SetPosition`]: Self::set_position
824            /// [`properties_changed`]: Server::properties_changed
825            /// [`CanControl`]: Self::can_control
826            #[doc(alias = "CanSeek")]
827            async fn can_seek(&self) -> fdo::Result<bool>;
828
829            /// Whether the media player may be controlled over this interface.
830            ///
831            /// When this property changes, the
832            /// `org.freedesktop.DBus.Properties.PropertiesChanged` signal via
833            /// [`properties_changed`] must *not* be emitted.
834            ///
835            /// This property is not expected to change, as it describes an intrinsic
836            /// capability of the implementation.
837            ///
838            /// If this is **false**, clients should assume that all properties on this
839            /// interface are read-only (and will raise errors if writing to them is
840            /// attempted), no methods are implemented and all other properties starting
841            /// with `Can` are also **false**.
842            ///
843            /// <details><summary>Rationale</summary>
844            ///
845            /// This allows clients to determine whether to present and enable controls
846            /// to the user in advance of attempting to call methods and write to
847            /// properties.
848            ///
849            /// </details>
850            ///
851            /// [`properties_changed`]: Server::properties_changed
852            #[doc(alias = "CanControl")]
853            async fn can_control(&self) -> fdo::Result<bool>;
854        }
855
856        #[doc = $extra_track_list_docs]
857        #[doc = ""]
858        /// Used to implement [org.mpris.MediaPlayer2.TrackList] interface, which
859        /// provides access to a short list of tracks which were recently played or will
860        /// be played shortly. This is intended to provide context to the
861        /// currently-playing track, rather than giving complete access to the media
862        /// player's playlist.
863        ///
864        /// Example use cases are the list of tracks from the same album as the
865        /// currently playing song or the [Rhythmbox] play queue.
866        ///
867        /// Each track in the tracklist has a unique identifier. The intention is that
868        /// this uniquely identifies the track within the scope of the tracklist. In
869        /// particular, if a media item (a particular music file, say) occurs twice in
870        /// the track list, each occurrence should have a different identifier. If a
871        /// track is removed from the middle of the playlist, it should not affect the
872        /// track ids of any other tracks in the tracklist.
873        ///
874        /// As a result, the traditional track identifiers of URLs and position in the
875        /// playlist cannot be used. Any scheme which satisfies the uniqueness
876        /// requirements is valid, as clients should not make any assumptions about the
877        /// value of the track id beyond the fact that it is a unique identifier.
878        ///
879        /// Note that the (memory and processing) burden of implementing this interface
880        /// and maintaining unique track ids for the playlist can be mitigated by only
881        /// exposing a subset of the playlist when it is very long (the 20 or so tracks
882        /// around the currently playing track, for example). This is a recommended
883        /// practice as the tracklist interface is not designed to enable browsing
884        /// through a large list of tracks, but rather to provide clients with context
885        /// about the currently playing track.
886        ///
887        /// [org.mpris.MediaPlayer2.TrackList]: https://specifications.freedesktop.org/mpris-spec/latest/Track_List_Interface.html
888        /// [Rhythmbox]: https://wiki.gnome.org/Apps/Rhythmbox
889        /// [`TrackList interface`]: TrackListInterface
890        #[$attr]
891        #[doc(alias = "org.mpris.MediaPlayer2.TrackList")]
892        pub trait $track_list_iface_ident: $player_iface_ident {
893            /// Gets all the metadata available for a set of tracks.
894            ///
895            /// # Parameters
896            ///
897            /// * `track_ids` - The list of track ids for which metadata is requested.
898            ///
899            /// # Returns
900            ///
901            /// * `metadata` - Metadata of the set of tracks given as input.
902            ///
903            /// Each set of metadata must have a [`mpris:trackid`] entry at the very
904            /// least, which contains a string that uniquely identifies this track
905            /// within the scope of the tracklist.
906            ///
907            /// [`mpris:trackid`]: Metadata::set_trackid
908            #[doc(alias = "GetTracksMetadata")]
909            async fn get_tracks_metadata(
910                &self,
911                track_ids: Vec<TrackId>,
912            ) -> fdo::Result<Vec<Metadata>>;
913
914            /// Adds a URI in the tracklist.
915            ///
916            /// # Parameters
917            ///
918            /// * `uri` - The uri of the item to add. Its uri scheme should be an
919            ///   element of the [`SupportedUriSchemes`] property and the mime-type
920            ///   should match one of the elements of the [`SupportedMimeTypes`]
921            ///   property.
922            /// * `after_track` - The identifier of the track after which the new item
923            ///   should be inserted. The path
924            ///   [`/org/mpris/MediaPlayer2/TrackList/NoTrack`] indicates that the track
925            ///   should be inserted at the start of the track list.
926            /// * `set_as_current` - Whether the newly inserted track should be
927            ///   considered as the current track. Setting this to **true** has the same
928            ///   effect as calling [`GoTo`] afterwards.
929            ///
930            /// If the [`CanEditTracks`] property is **false**, this has no effect.
931            ///
932            /// **Note:** Clients should not assume that the track has been added at the
933            /// time when this method returns. They should wait for a [`TrackAdded`] (or
934            /// [`TrackListReplaced`]) signal.
935            ///
936            /// [`SupportedUriSchemes`]: RootInterface::supported_uri_schemes
937            /// [`SupportedMimeTypes`]: RootInterface::supported_mime_types
938            /// [`/org/mpris/MediaPlayer2/TrackList/NoTrack`]: TrackId::NO_TRACK
939            /// [`GoTo`]: Self::go_to
940            /// [`CanEditTracks`]: Self::can_edit_tracks
941            /// [`TrackAdded`]: TrackListSignal::TrackAdded
942            /// [`TrackListReplaced`]: TrackListSignal::TrackListReplaced
943            #[doc(alias = "AddTrack")]
944            async fn add_track(
945                &self,
946                uri: Uri,
947                after_track: TrackId,
948                set_as_current: bool,
949            ) -> fdo::Result<()>;
950
951            /// Removes an item from the tracklist.
952            ///
953            /// # Parameters
954            ///
955            /// * `track_id` - Identifier of the track to be removed.
956            ///   [`/org/mpris/MediaPlayer2/TrackList/NoTrack`] is *not* a valid value
957            ///   for this argument.
958            ///
959            /// If the track is not part of this tracklist, this has no effect.
960            ///
961            /// If the [`CanEditTracks`] property is **false**, this has no effect.
962            ///
963            /// **Note:** Clients should not assume that the track has been removed at
964            /// the time when this method returns. They should wait for a
965            /// [`TrackRemoved`] (or [`TrackListReplaced`]) signal.
966            ///
967            /// [`/org/mpris/MediaPlayer2/TrackList/NoTrack`]: TrackId::NO_TRACK
968            /// [`CanEditTracks`]: Self::can_edit_tracks
969            /// [`TrackRemoved`]: TrackListSignal::TrackRemoved
970            /// [`TrackListReplaced`]: TrackListSignal::TrackListReplaced
971            #[doc(alias = "RemoveTrack")]
972            async fn remove_track(&self, track_id: TrackId) -> fdo::Result<()>;
973
974            /// Skip to the specified TrackId.
975            ///
976            /// # Parameters
977            ///
978            /// * `track_id` - Identifier of the track to skip to.
979            ///   [`/org/mpris/MediaPlayer2/TrackList/NoTrack`] is *not* a valid value
980            ///   for this argument.
981            ///
982            /// If the track is not part of this tracklist, this has no effect.
983            ///
984            /// If this object is not `/org/mpris/MediaPlayer2`, the current tracklist's
985            /// tracks should be replaced with the contents of this tracklist, and the
986            /// [`TrackListReplaced`] signal should be fired from
987            /// `/org/mpris/MediaPlayer2`.
988            ///
989            /// [`TrackListReplaced`]: TrackListSignal::TrackListReplaced
990            #[doc(alias = "GoTo")]
991            async fn go_to(&self, track_id: TrackId) -> fdo::Result<()>;
992
993            /// An array which contains the identifier of each track in the tracklist,
994            /// in order.
995            ///
996            /// When this property changes, the
997            /// `org.freedesktop.DBus.Properties.PropertiesChanged` signal via
998            /// [`track_list_properties_changed`] must be emitted *without* the new
999            /// value.
1000            ///
1001            /// The `org.freedesktop.DBus.Properties.PropertiesChanged` signal is
1002            /// emitted every time this property changes, but the signal message
1003            /// does not contain the new value. Client implementations should rather
1004            /// rely on the [`TrackAdded`], [`TrackRemoved`] and
1005            /// [`TrackListReplaced`] signals to keep their representation of the
1006            /// tracklist up to date.
1007            ///
1008            /// [`track_list_properties_changed`]: Server::track_list_properties_changed
1009            /// [`TrackAdded`]: TrackListSignal::TrackAdded
1010            /// [`TrackRemoved`]: TrackListSignal::TrackRemoved
1011            /// [`TrackListReplaced`]: TrackListSignal::TrackListReplaced
1012            #[doc(alias = "Tracks")]
1013            async fn tracks(&self) -> fdo::Result<Vec<TrackId>>;
1014
1015            /// Whether tracks can be added to and removed from the tracklist.
1016            ///
1017            /// When this property changes, the
1018            /// `org.freedesktop.DBus.Properties.PropertiesChanged` signal via
1019            /// [`track_list_properties_changed`] must be emitted with the new value.
1020            ///
1021            /// If **false**, calling [`AddTrack`] or [`RemoveTrack`] will have no
1022            /// effect, and may raise a [`NotSupported`] error.
1023            ///
1024            /// [`track_list_properties_changed`]: Server::track_list_properties_changed
1025            /// [`AddTrack`]: Self::add_track
1026            /// [`RemoveTrack`]: Self::remove_track
1027            /// [`NotSupported`]: fdo::Error::NotSupported
1028            #[doc(alias = "CanEditTracks")]
1029            async fn can_edit_tracks(&self) -> fdo::Result<bool>;
1030        }
1031
1032        #[doc = $extra_playlists_docs]
1033        #[doc = ""]
1034        /// Used to implement [org.mpris.MediaPlayer2.Playlists] interface, which
1035        /// provides access to the media player's playlists.
1036        ///
1037        /// Since D-Bus does not provide an easy way to check for what interfaces are
1038        /// exported on an object, clients should attempt to get one of the properties
1039        /// on this interface to see if it is implemented.
1040        ///
1041        /// [org.mpris.MediaPlayer2.Playlists]: https://specifications.freedesktop.org/mpris-spec/latest/Playlists_Interface.html
1042        #[$attr]
1043        #[doc(alias = "org.mpris.MediaPlayer2.Playlists")]
1044        pub trait $playlists_iface_ident: $player_iface_ident {
1045            /// Starts playing the given playlist.
1046            ///
1047            /// # Parameters
1048            ///
1049            /// * `playlist_id` - The id of the playlist to activate.
1050            ///
1051            /// Note that this must be implemented. If the media player does not allow
1052            /// clients to change the playlist, it should not implement this interface
1053            /// at all.
1054            ///
1055            /// It is up to the media player whether this completely replaces the
1056            /// current tracklist, or whether it is merely inserted into the tracklist
1057            /// and the first track starts. For example, if the media player is
1058            /// operating in a "jukebox" mode, it may just append the playlist to the
1059            /// list of upcoming tracks, and skip to the first track in the playlist.
1060            #[doc(alias = "ActivatePlaylist")]
1061            async fn activate_playlist(&self, playlist_id: PlaylistId) -> fdo::Result<()>;
1062
1063            /// Gets a set of playlists.
1064            ///
1065            /// # Parameters
1066            ///
1067            /// * `index` - The index of the first playlist to be fetched (according to
1068            ///   the ordering).
1069            /// * `max_count` - The maximum number of playlists to fetch.
1070            /// * `order` - The ordering that should be used.
1071            /// * `reverse_order` - Whether the order should be reversed.
1072            ///
1073            /// # Returns
1074            ///
1075            /// * `playlists` - A list of (at most `max_count`) playlists.
1076            #[doc(alias = "GetPlaylists")]
1077            async fn get_playlists(
1078                &self,
1079                index: u32,
1080                max_count: u32,
1081                order: PlaylistOrdering,
1082                reverse_order: bool,
1083            ) -> fdo::Result<Vec<Playlist>>;
1084
1085            /// The number of playlists available.
1086            ///
1087            /// When this property changes, the
1088            /// `org.freedesktop.DBus.Properties.PropertiesChanged` signal via
1089            /// [`playlists_properties_changed`] must be emitted with the new value.
1090            ///
1091            /// [`playlists_properties_changed`]: Server::playlists_properties_changed
1092            #[doc(alias = "PlaylistCount")]
1093            async fn playlist_count(&self) -> fdo::Result<u32>;
1094
1095            /// The available orderings. At least one must be offered.
1096            ///
1097            /// When this property changes, the
1098            /// `org.freedesktop.DBus.Properties.PropertiesChanged` signal via
1099            /// [`playlists_properties_changed`] must be emitted with the new value.
1100            ///
1101            /// <details><summary>Rationale</summary>
1102            ///
1103            /// Media players may not have access to all the data required for some
1104            /// orderings. For example, creation times are not available on UNIX
1105            /// filesystems (don't let the ctime fool you!). On the other hand, clients
1106            /// should have some way to get the "most recent" playlists.
1107            ///
1108            /// </details>
1109            ///
1110            /// [`playlists_properties_changed`]: Server::playlists_properties_changed
1111            #[doc(alias = "Orderings")]
1112            async fn orderings(&self) -> fdo::Result<Vec<PlaylistOrdering>>;
1113
1114            /// The currently-active playlist.
1115            ///
1116            /// When this property changes, the
1117            /// `org.freedesktop.DBus.Properties.PropertiesChanged` signal via
1118            /// [`playlists_properties_changed`] must be emitted with the new value.
1119            ///
1120            /// If there is no currently-active playlist, this should return [`None`].
1121            ///
1122            /// Note that this may not have a value even after [`ActivatePlaylist`] is
1123            /// called with a valid playlist id as [`ActivatePlaylist`] implementations
1124            /// have the option of simply inserting the contents of the playlist
1125            /// into the current tracklist.
1126            ///
1127            /// [`playlists_properties_changed`]: Server::playlists_properties_changed
1128            /// [`ActivatePlaylist`]: Self::activate_playlist
1129            #[doc(alias = "ActivePlaylist")]
1130            async fn active_playlist(&self) -> fdo::Result<Option<Playlist>>;
1131        }
1132    };
1133}
1134
1135define_iface!(
1136    #[trait_variant::make(Send + Sync)],
1137    RootInterface extra_docs "",
1138    PlayerInterface extra_docs "",
1139    TrackListInterface extra_docs "",
1140    PlaylistsInterface extra_docs ""
1141);
1142
1143define_iface!(
1144    #[allow(async_fn_in_trait)],
1145    LocalRootInterface extra_docs "Local version of [`RootInterface`] to be used with [`LocalServer`].",
1146    LocalPlayerInterface extra_docs "Local version of [`PlayerInterface`] to be used with [`LocalServer`].",
1147    LocalTrackListInterface extra_docs "Local version of [`TrackListInterface`] to be used with [`LocalServer`].",
1148    LocalPlaylistsInterface  extra_docs "Local version of [`PlaylistsInterface`] to be used with [`LocalServer`]."
1149);
1150
1151/// A playback rate.
1152///
1153/// This is a multiplier, so a value of 0.5 indicates that playback
1154/// is happening at half speed, while 1.5 means that 1.5 seconds of
1155/// "track time" is consumed every second.
1156#[doc(alias = "Playback_Rate")]
1157pub type PlaybackRate = f64;
1158
1159/// Audio volume level.
1160///
1161/// * 0.0 means mute.
1162/// * 1.0 is a sensible maximum volume level (ex: 0dB).
1163///
1164/// Note that the volume may be higher than 1.0, although generally
1165/// clients should not attempt to set it above 1.0.
1166pub type Volume = f64;
1167
1168/// Unique playlist identifier.
1169///
1170/// <details><summary>Rationale</summary>
1171///
1172/// Multiple playlists may have the same name.
1173///
1174/// This is a D-Bus object id as that is the definitive way to have unique
1175/// identifiers on D-Bus. It also allows for future optional expansions to
1176/// the specification where tracks are exported to D-Bus with an interface
1177/// similar to `org.gnome.UPnP.MediaItem2`.
1178///
1179/// </details>
1180#[doc(alias = "Playlist_Id")]
1181pub type PlaylistId = OwnedObjectPath;
1182
1183/// A unique resource identifier.
1184///
1185/// URIs should be sent as (UTF-8) strings. Local files should use the
1186/// "file://" schema.
1187pub type Uri = String;
1188
1189#[cfg(test)]
1190mod tests {
1191    use static_assertions::assert_trait_sub_all;
1192
1193    use super::*;
1194
1195    assert_trait_sub_all!(RootInterface: Send, Sync);
1196    assert_trait_sub_all!(PlayerInterface: Send, Sync);
1197    assert_trait_sub_all!(TrackListInterface: Send, Sync);
1198    assert_trait_sub_all!(PlaylistsInterface: Send, Sync);
1199
1200    assert_trait_sub_all!(PlayerInterface: RootInterface);
1201    assert_trait_sub_all!(TrackListInterface: PlayerInterface, RootInterface);
1202    assert_trait_sub_all!(PlaylistsInterface: PlayerInterface, RootInterface);
1203
1204    assert_trait_sub_all!(LocalPlayerInterface: LocalRootInterface);
1205    assert_trait_sub_all!(LocalTrackListInterface: LocalPlayerInterface, LocalRootInterface);
1206    assert_trait_sub_all!(LocalPlaylistsInterface: LocalPlayerInterface, LocalRootInterface);
1207}