use crate::AudioError;
#[cfg(target_os = "macos")]
pub(super) fn ensure_microphone_access() -> Result<(), AudioError> {
use objc2_av_foundation::{AVAuthorizationStatus, AVCaptureDevice, AVMediaTypeAudio};
let media =
unsafe { AVMediaTypeAudio }.ok_or_else(|| AudioError::Unsupported("AVMediaTypeAudio unavailable".into()))?;
let status = unsafe { AVCaptureDevice::authorizationStatusForMediaType(media) };
if status == AVAuthorizationStatus::Authorized {
return Ok(());
}
if status == AVAuthorizationStatus::Denied {
return Err(denied());
}
if status == AVAuthorizationStatus::Restricted {
return Err(AudioError::Unsupported(
"microphone access is restricted by system policy (parental controls / MDM)".into(),
));
}
if status == AVAuthorizationStatus::NotDetermined {
return request_access(media);
}
Ok(())
}
#[cfg(target_os = "macos")]
const PROMPT_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
#[cfg(target_os = "macos")]
fn request_access(media: &objc2_av_foundation::AVMediaType) -> Result<(), AudioError> {
use objc2_av_foundation::AVCaptureDevice;
let (tx, rx) = std::sync::mpsc::channel::<bool>();
let handler = block2::RcBlock::new(move |granted: objc2::runtime::Bool| {
let _ = tx.send(granted.as_bool());
});
unsafe { AVCaptureDevice::requestAccessForMediaType_completionHandler(media, &handler) };
match rx.recv_timeout(PROMPT_TIMEOUT) {
Ok(true) => Ok(()),
Ok(false) => Err(denied()),
Err(_) => Ok(()),
}
}
#[cfg(target_os = "macos")]
fn denied() -> AudioError {
AudioError::Unsupported(
"microphone access denied; grant it in System Settings > Privacy & Security > Microphone".into(),
)
}
#[cfg(not(target_os = "macos"))]
pub(super) fn ensure_microphone_access() -> Result<(), AudioError> {
Ok(())
}