#![allow(unsafe_code)]
use crate::{errors::{CoreAudioError, OSStatusCheck}, object::{AudioObject, Process, Tap}, property::TAP_UID};
use coreaudio_sys::AudioObjectID;
use objc2::AllocAnyThread;
use objc2_core_audio::{AudioHardwareCreateProcessTap, AudioHardwareDestroyProcessTap, CATapDescription, CATapMuteBehavior};
use objc2_foundation::{NSArray, NSNumber, NSString};
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub enum TapMute {
#[default]
Unmuted,
Muted,
MutedWhenTapped,
}
impl From<TapMute> for CATapMuteBehavior {
fn from(value: TapMute) -> Self {
match value {
TapMute::Unmuted => CATapMuteBehavior::Unmuted,
TapMute::Muted => CATapMuteBehavior::Muted,
TapMute::MutedWhenTapped => CATapMuteBehavior::MutedWhenTapped,
}
}
}
pub struct ProcessTap {
tap: AudioObject<Tap>,
uid: String,
}
impl ProcessTap {
pub fn stereo_mixdown(
processes: &[AudioObject<Process>],
name: &str,
mute: TapMute,
) -> Result<Self, CoreAudioError> {
let ids = process_ids(processes);
let description = unsafe {
CATapDescription::initStereoMixdownOfProcesses(CATapDescription::alloc(), &ids)
};
Self::create(&description, name, mute)
}
pub fn device_stream(
processes: &[AudioObject<Process>],
device_uid: &str,
stream: usize,
name: &str,
mute: TapMute,
) -> Result<Self, CoreAudioError> {
let ids = process_ids(processes);
let description = unsafe {
CATapDescription::initWithProcesses_andDeviceUID_withStream(
CATapDescription::alloc(),
&ids,
&NSString::from_str(device_uid),
stream as _,
)
};
Self::create(&description, name, mute)
}
fn create(description: &CATapDescription, name: &str, mute: TapMute) -> Result<Self, CoreAudioError> {
let mut id: AudioObjectID = 0;
unsafe {
description.setName(&NSString::from_str(name));
description.setPrivate(true);
description.setMuteBehavior(mute.into());
AudioHardwareCreateProcessTap(Some(description), &mut id).check()?;
}
let tap = AudioObject::<Tap>::from(id);
let uid = match tap.get_property(TAP_UID) {
Ok(uid) => uid,
Err(error) => {
unsafe {
AudioHardwareDestroyProcessTap(id);
}
return Err(error);
}
};
Ok(Self { tap, uid })
}
pub fn tap(&self) -> AudioObject<Tap> {
self.tap
}
pub fn uid(&self) -> &str {
&self.uid
}
}
fn process_ids(processes: &[AudioObject<Process>]) -> objc2::rc::Retained<NSArray<NSNumber>> {
let ids: Vec<_> = processes.iter().map(|process| NSNumber::new_u32(process.id())).collect();
NSArray::from_retained_slice(&ids)
}
impl Drop for ProcessTap {
fn drop(&mut self) {
unsafe {
AudioHardwareDestroyProcessTap(self.tap.id());
}
}
}