//! Contains the code for the MPRIS server & other helper functions. use std::{ env, hash::{DefaultHasher, Hash, Hasher}, process, sync::Arc, }; use arc_swap::ArcSwap; use mpris_server::{ zbus::{self, fdo, Result}, LoopStatus, Metadata, PlaybackRate, PlaybackStatus, PlayerInterface, Property, RootInterface, Time, TrackId, Volume, }; use rodio::Sink; use tokio::sync::{broadcast, mpsc}; use crate::{player::Current, ui::Update}; use crate::{ui, Message}; const ERROR: fdo::Error = fdo::Error::Failed(String::new()); struct Sender { inner: mpsc::Sender, } impl Sender { pub fn new(inner: mpsc::Sender) -> Self { Self { inner } } pub async fn send(&self, message: Message) -> fdo::Result<()> { self.inner .send(message) .await .map_err(|x| fdo::Error::Failed(x.to_string())) } pub async fn zbus(&self, message: Message) -> zbus::Result<()> { self.inner .send(message) .await .map_err(|x| zbus::Error::Failure(x.to_string())) } } impl Into for crate::Error { fn into(self) -> fdo::Error { fdo::Error::Failed(self.to_string()) } } /// The actual MPRIS player. pub struct Player { sink: Arc, current: ArcSwap, list: String, sender: Sender, } impl RootInterface for Player { async fn raise(&self) -> fdo::Result<()> { Err(ERROR) } async fn quit(&self) -> fdo::Result<()> { self.sender.send(Message::Quit).await } async fn can_quit(&self) -> fdo::Result { Ok(true) } async fn fullscreen(&self) -> fdo::Result { Ok(false) } async fn set_fullscreen(&self, _: bool) -> Result<()> { Ok(()) } async fn can_set_fullscreen(&self) -> fdo::Result { Ok(false) } async fn can_raise(&self) -> fdo::Result { Ok(false) } async fn has_track_list(&self) -> fdo::Result { Ok(false) } async fn identity(&self) -> fdo::Result { Ok("lowfi".to_owned()) } async fn desktop_entry(&self) -> fdo::Result { Ok("dev.talwat.lowfi".to_owned()) } async fn supported_uri_schemes(&self) -> fdo::Result> { Ok(vec!["https".to_owned()]) } async fn supported_mime_types(&self) -> fdo::Result> { Ok(vec!["audio/mpeg".to_owned()]) } } impl PlayerInterface for Player { async fn next(&self) -> fdo::Result<()> { self.sender.send(Message::Next).await } async fn previous(&self) -> fdo::Result<()> { Err(ERROR) } async fn pause(&self) -> fdo::Result<()> { self.sender.send(Message::Pause).await } async fn play_pause(&self) -> fdo::Result<()> { self.sender.send(Message::PlayPause).await } async fn stop(&self) -> fdo::Result<()> { self.pause().await } async fn play(&self) -> fdo::Result<()> { self.sender.send(Message::Play).await } async fn seek(&self, _offset: Time) -> fdo::Result<()> { Err(ERROR) } async fn set_position(&self, _track_id: TrackId, _position: Time) -> fdo::Result<()> { Err(ERROR) } async fn open_uri(&self, _uri: String) -> fdo::Result<()> { Err(ERROR) } async fn playback_status(&self) -> fdo::Result { Ok(if self.current.load().loading() { PlaybackStatus::Stopped } else if self.sink.is_paused() { PlaybackStatus::Paused } else { PlaybackStatus::Playing }) } async fn loop_status(&self) -> fdo::Result { Err(ERROR) } async fn set_loop_status(&self, _loop_status: LoopStatus) -> Result<()> { Ok(()) } async fn rate(&self) -> fdo::Result { Ok(self.sink.speed().into()) } async fn set_rate(&self, rate: PlaybackRate) -> Result<()> { self.sink.set_speed(rate as f32); Ok(()) } async fn shuffle(&self) -> fdo::Result { Ok(true) } async fn set_shuffle(&self, _shuffle: bool) -> Result<()> { Ok(()) } async fn metadata(&self) -> fdo::Result { Ok(match self.current.load().as_ref() { Current::Loading(_) => Metadata::new(), Current::Track(track) => { let mut hasher = DefaultHasher::new(); track.path.hash(&mut hasher); let id = mpris_server::zbus::zvariant::ObjectPath::try_from(format!( "/com/talwat/lowfi/{}/{}", self.list, hasher.finish() )) .unwrap(); let mut metadata = Metadata::builder() .trackid(id) .title(track.display.clone()) .album(self.list.clone()) .build(); metadata.set_length( track .duration .map(|x| Time::from_micros(x.as_micros() as i64)), ); metadata } }) } async fn volume(&self) -> fdo::Result { Ok(self.sink.volume().into()) } async fn set_volume(&self, volume: Volume) -> Result<()> { self.sender.zbus(Message::SetVolume(volume as f32)).await } async fn position(&self) -> fdo::Result