1use serde::{Deserialize, Serialize};
2
3use crate::format::{AudioFormat, ChannelLayout};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
7#[serde(tag = "type", rename_all = "snake_case")]
8pub enum Message {
9 Hello(Hello),
11 HelloAck(HelloAck),
12
13 FormatPropose(FormatPropose),
15 FormatAccept(FormatAccept),
16 FormatCounter(FormatCounter),
17 FormatReject(FormatReject),
18
19 Play(Play),
21 Pause(Pause),
22 Stop(Stop),
23 Seek(Seek),
24
25 VolumeSet(VolumeSet),
27 VolumeGet(VolumeGet),
28 VolumeReport(VolumeReport),
29 Mute(Mute),
30
31 Metadata(Metadata),
33
34 ZoneAssign(ZoneAssign),
36 ZoneUpdate(ZoneUpdate),
37 ZoneRelease(ZoneRelease),
38 ZoneAck(ZoneAck),
39
40 NextTrackPrepare(NextTrackPrepare),
42 NextTrackReady(NextTrackReady),
43 NextTrackReformat(NextTrackReformat),
44
45 Error(ErrorMsg),
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct Hello {
53 pub protocol_version: u32,
54 pub controller_id: String,
55 pub controller_name: String,
56 pub clock_port: u16,
57 #[serde(default)]
58 pub features: Vec<String>,
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct HelloAck {
63 pub protocol_version: u32,
64 pub endpoint_id: String,
65 pub endpoint_name: String,
66 pub capabilities: EndpointCapabilities,
67 pub audio_port: u16,
68 pub clock_port: u16,
69 pub buffer_size_ms: u32,
70}
71
72#[derive(Debug, Clone, Serialize, Deserialize)]
73pub struct EndpointCapabilities {
74 pub pcm_max_rate: u32,
75 pub pcm_max_bits: u8,
76 #[serde(default)]
77 pub dsd_max_rate: Option<u16>,
78 pub channels_max: u8,
79 pub formats: Vec<AudioFormat>,
80 #[serde(default)]
81 pub volume: Option<VolumeCapability>,
82 #[serde(default)]
83 pub gapless: bool,
84 #[serde(default)]
85 pub seek: bool,
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct VolumeCapability {
90 #[serde(rename = "type")]
91 pub vol_type: VolumeType,
92 pub range: [u8; 2],
93 pub step: u8,
94}
95
96#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
97#[serde(rename_all = "snake_case")]
98pub enum VolumeType {
99 Hw,
100 Sw,
101 Fixed,
102 None,
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize)]
108pub struct FormatPropose {
109 pub stream_id: String,
110 pub format: AudioFormat,
111 pub sample_rate: u32,
112 pub channels: u8,
113 pub channel_layout: ChannelLayout,
114 pub bits_per_sample: u8,
115 #[serde(default)]
116 pub dsd_rate: Option<u16>,
117}
118
119#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct FormatAccept {
121 pub stream_id: String,
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct FormatCounter {
126 pub stream_id: String,
127 pub format: AudioFormat,
128 pub sample_rate: u32,
129 pub channels: u8,
130 pub channel_layout: ChannelLayout,
131 pub bits_per_sample: u8,
132 #[serde(default)]
133 pub dsd_rate: Option<u16>,
134}
135
136#[derive(Debug, Clone, Serialize, Deserialize)]
137pub struct FormatReject {
138 pub stream_id: String,
139 pub reason: String,
140}
141
142#[derive(Debug, Clone, Serialize, Deserialize)]
145pub struct Play {
146 pub stream_id: String,
147}
148
149#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct Pause {
151 pub stream_id: String,
152}
153
154#[derive(Debug, Clone, Serialize, Deserialize)]
155pub struct Stop {
156 pub stream_id: String,
157}
158
159#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct Seek {
161 pub stream_id: String,
162 pub position_ms: u64,
163}
164
165#[derive(Debug, Clone, Serialize, Deserialize)]
168pub struct VolumeSet {
169 pub level: u8,
170}
171
172#[derive(Debug, Clone, Serialize, Deserialize)]
173pub struct VolumeGet {}
174
175#[derive(Debug, Clone, Serialize, Deserialize)]
176pub struct VolumeReport {
177 pub level: u8,
178 pub muted: bool,
179}
180
181#[derive(Debug, Clone, Serialize, Deserialize)]
182pub struct Mute {
183 pub muted: bool,
184}
185
186#[derive(Debug, Clone, Serialize, Deserialize)]
189pub struct Metadata {
190 pub track: TrackMetadata,
191}
192
193#[derive(Debug, Clone, Serialize, Deserialize)]
194pub struct TrackMetadata {
195 pub title: String,
196 pub artist: String,
197 pub album: String,
198 pub duration_ms: u64,
199 #[serde(default)]
200 pub artwork_url: Option<String>,
201 #[serde(default)]
202 pub format: Option<String>,
203}
204
205#[derive(Debug, Clone, Serialize, Deserialize)]
208pub struct ZoneAssign {
209 pub zone_id: String,
210 pub endpoint_id: String,
211}
212
213#[derive(Debug, Clone, Serialize, Deserialize)]
214pub struct ZoneUpdate {
215 pub zone_id: String,
216 pub endpoint_ids: Vec<String>,
217}
218
219#[derive(Debug, Clone, Serialize, Deserialize)]
220pub struct ZoneRelease {
221 pub zone_id: String,
222 pub endpoint_id: String,
223}
224
225#[derive(Debug, Clone, Serialize, Deserialize)]
226pub struct ZoneAck {
227 pub zone_id: String,
228 pub endpoint_id: String,
229 pub accepted: bool,
230 #[serde(default)]
231 pub reason: Option<String>,
232}
233
234#[derive(Debug, Clone, Serialize, Deserialize)]
237pub struct NextTrackPrepare {
238 pub stream_id: String,
239 pub format: AudioFormat,
240 pub sample_rate: u32,
241 pub channels: u8,
242 pub channel_layout: ChannelLayout,
243 pub bits_per_sample: u8,
244}
245
246#[derive(Debug, Clone, Serialize, Deserialize)]
247pub struct NextTrackReady {
248 pub stream_id: String,
249}
250
251#[derive(Debug, Clone, Serialize, Deserialize)]
252pub struct NextTrackReformat {
253 pub stream_id: String,
254 pub format: AudioFormat,
255 pub sample_rate: u32,
256}
257
258#[derive(Debug, Clone, Serialize, Deserialize)]
261pub struct ErrorMsg {
262 pub code: u32,
263 pub message: String,
264}
265
266impl Message {
269 pub fn encode_framed(&self) -> Vec<u8> {
270 let json = serde_json::to_vec(self).expect("message serialization cannot fail");
271 let len = json.len() as u32;
272 let mut buf = Vec::with_capacity(4 + json.len());
273 buf.extend_from_slice(&len.to_be_bytes());
274 buf.extend_from_slice(&json);
275 buf
276 }
277
278 pub fn decode_json(json: &[u8]) -> Result<Self, serde_json::Error> {
279 serde_json::from_slice(json)
280 }
281}
282
283#[cfg(test)]
284mod tests {
285 use super::*;
286
287 #[test]
288 fn hello_roundtrip() {
289 let msg = Message::Hello(Hello {
290 protocol_version: 1,
291 controller_id: "abc".into(),
292 controller_name: "Tune Server".into(),
293 clock_port: 9742,
294 features: vec!["flac_transport".into(), "dsd_native".into()],
295 });
296 let framed = msg.encode_framed();
297 let len = u32::from_be_bytes(framed[..4].try_into().unwrap()) as usize;
298 assert_eq!(len, framed.len() - 4);
299 let decoded = Message::decode_json(&framed[4..]).unwrap();
300 match decoded {
301 Message::Hello(h) => {
302 assert_eq!(h.controller_name, "Tune Server");
303 assert_eq!(h.features.len(), 2);
304 }
305 _ => panic!("wrong variant"),
306 }
307 }
308
309 #[test]
310 fn format_propose_json() {
311 let msg = Message::FormatPropose(FormatPropose {
312 stream_id: "abc123".into(),
313 format: AudioFormat::PcmS24le,
314 sample_rate: 192000,
315 channels: 2,
316 channel_layout: ChannelLayout::Stereo,
317 bits_per_sample: 24,
318 dsd_rate: None,
319 });
320 let json = serde_json::to_string_pretty(&msg).unwrap();
321 assert!(json.contains("format_propose"));
322 assert!(json.contains("192000"));
323 }
324}