use anyhow::Result;
use block2::RcBlock;
use objc2_media_player::{
MPNowPlayingInfoCenter, MPNowPlayingPlaybackState, MPRemoteCommandCenter, MPRemoteCommandEvent,
MPRemoteCommandHandlerStatus,
};
use std::ptr::NonNull;
use std::sync::Arc;
use std::thread;
use tokio::sync::mpsc;
#[derive(Debug, Clone)]
pub enum MacMediaEvent {
PlayPause,
Play,
Pause,
Next,
Previous,
Stop,
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub enum MacMediaCommand {
SetMetadata {
title: String,
artists: Vec<String>,
album: String,
duration_ms: u32,
},
SetPlaybackStatus(bool), SetPosition(u64), SetVolume(u8), SetStopped,
}
pub struct MacMediaManager {
event_rx: std::sync::Mutex<Option<mpsc::UnboundedReceiver<MacMediaEvent>>>,
command_tx: mpsc::UnboundedSender<MacMediaCommand>,
}
impl MacMediaManager {
pub fn new() -> Result<Self> {
let (event_tx, event_rx) = mpsc::unbounded_channel();
let (command_tx, mut command_rx) = mpsc::unbounded_channel::<MacMediaCommand>();
let event_tx = Arc::new(event_tx);
thread::spawn(move || {
let command_center = unsafe { MPRemoteCommandCenter::sharedCommandCenter() };
let tx = Arc::clone(&event_tx);
let play_handler: RcBlock<
dyn Fn(NonNull<MPRemoteCommandEvent>) -> MPRemoteCommandHandlerStatus,
> = RcBlock::new(move |_event: NonNull<MPRemoteCommandEvent>| {
let _ = tx.send(MacMediaEvent::Play);
MPRemoteCommandHandlerStatus::Success
});
unsafe {
command_center
.playCommand()
.addTargetWithHandler(&play_handler);
}
let tx = Arc::clone(&event_tx);
let pause_handler: RcBlock<
dyn Fn(NonNull<MPRemoteCommandEvent>) -> MPRemoteCommandHandlerStatus,
> = RcBlock::new(move |_event: NonNull<MPRemoteCommandEvent>| {
let _ = tx.send(MacMediaEvent::Pause);
MPRemoteCommandHandlerStatus::Success
});
unsafe {
command_center
.pauseCommand()
.addTargetWithHandler(&pause_handler);
}
let tx = Arc::clone(&event_tx);
let toggle_handler: RcBlock<
dyn Fn(NonNull<MPRemoteCommandEvent>) -> MPRemoteCommandHandlerStatus,
> = RcBlock::new(move |_event: NonNull<MPRemoteCommandEvent>| {
let _ = tx.send(MacMediaEvent::PlayPause);
MPRemoteCommandHandlerStatus::Success
});
unsafe {
command_center
.togglePlayPauseCommand()
.addTargetWithHandler(&toggle_handler);
}
let tx = Arc::clone(&event_tx);
let next_handler: RcBlock<
dyn Fn(NonNull<MPRemoteCommandEvent>) -> MPRemoteCommandHandlerStatus,
> = RcBlock::new(move |_event: NonNull<MPRemoteCommandEvent>| {
let _ = tx.send(MacMediaEvent::Next);
MPRemoteCommandHandlerStatus::Success
});
unsafe {
command_center
.nextTrackCommand()
.addTargetWithHandler(&next_handler);
}
let tx = Arc::clone(&event_tx);
let prev_handler: RcBlock<
dyn Fn(NonNull<MPRemoteCommandEvent>) -> MPRemoteCommandHandlerStatus,
> = RcBlock::new(move |_event: NonNull<MPRemoteCommandEvent>| {
let _ = tx.send(MacMediaEvent::Previous);
MPRemoteCommandHandlerStatus::Success
});
unsafe {
command_center
.previousTrackCommand()
.addTargetWithHandler(&prev_handler);
}
let tx = Arc::clone(&event_tx);
let stop_handler: RcBlock<
dyn Fn(NonNull<MPRemoteCommandEvent>) -> MPRemoteCommandHandlerStatus,
> = RcBlock::new(move |_event: NonNull<MPRemoteCommandEvent>| {
let _ = tx.send(MacMediaEvent::Stop);
MPRemoteCommandHandlerStatus::Success
});
unsafe {
command_center
.stopCommand()
.addTargetWithHandler(&stop_handler);
}
let info_center = unsafe { MPNowPlayingInfoCenter::defaultCenter() };
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("Failed to create macOS media runtime");
rt.block_on(async move {
while let Some(cmd) = command_rx.recv().await {
match cmd {
MacMediaCommand::SetMetadata {
title: _,
artists: _,
album: _,
duration_ms: _,
} => {
}
MacMediaCommand::SetPlaybackStatus(is_playing) => unsafe {
let state = if is_playing {
MPNowPlayingPlaybackState::Playing
} else {
MPNowPlayingPlaybackState::Paused
};
info_center.setPlaybackState(state);
},
MacMediaCommand::SetPosition(_position_ms) => {
}
MacMediaCommand::SetVolume(_volume_percent) => {
}
MacMediaCommand::SetStopped => unsafe {
info_center.setPlaybackState(MPNowPlayingPlaybackState::Stopped);
},
}
}
});
});
Ok(Self {
event_rx: std::sync::Mutex::new(Some(event_rx)),
command_tx,
})
}
pub fn take_event_rx(&self) -> Option<mpsc::UnboundedReceiver<MacMediaEvent>> {
self.event_rx.lock().ok()?.take()
}
pub fn set_metadata(&self, title: &str, artists: &[String], album: &str, duration_ms: u32) {
let _ = self.command_tx.send(MacMediaCommand::SetMetadata {
title: title.to_string(),
artists: artists.to_vec(),
album: album.to_string(),
duration_ms,
});
}
pub fn set_playback_status(&self, is_playing: bool) {
let _ = self
.command_tx
.send(MacMediaCommand::SetPlaybackStatus(is_playing));
}
#[allow(dead_code)]
pub fn set_position(&self, position_ms: u64) {
let _ = self
.command_tx
.send(MacMediaCommand::SetPosition(position_ms));
}
#[allow(dead_code)]
pub fn set_volume(&self, volume_percent: u8) {
let _ = self
.command_tx
.send(MacMediaCommand::SetVolume(volume_percent));
}
pub fn set_stopped(&self) {
let _ = self.command_tx.send(MacMediaCommand::SetStopped);
}
}