snapcast_control/protocol/
stream.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4// the stream
5/// A stream of audio maintained by the Snapcast server
6#[serde_with::skip_serializing_none]
7#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
8pub struct Stream {
9  pub id: String,
10  pub properties: Option<StreamProperties>,
11  pub status: StreamStatus,
12  pub uri: StreamUri,
13}
14
15#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
16#[serde(rename_all = "camelCase")]
17pub enum StreamStatus {
18  Idle,
19  Playing,
20  Disabled,
21  Unknown,
22}
23
24impl From<&str> for StreamStatus {
25  fn from(s: &str) -> Self {
26    match s {
27      "idle" => StreamStatus::Idle,
28      "playing" => StreamStatus::Playing,
29      "disabled" => StreamStatus::Disabled,
30      _ => StreamStatus::Unknown,
31    }
32  }
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
36pub struct StreamUri {
37  pub fragment: String,
38  pub host: String,
39  pub path: String,
40  pub query: HashMap<String, String>,
41  pub raw: String,
42  pub scheme: String,
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
46#[serde(rename_all = "camelCase")]
47pub enum StreamPlaybackStatus {
48  Playing,
49  Paused,
50  Stopped,
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
54#[serde(rename_all = "camelCase")]
55pub enum StreamLoopStatus {
56  None,
57  Track,
58  Playlist,
59}
60
61#[serde_with::skip_serializing_none]
62#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
63#[serde(rename_all = "camelCase")]
64pub struct StreamProperties {
65  pub playback_status: Option<StreamPlaybackStatus>,
66  pub loop_status: Option<StreamLoopStatus>,
67  pub shuffle: Option<bool>,
68  pub volume: Option<usize>,
69  pub mute: Option<bool>,
70  pub rate: Option<f64>,
71  pub position: Option<f64>,
72  pub can_go_next: bool,
73  pub can_go_previous: bool,
74  pub can_play: bool,
75  pub can_pause: bool,
76  pub can_seek: bool,
77  pub can_control: bool,
78  pub metadata: Option<StreamMetadata>,
79}
80
81#[serde_with::skip_serializing_none]
82#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
83#[serde(rename_all = "camelCase")]
84pub struct StreamMetadata {
85  track_id: Option<String>,
86  file: Option<String>,
87  duration: Option<f64>,
88  artist: Option<Vec<String>>,
89  artist_sort: Option<Vec<String>>,
90  album: Option<String>,
91  album_sort: Option<String>,
92  album_artist: Option<Vec<String>>,
93  album_artist_sort: Option<Vec<String>>,
94  name: Option<String>,
95  date: Option<String>,
96  original_date: Option<String>,
97  composer: Option<Vec<String>>,
98  performer: Option<String>,
99  work: Option<String>,
100  grouping: Option<String>,
101  label: Option<String>,
102  musicbrainz_artist_id: Option<String>,
103  musicbrainz_album_id: Option<String>,
104  musicbrainz_album_artist_id: Option<String>,
105  musicbrainz_track_id: Option<String>,
106  musicbrainz_release_track_id: Option<String>,
107  musicbrainz_work_id: Option<String>,
108  lyrics: Option<Vec<String>>,
109  bpm: Option<usize>,
110  auto_rating: Option<f64>,
111  comment: Option<Vec<String>>,
112  content_created: Option<String>,
113  disc_number: Option<usize>,
114  first_used: Option<String>,
115  genre: Option<Vec<String>>,
116  last_used: Option<String>,
117  lyricist: Option<Vec<String>>,
118  title: Option<String>,
119  track_number: Option<usize>,
120  url: Option<String>,
121  art_url: Option<String>,
122  art_data: Option<ArtData>,
123  use_count: Option<usize>,
124  user_rating: Option<f64>,
125  spotify_artist_id: Option<String>,
126  spotify_track_id: Option<String>,
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
130pub struct ArtData {
131  pub data: String,
132  pub extension: String,
133}
134
135// params and results
136#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
137pub struct AddStreamParams {
138  #[serde(rename = "streamUri")]
139  pub stream_uri: String,
140}
141
142#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
143pub struct AddStreamResult {
144  pub id: String,
145}
146
147#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
148pub struct RemoveStreamParams {
149  pub id: String,
150}
151
152#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
153pub struct RemoveStreamResult {
154  pub id: String,
155}
156
157#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
158pub struct ControlParams {
159  pub id: String,
160  #[serde(flatten)]
161  pub command: ControlCommand,
162}
163
164#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
165#[serde(rename_all = "camelCase", tag = "command", content = "params")]
166pub enum ControlCommand {
167  Play,
168  Pause,
169  PlayPause,
170  Stop,
171  Next,
172  Previous,
173  Seek { offset: f64 },
174  SetPosition { position: f64 },
175}
176
177pub type ControlResult = String;
178
179#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
180pub struct SetPropertyParams {
181  pub id: String,
182  #[serde(flatten)]
183  pub properties: SetPropertyProperties,
184}
185
186#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
187#[serde(rename_all = "camelCase", tag = "property", content = "value")]
188pub enum SetPropertyProperties {
189  LoopStatus(StreamLoopStatus),
190  Shuffle(bool),
191  Volume(usize),
192  Mute(bool),
193  Rate(f64),
194}
195
196pub type SetPropertiesResult = String;
197
198// notifications
199#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
200pub struct OnUpdateParams {
201  pub id: String,
202  pub stream: Stream,
203}
204
205#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
206pub struct OnPropertiesParams {
207  pub id: String,
208  pub properties: StreamProperties,
209}
210
211#[cfg(test)]
212mod tests {
213  use std::collections::HashMap;
214
215  use super::*;
216
217  #[test]
218  fn serialize_stream() {
219    let stream = Stream {
220      id: "stream 1".to_string(),
221      status: "idle".into(),
222      properties: None,
223      uri: StreamUri {
224        fragment: "".to_string(),
225        host: "".to_string(),
226        path: "/tmp/snapfifo".to_string(),
227        query: HashMap::new(),
228        raw: "pipe:///tmp/snapfifo?name=stream 1".to_string(),
229        scheme: "pipe".to_string(),
230      },
231    };
232
233    let json = serde_json::to_string(&stream).unwrap();
234    let expected = r#"{"id":"stream 1","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{},"raw":"pipe:///tmp/snapfifo?name=stream 1","scheme":"pipe"}}"#;
235
236    assert_eq!(json, expected);
237  }
238
239  #[test]
240  fn deserialize_stream() {
241    let json = r#"{"id":"stream 1","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"chunk_ms":"20","codec":"flac","name":"stream 1","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 1","scheme":"pipe"}}"#;
242    let stream: Stream = serde_json::from_str(json).unwrap();
243
244    assert_eq!(stream.id, "stream 1");
245  }
246}