use crate::linux::core::types::DefaultDeviceType;
use crate::linux::core::{
DEFAULT_AUDIO_CHANNELS, DEFAULT_PIPEWIRE_DO_TIMESTAMP, DEFAULT_PIPEWIRE_TIMEOUT, DaemonCore,
};
use crate::linux::pipewire::{PipewireManager, PipewireState};
use gstreamer::prelude::{ElementExt, ElementExtManual, GstBinExt, ObjectExt, PadExt};
use std::time::Duration;
use wayclip_core::models::error::WayclipError;
use wayclip_core::settings::UserSettings;
use wayclip_core::settings::recording::{AudioNode, AudioSettings};
pub fn init_default_audio_device(
device_type: DefaultDeviceType,
pipewire_state: &PipewireState,
) -> Result<Option<String>, WayclipError> {
if device_type == DefaultDeviceType::Background
&& let Some(sink_node) = &pipewire_state.default_sink_node_name
{
UserSettings::load()?.set_str("recording.audio.background.node_name", sink_node)?;
return Ok(Some(sink_node.clone()));
}
if device_type == DefaultDeviceType::Microphone
&& let Some(source_node) = &pipewire_state.default_source_node_name
{
UserSettings::load()?.set_str("recording.audio.microphone.node_name", source_node)?;
return Ok(Some(source_node.clone()));
}
Ok(None)
}
impl DaemonCore {
pub async fn check_audio_devices(
pipewire_manager: &PipewireManager,
audio_settings: &mut AudioSettings,
) -> Result<(), WayclipError> {
let mut reciever = pipewire_manager.subscribe();
let (microphone_node_name, background_node_name) = (
audio_settings.microphone.node_name.clone(),
audio_settings.background.node_name.clone(),
);
let state =
match tokio::time::timeout(Duration::from_secs(DEFAULT_PIPEWIRE_TIMEOUT), async {
loop {
let state = reciever.borrow().clone();
let mic_found = microphone_node_name.is_empty()
|| state
.devices
.iter()
.any(|d| d.node_name == microphone_node_name);
let bg_found = background_node_name.is_empty()
|| state
.devices
.iter()
.any(|d| d.node_name == background_node_name);
if mic_found && bg_found {
return state;
}
if reciever.changed().await.is_err() {
return state;
}
}
})
.await
{
Ok(state) => state,
Err(_) => pipewire_manager.current_state(),
};
log::debug!("Pipewire manager state: {:?}", state);
if microphone_node_name.is_empty() {
init_default_audio_device(DefaultDeviceType::Microphone, &state)?;
}
if background_node_name.is_empty() {
init_default_audio_device(DefaultDeviceType::Background, &state)?;
}
if !pipewire_manager.is_node_name_valid(microphone_node_name.as_str())
&& audio_settings.microphone.enabled
{
return Err(WayclipError::Audio(
format!("Audio device {} doesnt exist", microphone_node_name).into(),
));
};
if !pipewire_manager.is_node_name_valid(background_node_name.as_str())
&& audio_settings.background.enabled
{
return Err(WayclipError::Audio(
format!("Audio device {} doesnt exist", background_node_name).into(),
));
};
Ok(())
}
pub fn build_audio_source_pipeline(
&self,
pipeline: &gstreamer::Pipeline,
audio_node: &AudioNode,
audio_node_type: DefaultDeviceType,
sample_rate_hz: u64,
mix: &gstreamer::Element,
) -> Result<(), WayclipError> {
let node_id = &self
.pipewire_manager
.get_node_id_from_node_name(audio_node.node_name.as_str());
if audio_node.enabled && node_id.is_none() {
log::error!(
"audio device '{}' ({:?}) is enabled but has no PipeWire node id. skipping this audio source",
audio_node.node_name,
audio_node_type
);
}
if audio_node.enabled
&& let Some(id) = node_id
{
let safe_name = audio_node
.node_name
.replace(|c: char| !c.is_alphanumeric(), "_");
let pipewiresrc = gstreamer::ElementFactory::make("pipewiresrc")
.property("do-timestamp", DEFAULT_PIPEWIRE_DO_TIMESTAMP)
.property("target-object", id.to_string().as_str())
.name(format!("audio_src_{}", safe_name))
.build()?;
let mut properties_builder =
gstreamer::Structure::builder("properties").field("node.always-process", true);
if audio_node_type == DefaultDeviceType::Background {
properties_builder = properties_builder.field("stream.capture.sink", true);
}
let properties = properties_builder.build();
pipewiresrc.set_property("stream-properties", &properties);
let queue = gstreamer::ElementFactory::make("queue")
.name(format!("audio_queue_{}", safe_name))
.build()?;
let caps_filter = gstreamer::ElementFactory::make("capsfilter")
.name(format!("audio_caps_{}", safe_name))
.build()?;
let caps = gstreamer::Caps::builder("audio/x-raw")
.field("rate", sample_rate_hz as i32)
.field("channels", DEFAULT_AUDIO_CHANNELS)
.build();
caps_filter.set_property("caps", &caps);
let audioconvert = gstreamer::ElementFactory::make("audioconvert")
.name(format!("audio_convert_{}", safe_name))
.build()?;
let audioresample = gstreamer::ElementFactory::make("audioresample")
.name(format!("audio_resample_{}", safe_name))
.build()?;
let mix_sink_pad = mix
.request_pad_simple("sink_%u")
.ok_or_else(|| WayclipError::Audio("Couldnt request mix sink pad".into()))?;
mix_sink_pad.set_property("volume", audio_node.level.0);
let audio_source_pipeline = [
pipewiresrc,
queue,
caps_filter,
audioconvert,
audioresample.clone(),
];
for element in audio_source_pipeline.iter() {
pipeline.add(element)?
}
gstreamer::Element::link_many(
audio_source_pipeline
.iter()
.collect::<Vec<&gstreamer::Element>>()
.as_slice(),
)?;
let resample_src_pad = audioresample
.static_pad("src")
.ok_or_else(|| WayclipError::Audio("Couldnt request src pad".into()))?;
resample_src_pad.link(&mix_sink_pad)?;
}
Ok(())
}
}