use std::sync::mpsc::{self, Receiver, Sender};
use std::time::Duration;
use souvlaki::{
MediaControlEvent, MediaControls, MediaMetadata, MediaPlayback, MediaPosition, PlatformConfig,
SeekDirection,
};
use crate::action::Action;
const SEEK_BY: i64 = 10;
#[derive(Debug, Default, PartialEq)]
struct Shown {
title: String,
artist: String,
album: String,
duration: Option<Duration>,
playing: Option<bool>,
position: u64,
}
pub struct Media {
controls: Option<MediaControls>,
events: Receiver<MediaControlEvent>,
shown: Shown,
}
impl Media {
pub fn new(wake: impl Fn() + Send + 'static) -> Media {
let (send, events) = mpsc::channel();
Media {
controls: attach(send, wake),
events,
shown: Shown::default(),
}
}
pub fn none() -> Media {
let (_, events) = mpsc::channel();
Media {
controls: None,
events,
shown: Shown::default(),
}
}
pub fn drain(&self, playing: bool) -> Vec<Action> {
self.events
.try_iter()
.filter_map(|e| action(e, playing))
.collect()
}
pub fn publish(
&mut self,
track: Option<(&str, &str, &str, Option<Duration>)>,
playing: Option<bool>,
position: Duration,
) {
let Some(controls) = self.controls.as_mut() else {
return;
};
let (title, artist, album, duration) = track.unwrap_or_default();
let next = Shown {
title: title.to_string(),
artist: artist.to_string(),
album: album.to_string(),
duration,
playing,
position: position.as_secs(),
};
if next.title != self.shown.title
|| next.artist != self.shown.artist
|| next.album != self.shown.album
|| next.duration != self.shown.duration
{
let _ = controls.set_metadata(MediaMetadata {
title: Some(&next.title).filter(|t| !t.is_empty()).map(|t| &**t),
artist: Some(&next.artist).filter(|a| !a.is_empty()).map(|a| &**a),
album: Some(&next.album).filter(|a| !a.is_empty()).map(|a| &**a),
duration: next.duration,
cover_url: None,
});
}
if next.playing != self.shown.playing || next.position != self.shown.position {
let progress = Some(MediaPosition(position));
let _ = controls.set_playback(match next.playing {
Some(true) => MediaPlayback::Playing { progress },
Some(false) => MediaPlayback::Paused { progress },
None => MediaPlayback::Stopped,
});
}
self.shown = next;
}
}
fn attach(
send: Sender<MediaControlEvent>,
wake: impl Fn() + Send + 'static,
) -> Option<MediaControls> {
let config = PlatformConfig {
dbus_name: "playr",
display_name: "playr",
hwnd: None,
};
let mut controls = MediaControls::new(config).ok()?;
controls
.attach(move |event| {
if send.send(event).is_ok() {
wake();
}
})
.ok()?;
Some(controls)
}
pub fn action(event: MediaControlEvent, playing: bool) -> Option<Action> {
match event {
MediaControlEvent::Play if !playing => Some(Action::TogglePause),
MediaControlEvent::Pause if playing => Some(Action::TogglePause),
MediaControlEvent::Play | MediaControlEvent::Pause => None,
MediaControlEvent::Toggle => Some(Action::TogglePause),
MediaControlEvent::Next => Some(Action::Next),
MediaControlEvent::Previous => Some(Action::Prev),
MediaControlEvent::Stop | MediaControlEvent::Quit => Some(Action::Stop),
MediaControlEvent::Seek(SeekDirection::Forward) => Some(Action::SeekBy(SEEK_BY)),
MediaControlEvent::Seek(SeekDirection::Backward) => Some(Action::SeekBy(-SEEK_BY)),
MediaControlEvent::SeekBy(SeekDirection::Forward, by) => {
Some(Action::SeekBy(by.as_secs() as i64))
}
MediaControlEvent::SeekBy(SeekDirection::Backward, by) => {
Some(Action::SeekBy(-(by.as_secs() as i64)))
}
MediaControlEvent::SetPosition(MediaPosition(at)) => Some(Action::SeekTo(at)),
MediaControlEvent::SetVolume(v) => Some(Action::SetVolume(v as f32)),
MediaControlEvent::OpenUri(_) | MediaControlEvent::Raise => None,
}
}