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#[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 pub output_devices: Property<Vec<Arc<OutputDevice>>>,
45
46 pub input_devices: Property<Vec<Arc<InputDevice>>>,
48
49 pub default_output: Property<Option<Arc<OutputDevice>>>,
51
52 pub default_input: Property<Option<Arc<InputDevice>>>,
54
55 pub playback_streams: Property<Vec<Arc<AudioStream>>>,
57
58 pub recording_streams: Property<Vec<Arc<AudioStream>>>,
60}
61
62impl AudioService {
63 #[instrument]
70 pub async fn new() -> Result<Arc<Self>, Error> {
71 Self::builder().build().await
72 }
73
74 pub fn builder() -> AudioServiceBuilder {
76 AudioServiceBuilder::new()
77 }
78
79 #[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 #[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 #[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 #[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 #[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 #[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}