synapto-plugin-linux-host-audio 0.1.0-dev.1

Linux host audio capture and playback plugin for the Synapto framework
use synapto_interface::peer_input_audio::AudioInputPlugin;
use synapto_interface::cognitive_output_audio::AudioOutputPlugin;
use synapto_interface::plugin::Plugin;
use async_trait::async_trait;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::sync::{Arc, Mutex};

mod audio_utils;
mod capture;
mod speaker;

pub use speaker::play;

#[derive(Serialize, Deserialize, Clone, Debug, Default, JsonSchema)]
pub struct HostAudioConfig {
    pub audio_input_target: Option<String>,
    pub audio_output_target: Option<String>,
}

#[derive(Debug)]
pub struct Terminate;

pub struct HostAudioInputPlugin {
    config: HostAudioConfig,
    capture_quit_tx: Arc<Mutex<Option<pipewire::channel::Sender<Terminate>>>>,
}

#[async_trait::async_trait]
impl Plugin for HostAudioInputPlugin {
    fn register<R: synapto_interface::plugin::PluginRegistry + ?Sized>(
        self: std::sync::Arc<Self>,
        registry: &mut R,
    ) where
        Self: Sized,
    {
        registry.register_audio_input(self);
    }

    async fn create(context: &synapto_interface::plugin::PluginInitContext<'_>) -> Result<Self, String> {
        let config: HostAudioConfig = context.config()?;
        Ok(Self {
            config,
            capture_quit_tx: Arc::new(Mutex::new(None)),
        })
    }
}

#[async_trait]
impl AudioInputPlugin for HostAudioInputPlugin {
    async fn start(
        &self,
        tx: synapto_interface::sync::mpsc::Sender<synapto_interface::peer_input_audio::PeerInputAudio>,
    ) -> Result<(), String> {
        let config = self.config.clone();
        let capture_quit_tx = self.capture_quit_tx.clone();
        std::thread::spawn(move || {
            if let Err(e) = capture::run_capture_task(config, tx, capture_quit_tx) {
                tracing::error!("HostAudio Capture task error: {}", e);
            }
        });
        Ok(())
    }
}

impl Drop for HostAudioInputPlugin {
    fn drop(&mut self) {
        tracing::debug!("Dropping HostAudioInputPlugin, signalling capture thread to quit");
        if let Some(tx) = self
            .capture_quit_tx
            .lock()
            .unwrap_or_else(|e| panic!("Failed to lock: {:?}", e))
            .take()
            && let Err(e) = tx.send(Terminate)
        {
            tracing::error!("Channel send failed: {:?}", e);
        }
    }
}

pub struct HostAudioOutputPlugin {
    config: HostAudioConfig,
    playback_quit_tx: Arc<Mutex<Option<pipewire::channel::Sender<Terminate>>>>,
}

#[async_trait]
impl Plugin for HostAudioOutputPlugin {
    fn register<R: synapto_interface::plugin::PluginRegistry + ?Sized>(
        self: std::sync::Arc<Self>,
        registry: &mut R,
    ) where
        Self: Sized,
    {
        registry.register_audio_output(self);
    }

    async fn create(context: &synapto_interface::plugin::PluginInitContext<'_>) -> Result<Self, String> {
        let config: HostAudioConfig = context.config()?;
        Ok(Self {
            config,
            playback_quit_tx: Arc::new(Mutex::new(None)),
        })
    }
}

#[async_trait]
impl AudioOutputPlugin for HostAudioOutputPlugin {
    async fn start(
        &self,
        rx: synapto_interface::sync::mpsc::Receiver<synapto_interface::cognitive_output_audio::CognitiveOutputAudio>,
    ) -> Result<(), String> {
        let config = self.config.clone();
        let playback_quit_tx = self.playback_quit_tx.clone();
        std::thread::spawn(move || {
            if let Err(e) = speaker::run_playback_task(config, rx, playback_quit_tx) {
                tracing::error!("HostAudio Playback task error: {}", e);
            }
        });
        Ok(())
    }
}

impl Drop for HostAudioOutputPlugin {
    fn drop(&mut self) {
        tracing::debug!("Dropping HostAudioOutputPlugin, signalling playback thread to quit");
        if let Some(tx) = self
            .playback_quit_tx
            .lock()
            .unwrap_or_else(|e| panic!("Failed to lock: {:?}", e))
            .take()
            && let Err(e) = tx.send(Terminate)
        {
            tracing::error!("Channel send failed: {:?}", e);
        }
    }
}