1use super::{ContextType, Cursors, EpisodeId, ExternalUrls, ItemType, Track, TrackId, TrackItem};
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
6pub struct Device {
7 pub id: Option<String>,
10
11 pub is_active: bool,
13
14 pub is_private_session: bool,
16
17 pub is_restricted: bool,
20
21 pub name: String,
24
25 #[serde(rename = "type")]
26 pub type_: String,
28
29 pub volume_percent: Option<u8>,
31
32 pub supports_volume: bool,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
38pub struct Devices {
39 #[serde(default)]
41 pub devices: Vec<Device>,
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
46#[serde(rename_all = "lowercase")]
47pub enum RepeatState {
48 Track,
49 Context,
50 Off,
51}
52
53impl std::fmt::Display for RepeatState {
54 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55 let s = match self {
56 Self::Track => "track",
57 Self::Context => "context",
58 Self::Off => "off",
59 };
60 write!(f, "{s}")
61 }
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
66pub struct Context {
67 #[serde(rename = "type")]
69 pub type_: ItemType,
70
71 pub href: Option<String>,
73
74 pub external_urls: ExternalUrls,
76
77 pub uri: String,
79}
80
81#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
83#[serde(rename_all = "lowercase")]
84pub enum CurrentlyPlayingType {
85 Track,
86 Episode,
87 Ad,
88 Unknown,
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
93pub struct PlaybackState {
94 pub device: Device,
96
97 pub repeat_state: RepeatState,
99
100 pub shuffle_state: bool,
102
103 pub context: Option<Context>,
105
106 pub timestamp: Option<i64>,
108
109 pub progress_ms: Option<u32>,
111
112 pub is_playing: bool,
114
115 pub item: Option<TrackItem>,
117
118 pub currently_playing_type: CurrentlyPlayingType,
120
121 pub actions: Actions,
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
127pub struct Actions {
128 #[serde(skip_serializing_if = "Option::is_none")]
130 pub interrupting_playback: Option<bool>,
131
132 #[serde(skip_serializing_if = "Option::is_none")]
134 pub pausing: Option<bool>,
135
136 #[serde(skip_serializing_if = "Option::is_none")]
138 pub resuming: Option<bool>,
139
140 #[serde(skip_serializing_if = "Option::is_none")]
142 pub seeking: Option<bool>,
143
144 #[serde(skip_serializing_if = "Option::is_none")]
146 pub skipping_next: Option<bool>,
147
148 #[serde(skip_serializing_if = "Option::is_none")]
150 pub skipping_prev: Option<bool>,
151
152 #[serde(skip_serializing_if = "Option::is_none")]
154 pub toggling_repeat_context: Option<bool>,
155
156 #[serde(skip_serializing_if = "Option::is_none")]
158 pub toggling_shuffle: Option<bool>,
159
160 #[serde(skip_serializing_if = "Option::is_none")]
162 pub toggling_repeat_track: Option<bool>,
163
164 #[serde(skip_serializing_if = "Option::is_none")]
166 pub transferring_playback: Option<bool>,
167}
168
169#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
171pub struct CurrentlyPlaying {
172 pub context: Option<Context>,
174
175 pub timestamp: Option<i64>,
177
178 pub progress_ms: Option<u32>,
180
181 pub is_playing: bool,
183
184 pub item: Option<TrackItem>,
186
187 pub currently_playing_type: CurrentlyPlayingType,
189
190 pub actions: Actions,
192}
193
194#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
196pub struct PlayHistory {
197 pub track: Track,
199
200 pub played_at: String,
202
203 pub context: Context,
205}
206
207#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
209pub struct RecentlyPlayedTracks {
210 pub href: String,
212
213 pub limit: usize,
215
216 pub next: Option<String>,
218
219 pub cursors: Option<Cursors>,
221
222 pub total: Option<usize>,
224
225 pub items: Vec<PlayHistory>,
227}
228
229#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
231pub struct Queue {
232 pub currently_playing: Option<TrackItem>,
234
235 pub queue: Vec<TrackItem>,
237}
238
239#[derive(Debug, Clone, PartialEq, Eq)]
241pub enum Offset {
242 Position(usize),
243 Uri(ContextType),
244}
245
246impl From<usize> for Offset {
247 fn from(position: usize) -> Self {
248 Self::Position(position)
249 }
250}
251
252#[derive(Debug, Clone, PartialEq, Eq)]
254pub enum QueryRange {
255 Before(i64),
256 After(i64),
257}
258
259#[derive(Debug, Clone, PartialEq, Eq)]
261pub enum PlaylistItem {
262 Track(TrackId),
263 Episode(EpisodeId),
264}
265
266impl From<TrackId> for PlaylistItem {
267 fn from(track: TrackId) -> Self {
268 Self::Track(track)
269 }
270}
271
272impl From<EpisodeId> for PlaylistItem {
273 fn from(episode: EpisodeId) -> Self {
274 Self::Episode(episode)
275 }
276}
277
278impl std::fmt::Display for PlaylistItem {
279 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
280 let s = match self {
281 Self::Track(track) => track.uri(),
282 Self::Episode(episode) => episode.uri(),
283 };
284 write!(f, "{s}")
285 }
286}
287
288#[cfg(test)]
289mod tests {
290 use super::*;
291
292 #[test]
293 fn playback_state() {
294 let json = r#"
295 {
296 "device": {
297 "id": "string",
298 "is_active": false,
299 "is_private_session": false,
300 "is_restricted": false,
301 "name": "Kitchen speaker",
302 "type": "computer",
303 "volume_percent": 59,
304 "supports_volume": false
305 },
306 "repeat_state": "off",
307 "shuffle_state": false,
308 "context": {
309 "type": "track",
310 "href": "string",
311 "external_urls": {
312 "spotify": "string"
313 },
314 "uri": "string"
315 },
316 "timestamp": 0,
317 "progress_ms": 0,
318 "is_playing": false,
319 "item": {
320 "album": {
321 "album_type": "compilation",
322 "total_tracks": 9,
323 "available_markets": ["CA", "BR", "IT"],
324 "external_urls": {
325 "spotify": "string"
326 },
327 "href": "string",
328 "id": "2up3OPMp9Tb4dAKM2erWXQ",
329 "images": [
330 {
331 "url": "https://i.scdn.co/image/ab67616d00001e02ff9ca10b55ce82ae553c8228",
332 "height": 300,
333 "width": 300
334 }
335 ],
336 "name": "string",
337 "release_date": "1981-12",
338 "release_date_precision": "year",
339 "restrictions": {
340 "reason": "market"
341 },
342 "type": "album",
343 "uri": "spotify:album:2up3OPMp9Tb4dAKM2erWXQ",
344 "artists": [
345 {
346 "external_urls": {
347 "spotify": "string"
348 },
349 "href": "string",
350 "id": "string",
351 "name": "string",
352 "type": "artist",
353 "uri": "string"
354 }
355 ]
356 },
357 "artists": [
358 {
359 "external_urls": {
360 "spotify": "string"
361 },
362 "followers": {
363 "href": "string",
364 "total": 0
365 },
366 "genres": ["Prog rock", "Grunge"],
367 "href": "string",
368 "id": "string",
369 "images": [
370 {
371 "url": "https://i.scdn.co/image/ab67616d00001e02ff9ca10b55ce82ae553c8228",
372 "height": 300,
373 "width": 300
374 }
375 ],
376 "name": "string",
377 "popularity": 0,
378 "type": "artist",
379 "uri": "string"
380 }
381 ],
382 "available_markets": ["US"],
383 "disc_number": 0,
384 "duration_ms": 0,
385 "explicit": false,
386 "external_ids": {
387 "isrc": "string",
388 "ean": "string",
389 "upc": "string"
390 },
391 "external_urls": {
392 "spotify": "string"
393 },
394 "href": "string",
395 "id": "string",
396 "is_playable": false,
397 "linked_from": {},
398 "restrictions": {
399 "reason": "string"
400 },
401 "name": "string",
402 "popularity": 0,
403 "preview_url": "string",
404 "track_number": 0,
405 "type": "track",
406 "uri": "string",
407 "is_local": false
408 },
409 "currently_playing_type": "unknown",
410 "actions": {
411 "interrupting_playback": false,
412 "pausing": false,
413 "resuming": false,
414 "seeking": false,
415 "skipping_next": false,
416 "skipping_prev": false,
417 "toggling_repeat_context": false,
418 "toggling_shuffle": false,
419 "toggling_repeat_track": false,
420 "transferring_playback": false
421 }
422 }
423 "#;
424
425 crate::test::assert_deserialized!(PlaybackState, json);
426 }
427}