1#![deny(unsafe_code)]
2#![warn(clippy::redundant_closure)]
3#![warn(clippy::implicit_clone)]
4#![warn(clippy::uninlined_format_args)]
5#![warn(missing_docs)]
6
7pub mod config;
47pub mod connection;
48pub(crate) mod controller;
49#[cfg(feature = "encryption")]
50pub(crate) mod crypto;
51pub mod decoder;
52pub(crate) mod double_buffer;
53pub mod stream;
54pub mod time_provider;
55
56#[cfg(feature = "mdns")]
57pub mod discovery;
58
59#[cfg(feature = "resampler")]
60pub mod resampler;
61
62use tokio::sync::mpsc;
63
64const EVENT_CHANNEL_SIZE: usize = 256;
65const COMMAND_CHANNEL_SIZE: usize = 64;
66const AUDIO_CHANNEL_SIZE: usize = 256;
67
68#[cfg(feature = "custom-protocol")]
70pub use snapcast_proto::CustomMessage;
71pub use snapcast_proto::SampleFormat;
72pub use snapcast_proto::{DEFAULT_STREAM_PORT, PROTOCOL_VERSION};
73
74#[derive(Debug, Clone)]
76pub struct AudioFrame {
77 pub samples: Vec<f32>,
79 pub sample_rate: u32,
81 pub channels: u16,
83 pub timestamp_usec: i64,
85}
86
87#[derive(Debug, Clone)]
89pub enum ClientEvent {
90 Connected {
92 host: String,
94 port: u16,
96 },
97 Disconnected {
99 reason: String,
101 },
102 StreamStarted {
104 codec: String,
106 format: SampleFormat,
108 },
109 ServerSettings {
111 buffer_ms: i32,
113 latency: i32,
115 volume: u16,
117 muted: bool,
119 },
120 VolumeChanged {
122 volume: u16,
124 muted: bool,
126 },
127 TimeSyncComplete {
129 diff_ms: f64,
131 },
132 #[cfg(feature = "custom-protocol")]
133 CustomMessage(snapcast_proto::CustomMessage),
135}
136
137#[derive(Debug, Clone)]
139pub enum ClientCommand {
140 SetVolume {
142 volume: u16,
144 muted: bool,
146 },
147 #[cfg(feature = "custom-protocol")]
149 SendCustom(snapcast_proto::CustomMessage),
150 Stop,
152}
153
154#[derive(Debug, Clone)]
156pub struct ClientConfig {
157 pub scheme: String,
159 pub host: String,
161 pub port: u16,
163 pub auth: Option<crate::config::Auth>,
165 #[cfg(feature = "tls")]
167 pub server_certificate: Option<std::path::PathBuf>,
168 #[cfg(feature = "tls")]
170 pub certificate: Option<std::path::PathBuf>,
171 #[cfg(feature = "tls")]
173 pub certificate_key: Option<std::path::PathBuf>,
174 #[cfg(feature = "tls")]
176 pub key_password: Option<String>,
177 pub instance: u32,
179 pub host_id: String,
181 pub latency: i32,
183 pub mdns_service_type: String,
185 pub client_name: String,
187 #[cfg(feature = "encryption")]
189 pub encryption_psk: Option<String>,
190}
191
192impl Default for ClientConfig {
193 fn default() -> Self {
194 Self {
195 scheme: "tcp".into(),
196 host: String::new(),
197 port: snapcast_proto::DEFAULT_STREAM_PORT,
198 auth: None,
199 #[cfg(feature = "tls")]
200 server_certificate: None,
201 #[cfg(feature = "tls")]
202 certificate: None,
203 #[cfg(feature = "tls")]
204 certificate_key: None,
205 #[cfg(feature = "tls")]
206 key_password: None,
207 instance: 1,
208 host_id: String::new(),
209 latency: 0,
210 mdns_service_type: "_snapcast._tcp.local.".into(),
211 client_name: "Snapclient".into(),
212 #[cfg(feature = "encryption")]
213 encryption_psk: None,
214 }
215 }
216}
217
218pub struct SnapClient {
220 config: ClientConfig,
221 event_tx: mpsc::Sender<ClientEvent>,
222 command_tx: mpsc::Sender<ClientCommand>,
223 command_rx: Option<mpsc::Receiver<ClientCommand>>,
224 pub time_provider: std::sync::Arc<std::sync::Mutex<time_provider::TimeProvider>>,
226 pub stream: std::sync::Arc<std::sync::Mutex<stream::Stream>>,
228}
229
230impl SnapClient {
231 pub fn new(
233 config: ClientConfig,
234 ) -> (
235 Self,
236 mpsc::Receiver<ClientEvent>,
237 mpsc::Receiver<AudioFrame>,
238 ) {
239 let (event_tx, event_rx) = mpsc::channel(EVENT_CHANNEL_SIZE);
240 let (command_tx, command_rx) = mpsc::channel(COMMAND_CHANNEL_SIZE);
241 let (_audio_tx, audio_rx) = mpsc::channel(AUDIO_CHANNEL_SIZE);
242 let time_provider =
243 std::sync::Arc::new(std::sync::Mutex::new(time_provider::TimeProvider::new()));
244 let stream = std::sync::Arc::new(std::sync::Mutex::new(stream::Stream::new(
245 SampleFormat::default(),
246 )));
247 let client = Self {
248 config,
249 event_tx,
250 command_tx,
251 command_rx: Some(command_rx),
252 time_provider,
253 stream,
254 };
255 (client, event_rx, audio_rx)
256 }
257
258 pub fn command_sender(&self) -> mpsc::Sender<ClientCommand> {
260 self.command_tx.clone()
261 }
262
263 pub async fn run(&mut self) -> anyhow::Result<()> {
265 let command_rx = self
266 .command_rx
267 .take()
268 .ok_or_else(|| anyhow::anyhow!("run() already called"))?;
269
270 let mut ctrl = controller::Controller::new(
271 self.config.clone(),
272 self.event_tx.clone(),
273 command_rx,
274 std::sync::Arc::clone(&self.time_provider),
275 std::sync::Arc::clone(&self.stream),
276 );
277 ctrl.run().await
278 }
279}