use core::ffi::{c_double, c_int, c_void};
use std::marker::PhantomData;
use crate::ffi::{self, MpCommandCallback};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(i32)]
#[non_exhaustive]
pub enum Command {
Play = 0,
Pause = 1,
Stop = 2,
TogglePlayPause = 3,
NextTrack = 4,
PreviousTrack = 5,
SkipForward = 6,
SkipBackward = 7,
SeekForward = 8,
SeekBackward = 9,
ChangePlaybackPosition = 10,
}
impl Command {
#[must_use]
fn from_id(id: i32) -> Option<Self> {
match id {
0 => Some(Self::Play),
1 => Some(Self::Pause),
2 => Some(Self::Stop),
3 => Some(Self::TogglePlayPause),
4 => Some(Self::NextTrack),
5 => Some(Self::PreviousTrack),
6 => Some(Self::SkipForward),
7 => Some(Self::SkipBackward),
8 => Some(Self::SeekForward),
9 => Some(Self::SeekBackward),
10 => Some(Self::ChangePlaybackPosition),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[repr(i32)]
#[non_exhaustive]
pub enum HandlerStatus {
#[default]
Success = 0,
NoSuchContent = 100,
NoActionableNowPlayingItem = 110,
DeviceNotFound = 120,
CommandFailed = 200,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[repr(i32)]
pub enum SeekType {
#[default]
BeginSeeking = 0,
EndSeeking = 1,
}
impl SeekType {
#[must_use]
fn from_raw(raw: i32) -> Self {
if raw == 1 { Self::EndSeeking } else { Self::BeginSeeking }
}
}
#[derive(Debug, Clone)]
pub struct CommandEvent {
pub command: Command,
pub timestamp: f64,
pub skip_interval: Option<f64>,
pub seek_type: Option<SeekType>,
pub position: Option<f64>,
}
impl CommandEvent {
fn from_raw(command_id: i32, timestamp: f64, extra: f64, seek_type_raw: i32) -> Self {
let command = Command::from_id(command_id).unwrap_or(Command::Play);
let skip_interval = matches!(command, Command::SkipForward | Command::SkipBackward)
.then_some(extra);
let seek_type = matches!(command, Command::SeekForward | Command::SeekBackward)
.then(|| SeekType::from_raw(seek_type_raw));
let position = matches!(command, Command::ChangePlaybackPosition).then_some(extra);
Self {
command,
timestamp,
skip_interval,
seek_type,
position,
}
}
}
type HandlerBox = Box<dyn FnMut(CommandEvent) -> HandlerStatus + Send>;
unsafe extern "C" fn command_trampoline(
refcon: *mut c_void,
command_id: c_int,
timestamp: c_double,
extra: c_double,
seek_type: c_int,
) -> c_int {
if refcon.is_null() {
return HandlerStatus::CommandFailed as i32;
}
let handler = &mut *(refcon.cast::<HandlerBox>());
let event = CommandEvent::from_raw(command_id, timestamp, extra, seek_type);
handler(event) as i32
}
pub struct CommandToken {
token_ptr: *mut c_void,
closure_ptr: *mut HandlerBox,
_not_sync: PhantomData<*mut ()>,
}
unsafe impl Send for CommandToken {}
impl Drop for CommandToken {
fn drop(&mut self) {
if self.token_ptr.is_null() {
if !self.closure_ptr.is_null() {
unsafe { drop(Box::from_raw(self.closure_ptr)) }
}
return;
}
unsafe {
ffi::mp_remote_command_remove_handler(self.token_ptr);
ffi::mp_command_token_release(self.token_ptr);
drop(Box::from_raw(self.closure_ptr));
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct RemoteCommandCenter;
impl RemoteCommandCenter {
#[must_use]
pub fn shared() -> Self {
Self
}
pub fn add_handler<F>(&self, command: Command, handler: F) -> CommandToken
where
F: FnMut(CommandEvent) -> HandlerStatus + Send + 'static,
{
let boxed: Box<HandlerBox> = Box::new(Box::new(handler));
let closure_ptr = Box::into_raw(boxed);
let token_ptr = unsafe {
ffi::mp_remote_command_add_handler(
command as i32,
Some(command_trampoline as MpCommandCallback),
closure_ptr.cast::<c_void>(),
)
};
CommandToken {
token_ptr,
closure_ptr,
_not_sync: PhantomData,
}
}
pub fn on_play<F>(&self, handler: F) -> CommandToken
where
F: FnMut(CommandEvent) -> HandlerStatus + Send + 'static,
{
self.add_handler(Command::Play, handler)
}
pub fn on_pause<F>(&self, handler: F) -> CommandToken
where
F: FnMut(CommandEvent) -> HandlerStatus + Send + 'static,
{
self.add_handler(Command::Pause, handler)
}
pub fn on_stop<F>(&self, handler: F) -> CommandToken
where
F: FnMut(CommandEvent) -> HandlerStatus + Send + 'static,
{
self.add_handler(Command::Stop, handler)
}
pub fn on_toggle_play_pause<F>(&self, handler: F) -> CommandToken
where
F: FnMut(CommandEvent) -> HandlerStatus + Send + 'static,
{
self.add_handler(Command::TogglePlayPause, handler)
}
pub fn on_next_track<F>(&self, handler: F) -> CommandToken
where
F: FnMut(CommandEvent) -> HandlerStatus + Send + 'static,
{
self.add_handler(Command::NextTrack, handler)
}
pub fn on_previous_track<F>(&self, handler: F) -> CommandToken
where
F: FnMut(CommandEvent) -> HandlerStatus + Send + 'static,
{
self.add_handler(Command::PreviousTrack, handler)
}
pub fn on_skip_forward<F>(&self, handler: F) -> CommandToken
where
F: FnMut(CommandEvent) -> HandlerStatus + Send + 'static,
{
self.add_handler(Command::SkipForward, handler)
}
pub fn on_skip_backward<F>(&self, handler: F) -> CommandToken
where
F: FnMut(CommandEvent) -> HandlerStatus + Send + 'static,
{
self.add_handler(Command::SkipBackward, handler)
}
pub fn on_seek_forward<F>(&self, handler: F) -> CommandToken
where
F: FnMut(CommandEvent) -> HandlerStatus + Send + 'static,
{
self.add_handler(Command::SeekForward, handler)
}
pub fn on_seek_backward<F>(&self, handler: F) -> CommandToken
where
F: FnMut(CommandEvent) -> HandlerStatus + Send + 'static,
{
self.add_handler(Command::SeekBackward, handler)
}
pub fn on_change_playback_position<F>(&self, handler: F) -> CommandToken
where
F: FnMut(CommandEvent) -> HandlerStatus + Send + 'static,
{
self.add_handler(Command::ChangePlaybackPosition, handler)
}
}