spotify_web_api/model/
player.rs

1use super::{ContextType, Cursors, EpisodeId, ExternalUrls, ItemType, Track, TrackId, TrackItem};
2use serde::{Deserialize, Serialize};
3
4/// A playback device (speaker, phone, computer, etc.).
5#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
6pub struct Device {
7    /// The device ID. This ID is unique and persistent to some extent.
8    /// However, this is not guaranteed and any cached `device_id` should periodically be cleared out and refetched as necessary.
9    pub id: Option<String>,
10
11    /// If this device is the currently active device.
12    pub is_active: bool,
13
14    /// If this device is currently in a private session.
15    pub is_private_session: bool,
16
17    /// Whether controlling this device is restricted.
18    /// At present if this is "true" then no Web API commands will be accepted by this device.
19    pub is_restricted: bool,
20
21    /// A human-readable name for the device. Some devices have a name that the user can configure (e.g. "Loudest speaker")
22    /// and some devices have a generic name associated with the manufacturer or device model.
23    pub name: String,
24
25    #[serde(rename = "type")]
26    /// Device type, such as "computer", "smartphone" or "speaker".
27    pub type_: String,
28
29    /// The current volume in percent.
30    pub volume_percent: Option<u8>,
31
32    /// If this device can be used to set the volume.
33    pub supports_volume: bool,
34}
35
36/// A list of available playback devices.
37#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
38pub struct Devices {
39    /// The list of devices.
40    #[serde(default)]
41    pub devices: Vec<Device>,
42}
43
44/// The repeat mode state for playback.
45#[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/// The playback context (album, playlist, artist, etc.).
65#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
66pub struct Context {
67    /// The object type.
68    #[serde(rename = "type")]
69    pub type_: ItemType,
70
71    /// A link to the Web API endpoint providing full details of the track.
72    pub href: Option<String>,
73
74    /// External URLs for this context.
75    pub external_urls: ExternalUrls,
76
77    /// The Spotify URI for the context.
78    pub uri: String,
79}
80
81/// The type of the currently playing item.
82#[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/// The current playback state including device, track, and progress.
92#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
93pub struct PlaybackState {
94    /// The device that is currently active.
95    pub device: Device,
96
97    /// off, track, context
98    pub repeat_state: RepeatState,
99
100    /// If shuffle is on or off.
101    pub shuffle_state: bool,
102
103    /// The context object.
104    pub context: Option<Context>,
105
106    /// Unix Millisecond Timestamp when data was fetched.
107    pub timestamp: Option<i64>,
108
109    /// Progress into the currently playing track or episode.
110    pub progress_ms: Option<u32>,
111
112    /// If something is currently playing, return true.
113    pub is_playing: bool,
114
115    /// The currently playing track or episode.
116    pub item: Option<TrackItem>,
117
118    /// The object type of the currently playing item. Can be one of track, episode, ad or unknown.
119    pub currently_playing_type: CurrentlyPlayingType,
120
121    /// Allows to update the user interface based on which playback actions are available within the current context.
122    pub actions: Actions,
123}
124
125/// Available playback actions in the current context.
126#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
127pub struct Actions {
128    /// Interrupting playback.
129    #[serde(skip_serializing_if = "Option::is_none")]
130    pub interrupting_playback: Option<bool>,
131
132    /// Pausing playback.
133    #[serde(skip_serializing_if = "Option::is_none")]
134    pub pausing: Option<bool>,
135
136    /// Resuming playback.
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub resuming: Option<bool>,
139
140    /// Seeking playback location.
141    #[serde(skip_serializing_if = "Option::is_none")]
142    pub seeking: Option<bool>,
143
144    /// Skipping to the next context.
145    #[serde(skip_serializing_if = "Option::is_none")]
146    pub skipping_next: Option<bool>,
147
148    /// Skipping to the previous context.
149    #[serde(skip_serializing_if = "Option::is_none")]
150    pub skipping_prev: Option<bool>,
151
152    /// Toggling repeat context flag.
153    #[serde(skip_serializing_if = "Option::is_none")]
154    pub toggling_repeat_context: Option<bool>,
155
156    /// Toggling shuffle flag.
157    #[serde(skip_serializing_if = "Option::is_none")]
158    pub toggling_shuffle: Option<bool>,
159
160    /// Toggling repeat track flag.
161    #[serde(skip_serializing_if = "Option::is_none")]
162    pub toggling_repeat_track: Option<bool>,
163
164    /// Transferring playback between devices.
165    #[serde(skip_serializing_if = "Option::is_none")]
166    pub transferring_playback: Option<bool>,
167}
168
169/// Information about the currently playing track or episode.
170#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
171pub struct CurrentlyPlaying {
172    /// The context object.
173    pub context: Option<Context>,
174
175    /// Unix Millisecond Timestamp when data was fetched.
176    pub timestamp: Option<i64>,
177
178    /// Progress into the currently playing track or episode.
179    pub progress_ms: Option<u32>,
180
181    /// If something is currently playing, return true.
182    pub is_playing: bool,
183
184    /// The currently playing track or episode.
185    pub item: Option<TrackItem>,
186
187    /// The object type of the currently playing item. Can be one of track, episode, ad or unknown.
188    pub currently_playing_type: CurrentlyPlayingType,
189
190    /// Allows to update the user interface based on which playback actions are available within the current context.
191    pub actions: Actions,
192}
193
194/// A track in the user's play history.
195#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
196pub struct PlayHistory {
197    /// The track the user listened to.
198    pub track: Track,
199
200    /// The date and time the track was played.
201    pub played_at: String,
202
203    /// The context the track was played from.
204    pub context: Context,
205}
206
207/// A list of recently played tracks.
208#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
209pub struct RecentlyPlayedTracks {
210    /// A link to the Web API endpoint returning the full result of the request.
211    pub href: String,
212
213    /// The maximum number of items in the response (as set in the query or by default).
214    pub limit: usize,
215
216    /// URL to the next page of items.
217    pub next: Option<String>,
218
219    /// The cursors used to find the next set of items.
220    pub cursors: Option<Cursors>,
221
222    /// The total number of items available to return.
223    pub total: Option<usize>,
224
225    /// The play history items.
226    pub items: Vec<PlayHistory>,
227}
228
229/// The user's playback queue.
230#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
231pub struct Queue {
232    /// The currently playing track or episode.
233    pub currently_playing: Option<TrackItem>,
234
235    /// The tracks or episodes in the queue.
236    pub queue: Vec<TrackItem>,
237}
238
239/// An offset for starting playback at a specific position or URI.
240#[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/// A time range for querying recently played tracks.
253#[derive(Debug, Clone, PartialEq, Eq)]
254pub enum QueryRange {
255    Before(i64),
256    After(i64),
257}
258
259/// An item that can be added to a playlist (track or episode).
260#[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}