Skip to main content

wayle_audio/
service.rs

1use std::sync::Arc;
2
3use derive_more::Debug;
4use tokio::task::JoinHandle;
5use tokio_util::sync::CancellationToken;
6use tracing::{error, instrument};
7use wayle_core::Property;
8use wayle_traits::Reactive;
9use zbus::Connection;
10
11use super::core::{
12    device::{
13        input::{InputDeviceParams, LiveInputDeviceParams},
14        output::{LiveOutputDeviceParams, OutputDeviceParams},
15    },
16    stream::{AudioStreamParams, LiveAudioStreamParams},
17};
18use crate::{
19    backend::types::{CommandSender, EventSender},
20    builder::AudioServiceBuilder,
21    core::{
22        device::{input::InputDevice, output::OutputDevice},
23        stream::AudioStream,
24    },
25    error::Error,
26    types::{device::DeviceKey, stream::StreamKey},
27};
28
29/// Pipewire Audio management service. See [crate-level docs](crate) for usage patterns.
30#[derive(Debug)]
31pub struct AudioService {
32    #[debug(skip)]
33    pub(crate) command_tx: CommandSender,
34    #[debug(skip)]
35    pub(crate) event_tx: EventSender,
36    #[debug(skip)]
37    pub(crate) cancellation_token: CancellationToken,
38    #[debug(skip)]
39    pub(crate) backend_handle: Option<JoinHandle<Result<(), Error>>>,
40    #[debug(skip)]
41    pub(crate) _connection: Option<Connection>,
42
43    /// All PulseAudio sinks: speakers, headphones, Bluetooth outputs, virtual sinks.
44    pub output_devices: Property<Vec<Arc<OutputDevice>>>,
45
46    /// All PulseAudio sources: microphones, monitor sources, virtual inputs.
47    pub input_devices: Property<Vec<Arc<InputDevice>>>,
48
49    /// Current default sink, or `None` if unset.
50    pub default_output: Property<Option<Arc<OutputDevice>>>,
51
52    /// Current default source, or `None` if unset.
53    pub default_input: Property<Option<Arc<InputDevice>>>,
54
55    /// Applications currently playing audio.
56    pub playback_streams: Property<Vec<Arc<AudioStream>>>,
57
58    /// Applications currently recording audio.
59    pub recording_streams: Property<Vec<Arc<AudioStream>>>,
60}
61
62impl AudioService {
63    /// Creates a new audio service instance with default configuration.
64    ///
65    /// Initializes PulseAudio connection and discovers available devices and streams.
66    ///
67    /// # Errors
68    /// Returns error if PulseAudio connection fails or service initialization fails.
69    #[instrument]
70    pub async fn new() -> Result<Arc<Self>, Error> {
71        Self::builder().build().await
72    }
73
74    /// Creates a builder for configuring an AudioService.
75    pub fn builder() -> AudioServiceBuilder {
76        AudioServiceBuilder::new()
77    }
78
79    /// Returns a snapshot of the output device's current state.
80    ///
81    /// The returned [`OutputDevice`] properties will not update after this call.
82    /// For live updates, see [`output_device_monitored`](Self::output_device_monitored).
83    ///
84    /// # Errors
85    ///
86    /// Returns [`Error::DeviceNotFound`] if no sink exists with this key.
87    #[instrument(skip(self), fields(device_key = ?key), err)]
88    pub async fn output_device(&self, key: DeviceKey) -> Result<OutputDevice, Error> {
89        OutputDevice::get(OutputDeviceParams {
90            command_tx: &self.command_tx,
91            device_key: key,
92        })
93        .await
94    }
95
96    /// Returns a live-updating output device instance.
97    ///
98    /// The returned [`OutputDevice`] properties update automatically when
99    /// PulseAudio state changes. Monitoring stops when the `Arc` is dropped.
100    ///
101    /// # Errors
102    ///
103    /// Returns [`Error::DeviceNotFound`] if no sink exists with this key.
104    #[instrument(skip(self), fields(device_key = ?key), err)]
105    pub async fn output_device_monitored(
106        &self,
107        key: DeviceKey,
108    ) -> Result<Arc<OutputDevice>, Error> {
109        OutputDevice::get_live(LiveOutputDeviceParams {
110            command_tx: &self.command_tx,
111            event_tx: &self.event_tx,
112            device_key: key,
113            cancellation_token: &self.cancellation_token,
114        })
115        .await
116    }
117
118    /// Returns a snapshot of the input device's current state.
119    ///
120    /// # Errors
121    ///
122    /// Returns [`Error::DeviceNotFound`] if no source exists with this key.
123    #[instrument(skip(self), fields(device_key = ?key), err)]
124    pub async fn input_device(&self, key: DeviceKey) -> Result<InputDevice, Error> {
125        InputDevice::get(InputDeviceParams {
126            command_tx: &self.command_tx,
127            device_key: key,
128        })
129        .await
130    }
131
132    /// Returns a live-updating input device instance.
133    ///
134    /// # Errors
135    ///
136    /// Returns [`Error::DeviceNotFound`] if no source exists with this key.
137    #[instrument(skip(self), fields(device_key = ?key), err)]
138    pub async fn input_device_monitored(&self, key: DeviceKey) -> Result<Arc<InputDevice>, Error> {
139        InputDevice::get_live(LiveInputDeviceParams {
140            command_tx: &self.command_tx,
141            event_tx: &self.event_tx,
142            device_key: key,
143            cancellation_token: &self.cancellation_token,
144        })
145        .await
146    }
147
148    /// Returns a snapshot of the audio stream's current state.
149    ///
150    /// # Errors
151    ///
152    /// Returns [`Error::StreamNotFound`] if no stream exists with this key.
153    #[instrument(skip(self), fields(stream_key = ?key), err)]
154    pub async fn audio_stream(&self, key: StreamKey) -> Result<AudioStream, Error> {
155        AudioStream::get(AudioStreamParams {
156            command_tx: &self.command_tx,
157            stream_key: key,
158        })
159        .await
160    }
161
162    /// Returns a live-updating audio stream instance.
163    ///
164    /// # Errors
165    ///
166    /// Returns [`Error::StreamNotFound`] if no stream exists with this key.
167    #[instrument(skip(self), fields(stream_key = ?key), err)]
168    pub async fn audio_stream_monitored(&self, key: StreamKey) -> Result<Arc<AudioStream>, Error> {
169        AudioStream::get_live(LiveAudioStreamParams {
170            command_tx: &self.command_tx,
171            event_tx: &self.event_tx,
172            stream_key: key,
173            cancellation_token: &self.cancellation_token,
174        })
175        .await
176    }
177}
178
179impl Drop for AudioService {
180    fn drop(&mut self) {
181        self.cancellation_token.cancel();
182
183        let Some(handle) = self.backend_handle.take() else {
184            return;
185        };
186
187        let Ok(rt) = tokio::runtime::Handle::try_current() else {
188            return;
189        };
190
191        let result = tokio::task::block_in_place(|| rt.block_on(handle));
192        match result {
193            Ok(Ok(())) => {}
194            Ok(Err(e)) => error!(error = %e, "PulseAudio backend shutdown error"),
195            Err(e) => error!(error = %e, "PulseAudio backend task panicked"),
196        }
197    }
198}