use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use super::backend::{AudioBackend, AudioEngineHandle, BackendError, DeviceInfo, SampleRateWatch};
use super::engine;
pub struct IosAudioBackend;
static ROUTE: parking_lot::RwLock<Option<String>> = parking_lot::RwLock::new(None);
pub fn set_route(name: String) {
let changed = ROUTE.write().replace(name.clone()).as_ref() != Some(&name);
if changed {
crate::remote::outputs::refresh_devices();
}
}
pub trait AudioSession: Send + Sync {
fn activate(&self, sample_rate: f64) -> Option<f64>;
fn follow(&self, sample_rate: f64) -> f64;
fn release(&self);
}
static SESSION: parking_lot::RwLock<Option<Arc<dyn AudioSession>>> = parking_lot::RwLock::new(None);
static HELD: AtomicBool = AtomicBool::new(false);
pub fn set_session(session: Arc<dyn AudioSession>) {
*SESSION.write() = Some(session);
}
#[cfg(test)]
pub(crate) static TEST_SESSION: parking_lot::Mutex<()> = parking_lot::Mutex::new(());
static GRANTED: AtomicU64 = AtomicU64::new(0);
static REQUESTED: AtomicU64 = AtomicU64::new(0);
type RateWatch = Arc<dyn Fn(f64) + Send + Sync>;
static WATCH: parking_lot::Mutex<Option<RateWatch>> = parking_lot::Mutex::new(None);
fn activate(session: &dyn AudioSession, sample_rate: f64) -> Option<f64> {
REQUESTED.store(sample_rate.to_bits(), Ordering::Release);
let granted = session.activate(sample_rate)?;
HELD.store(true, Ordering::Release);
GRANTED.store(granted.to_bits(), Ordering::Release);
Some(granted)
}
pub(crate) fn activate_session(sample_rate: f64) -> bool {
let session = SESSION.read().clone();
let Some(session) = session else { return true };
let Some(granted) = activate(session.as_ref(), sample_rate) else {
return false;
};
tell_watch(granted);
true
}
fn tell_watch(granted: f64) {
let watch = WATCH.lock().clone();
if let Some(watch) = watch {
watch(granted);
}
}
pub(crate) fn follow_route() {
if !session_held() {
return;
}
let session = SESSION.read().clone();
let Some(session) = session else { return };
let granted = session.follow(f64::from_bits(REQUESTED.load(Ordering::Acquire)));
GRANTED.store(granted.to_bits(), Ordering::Release);
tell_watch(granted);
}
pub(crate) fn session_held() -> bool {
HELD.load(Ordering::Acquire)
}
pub(crate) fn release_session() {
let session = SESSION.read().clone();
if let Some(session) = session {
HELD.store(false, Ordering::Release);
session.release();
}
}
fn current_route() -> DeviceInfo {
DeviceInfo {
name: ROUTE
.read()
.clone()
.unwrap_or_else(|| "System Output".to_string()),
sample_rates: vec![44100.0, 48000.0],
platform_id: 0,
kind: Default::default(),
}
}
impl AudioBackend for IosAudioBackend {
fn list_devices(&self) -> Result<Vec<DeviceInfo>, BackendError> {
Ok(vec![current_route()])
}
fn default_device(&self) -> Result<DeviceInfo, BackendError> {
Ok(current_route())
}
fn supported_sample_rates(&self, _device: &DeviceInfo) -> Result<Vec<f64>, BackendError> {
Ok(current_route().sample_rates)
}
fn get_device_sample_rate(&self, _device: &DeviceInfo) -> Result<f64, BackendError> {
if !session_held() {
return Ok(0.0);
}
Ok(f64::from_bits(GRANTED.load(Ordering::Acquire)))
}
fn set_device_sample_rate(&self, _device: &DeviceInfo, rate: f64) -> Result<f64, BackendError> {
REQUESTED.store(rate.to_bits(), Ordering::Release);
let session = SESSION.read().clone();
match session {
Some(session) if session_held() => {
let granted = session.follow(rate);
GRANTED.store(granted.to_bits(), Ordering::Release);
Ok(granted)
}
_ => Ok(rate),
}
}
fn watch_device_sample_rate(
&self,
_device: &DeviceInfo,
on_change: Box<dyn Fn(f64) + Send + Sync>,
) -> Option<Box<dyn SampleRateWatch>> {
let watch: RateWatch = Arc::from(on_change);
*WATCH.lock() = Some(watch.clone());
Some(Box::new(Watching(watch)))
}
fn create_engine(
&self,
_device: &DeviceInfo,
sample_rate: f64,
channels: u32,
consumer: rtrb::Consumer<f32>,
samples_played: Arc<AtomicU64>,
) -> Result<Box<dyn AudioEngineHandle>, BackendError> {
let engine = engine::AudioEngine::new(0, sample_rate, channels, consumer, samples_played)
.map_err(|e| BackendError::StreamCreation(e.to_string()))?;
Ok(Box::new(engine))
}
}
struct Watching(RateWatch);
impl SampleRateWatch for Watching {}
impl Drop for Watching {
fn drop(&mut self) {
let mut slot = WATCH.lock();
if slot.as_ref().is_some_and(|w| Arc::ptr_eq(w, &self.0)) {
*slot = None;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
struct TwoRateDac {
activations: AtomicU64,
refuse: AtomicBool,
unplugged: AtomicBool,
}
impl TwoRateDac {
fn rate_for(&self, sample_rate: f64) -> f64 {
if sample_rate == 44100.0 && !self.unplugged.load(Ordering::Relaxed) {
44100.0
} else {
48000.0
}
}
}
impl AudioSession for TwoRateDac {
fn activate(&self, sample_rate: f64) -> Option<f64> {
if self.refuse.load(Ordering::Relaxed) {
return None;
}
self.activations.fetch_add(1, Ordering::Relaxed);
Some(self.rate_for(sample_rate))
}
fn follow(&self, sample_rate: f64) -> f64 {
self.rate_for(sample_rate)
}
fn release(&self) {}
}
#[test]
fn the_rate_reported_is_the_one_the_session_grants() {
let _session = TEST_SESSION.lock();
let dac = Arc::new(TwoRateDac {
activations: AtomicU64::new(0),
refuse: AtomicBool::new(false),
unplugged: AtomicBool::new(false),
});
set_session(dac.clone());
let backend = IosAudioBackend;
let device = backend.default_device().unwrap();
let heard = Arc::new(AtomicU64::new(0));
let watch = {
let heard = heard.clone();
backend.watch_device_sample_rate(
&device,
Box::new(move |rate| heard.store(rate.to_bits(), Ordering::Relaxed)),
)
};
assert_eq!(
backend.set_device_sample_rate(&device, 96000.0).unwrap(),
96000.0
);
assert_eq!(dac.activations.load(Ordering::Relaxed), 0);
dac.refuse.store(true, Ordering::Relaxed);
assert!(!activate_session(96000.0));
assert!(!session_held());
assert_eq!(heard.load(Ordering::Relaxed), 0);
dac.refuse.store(false, Ordering::Relaxed);
assert!(activate_session(96000.0));
assert_eq!(f64::from_bits(heard.load(Ordering::Relaxed)), 48000.0);
assert_eq!(backend.get_device_sample_rate(&device).unwrap(), 48000.0);
assert_eq!(
backend.set_device_sample_rate(&device, 44100.0).unwrap(),
44100.0
);
assert_eq!(backend.get_device_sample_rate(&device).unwrap(), 44100.0);
let activations = dac.activations.load(Ordering::Relaxed);
dac.refuse.store(true, Ordering::Relaxed);
assert_eq!(
backend.set_device_sample_rate(&device, 96000.0).unwrap(),
48000.0
);
assert_eq!(dac.activations.load(Ordering::Relaxed), activations);
dac.refuse.store(false, Ordering::Relaxed);
backend.set_device_sample_rate(&device, 44100.0).unwrap();
let activations = dac.activations.load(Ordering::Relaxed);
dac.unplugged.store(true, Ordering::Relaxed);
follow_route();
assert_eq!(f64::from_bits(heard.load(Ordering::Relaxed)), 48000.0);
assert_eq!(backend.get_device_sample_rate(&device).unwrap(), 48000.0);
dac.unplugged.store(false, Ordering::Relaxed);
follow_route();
assert_eq!(f64::from_bits(heard.load(Ordering::Relaxed)), 44100.0);
assert_eq!(dac.activations.load(Ordering::Relaxed), activations);
release_session();
dac.unplugged.store(true, Ordering::Relaxed);
follow_route();
assert_eq!(f64::from_bits(heard.load(Ordering::Relaxed)), 44100.0);
assert_eq!(backend.get_device_sample_rate(&device).unwrap(), 0.0);
let newer = backend.watch_device_sample_rate(&device, Box::new(|_| {}));
drop(watch);
assert!(WATCH.lock().is_some());
drop(newer);
assert!(WATCH.lock().is_none());
release_session();
}
}