use std::cell::OnceCell;
use std::sync::mpsc;
use std::thread;
use anyhow::{Context, anyhow};
use log::{error, info};
use uuid::Uuid;
use crate::common::audiofox::Audiofox;
use crate::common::callback::{AudioCallback, AudioCallbackFn};
use crate::common::result::Result;
use crate::common::source::{AudioSource, AudioSources};
use crate::platform::linux::command::Response;
use crate::platform::linux::pipewire::pw_thread;
#[derive(Debug)]
pub enum Command {
Stop,
List,
Init(Uuid, AudioCallback),
Link(Uuid, AudioSources),
End(Uuid),
}
pub struct PlatformManager {
sender: pw::channel::Sender<Command>,
receiver: mpsc::Receiver<Response>,
handle: OnceCell<thread::JoinHandle<()>>,
}
pub struct PlatformHandle {
sender: pw::channel::Sender<Command>,
uuid: Uuid,
}
impl Audiofox for PlatformManager {
type LinkHandle = PlatformHandle;
fn start() -> Result<Self> {
pw::init();
let (pw_sender, receiver) = mpsc::channel::<Response>();
let (sender, pw_receiver) = pw::channel::channel();
let handle = thread::spawn(move || pw_thread(&pw_sender, pw_receiver)).into();
receive!(receiver, Response::Ready => {
info!("pipewire thread successfully started");
Self { sender, receiver, handle } }
).context("Failed to initialize PipeWire thread")
}
fn list(&self) -> Result<AudioSources> {
roundtrip!(self.sender, self.receiver,
Command::List,
Response::List(sources) => sources
).context("List command failed")
}
fn link<F>(&self, uuid: Uuid, sources: &[AudioSource], callback: F) -> Result<Self::LinkHandle>
where
F: AudioCallbackFn,
{
info!("initializing stream node...");
roundtrip!(self.sender, self.receiver,
Command::Init(uuid, callback.into()),
Response::Init => ()
).context("Failed to initialize stream")?;
info!("stream node initialized, linking targets...");
roundtrip!(self.sender, self.receiver,
Command::Link(uuid, sources.into()),
Response::Link => PlatformHandle { sender: self.sender.clone(), uuid }
).context("Failed to create LinkHandle")
}
}
impl Drop for PlatformManager {
fn drop(&mut self) {
if let Err(e) = || -> Result<()> {
send!(self.sender, Command::Stop).context("PipeWire thread already stopped")?;
self.handle
.take().context("PipeWire thread handle already taken")?
.join().map_err(|_| anyhow!("Failed to join Pipewire thread"))
}() {
error!("cleanup failed: {e}");
}
}
}
impl Drop for PlatformHandle {
fn drop(&mut self) {
send_logged!(self.sender, Command::End(self.uuid));
}
}