feat: add mpris2-zbus for org.mpris.MediaPlayer2
This commit is contained in:
parent
4d8815361d
commit
945f80f036
21 changed files with 2203 additions and 0 deletions
5
mpris2/src/bindings.rs
Normal file
5
mpris2/src/bindings.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
pub mod media_player;
|
||||
pub mod player;
|
||||
pub mod playlist;
|
||||
pub mod track_list;
|
||||
63
mpris2/src/bindings/media_player.rs
Normal file
63
mpris2/src/bindings/media_player.rs
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
//! # DBus interface proxies for: `org.mpris.MediaPlayer2`
|
||||
//!
|
||||
//! This code was generated by `zbus-xmlgen` `2.0.1` from DBus introspection data.
|
||||
//! Source: `Interface '/org/mpris/MediaPlayer2' from service 'org.mpris.MediaPlayer2.firefox.instance103520' on session bus`.
|
||||
//!
|
||||
//! You may prefer to adapt it, instead of using it verbatim.
|
||||
//!
|
||||
//! More information can be found in the
|
||||
//! [Writing a client proxy](https://dbus.pages.freedesktop.org/zbus/client.html)
|
||||
//! section of the zbus documentation.
|
||||
//!
|
||||
//! This DBus object implements
|
||||
//! [standard DBus interfaces](https://dbus.freedesktop.org/doc/dbus-specification.html),
|
||||
//! (`org.freedesktop.DBus.*`) for which the following zbus proxies can be used:
|
||||
//!
|
||||
//! * [`zbus::fdo::PropertiesProxy`]
|
||||
//! * [`zbus::fdo::IntrospectableProxy`]
|
||||
//! * [`zbus::fdo::PeerProxy`]
|
||||
//!
|
||||
//! …consequently `zbus-xmlgen` did not generate code for the above interfaces.
|
||||
|
||||
use zbus::dbus_proxy;
|
||||
|
||||
#[dbus_proxy(
|
||||
interface = "org.mpris.MediaPlayer2",
|
||||
default_path = "/org/mpris/MediaPlayer2"
|
||||
)]
|
||||
trait MediaPlayer2 {
|
||||
/// Quit method
|
||||
fn quit(&self) -> zbus::Result<()>;
|
||||
|
||||
/// Raise method
|
||||
fn raise(&self) -> zbus::Result<()>;
|
||||
|
||||
/// CanQuit property
|
||||
#[dbus_proxy(property)]
|
||||
fn can_quit(&self) -> zbus::Result<bool>;
|
||||
|
||||
/// CanRaise property
|
||||
#[dbus_proxy(property)]
|
||||
fn can_raise(&self) -> zbus::Result<bool>;
|
||||
|
||||
/// DesktopEntry property
|
||||
#[dbus_proxy(property)]
|
||||
fn desktop_entry(&self) -> zbus::Result<String>;
|
||||
|
||||
/// HasTrackList property
|
||||
#[dbus_proxy(property)]
|
||||
fn has_track_list(&self) -> zbus::Result<bool>;
|
||||
|
||||
/// Identity property
|
||||
#[dbus_proxy(property)]
|
||||
fn identity(&self) -> zbus::Result<String>;
|
||||
|
||||
/// SupportedMimeTypes property
|
||||
#[dbus_proxy(property)]
|
||||
fn supported_mime_types(&self) -> zbus::Result<Vec<String>>;
|
||||
|
||||
/// SupportedUriSchemes property
|
||||
#[dbus_proxy(property)]
|
||||
fn supported_uri_schemes(&self) -> zbus::Result<Vec<String>>;
|
||||
}
|
||||
131
mpris2/src/bindings/player.rs
Normal file
131
mpris2/src/bindings/player.rs
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
//! # DBus interface proxies for: `org.mpris.MediaPlayer2.Player`
|
||||
//!
|
||||
//! This code was generated by `zbus-xmlgen` `2.0.1` from DBus introspection data.
|
||||
//! Source: `Interface '/org/mpris/MediaPlayer2' from service 'org.mpris.MediaPlayer2.firefox.instance103520' on session bus`.
|
||||
//!
|
||||
//! You may prefer to adapt it, instead of using it verbatim.
|
||||
//!
|
||||
//! More information can be found in the
|
||||
//! [Writing a client proxy](https://dbus.pages.freedesktop.org/zbus/client.html)
|
||||
//! section of the zbus documentation.
|
||||
//!
|
||||
//! This DBus object implements
|
||||
//! [standard DBus interfaces](https://dbus.freedesktop.org/doc/dbus-specification.html),
|
||||
//! (`org.freedesktop.DBus.*`) for which the following zbus proxies can be used:
|
||||
//!
|
||||
//! * [`zbus::fdo::PropertiesProxy`]
|
||||
//! * [`zbus::fdo::IntrospectableProxy`]
|
||||
//! * [`zbus::fdo::PeerProxy`]
|
||||
//!
|
||||
//! …consequently `zbus-xmlgen` did not generate code for the above interfaces.
|
||||
|
||||
use crate::track::TrackId;
|
||||
use zbus::dbus_proxy;
|
||||
|
||||
#[dbus_proxy(
|
||||
interface = "org.mpris.MediaPlayer2.Player",
|
||||
default_path = "/org/mpris/MediaPlayer2"
|
||||
)]
|
||||
trait Player {
|
||||
/// Next method
|
||||
fn next(&self) -> zbus::Result<()>;
|
||||
|
||||
/// OpenUri method
|
||||
fn open_uri(&self, uri: &str) -> zbus::Result<()>;
|
||||
|
||||
/// Pause method
|
||||
fn pause(&self) -> zbus::Result<()>;
|
||||
|
||||
/// Play method
|
||||
fn play(&self) -> zbus::Result<()>;
|
||||
|
||||
/// PlayPause method
|
||||
fn play_pause(&self) -> zbus::Result<()>;
|
||||
|
||||
/// Previous method
|
||||
fn previous(&self) -> zbus::Result<()>;
|
||||
|
||||
/// Seek method
|
||||
fn seek(&self, offset: i64) -> zbus::Result<()>;
|
||||
|
||||
/// SetPosition method
|
||||
fn set_position(&self, track_id: &TrackId, position: i64) -> zbus::Result<()>;
|
||||
|
||||
/// Stop method
|
||||
fn stop(&self) -> zbus::Result<()>;
|
||||
|
||||
/// Seeked signal
|
||||
#[dbus_proxy(signal)]
|
||||
fn seeked(&self, position: i64) -> zbus::Result<()>;
|
||||
|
||||
/// CanControl property
|
||||
#[dbus_proxy(property)]
|
||||
fn can_control(&self) -> zbus::Result<bool>;
|
||||
|
||||
/// CanGoNext property
|
||||
#[dbus_proxy(property)]
|
||||
fn can_go_next(&self) -> zbus::Result<bool>;
|
||||
|
||||
/// CanGoPrevious property
|
||||
#[dbus_proxy(property)]
|
||||
fn can_go_previous(&self) -> zbus::Result<bool>;
|
||||
|
||||
/// CanPause property
|
||||
#[dbus_proxy(property)]
|
||||
fn can_pause(&self) -> zbus::Result<bool>;
|
||||
|
||||
/// CanPlay property
|
||||
#[dbus_proxy(property)]
|
||||
fn can_play(&self) -> zbus::Result<bool>;
|
||||
|
||||
/// CanSeek property
|
||||
#[dbus_proxy(property)]
|
||||
fn can_seek(&self) -> zbus::Result<bool>;
|
||||
|
||||
/// MaximumRate property
|
||||
#[dbus_proxy(property)]
|
||||
fn maximum_rate(&self) -> zbus::Result<f64>;
|
||||
|
||||
/// Metadata property
|
||||
#[dbus_proxy(property)]
|
||||
fn metadata(
|
||||
&self,
|
||||
) -> zbus::Result<std::collections::HashMap<String, zbus::zvariant::OwnedValue>>;
|
||||
|
||||
/// MinimumRate property
|
||||
#[dbus_proxy(property)]
|
||||
fn minimum_rate(&self) -> zbus::Result<f64>;
|
||||
|
||||
/// PlaybackStatus property
|
||||
#[dbus_proxy(property)]
|
||||
fn playback_status(&self) -> zbus::Result<String>;
|
||||
|
||||
/// Position property
|
||||
#[dbus_proxy(property)]
|
||||
fn position(&self) -> zbus::Result<i64>;
|
||||
|
||||
/// Rate property
|
||||
#[dbus_proxy(property)]
|
||||
fn rate(&self) -> zbus::Result<f64>;
|
||||
#[dbus_proxy(property)]
|
||||
fn set_rate(&self, value: f64) -> zbus::Result<()>;
|
||||
|
||||
/// Shuffle property (optional)
|
||||
#[dbus_proxy(property)]
|
||||
fn shuffle(&self) -> zbus::Result<bool>;
|
||||
#[dbus_proxy(property)]
|
||||
fn set_shuffle(&self, value: bool) -> zbus::Result<()>;
|
||||
|
||||
/// LoopStatus property (optional)
|
||||
#[dbus_proxy(property)]
|
||||
fn loop_status(&self) -> zbus::Result<String>;
|
||||
#[dbus_proxy(property)]
|
||||
fn set_loop_status(&self, value: String) -> zbus::Result<()>;
|
||||
|
||||
/// Volume property
|
||||
#[dbus_proxy(property)]
|
||||
fn volume(&self) -> zbus::Result<f64>;
|
||||
#[dbus_proxy(property)]
|
||||
fn set_volume(&self, value: f64) -> zbus::Result<()>;
|
||||
}
|
||||
57
mpris2/src/bindings/playlist.rs
Normal file
57
mpris2/src/bindings/playlist.rs
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
//! # DBus interface proxies for: `org.mpris.MediaPlayer2`, `org.mpris.MediaPlayer2.Player`, `org.mpris.MediaPlayer2.TrackList`, `org.mpris.MediaPlayer2.Playlists`
|
||||
//!
|
||||
//! This code was generated by `zbus-xmlgen` `2.0.1` from DBus introspection data.
|
||||
//! Source: `Interface '/org/mpris/MediaPlayer2' from service 'org.mpris.MediaPlayer2.org.gnome.Music' on session bus`.
|
||||
//!
|
||||
//! You may prefer to adapt it, instead of using it verbatim.
|
||||
//!
|
||||
//! More information can be found in the
|
||||
//! [Writing a client proxy](https://dbus.pages.freedesktop.org/zbus/client.html)
|
||||
//! section of the zbus documentation.
|
||||
//!
|
||||
//! This DBus object implements
|
||||
//! [standard DBus interfaces](https://dbus.freedesktop.org/doc/dbus-specification.html),
|
||||
//! (`org.freedesktop.DBus.*`) for which the following zbus proxies can be used:
|
||||
//!
|
||||
//! * [`zbus::fdo::IntrospectableProxy`]
|
||||
//! * [`zbus::fdo::PropertiesProxy`]
|
||||
//!
|
||||
//! …consequently `zbus-xmlgen` did not generate code for the above interfaces.
|
||||
|
||||
use crate::playlists::{id::PlaylistId, ordering::PlaylistOrdering, playlist::Playlist};
|
||||
use zbus::dbus_proxy;
|
||||
|
||||
#[dbus_proxy(
|
||||
interface = "org.mpris.MediaPlayer2.Playlists",
|
||||
default_path = "/org/mpris/MediaPlayer2"
|
||||
)]
|
||||
trait Playlists {
|
||||
/// ActivatePlaylist method
|
||||
fn activate_playlist(&self, playlist_id: &PlaylistId) -> zbus::Result<()>;
|
||||
|
||||
/// GetPlaylists method
|
||||
fn get_playlists(
|
||||
&self,
|
||||
index: u32,
|
||||
max_count: u32,
|
||||
order: PlaylistOrdering,
|
||||
reverse_order: bool,
|
||||
) -> zbus::Result<Vec<Playlist>>;
|
||||
|
||||
/// PlaylistChanged signal
|
||||
#[dbus_proxy(signal)]
|
||||
fn playlist_changed(&self, playlist: Playlist) -> zbus::Result<()>;
|
||||
|
||||
/// ActivePlaylist property
|
||||
#[dbus_proxy(property)]
|
||||
fn active_playlist(&self) -> zbus::Result<(bool, Playlist)>;
|
||||
|
||||
/// Orderings property
|
||||
#[dbus_proxy(property)]
|
||||
fn orderings(&self) -> zbus::Result<Vec<String>>;
|
||||
|
||||
/// PlaylistCount property
|
||||
#[dbus_proxy(property)]
|
||||
fn playlist_count(&self) -> zbus::Result<u32>;
|
||||
}
|
||||
58
mpris2/src/bindings/track_list.rs
Normal file
58
mpris2/src/bindings/track_list.rs
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
//! # DBus interface proxies for: `org.mpris.MediaPlayer2`, `org.mpris.MediaPlayer2.Player`, `org.mpris.MediaPlayer2.TrackList`, `org.mpris.MediaPlayer2.Playlists`
|
||||
//!
|
||||
//! This code was generated by `zbus-xmlgen` `2.0.1` from DBus introspection data.
|
||||
//! Source: `Interface '/org/mpris/MediaPlayer2' from service 'org.mpris.MediaPlayer2.org.gnome.Music' on session bus`.
|
||||
//!
|
||||
//! You may prefer to adapt it, instead of using it verbatim.
|
||||
//!
|
||||
//! More information can be found in the
|
||||
//! [Writing a client proxy](https://dbus.pages.freedesktop.org/zbus/client.html)
|
||||
//! section of the zbus documentation.
|
||||
//!
|
||||
//! This DBus object implements
|
||||
//! [standard DBus interfaces](https://dbus.freedesktop.org/doc/dbus-specification.html),
|
||||
//! (`org.freedesktop.DBus.*`) for which the following zbus proxies can be used:
|
||||
//!
|
||||
//! * [`zbus::fdo::IntrospectableProxy`]
|
||||
//! * [`zbus::fdo::PropertiesProxy`]
|
||||
//!
|
||||
//! …consequently `zbus-xmlgen` did not generate code for the above interfaces.
|
||||
|
||||
use crate::track::TrackId;
|
||||
use zbus::dbus_proxy;
|
||||
|
||||
#[dbus_proxy(
|
||||
interface = "org.mpris.MediaPlayer2.TrackList",
|
||||
default_path = "/org/mpris/MediaPlayer2"
|
||||
)]
|
||||
trait TrackList {
|
||||
/// AddTrack method
|
||||
fn add_track(&self, uri: &str, after_track: &TrackId, set_as_current: bool)
|
||||
-> zbus::Result<()>;
|
||||
|
||||
/// GetTracksMetadata method
|
||||
fn get_tracks_metadata(
|
||||
&self,
|
||||
track_ids: Vec<TrackId>,
|
||||
) -> zbus::Result<Vec<std::collections::HashMap<String, zbus::zvariant::OwnedValue>>>;
|
||||
|
||||
/// GoTo method
|
||||
fn go_to(&self, track_id: &TrackId) -> zbus::Result<()>;
|
||||
|
||||
/// RemoveTrack method
|
||||
fn remove_track(&self, track_id: &TrackId) -> zbus::Result<()>;
|
||||
|
||||
/// TrackListReplaced signal
|
||||
#[dbus_proxy(signal)]
|
||||
fn track_list_replaced(&self, tracks: Vec<TrackId>, current_track: TrackId)
|
||||
-> zbus::Result<()>;
|
||||
|
||||
/// CanEditTracks property
|
||||
#[dbus_proxy(property)]
|
||||
fn can_edit_tracks(&self) -> zbus::Result<bool>;
|
||||
|
||||
/// Tracks property
|
||||
#[dbus_proxy(property)]
|
||||
fn tracks(&self) -> zbus::Result<Vec<TrackId>>;
|
||||
}
|
||||
51
mpris2/src/error.rs
Normal file
51
mpris2/src/error.rs
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
/// Invalid enum variant when converting from String.
|
||||
#[error("Invalid enum variant: {got}, expected something in {expected:?}")]
|
||||
InvalidEnum {
|
||||
got: String,
|
||||
expected: &'static [&'static str],
|
||||
},
|
||||
|
||||
#[error("Tried to extract a {wanted}, but it was actually {actual}")]
|
||||
IncorrectVariant {
|
||||
wanted: &'static str,
|
||||
actual: &'static str,
|
||||
},
|
||||
|
||||
#[error("Tried to convert Value::{wanted}, but it was got {actual:?}")]
|
||||
IncorrectValue {
|
||||
wanted: &'static str,
|
||||
actual: zvariant::OwnedValue,
|
||||
},
|
||||
|
||||
/// A zbus error.
|
||||
#[error("zbus error: {0}")]
|
||||
Zbus(zbus::Error),
|
||||
|
||||
/// A zbus::fdo error.
|
||||
#[error("zbus fdo error: {0}")]
|
||||
Fdo(zbus::fdo::Error),
|
||||
}
|
||||
|
||||
impl From<zbus::fdo::Error> for Error {
|
||||
fn from(err: zbus::fdo::Error) -> Self {
|
||||
match err {
|
||||
zbus::fdo::Error::ZBus(err) => Self::Zbus(err),
|
||||
_ => Self::Fdo(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<zbus::Error> for Error {
|
||||
fn from(err: zbus::Error) -> Self {
|
||||
match err {
|
||||
zbus::Error::FDO(err) => Self::Fdo(*err),
|
||||
_ => Self::Zbus(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
21
mpris2/src/lib.rs
Normal file
21
mpris2/src/lib.rs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
pub mod bindings;
|
||||
pub mod error;
|
||||
pub mod media_player;
|
||||
pub mod metadata;
|
||||
pub mod player;
|
||||
pub mod playlists;
|
||||
pub mod track;
|
||||
pub mod track_list;
|
||||
|
||||
pub(crate) fn handle_optional<T>(input: zbus::Result<T>) -> error::Result<Option<T>> {
|
||||
match input {
|
||||
Ok(input) => Ok(Some(input)),
|
||||
Err(zbus::Error::FDO(fdo_error))
|
||||
if matches!(*fdo_error, zbus::fdo::Error::NotSupported(_)) =>
|
||||
{
|
||||
Ok(None)
|
||||
}
|
||||
Err(err) => Err(error::Error::from(err)),
|
||||
}
|
||||
}
|
||||
111
mpris2/src/media_player.rs
Normal file
111
mpris2/src/media_player.rs
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
use crate::{
|
||||
bindings::{
|
||||
media_player::MediaPlayer2Proxy, player::PlayerProxy, playlist::PlaylistsProxy,
|
||||
track_list::TrackListProxy,
|
||||
},
|
||||
error::{Error, Result},
|
||||
player::Player,
|
||||
playlists::Playlists,
|
||||
track_list::TrackList,
|
||||
};
|
||||
use std::ops::Deref;
|
||||
use zbus::{fdo::DBusProxy, names::OwnedBusName, Connection};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MediaPlayer {
|
||||
proxy: MediaPlayer2Proxy<'static>,
|
||||
}
|
||||
|
||||
impl MediaPlayer {
|
||||
/// Creates a new instance of the `org.mpris.MediaPlayer2` interface.
|
||||
pub async fn new(connection: &Connection, name: OwnedBusName) -> Result<Self> {
|
||||
MediaPlayer2Proxy::builder(connection)
|
||||
.destination(name)?
|
||||
.build()
|
||||
.await
|
||||
.map(Self::from)
|
||||
.map_err(Error::from)
|
||||
}
|
||||
|
||||
/// Gets the names of all the MPRIS players that are available on the current session.
|
||||
pub async fn available_players(connection: &Connection) -> Result<Vec<OwnedBusName>> {
|
||||
let dbus = DBusProxy::builder(connection)
|
||||
.path("/org/freedesktop/DBus")?
|
||||
.build()
|
||||
.await?;
|
||||
let mut players = Vec::new();
|
||||
for name in dbus.list_names().await? {
|
||||
if name.starts_with("org.mpris.MediaPlayer2.") {
|
||||
players.push(name);
|
||||
}
|
||||
}
|
||||
Ok(players)
|
||||
}
|
||||
|
||||
/// Gets a new instance of all the MPRIS players that are available on the current session.
|
||||
pub async fn new_all(connection: &Connection) -> Result<Vec<Self>> {
|
||||
let players = Self::available_players(connection).await?;
|
||||
let mut instances = Vec::with_capacity(players.len());
|
||||
for player in players {
|
||||
instances.push(Self::new(connection, player).await?);
|
||||
}
|
||||
Ok(instances)
|
||||
}
|
||||
|
||||
/// Returns an instance to the `org.mpris.MediaPlayer2.Player` interface of this object.
|
||||
pub async fn player(&self) -> Result<Player> {
|
||||
PlayerProxy::builder(self.proxy.connection())
|
||||
.destination(self.proxy.destination().to_owned())?
|
||||
.build()
|
||||
.await
|
||||
.map(Player::from)
|
||||
.map_err(Error::from)
|
||||
}
|
||||
|
||||
/// Returns an instance to the `org.mpris.MediaPlayer2.TrackList` interface of this object,
|
||||
/// if a track list is available.
|
||||
pub async fn track_list(&self) -> Result<Option<TrackList>> {
|
||||
if self.proxy.has_track_list().await? {
|
||||
TrackListProxy::builder(self.proxy.connection())
|
||||
.destination(self.proxy.destination().to_owned())?
|
||||
.build()
|
||||
.await
|
||||
.map(TrackList::from)
|
||||
.map(Some)
|
||||
.map_err(Error::from)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an instance to the `org.mpris.MediaPlayer2.Playlists` interface of this object,
|
||||
/// if a track list is available.
|
||||
pub async fn playlists(&self) -> Result<Option<Playlists>> {
|
||||
if self.proxy.has_track_list().await? {
|
||||
PlaylistsProxy::builder(self.proxy.connection())
|
||||
.destination(self.proxy.destination().to_owned())?
|
||||
.build()
|
||||
.await
|
||||
.map(Playlists::from)
|
||||
.map(Some)
|
||||
.map_err(Error::from)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for MediaPlayer {
|
||||
type Target = MediaPlayer2Proxy<'static>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.proxy
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MediaPlayer2Proxy<'static>> for MediaPlayer {
|
||||
fn from(proxy: MediaPlayer2Proxy<'static>) -> Self {
|
||||
Self { proxy }
|
||||
}
|
||||
}
|
||||
533
mpris2/src/metadata.rs
Normal file
533
mpris2/src/metadata.rs
Normal file
|
|
@ -0,0 +1,533 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
use crate::error::{Error, Result};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fmt,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
use time::{Duration, OffsetDateTime};
|
||||
use zbus::zvariant::{OwnedObjectPath, Value as ZValue};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Metadata {
|
||||
inner: HashMap<String, MetadataValue>,
|
||||
}
|
||||
|
||||
impl Metadata {
|
||||
/// `xesam:album`: The track artist(s).
|
||||
pub fn album(&self) -> Option<String> {
|
||||
self.inner
|
||||
.get("xesam:album")
|
||||
.cloned()
|
||||
.and_then(|v| v.try_into_string().ok())
|
||||
}
|
||||
|
||||
/// `xesam:artist`: The track artist(s).
|
||||
pub fn artists(&self) -> Option<Vec<String>> {
|
||||
self.inner
|
||||
.get("xesam:artist")
|
||||
.cloned()
|
||||
.and_then(|artists| artists.try_into_array().ok())
|
||||
.map(|artists| {
|
||||
artists
|
||||
.into_iter()
|
||||
.filter_map(|v| v.try_into_string().ok())
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
/// `xesam:asText`: The track lyrics.
|
||||
pub fn lyrics(&self) -> Option<String> {
|
||||
self.inner
|
||||
.get("xesam:asText")
|
||||
.cloned()
|
||||
.and_then(|v| v.try_into_string().ok())
|
||||
}
|
||||
|
||||
/// `xesam:albumArtist`: The album artist(s).
|
||||
pub fn album_artists(&self) -> Option<Vec<String>> {
|
||||
self.inner
|
||||
.get("xesam:albumArtist")
|
||||
.cloned()
|
||||
.and_then(|artists| artists.try_into_array().ok())
|
||||
.map(|artists| {
|
||||
artists
|
||||
.into_iter()
|
||||
.filter_map(|v| v.try_into_string().ok())
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
/// `xesam:audioBPM`: The speed of the music, in beats per minute.
|
||||
pub fn bpm(&self) -> Option<u64> {
|
||||
self.inner
|
||||
.get("xesam:audioBPM")
|
||||
.cloned()
|
||||
.and_then(|v| v.try_into_uint().ok())
|
||||
}
|
||||
|
||||
/// `xesam:autoRating`: An automatically-generated rating, based on things such as how often it has been played.
|
||||
/// This should be in the range 0.0 to 1.0.
|
||||
pub fn auto_rating(&self) -> Option<f64> {
|
||||
self.inner
|
||||
.get("xesam:autoRating")
|
||||
.cloned()
|
||||
.and_then(|v| v.try_into_double().ok())
|
||||
}
|
||||
|
||||
/// `xesam:composer`: The composer(s) of the track.
|
||||
pub fn composer(&self) -> Option<Vec<String>> {
|
||||
self.inner
|
||||
.get("xesam:composer")
|
||||
.cloned()
|
||||
.and_then(|artists| artists.try_into_array().ok())
|
||||
.map(|artists| {
|
||||
artists
|
||||
.into_iter()
|
||||
.filter_map(|v| v.try_into_string().ok())
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
/// `xesam:contentCreated`: When the track was created. Usually only the year component will be useful.
|
||||
pub fn created(&self) -> Option<OffsetDateTime> {
|
||||
self.inner
|
||||
.get("xesam:contentCreated")
|
||||
.cloned()
|
||||
.and_then(|v| v.try_into_date().ok())
|
||||
}
|
||||
|
||||
/// `xesam:discNumber`: The disc number on the album that this track is from.
|
||||
pub fn disc_number(&self) -> Option<u64> {
|
||||
self.inner
|
||||
.get("xesam:discNumber")
|
||||
.cloned()
|
||||
.and_then(|v| v.try_into_uint().ok())
|
||||
}
|
||||
|
||||
/// `xesam:firstUsed`: When the track was first played.
|
||||
pub fn first_played(&self) -> Option<OffsetDateTime> {
|
||||
self.inner
|
||||
.get("xesam:firstUsed")
|
||||
.cloned()
|
||||
.and_then(|v| v.try_into_date().ok())
|
||||
}
|
||||
|
||||
/// `xesam:genre`: The genre(s) of the track.
|
||||
pub fn genre(&self) -> Option<Vec<String>> {
|
||||
self.inner
|
||||
.get("xesam:genre")
|
||||
.cloned()
|
||||
.and_then(|artists| artists.try_into_array().ok())
|
||||
.map(|artists| {
|
||||
artists
|
||||
.into_iter()
|
||||
.filter_map(|v| v.try_into_string().ok())
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
/// `xesam:lastUsed`: When the track was last played.
|
||||
pub fn last_played(&self) -> Option<OffsetDateTime> {
|
||||
self.inner
|
||||
.get("xesam:lastUsed")
|
||||
.cloned()
|
||||
.and_then(|v| v.try_into_date().ok())
|
||||
}
|
||||
|
||||
/// `xesam:lyricist`: The lyricist(s) of the track.
|
||||
pub fn lyricist(&self) -> Option<Vec<String>> {
|
||||
self.inner
|
||||
.get("xesam:lyricist")
|
||||
.cloned()
|
||||
.and_then(|artists| artists.try_into_array().ok())
|
||||
.map(|artists| {
|
||||
artists
|
||||
.into_iter()
|
||||
.filter_map(|v| v.try_into_string().ok())
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
/// `xesam:title`: The track title.
|
||||
pub fn title(&self) -> Option<String> {
|
||||
self.inner
|
||||
.get("xesam:title")
|
||||
.cloned()
|
||||
.and_then(|v| v.try_into_string().ok())
|
||||
}
|
||||
|
||||
/// `xesam:trackNumber`: The track number on the album that this track is from.
|
||||
pub fn track_number(&self) -> Option<u64> {
|
||||
self.inner
|
||||
.get("xesam:trackNumber")
|
||||
.cloned()
|
||||
.and_then(|v| v.try_into_uint().ok())
|
||||
}
|
||||
|
||||
/// `xesam:url`: The location of the media file.
|
||||
pub fn url(&self) -> Option<String> {
|
||||
self.inner
|
||||
.get("xesam:url")
|
||||
.cloned()
|
||||
.and_then(|v| v.try_into_string().ok())
|
||||
}
|
||||
|
||||
/// `xesam:useCount`: The number of times the track has been played.
|
||||
pub fn use_count(&self) -> Option<u64> {
|
||||
self.inner
|
||||
.get("xesam:useCount")
|
||||
.cloned()
|
||||
.and_then(|v| v.try_into_uint().ok())
|
||||
}
|
||||
|
||||
/// `xesam:userRating`: The user's rating of the track.
|
||||
pub fn user_rating(&self) -> Option<f64> {
|
||||
self.inner
|
||||
.get("xesam:userRating")
|
||||
.cloned()
|
||||
.and_then(|v| v.try_into_double().ok())
|
||||
}
|
||||
|
||||
/// `mpris:trackid`: D-Bus path: A unique identity for this track within the context of an MPRIS object (eg: tracklist).
|
||||
pub fn track_id(&self) -> Option<OwnedObjectPath> {
|
||||
self.inner
|
||||
.get("mpris:trackid")
|
||||
.cloned()
|
||||
.and_then(|v| v.try_into_string().ok())
|
||||
.and_then(|path| OwnedObjectPath::try_from(path).ok())
|
||||
}
|
||||
|
||||
/// `mpris:length`: The length of the track in microseconds.
|
||||
pub fn length(&self) -> Option<Duration> {
|
||||
self.inner
|
||||
.get("mpris:length")
|
||||
.cloned()
|
||||
.and_then(|v| match &v {
|
||||
MetadataValue::Int(i) => Some(*i),
|
||||
MetadataValue::UInt(u) => Some(*u as i64),
|
||||
MetadataValue::Str(s) => s.parse().ok(),
|
||||
_ => None,
|
||||
})
|
||||
.map(Duration::microseconds)
|
||||
}
|
||||
|
||||
/// `mpris:artUrl`: The location of an image representing the track or album.
|
||||
/// Clients should not assume this will continue to exist when the media player stops giving out the URL.
|
||||
pub fn art_url(&self) -> Option<String> {
|
||||
self.inner
|
||||
.get("mpris:artUrl")
|
||||
.cloned()
|
||||
.and_then(|v| v.try_into_string().ok())
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Metadata {
|
||||
type Target = HashMap<String, MetadataValue>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for Metadata {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Metadata {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{{")?;
|
||||
let mut iter = self.inner.iter().peekable();
|
||||
while let Some((k, v)) = iter.next() {
|
||||
if iter.peek().is_some() {
|
||||
write!(f, "{}: {}, ", k, v)?;
|
||||
} else {
|
||||
write!(f, "{}: {}", k, v)?;
|
||||
}
|
||||
}
|
||||
write!(f, "}}")
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, V: Into<ZValue<'a>>> From<HashMap<String, V>> for Metadata {
|
||||
fn from(map: HashMap<String, V>) -> Self {
|
||||
Self {
|
||||
inner: map
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k, MetadataValue::from(&v.into())))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub enum MetadataValue {
|
||||
Str(String),
|
||||
Double(f64),
|
||||
Int(i64),
|
||||
UInt(u64),
|
||||
Bool(bool),
|
||||
Array(Vec<MetadataValue>),
|
||||
Dict(HashMap<String, MetadataValue>),
|
||||
__Unsupported,
|
||||
}
|
||||
|
||||
impl MetadataValue {
|
||||
fn variant(&self) -> &'static str {
|
||||
match self {
|
||||
MetadataValue::Str(_) => "Str",
|
||||
MetadataValue::Double(_) => "Double",
|
||||
MetadataValue::Int(_) => "Int",
|
||||
MetadataValue::UInt(_) => "UInt",
|
||||
MetadataValue::Bool(_) => "Bool",
|
||||
MetadataValue::Array(_) => "Array",
|
||||
MetadataValue::Dict(_) => "Dict",
|
||||
MetadataValue::__Unsupported => "Unsupported",
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to extract a string from the variant,
|
||||
/// returning an error if the variant is not a string.
|
||||
pub fn try_into_string(self) -> Result<String> {
|
||||
match self {
|
||||
MetadataValue::Str(s) => Ok(s),
|
||||
_ => Err(Error::IncorrectVariant {
|
||||
wanted: "Str",
|
||||
actual: self.variant(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to extract a string from the variant,
|
||||
/// panicking if the variant is not a string.
|
||||
pub fn into_string(self) -> String {
|
||||
self.try_into_string()
|
||||
.unwrap_or_else(|err| panic!("{}", err))
|
||||
}
|
||||
|
||||
/// Tries to extract a date/time from the variant,
|
||||
/// returning an error if the variant is not a date/time.
|
||||
pub fn try_into_date(self) -> Result<OffsetDateTime> {
|
||||
let variant = self.variant();
|
||||
match self {
|
||||
MetadataValue::Str(s) => {
|
||||
OffsetDateTime::parse(&s, &time::format_description::well_known::Rfc3339).map_err(
|
||||
|_| Error::IncorrectVariant {
|
||||
wanted: "String (DateTime)",
|
||||
actual: variant,
|
||||
},
|
||||
)
|
||||
}
|
||||
_ => Err(Error::IncorrectVariant {
|
||||
wanted: "String (DateTime)",
|
||||
actual: variant,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to extract a date/time from the variant,
|
||||
/// panicking if the variant is not a date/time.
|
||||
pub fn into_date(self) -> OffsetDateTime {
|
||||
self.try_into_date().unwrap_or_else(|err| panic!("{}", err))
|
||||
}
|
||||
|
||||
/// Tries to extract a double from the variant,
|
||||
/// returning an error if the variant is not a double.
|
||||
pub fn try_into_double(self) -> Result<f64> {
|
||||
match self {
|
||||
MetadataValue::Double(d) => Ok(d),
|
||||
_ => Err(Error::IncorrectVariant {
|
||||
wanted: "Double",
|
||||
actual: self.variant(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to extract a double from the variant,
|
||||
/// panicking if the variant is not a double.
|
||||
pub fn into_double(self) -> f64 {
|
||||
self.try_into_double()
|
||||
.unwrap_or_else(|err| panic!("{}", err))
|
||||
}
|
||||
|
||||
/// Tries to extract an integer from the variant,
|
||||
/// returning an error if the variant is not an integer.
|
||||
pub fn try_into_int(self) -> Result<i64> {
|
||||
match self {
|
||||
MetadataValue::Int(i) => Ok(i),
|
||||
_ => Err(Error::IncorrectVariant {
|
||||
wanted: "Int",
|
||||
actual: self.variant(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to extract an integer from the variant,
|
||||
/// panicking if the variant is not an integer.
|
||||
pub fn into_int(self) -> i64 {
|
||||
self.try_into_int().unwrap_or_else(|err| panic!("{}", err))
|
||||
}
|
||||
|
||||
/// Tries to extract an unsigned integer from the variant,
|
||||
/// returning an error if the variant is not an unsigned integer.
|
||||
pub fn try_into_uint(self) -> Result<u64> {
|
||||
match self {
|
||||
MetadataValue::UInt(u) => Ok(u),
|
||||
_ => Err(Error::IncorrectVariant {
|
||||
wanted: "UInt",
|
||||
actual: self.variant(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to extract an unsigned integer from the variant,
|
||||
/// panicking if the variant is not an unsigned integer.
|
||||
pub fn into_uint(self) -> u64 {
|
||||
self.try_into_uint().unwrap_or_else(|err| panic!("{}", err))
|
||||
}
|
||||
|
||||
/// Tries to extract a boolean from the variant,
|
||||
/// returning an error if the variant is not a boolean.
|
||||
pub fn try_into_bool(self) -> Result<bool> {
|
||||
match self {
|
||||
MetadataValue::Bool(b) => Ok(b),
|
||||
_ => Err(Error::IncorrectVariant {
|
||||
wanted: "Bool",
|
||||
actual: self.variant(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to extract a boolean from the variant,
|
||||
/// panicking if the variant is not a boolean.
|
||||
pub fn into_bool(self) -> bool {
|
||||
self.try_into_bool().unwrap_or_else(|err| panic!("{}", err))
|
||||
}
|
||||
|
||||
/// Tries to extract an array from the variant,
|
||||
/// returning an error if the variant is not an array.
|
||||
pub fn try_into_array(self) -> Result<Vec<MetadataValue>> {
|
||||
match self {
|
||||
MetadataValue::Array(a) => Ok(a),
|
||||
_ => Err(Error::IncorrectVariant {
|
||||
wanted: "Array",
|
||||
actual: self.variant(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to extract an array from the variant,
|
||||
/// panicking if the variant is not an array.
|
||||
pub fn into_array(self) -> Vec<MetadataValue> {
|
||||
self.try_into_array()
|
||||
.unwrap_or_else(|err| panic!("{}", err))
|
||||
}
|
||||
|
||||
/// Tries to extract a dictionary from the variant,
|
||||
/// returning an error if the variant is not a dictionary.
|
||||
/// The dictionary is returned as a map from string keys to values.
|
||||
pub fn try_into_dict(self) -> Result<HashMap<String, MetadataValue>> {
|
||||
match self {
|
||||
MetadataValue::Dict(d) => Ok(d),
|
||||
_ => Err(Error::IncorrectVariant {
|
||||
wanted: "Dict",
|
||||
actual: self.variant(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to extract a dictionary from the variant,
|
||||
/// panicking if the variant is not a dictionary.
|
||||
/// The dictionary is returned as a map from string keys to values.
|
||||
pub fn into_dict(self) -> HashMap<String, MetadataValue> {
|
||||
self.try_into_dict().unwrap_or_else(|err| panic!("{}", err))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&ZValue<'a>> for MetadataValue {
|
||||
fn from(value: &ZValue) -> Self {
|
||||
match value {
|
||||
ZValue::U8(u) => Self::UInt(*u as u64),
|
||||
ZValue::Bool(b) => Self::Bool(*b),
|
||||
ZValue::I16(i) => Self::Int(*i as i64),
|
||||
ZValue::U16(u) => Self::UInt(*u as u64),
|
||||
ZValue::I32(i) => Self::Int(*i as i64),
|
||||
ZValue::U32(u) => Self::UInt(*u as u64),
|
||||
ZValue::I64(i) => Self::Int(*i),
|
||||
ZValue::U64(u) => Self::UInt(*u),
|
||||
ZValue::F64(f) => Self::Double(*f),
|
||||
ZValue::Str(s) => Self::Str(s.to_string()),
|
||||
ZValue::ObjectPath(path) => Self::Str(path.to_string()),
|
||||
ZValue::Array(a) => Self::Array(a.iter().map(|v| v.into()).collect()),
|
||||
ZValue::Dict(d) => Self::Dict(
|
||||
HashMap::<String, ZValue>::try_from(d.to_owned())
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k, (&v).into()))
|
||||
.collect(),
|
||||
),
|
||||
ZValue::Value(value) => Self::from(&**value),
|
||||
_ => Self::__Unsupported,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for MetadataValue {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::__Unsupported => write!(f, "__Unsupported"),
|
||||
Self::Int(i) => write!(f, "{}", i),
|
||||
Self::UInt(u) => write!(f, "{}", u),
|
||||
Self::Double(d) => write!(f, "{}", d),
|
||||
Self::Str(s) => write!(f, "{}", s),
|
||||
Self::Bool(b) => write!(f, "{}", b),
|
||||
Self::Array(a) => write!(f, "{:?}", a),
|
||||
Self::Dict(d) => {
|
||||
let mut debug_struct = f.debug_struct("Dict");
|
||||
for (k, v) in d {
|
||||
debug_struct.field(k, &v);
|
||||
}
|
||||
debug_struct.finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for MetadataValue {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::__Unsupported => write!(f, "__Unsupported"),
|
||||
Self::Int(i) => write!(f, "{}", i),
|
||||
Self::UInt(u) => write!(f, "{}", u),
|
||||
Self::Double(d) => write!(f, "{}", d),
|
||||
Self::Str(s) => write!(f, "\"{}\"", s),
|
||||
Self::Bool(b) => write!(f, "{}", b),
|
||||
Self::Array(a) => {
|
||||
write!(f, "[")?;
|
||||
let mut iter = a.iter().peekable();
|
||||
while let Some(value) = iter.next() {
|
||||
if iter.peek().is_some() {
|
||||
write!(f, "{}, ", value)?;
|
||||
} else {
|
||||
write!(f, "{}", value)?;
|
||||
}
|
||||
}
|
||||
write!(f, "]")
|
||||
}
|
||||
Self::Dict(d) => {
|
||||
write!(f, "{{")?;
|
||||
let mut iter = d.iter().peekable();
|
||||
while let Some((k, v)) = iter.next() {
|
||||
if iter.peek().is_some() {
|
||||
write!(f, "{}: {}, ", k, v)?;
|
||||
} else {
|
||||
write!(f, "{}: {}", k, v)?;
|
||||
}
|
||||
}
|
||||
write!(f, "}}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
267
mpris2/src/player.rs
Normal file
267
mpris2/src/player.rs
Normal file
|
|
@ -0,0 +1,267 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
use crate::{
|
||||
bindings::{media_player::MediaPlayer2Proxy, player::PlayerProxy},
|
||||
error::{Error, Result},
|
||||
handle_optional,
|
||||
media_player::MediaPlayer,
|
||||
metadata::Metadata,
|
||||
track::TrackId,
|
||||
};
|
||||
use std::{
|
||||
fmt::{self, Display},
|
||||
ops::Deref,
|
||||
str::FromStr,
|
||||
};
|
||||
use time::Duration;
|
||||
use zbus::{names::OwnedBusName, Connection};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Player {
|
||||
proxy: PlayerProxy<'static>,
|
||||
}
|
||||
|
||||
impl Player {
|
||||
/// Creates a new instance of the `org.mpris.MediaPlayer2.Player` interface.
|
||||
pub async fn new(connection: &Connection, name: OwnedBusName) -> Result<Self> {
|
||||
PlayerProxy::builder(connection)
|
||||
.destination(name)?
|
||||
.build()
|
||||
.await
|
||||
.map(Self::from)
|
||||
.map_err(Error::from)
|
||||
}
|
||||
|
||||
/// Returns this player's `org.mpris.MediaPlayer2` instance
|
||||
pub async fn media_player(&self) -> Result<MediaPlayer> {
|
||||
let proxy = MediaPlayer2Proxy::builder(self.proxy.connection())
|
||||
.destination(self.proxy.destination().to_owned())?
|
||||
.build()
|
||||
.await?;
|
||||
Ok(proxy.into())
|
||||
}
|
||||
|
||||
/// Seeks the specified duration.
|
||||
pub async fn seek(&self, duration: Duration) -> Result<bool> {
|
||||
if self.proxy.can_seek().await? {
|
||||
self.proxy
|
||||
.seek(duration.whole_microseconds() as i64)
|
||||
.await?;
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the current track position.
|
||||
///
|
||||
/// If `track` does not match the id of the currently-playing track, the call is ignored as "stale".
|
||||
pub async fn set_position(&self, track: &TrackId, position: Duration) -> Result<()> {
|
||||
self.proxy
|
||||
.set_position(track, position.whole_microseconds() as i64)
|
||||
.await
|
||||
.map_err(Error::from)
|
||||
}
|
||||
|
||||
/// How far into the current track the player is.
|
||||
///
|
||||
/// Not all players support this, and it will return None if this is the case.
|
||||
pub async fn position(&self) -> Result<Option<Duration>> {
|
||||
handle_optional(self.proxy.position().await.map(Duration::microseconds))
|
||||
}
|
||||
|
||||
/// Gets the current playback status of the player.
|
||||
pub async fn playback_status(&self) -> Result<PlaybackStatus> {
|
||||
self.proxy
|
||||
.playback_status()
|
||||
.await
|
||||
.map_err(Error::from)
|
||||
.and_then(|status| PlaybackStatus::from_str(&status))
|
||||
}
|
||||
|
||||
/// Returns the current rate of playback.
|
||||
///
|
||||
/// Not all players support this, and it will return None if this is the case.
|
||||
pub async fn rate(&self) -> Result<Option<f64>> {
|
||||
handle_optional(self.proxy.rate().await)
|
||||
}
|
||||
|
||||
/// Sets the current rate of playback.
|
||||
pub async fn set_rate(&self, value: f64) -> Result<()> {
|
||||
handle_optional(self.proxy.set_rate(value).await).map(|_| ())
|
||||
}
|
||||
|
||||
/// Returns the minimum supported rate for the player.
|
||||
///
|
||||
/// Not all players support this, and it will return None if this is the case.
|
||||
pub async fn minimum_rate(&self) -> Result<Option<f64>> {
|
||||
handle_optional(self.proxy.minimum_rate().await)
|
||||
}
|
||||
|
||||
/// Returns the minimum supported rate for the player.
|
||||
///
|
||||
/// Not all players support this, and it will return None if this is the case.
|
||||
pub async fn maximum_rate(&self) -> Result<Option<f64>> {
|
||||
handle_optional(self.proxy.maximum_rate().await)
|
||||
}
|
||||
|
||||
/// Returns the range of playback rates available for the player.
|
||||
///
|
||||
/// Not all players support this, and it will return None if this is the case.
|
||||
pub async fn available_rates(&self) -> Result<Option<std::ops::RangeInclusive<f64>>> {
|
||||
let minimum = match self.minimum_rate().await? {
|
||||
Some(min) => min,
|
||||
None => return Ok(None),
|
||||
};
|
||||
let maximum = match self.maximum_rate().await? {
|
||||
Some(max) => max,
|
||||
None => return Ok(None),
|
||||
};
|
||||
Ok(Some(minimum..=maximum))
|
||||
}
|
||||
|
||||
/// Returns the metadata for the player.
|
||||
pub async fn metadata(&self) -> Result<Metadata> {
|
||||
self.proxy
|
||||
.metadata()
|
||||
.await
|
||||
.map(|metadata| metadata.into())
|
||||
.map_err(Error::from)
|
||||
}
|
||||
|
||||
/// Whether the current playlist is shuffled or not.
|
||||
///
|
||||
/// A value of false indicates that playback is progressing linearly through a playlist,
|
||||
/// while true means playback is progressing through a playlist in some other order.
|
||||
pub async fn shuffle(&self) -> Result<Option<bool>> {
|
||||
if self.can_control().await? {
|
||||
handle_optional(self.proxy.shuffle().await)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Set whether the current playlist is shuffled or not.
|
||||
///
|
||||
/// A value of false indicates that playback is progressing linearly through a playlist,
|
||||
/// while true means playback is progressing through a playlist in some other order.
|
||||
pub async fn set_shuffle(&self, value: bool) -> Result<()> {
|
||||
if self.proxy.can_control().await? {
|
||||
self.proxy.set_shuffle(value).await.map_err(Error::from)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// The current loop / repeat status.
|
||||
pub async fn loop_status(&self) -> Result<Option<LoopStatus>> {
|
||||
if self.proxy.can_control().await? {
|
||||
handle_optional(self.proxy.loop_status().await)
|
||||
.map(|status| status.and_then(|status| LoopStatus::from_str(&status).ok()))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the current loop / repeat status.
|
||||
pub async fn set_loop_status(&self, value: LoopStatus) -> Result<()> {
|
||||
if self.proxy.can_control().await? {
|
||||
handle_optional(self.proxy.set_loop_status(value.to_string()).await).map(|_| ())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Player {
|
||||
type Target = PlayerProxy<'static>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.proxy
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PlayerProxy<'static>> for Player {
|
||||
fn from(proxy: PlayerProxy<'static>) -> Self {
|
||||
Self { proxy }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum PlaybackStatus {
|
||||
/// A track is currently playing.
|
||||
Playing,
|
||||
/// A track is currently paused.
|
||||
Paused,
|
||||
/// There is no track currently playing.
|
||||
Stopped,
|
||||
}
|
||||
|
||||
impl FromStr for PlaybackStatus {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
match s.to_lowercase().trim() {
|
||||
"playing" => Ok(Self::Playing),
|
||||
"paused" => Ok(Self::Paused),
|
||||
"stopped" => Ok(Self::Stopped),
|
||||
_ => Err(Error::InvalidEnum {
|
||||
got: s.to_string(),
|
||||
expected: &["Playing", "Paused", "Stopped"],
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for PlaybackStatus {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
Self::Playing => "Playing",
|
||||
Self::Paused => "Paused",
|
||||
Self::Stopped => "Stopped",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum LoopStatus {
|
||||
/// The playback will stop when there are no more tracks to play
|
||||
None,
|
||||
/// The current track will start again from the begining once it has finished playing
|
||||
Track,
|
||||
/// The playback loops through a list of tracks
|
||||
Playlist,
|
||||
}
|
||||
|
||||
impl FromStr for LoopStatus {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
match s.to_lowercase().trim() {
|
||||
"none" => Ok(Self::None),
|
||||
"track" => Ok(Self::Track),
|
||||
"playlist" => Ok(Self::Playlist),
|
||||
_ => Err(Error::InvalidEnum {
|
||||
got: s.to_string(),
|
||||
expected: &["Playing", "Paused", "Stopped"],
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for LoopStatus {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
Self::None => "None",
|
||||
Self::Track => "Track",
|
||||
Self::Playlist => "Playlist",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
41
mpris2/src/playlists.rs
Normal file
41
mpris2/src/playlists.rs
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
pub mod id;
|
||||
pub mod ordering;
|
||||
pub mod playlist;
|
||||
|
||||
use crate::{
|
||||
bindings::playlist::PlaylistsProxy,
|
||||
error::{Error, Result},
|
||||
};
|
||||
use std::ops::Deref;
|
||||
use zbus::{names::OwnedBusName, Connection};
|
||||
|
||||
pub struct Playlists {
|
||||
proxy: PlaylistsProxy<'static>,
|
||||
}
|
||||
|
||||
impl Playlists {
|
||||
/// Creates a new instance of the `org.mpris.MediaPlayer2.Playlists` interface.
|
||||
pub async fn new(connection: &Connection, name: OwnedBusName) -> Result<Self> {
|
||||
PlaylistsProxy::builder(connection)
|
||||
.destination(name)?
|
||||
.build()
|
||||
.await
|
||||
.map(Self::from)
|
||||
.map_err(Error::from)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Playlists {
|
||||
type Target = PlaylistsProxy<'static>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.proxy
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PlaylistsProxy<'static>> for Playlists {
|
||||
fn from(proxy: PlaylistsProxy<'static>) -> Self {
|
||||
Self { proxy }
|
||||
}
|
||||
}
|
||||
53
mpris2/src/playlists/id.rs
Normal file
53
mpris2/src/playlists/id.rs
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
cmp::{Ord, Ordering, PartialOrd},
|
||||
fmt::{self, Display},
|
||||
ops::Deref,
|
||||
};
|
||||
use zvariant::{ObjectPath, OwnedObjectPath, Type, Value};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Type, Serialize, Deserialize, Value)]
|
||||
pub struct PlaylistId(OwnedObjectPath);
|
||||
|
||||
impl PlaylistId {
|
||||
pub fn into_inner(self) -> OwnedObjectPath {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn into_static_path(self) -> ObjectPath<'static> {
|
||||
self.0.into_inner().into_owned()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for PlaylistId {
|
||||
type Target = OwnedObjectPath;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AsRef<ObjectPath<'a>> for PlaylistId {
|
||||
fn as_ref(&self) -> &ObjectPath<'a> {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for PlaylistId {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
self.0.as_str().partial_cmp(other.0.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for PlaylistId {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.0.as_str().cmp(other.0.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for PlaylistId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.0.as_str())
|
||||
}
|
||||
}
|
||||
120
mpris2/src/playlists/ordering.rs
Normal file
120
mpris2/src/playlists/ordering.rs
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
use crate::error::{Error, Result};
|
||||
use serde::{
|
||||
de::{self, Deserialize, Visitor},
|
||||
ser::{Serialize, Serializer},
|
||||
};
|
||||
use std::{
|
||||
fmt::{self, Display},
|
||||
str::FromStr,
|
||||
};
|
||||
use zvariant::{OwnedValue, Signature, Type, Value};
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum PlaylistOrdering {
|
||||
/// Alphabetical ordering by name, ascending.
|
||||
Alphabetical,
|
||||
/// Ordering by creation date, oldest first.
|
||||
CreationDate,
|
||||
/// Ordering by last modified date, oldest first.
|
||||
ModifiedDate,
|
||||
/// Ordering by date of last playback, oldest first.
|
||||
LastPlayDate,
|
||||
/// A user-defined ordering.
|
||||
UserDefined,
|
||||
}
|
||||
|
||||
impl Type for PlaylistOrdering {
|
||||
fn signature() -> Signature<'static> {
|
||||
String::signature()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<Value<'a>> for PlaylistOrdering {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: Value<'a>) -> Result<Self> {
|
||||
match value {
|
||||
Value::Str(value) => Self::from_str(&value),
|
||||
_ => Err(Error::IncorrectValue {
|
||||
wanted: "Str",
|
||||
actual: OwnedValue::from(value),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<PlaylistOrdering> for Value<'a> {
|
||||
fn from(ordering: PlaylistOrdering) -> Self {
|
||||
Value::Str(ordering.to_string().into())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for PlaylistOrdering {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
match s.to_lowercase().trim() {
|
||||
"alphabetical" => Ok(Self::Alphabetical),
|
||||
"created" => Ok(Self::CreationDate),
|
||||
"modified" => Ok(Self::ModifiedDate),
|
||||
"played" => Ok(Self::LastPlayDate),
|
||||
"user" => Ok(Self::UserDefined),
|
||||
_ => Err(Error::InvalidEnum {
|
||||
got: s.to_string(),
|
||||
expected: &["Alphabetical", "Created", "Modified", "Played", "User"],
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for PlaylistOrdering {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
Self::Alphabetical => "Alphabetical",
|
||||
Self::CreationDate => "Created",
|
||||
Self::ModifiedDate => "Modified",
|
||||
Self::LastPlayDate => "Played",
|
||||
Self::UserDefined => "User",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for PlaylistOrdering {
|
||||
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(self.to_string().as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for PlaylistOrdering {
|
||||
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
|
||||
where
|
||||
D: de::Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_str(PlaylistOrderingVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
struct PlaylistOrderingVisitor;
|
||||
|
||||
impl Visitor<'_> for PlaylistOrderingVisitor {
|
||||
type Value = PlaylistOrdering;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str("a string")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, s: &str) -> std::result::Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
PlaylistOrdering::from_str(s).map_err(de::Error::custom)
|
||||
}
|
||||
}
|
||||
21
mpris2/src/playlists/playlist.rs
Normal file
21
mpris2/src/playlists/playlist.rs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
use super::id::PlaylistId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use zbus::zvariant::{Type, Value};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Type, Value, Serialize, Deserialize)]
|
||||
pub struct Playlist((PlaylistId, String, String));
|
||||
|
||||
impl Playlist {
|
||||
pub fn id(&self) -> &PlaylistId {
|
||||
&self.0 .0
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &str {
|
||||
&self.0 .1
|
||||
}
|
||||
|
||||
pub fn icon(&self) -> &str {
|
||||
&self.0 .2
|
||||
}
|
||||
}
|
||||
54
mpris2/src/track.rs
Normal file
54
mpris2/src/track.rs
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
fmt::{self, Display},
|
||||
ops::Deref,
|
||||
};
|
||||
use zbus::zvariant::{ObjectPath, OwnedObjectPath, Type, Value};
|
||||
|
||||
/// A reference to an MPRIS track.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Type, Serialize, Deserialize, Value)]
|
||||
pub struct TrackId(OwnedObjectPath);
|
||||
|
||||
impl TrackId {
|
||||
pub fn into_inner(self) -> OwnedObjectPath {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn into_static_path(self) -> ObjectPath<'static> {
|
||||
self.0.into_inner().into_owned()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for TrackId {
|
||||
type Target = OwnedObjectPath;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AsRef<ObjectPath<'a>> for TrackId {
|
||||
fn as_ref(&self) -> &ObjectPath<'a> {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for TrackId {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
self.0.as_str().partial_cmp(other.0.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for TrackId {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.0.as_str().cmp(other.0.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for TrackId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.0.as_str())
|
||||
}
|
||||
}
|
||||
93
mpris2/src/track_list.rs
Normal file
93
mpris2/src/track_list.rs
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
use crate::{
|
||||
bindings::track_list::TrackListProxy,
|
||||
error::{Error, Result},
|
||||
metadata::Metadata,
|
||||
track::TrackId,
|
||||
};
|
||||
use std::{collections::BTreeMap, ops::Deref};
|
||||
use zbus::{names::OwnedBusName, Connection};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TrackList {
|
||||
proxy: TrackListProxy<'static>,
|
||||
}
|
||||
|
||||
impl TrackList {
|
||||
/// Creates a new instance of the `org.mpris.MediaPlayer2.TrackList` interface.
|
||||
pub async fn new(connection: &Connection, name: OwnedBusName) -> Result<Self> {
|
||||
TrackListProxy::builder(connection)
|
||||
.destination(name)?
|
||||
.build()
|
||||
.await
|
||||
.map(Self::from)
|
||||
.map_err(Error::from)
|
||||
}
|
||||
|
||||
/// Adds a new track to this track list.
|
||||
pub async fn add_track<S: ToString>(
|
||||
&self,
|
||||
uri: S,
|
||||
after: &TrackId,
|
||||
set_as_current: bool,
|
||||
) -> Result<()> {
|
||||
let uri = uri.to_string();
|
||||
self.proxy
|
||||
.add_track(&uri, after, set_as_current)
|
||||
.await
|
||||
.map_err(Error::from)
|
||||
}
|
||||
|
||||
/// Gets the metadata of the given tracks.
|
||||
pub async fn get_tracks_metadata<T: AsRef<[TrackId]>>(
|
||||
&self,
|
||||
tracks: T,
|
||||
) -> Result<Vec<Metadata>> {
|
||||
self.proxy
|
||||
.get_tracks_metadata(tracks.as_ref().to_vec())
|
||||
.await
|
||||
.map(|x| x.into_iter().map(Metadata::from).collect())
|
||||
.map_err(Error::from)
|
||||
}
|
||||
|
||||
/// Goes to the specified track.
|
||||
pub async fn go_to(&self, track: &TrackId) -> Result<()> {
|
||||
self.proxy.go_to(track).await.map_err(Error::from)
|
||||
}
|
||||
|
||||
/// Removes the specified track.
|
||||
pub async fn remove(&self, track: &TrackId) -> Result<()> {
|
||||
self.proxy.remove_track(track).await.map_err(Error::from)
|
||||
}
|
||||
|
||||
/// Returns a list of all available [Track]s.
|
||||
pub async fn tracks(&self) -> Result<Vec<TrackId>> {
|
||||
self.proxy
|
||||
.tracks()
|
||||
.await
|
||||
.map(|x| x.into_iter().map(TrackId::from).collect())
|
||||
.map_err(Error::from)
|
||||
}
|
||||
|
||||
/// Returns a list of all available [Track]s and their associated metadata,
|
||||
/// in order.
|
||||
pub async fn detailed_tracks(&self) -> Result<BTreeMap<TrackId, Metadata>> {
|
||||
let tracks = self.tracks().await?;
|
||||
let metadata = self.get_tracks_metadata(&tracks).await?;
|
||||
Ok(tracks.into_iter().zip(metadata.into_iter()).collect())
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for TrackList {
|
||||
type Target = TrackListProxy<'static>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.proxy
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TrackListProxy<'static>> for TrackList {
|
||||
fn from(proxy: TrackListProxy<'static>) -> Self {
|
||||
Self { proxy }
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue