use std::ptr::NonNull;
use block2::RcBlock;
use objc2::runtime::AnyObject;
use objc2_avf_audio::{
AVAudioSessionMediaServicesWereLostNotification,
AVAudioSessionMediaServicesWereResetNotification, AVAudioSessionRouteChangeNotification,
AVAudioSessionRouteChangeReason, AVAudioSessionRouteChangeReasonKey,
};
use objc2_foundation::{NSNotification, NSNotificationCenter, NSNumber, NSString};
use crate::{
host::{emit_error, latch::Latch, ErrorCallbackArc},
Error, ErrorKind,
};
unsafe fn route_change_error(notification: &NSNotification) -> Option<Error> {
let user_info = notification.userInfo()?;
let key = AVAudioSessionRouteChangeReasonKey?;
let dict = unsafe { user_info.cast_unchecked::<NSString, AnyObject>() };
let value = dict.objectForKey(key)?;
let number = value.downcast_ref::<NSNumber>()?;
let reason = AVAudioSessionRouteChangeReason(number.unsignedIntegerValue());
match reason {
AVAudioSessionRouteChangeReason::OldDeviceUnavailable => Some(Error::with_message(
ErrorKind::DeviceChanged,
"Audio route changed",
)),
AVAudioSessionRouteChangeReason::CategoryChange
| AVAudioSessionRouteChangeReason::Override
| AVAudioSessionRouteChangeReason::RouteConfigurationChange => Some(Error::with_message(
ErrorKind::StreamInvalidated,
"Audio route changed",
)),
AVAudioSessionRouteChangeReason::NoSuitableRouteForCategory => Some(Error::with_message(
ErrorKind::DeviceNotAvailable,
"No suitable audio route for the session category",
)),
_ => None,
}
}
pub(super) struct SessionEventManager {
latch: Latch,
observers: Vec<
objc2::rc::Retained<objc2::runtime::ProtocolObject<dyn objc2::runtime::NSObjectProtocol>>,
>,
}
unsafe impl Send for SessionEventManager {}
unsafe impl Sync for SessionEventManager {}
impl SessionEventManager {
pub(super) fn new(error_callback: ErrorCallbackArc, latch: Latch) -> Self {
let nc = NSNotificationCenter::defaultCenter();
let mut observers = Vec::new();
let waiter = latch.waiter();
{
let cb = error_callback.clone();
let w = waiter.clone();
let block = RcBlock::new(move |notif: NonNull<NSNotification>| {
if w.is_released() {
if let Some(err) = unsafe { route_change_error(notif.as_ref()) } {
emit_error(&cb, err);
}
}
});
if let Some(name) = unsafe { AVAudioSessionRouteChangeNotification } {
let observer = unsafe {
nc.addObserverForName_object_queue_usingBlock(Some(name), None, None, &block)
};
observers.push(observer);
}
}
{
let cb = error_callback.clone();
let w = waiter.clone();
let block = RcBlock::new(move |_: NonNull<NSNotification>| {
if w.is_released() {
emit_error(
&cb,
Error::with_message(
ErrorKind::DeviceNotAvailable,
"Audio media services were lost",
),
);
}
});
if let Some(name) = unsafe { AVAudioSessionMediaServicesWereLostNotification } {
let observer = unsafe {
nc.addObserverForName_object_queue_usingBlock(Some(name), None, None, &block)
};
observers.push(observer);
}
}
{
let cb = error_callback.clone();
let w = waiter;
let block = RcBlock::new(move |_: NonNull<NSNotification>| {
if w.is_released() {
emit_error(
&cb,
Error::with_message(
ErrorKind::StreamInvalidated,
"Audio media services were reset",
),
);
}
});
if let Some(name) = unsafe { AVAudioSessionMediaServicesWereResetNotification } {
let observer = unsafe {
nc.addObserverForName_object_queue_usingBlock(Some(name), None, None, &block)
};
observers.push(observer);
}
}
Self { latch, observers }
}
pub(super) fn signal_ready(&self) {
self.latch.release();
}
}
impl Drop for SessionEventManager {
fn drop(&mut self) {
let nc = NSNotificationCenter::defaultCenter();
for observer in &self.observers {
unsafe { nc.removeObserver(observer.as_ref()) };
}
}
}