use block2::RcBlock;
use core::ffi::c_int;
use dispatch2::ffi::{dispatch_queue_create, DISPATCH_QUEUE_SERIAL};
use objc2::{runtime::AnyObject, Encoding};
use objc2_core_foundation::{CFData, CFDate, CFDictionary};
use objc2_foundation::{NSNumber, NSString};
use std::{
collections::HashMap,
ffi::c_void,
ptr::{self, NonNull},
rc::Rc,
sync::{Arc, Condvar, Mutex},
time::Duration,
};
use crate::{low_level::c_functions::*, Command, Id, InfoTypes, Number};
const TIMEOUT_DURATION: Duration = Duration::from_secs(5);
macro_rules! safely_dispatch_and_wait {
($closure:expr, $type:ty, $func:ident) => {{
let result = Arc::new((Mutex::new(None), Condvar::new()));
let result_clone = Arc::clone(&result);
let block = RcBlock::new(move |arg: $type| {
let (lock, cvar) = &*result_clone;
let mut result_guard = lock.lock().unwrap();
*result_guard = $closure(arg);
cvar.notify_one();
});
unsafe {
let queue = dispatch_queue_create(ptr::null(), DISPATCH_QUEUE_SERIAL);
if queue.is_null() {
return None;
}
$func(queue, &block);
}
let (lock, cvar) = &*result;
let result_guard = match lock.lock() {
Ok(guard) => guard,
Err(_) => return None,
};
let (result_guard, timeout_result) = match cvar.wait_timeout(result_guard, TIMEOUT_DURATION)
{
Ok(res) => res,
Err(_) => return None,
};
if timeout_result.timed_out() {
None
} else {
result_guard.clone()
}
}};
}
pub fn get_now_playing_application_is_playing() -> Option<bool> {
safely_dispatch_and_wait!(
|is_playing: c_int| Some(is_playing != 0),
c_int,
MRMediaRemoteGetNowPlayingApplicationIsPlaying
)
}
pub fn get_now_playing_client() -> Option<Id> {
safely_dispatch_and_wait!(
|id: Id| {
if !id.is_null() {
Some(id)
} else {
None
}
},
Id,
MRMediaRemoteGetNowPlayingClient
)
}
pub fn get_now_playing_application_pid() -> Option<i32> {
safely_dispatch_and_wait!(
|pid: c_int| {
if pid != 0 {
Some(pid)
} else {
None
}
},
c_int,
MRMediaRemoteGetNowPlayingApplicationPID
)
}
pub fn get_now_playing_info() -> Option<HashMap<String, InfoTypes>> {
#![allow(useless_ptr_null_checks)]
let info = safely_dispatch_and_wait!(
|dict: NonNull<CFDictionary>| {
if dict.as_ptr().is_null() {
return None;
}
unsafe {
let count = dict.as_ref().count();
let mut keys: Vec<*const c_void> = vec![ptr::null(); count.try_into().unwrap()];
let mut values: Vec<*const c_void> = vec![ptr::null(); count.try_into().unwrap()];
dict.as_ref()
.keys_and_values(keys.as_mut_ptr(), values.as_mut_ptr());
let mut info = HashMap::<String, InfoTypes>::new();
for i in 0..count.try_into().unwrap() {
let key_ptr = keys[i];
let val_ptr = values[i];
let key_ref = &*(key_ptr as *const NSString);
let val_ref = &*(val_ptr as *const AnyObject);
let class_name = val_ref.class().name().to_str().unwrap_or_default();
let value = match class_name {
"__NSCFNumber" => {
let num_ref = &*(val_ptr as *const NSNumber);
let number = match num_ref.encoding() {
Encoding::Char
| Encoding::Short
| Encoding::Int
| Encoding::Long
| Encoding::LongLong => Number::Signed(num_ref.as_i64()),
Encoding::UChar
| Encoding::UShort
| Encoding::UInt
| Encoding::ULong
| Encoding::ULongLong => Number::Unsigned(num_ref.as_u64()),
Encoding::Float | Encoding::Double => {
Number::Floating(num_ref.as_f64())
}
_ => unreachable!(),
};
InfoTypes::Number(number)
}
"__NSCFString" | "__NSCFConstantString" | "NSTaggedPointerString" => {
let str_ref = &*(val_ptr as *const NSString);
InfoTypes::String(str_ref.to_string())
}
"__NSTaggedDate" => {
let date_ref = &*(val_ptr as *const CFDate);
InfoTypes::SystemTime(date_ref.to_system_time().unwrap())
}
"NSSubrangeData" | "_NSInlineData" => {
let data_ref = &*(val_ptr as *const CFData);
InfoTypes::Data(data_ref.to_vec())
}
_ => InfoTypes::Unsupported,
};
info.insert(key_ref.to_string(), value);
}
Some(Rc::new(info))
}
},
NonNull<CFDictionary>,
MRMediaRemoteGetNowPlayingInfo
);
info.and_then(|info| Rc::try_unwrap(info).ok())
}
macro_rules! get_bundle_identifier {
($getter:ident) => {
safely_dispatch_and_wait!(
|id: Id| {
if !id.is_null() {
unsafe {
let property = $getter(id);
if !property.is_null() {
return Some((*property).to_string());
}
}
}
None
},
Id,
MRMediaRemoteGetNowPlayingClient
)
};
}
pub fn get_now_playing_client_parent_app_bundle_identifier() -> Option<String> {
get_bundle_identifier!(MRNowPlayingClientGetParentAppBundleIdentifier)
}
pub fn get_now_playing_client_bundle_identifier() -> Option<String> {
get_bundle_identifier!(MRNowPlayingClientGetBundleIdentifier)
}
pub fn send_command(command: Command) -> bool {
unsafe { MRMediaRemoteSendCommand(command.into(), ptr::null()) }
}
pub fn set_playback_speed(speed: i32) {
unsafe { MRMediaRemoteSetPlaybackSpeed(speed) }
}
pub fn set_elapsed_time(elapsed_time: f64) {
unsafe { MRMediaRemoteSetElapsedTime(elapsed_time) }
}
pub fn register_for_now_playing_notifications() {
unsafe {
let queue = dispatch_queue_create(ptr::null(), DISPATCH_QUEUE_SERIAL);
MRMediaRemoteRegisterForNowPlayingNotifications(queue);
}
}
pub fn unregister_for_now_playing_notifications() {
unsafe {
MRMediaRemoteUnregisterForNowPlayingNotifications();
}
}