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