feat: add mpris2-zbus for org.mpris.MediaPlayer2

This commit is contained in:
Michael Aaron Murphy 2023-02-01 17:42:52 +01:00
parent 4d8815361d
commit 945f80f036
No known key found for this signature in database
GPG key ID: B2732D4240C9212C
21 changed files with 2203 additions and 0 deletions

5
mpris2/src/bindings.rs Normal file
View 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;

View 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>>;
}

View 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<()>;
}

View 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>;
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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 }
}
}

View 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())
}
}

View 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)
}
}

View 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
View 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
View 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 }
}
}