use cosmic::iced::futures::{self, SinkExt, Stream}; use cosmic::iced::{Subscription, stream}; use mpris_server::zbus::{Result, fdo}; use mpris_server::{ LoopStatus, Metadata, PlaybackRate, PlaybackStatus, PlayerInterface, Property, RootInterface, Server, Signal, Time, TrackId, Volume, }; use std::any::TypeId; use std::{future, process}; use tokio::sync::{Mutex, mpsc}; use crate::config::RepeatState; use crate::{Message, MprisEvent, MprisMeta, MprisState}; impl MprisMeta { fn metadata(&self) -> Metadata { let mut meta = Metadata::builder() //TODO: better track id .trackid( mpris_server::TrackId::try_from(format!( "/com/system76/CosmicPlayer/pid{}/TrackList/0", process::id() )) .unwrap(), ) .length(Time::from_micros(self.duration_micros)); if let Some(url) = &self.url_opt { meta = meta.url(url.clone()); } if !self.album.is_empty() { meta = meta.album(&self.album); } if let Some(album_art) = &self.album_art_opt { meta = meta.art_url(album_art.clone()); } if !self.artists.is_empty() { meta = meta.artist(&self.artists); } //TODO: content_created if !self.title.is_empty() { meta = meta.title(&self.title); } //TODO .disc_number(self.disc_number) if self.track_number > 0 { meta = meta.track_number(self.track_number); } //TODO: track count? //TODO: more keys, see https://docs.rs/mpris-server/0.8.1/mpris_server/builder/struct.MetadataBuilder.html meta.build() } } impl MprisState { fn playback_status(&self) -> PlaybackStatus { if self.paused { PlaybackStatus::Paused } else { PlaybackStatus::Playing } } fn loop_status(&self) -> LoopStatus { match self.repeat_state { RepeatState::Disabled => LoopStatus::None, RepeatState::Track => LoopStatus::Track, } } } pub struct Player { msg_tx: Mutex>, meta: Mutex, state: Mutex, } impl Player { async fn message(&self, message: Message) -> fdo::Result<()> { self.msg_tx .lock() .await .send(message) .await .map_err(|err| fdo::Error::Failed(err.to_string())) } } impl RootInterface for Player { async fn raise(&self) -> fdo::Result<()> { log::info!("Raise"); Ok(()) } async fn quit(&self) -> fdo::Result<()> { log::info!("Quit"); Ok(()) } async fn can_quit(&self) -> fdo::Result { log::info!("CanQuit"); Ok(false) } async fn fullscreen(&self) -> fdo::Result { log::info!("Fullscreen"); let state = self.state.lock().await; Ok(state.fullscreen) } async fn set_fullscreen(&self, fullscreen: bool) -> Result<()> { log::info!("SetFullscreen({})", fullscreen); Ok(()) } async fn can_set_fullscreen(&self) -> fdo::Result { log::info!("CanSetFullscreen"); Ok(false) } async fn can_raise(&self) -> fdo::Result { log::info!("CanRaise"); Ok(false) } async fn has_track_list(&self) -> fdo::Result { log::info!("HasTrackList"); Ok(false) } async fn identity(&self) -> fdo::Result { log::info!("Identity"); Ok("COSMIC Player".to_string()) } async fn desktop_entry(&self) -> fdo::Result { log::info!("DesktopEntry"); Ok("com.system76.CosmicPlayer".to_string()) } async fn supported_uri_schemes(&self) -> fdo::Result> { log::info!("SupportedUriSchemes"); Ok(vec![]) } async fn supported_mime_types(&self) -> fdo::Result> { log::info!("SupportedMimeTypes"); Ok(vec![]) } } impl PlayerInterface for Player { async fn next(&self) -> fdo::Result<()> { log::info!("Next"); self.message(Message::PlayNext).await } async fn previous(&self) -> fdo::Result<()> { log::info!("Previous"); self.message(Message::PlayPrev).await } async fn pause(&self) -> fdo::Result<()> { log::info!("Pause"); self.message(Message::Pause).await } async fn play_pause(&self) -> fdo::Result<()> { log::info!("PlayPause"); self.message(Message::PlayPause).await } async fn stop(&self) -> fdo::Result<()> { log::info!("Stop"); Ok(()) } async fn play(&self) -> fdo::Result<()> { log::info!("Play"); self.message(Message::Play).await } async fn seek(&self, offset: Time) -> fdo::Result<()> { log::info!("Seek({:?})", offset); Ok(()) } async fn set_position(&self, track_id: TrackId, position: Time) -> fdo::Result<()> { log::info!("SetPosition({}, {:?})", track_id, position); Ok(()) } async fn open_uri(&self, uri: String) -> fdo::Result<()> { log::info!("OpenUri({})", uri); Ok(()) } async fn playback_status(&self) -> fdo::Result { log::info!("PlaybackStatus"); let state = self.state.lock().await; Ok(state.playback_status()) } async fn loop_status(&self) -> fdo::Result { log::info!("LoopStatus"); let state = self.state.lock().await; Ok(state.loop_status()) } async fn set_loop_status(&self, loop_status: LoopStatus) -> Result<()> { log::info!("SetLoopStatus({})", loop_status); let repeat_state = match loop_status { LoopStatus::None => RepeatState::Disabled, LoopStatus::Track | LoopStatus::Playlist => RepeatState::Track, }; self.message(Message::RepeatToggled(repeat_state)).await?; Ok(()) } async fn rate(&self) -> fdo::Result { log::info!("Rate"); Ok(1.0) } async fn set_rate(&self, rate: PlaybackRate) -> Result<()> { log::info!("SetRate({})", rate); Ok(()) } async fn shuffle(&self) -> fdo::Result { log::info!("Shuffle"); Ok(false) } async fn set_shuffle(&self, shuffle: bool) -> Result<()> { log::info!("SetShuffle({})", shuffle); Ok(()) } async fn metadata(&self) -> fdo::Result { log::info!("Metadata"); let meta = self.meta.lock().await; Ok(meta.metadata()) } async fn volume(&self) -> fdo::Result { log::info!("Volume"); let state = self.state.lock().await; Ok(state.volume) } async fn set_volume(&self, volume: Volume) -> Result<()> { log::info!("SetVolume({})", volume); self.message(Message::AudioVolume(volume)).await?; Ok(()) } async fn position(&self) -> fdo::Result