use std::collections::HashMap;
use std::convert::From;
use std::convert::TryFrom;
use std::convert::TryInto;
use std::sync::{mpsc, Arc, Mutex};
use std::thread::{self, JoinHandle};
use std::time::Duration;
use zbus::{dbus_interface, ConnectionBuilder, SignalContext};
use zvariant::{ObjectPath, Value};
use crate::{
MediaControlEvent, MediaMetadata, MediaPlayback, MediaPosition, PlatformConfig, SeekDirection,
};
#[derive(Debug)]
pub struct Error;
pub struct MediaControls {
thread: Option<ServiceThreadHandle>,
dbus_name: String,
friendly_name: String,
}
struct ServiceThreadHandle {
event_channel: mpsc::Sender<InternalEvent>,
thread: JoinHandle<()>,
}
#[derive(Clone, PartialEq, Debug)]
enum InternalEvent {
ChangeMetadata(OwnedMetadata),
ChangePlayback(MediaPlayback),
ChangeVolume(f64),
Kill,
}
#[derive(Clone, Debug)]
struct ServiceState {
metadata: OwnedMetadata,
playback_status: MediaPlayback,
volume: f64,
}
#[derive(Clone, PartialEq, Eq, Debug, Default)]
struct OwnedMetadata {
pub title: Option<String>,
pub album: Option<String>,
pub artist: Option<String>,
pub cover_url: Option<String>,
pub duration: Option<i64>,
}
impl From<MediaMetadata<'_>> for OwnedMetadata {
fn from(other: MediaMetadata) -> Self {
OwnedMetadata {
title: other.title.map(|s| s.to_string()),
artist: other.artist.map(|s| s.to_string()),
album: other.album.map(|s| s.to_string()),
cover_url: other.cover_url.map(|s| s.to_string()),
duration: other.duration.map(|d| d.as_micros().try_into().unwrap()),
}
}
}
impl MediaControls {
pub fn new(config: PlatformConfig) -> Result<Self, Error> {
let PlatformConfig {
dbus_name,
display_name,
..
} = config;
Ok(Self {
thread: None,
dbus_name: dbus_name.to_string(),
friendly_name: display_name.to_string(),
})
}
pub fn attach<F>(&mut self, event_handler: F) -> Result<(), Error>
where
F: Fn(MediaControlEvent) + Send + 'static,
{
self.detach()?;
let dbus_name = self.dbus_name.clone();
let friendly_name = self.friendly_name.clone();
let event_handler = Arc::new(Mutex::new(event_handler));
let (event_channel, rx) = mpsc::channel();
self.thread = Some(ServiceThreadHandle {
event_channel,
thread: thread::spawn(move || {
pollster::block_on(run_service(dbus_name, friendly_name, event_handler, rx))
.unwrap();
}),
});
Ok(())
}
pub fn detach(&mut self) -> Result<(), Error> {
if let Some(ServiceThreadHandle {
event_channel,
thread,
}) = self.thread.take()
{
event_channel.send(InternalEvent::Kill).unwrap();
thread.join().unwrap();
}
Ok(())
}
pub fn set_playback(&mut self, playback: MediaPlayback) -> Result<(), Error> {
self.send_internal_event(InternalEvent::ChangePlayback(playback));
Ok(())
}
pub fn set_metadata(&mut self, metadata: MediaMetadata) -> Result<(), Error> {
self.send_internal_event(InternalEvent::ChangeMetadata(metadata.into()));
Ok(())
}
pub fn set_volume(&mut self, volume: f64) -> Result<(), Error> {
self.send_internal_event(InternalEvent::ChangeVolume(volume));
Ok(())
}
fn send_internal_event(&mut self, event: InternalEvent) {
let channel = &self.thread.as_ref().unwrap().event_channel;
channel.send(event).unwrap();
}
}
struct AppInterface {
friendly_name: String,
event_handler: Arc<Mutex<dyn Fn(MediaControlEvent) + Send + 'static>>,
}
#[dbus_interface(name = "org.mpris.MediaPlayer2")]
impl AppInterface {
fn raise(&self) {
self.send_event(MediaControlEvent::Raise);
}
fn quit(&self) {
self.send_event(MediaControlEvent::Quit);
}
#[dbus_interface(property)]
fn can_quit(&self) -> bool {
true
}
#[dbus_interface(property)]
fn can_raise(&self) -> bool {
true
}
#[dbus_interface(property)]
fn has_tracklist(&self) -> bool {
false
}
#[dbus_interface(property)]
fn identity(&self) -> &str {
&self.friendly_name
}
#[dbus_interface(property)]
fn supported_uri_schemes(&self) -> &[&str] {
&[]
}
#[dbus_interface(property)]
fn supported_mime_types(&self) -> &[&str] {
&[]
}
}
impl AppInterface {
fn send_event(&self, event: MediaControlEvent) {
(self.event_handler.lock().unwrap())(event);
}
}
struct PlayerInterface {
state: ServiceState,
event_handler: Arc<Mutex<dyn Fn(MediaControlEvent) + Send + 'static>>,
}
impl PlayerInterface {
fn send_event(&self, event: MediaControlEvent) {
(self.event_handler.lock().unwrap())(event);
}
}
#[dbus_interface(name = "org.mpris.MediaPlayer2.Player")]
impl PlayerInterface {
fn next(&self) {
self.send_event(MediaControlEvent::Next);
}
fn previous(&self) {
self.send_event(MediaControlEvent::Previous);
}
fn pause(&self) {
self.send_event(MediaControlEvent::Pause);
}
fn play_pause(&self) {
self.send_event(MediaControlEvent::Toggle);
}
fn stop(&self) {
self.send_event(MediaControlEvent::Stop);
}
fn play(&self) {
self.send_event(MediaControlEvent::Play);
}
fn seek(&self, offset: i64) {
let abs_offset = offset.unsigned_abs();
let direction = if offset > 0 {
SeekDirection::Forward
} else {
SeekDirection::Backward
};
self.send_event(MediaControlEvent::SeekBy(
direction,
Duration::from_micros(abs_offset),
));
}
fn set_position(&self, _track_id: zvariant::ObjectPath, position: i64) {
if let Ok(micros) = position.try_into() {
if let Some(duration) = self.state.metadata.duration {
if position > duration {
return;
}
}
let position = Duration::from_micros(micros);
self.send_event(MediaControlEvent::SetPosition(MediaPosition(position)));
}
}
fn open_uri(&self, uri: String) {
self.send_event(MediaControlEvent::OpenUri(uri));
}
#[dbus_interface(property)]
fn playback_status(&self) -> &'static str {
match self.state.playback_status {
MediaPlayback::Playing { .. } => "Playing",
MediaPlayback::Paused { .. } => "Paused",
MediaPlayback::Stopped => "Stopped",
}
}
#[dbus_interface(property)]
fn rate(&self) -> f64 {
1.0
}
#[dbus_interface(property)]
fn metadata(&self) -> HashMap<&str, Value> {
let mut dict = HashMap::<&str, Value>::new();
let OwnedMetadata {
ref title,
ref album,
ref artist,
ref cover_url,
ref duration,
} = self.state.metadata;
dict.insert(
"mpris:trackid",
Value::new(ObjectPath::try_from("/").unwrap()),
);
if let Some(length) = duration {
dict.insert("mpris:length", Value::new(*length));
}
if let Some(cover_url) = cover_url {
dict.insert("mpris:artUrl", Value::new(cover_url.clone()));
}
if let Some(title) = title {
dict.insert("xesam:title", Value::new(title.clone()));
}
if let Some(artist) = artist {
dict.insert("xesam:artist", Value::new(vec![artist.clone()]));
}
if let Some(album) = album {
dict.insert("xesam:album", Value::new(album.clone()));
}
dict
}
#[dbus_interface(property)]
fn volume(&self) -> f64 {
self.state.volume
}
#[dbus_interface(property)]
fn set_volume(&self, volume: f64) {
self.send_event(MediaControlEvent::SetVolume(volume));
}
#[dbus_interface(property)]
fn position(&self) -> i64 {
let position = match self.state.playback_status {
MediaPlayback::Playing {
progress: Some(pos),
}
| MediaPlayback::Paused {
progress: Some(pos),
} => pos.0.as_micros(),
_ => 0,
};
position.try_into().unwrap_or(0)
}
#[dbus_interface(property)]
fn minimum_rate(&self) -> f64 {
1.0
}
#[dbus_interface(property)]
fn maximum_rate(&self) -> f64 {
1.0
}
#[dbus_interface(property)]
fn can_go_next(&self) -> bool {
true
}
#[dbus_interface(property)]
fn can_go_previous(&self) -> bool {
true
}
#[dbus_interface(property)]
fn can_play(&self) -> bool {
true
}
#[dbus_interface(property)]
fn can_pause(&self) -> bool {
true
}
#[dbus_interface(property)]
fn can_seek(&self) -> bool {
true
}
#[dbus_interface(property)]
fn can_control(&self) -> bool {
true
}
}
async fn run_service(
dbus_name: String,
friendly_name: String,
event_handler: Arc<Mutex<dyn Fn(MediaControlEvent) + Send + 'static>>,
event_channel: mpsc::Receiver<InternalEvent>,
) -> zbus::Result<()> {
let app = AppInterface {
friendly_name,
event_handler: event_handler.clone(),
};
let player = PlayerInterface {
state: ServiceState {
metadata: OwnedMetadata::default(),
playback_status: MediaPlayback::Stopped,
volume: 1.0,
},
event_handler,
};
let name = format!("org.mpris.MediaPlayer2.{dbus_name}");
let path = ObjectPath::try_from("/org/mpris/MediaPlayer2")?;
let connection = ConnectionBuilder::session()?
.serve_at(&path, app)?
.serve_at(&path, player)?
.name(name.as_str())?
.build()
.await?;
loop {
if let Ok(event) = event_channel.recv_timeout(Duration::from_millis(10)) {
if event == InternalEvent::Kill {
break;
}
let interface_ref = connection
.object_server()
.interface::<_, PlayerInterface>(&path)
.await?;
let mut interface = interface_ref.get_mut().await;
let ctxt = SignalContext::new(&connection, &path)?;
match event {
InternalEvent::ChangeMetadata(metadata) => {
interface.state.metadata = metadata;
interface.metadata_changed(&ctxt).await?;
}
InternalEvent::ChangePlayback(playback) => {
interface.state.playback_status = playback;
interface.playback_status_changed(&ctxt).await?;
}
InternalEvent::ChangeVolume(volume) => {
interface.state.volume = volume;
interface.volume_changed(&ctxt).await?;
}
InternalEvent::Kill => (),
}
}
}
Ok(())
}