spotify_rs/endpoint/
player.rs

1use std::{fmt::Debug, marker::PhantomData};
2
3use reqwest::Method;
4use serde::Serialize;
5use serde_json::{json, Value};
6
7use crate::{
8    auth::{AuthFlow, Authorised},
9    client::Body,
10    error::Result,
11    model::{
12        player::{CurrentlyPlayingItem, Device, Devices, PlayHistory, PlaybackState, Queue},
13        CursorPage,
14    },
15    Nil,
16};
17
18use super::{Client, Endpoint};
19
20impl Endpoint for TransferPlaybackEndpoint {}
21impl Endpoint for StartPlaybackEndpoint {}
22impl Endpoint for SeekToPositionEndpoint {}
23impl Endpoint for SetRepeatModeEndpoint {}
24impl Endpoint for SetPlaybackVolumeEndpoint {}
25impl Endpoint for ToggleShuffleEndpoint {}
26impl<T: TimestampMarker> Endpoint for RecentlyPlayedTracksEndpoint<T> {
27    fn endpoint_url(&self) -> &'static str {
28        "/me/player/recently-played"
29    }
30}
31impl Endpoint for AddItemToQueueEndpoint {}
32
33pub async fn get_playback_state(
34    market: Option<&str>,
35    spotify: &Client<impl AuthFlow + Authorised>,
36) -> Result<PlaybackState> {
37    let market = market.map(|m| [("market", m)]);
38    spotify
39        .get::<[(&str, &str); 1], _>("/me/player".to_owned(), market)
40        .await
41}
42
43pub fn transfer_playback(device_id: impl Into<String>) -> TransferPlaybackEndpoint {
44    TransferPlaybackEndpoint {
45        device_ids: vec![device_id.into()],
46        play: None,
47    }
48}
49
50pub async fn get_available_devices(
51    spotify: &Client<impl AuthFlow + Authorised>,
52) -> Result<Vec<Device>> {
53    spotify
54        .get::<(), _>("/me/player/devices".to_owned(), None)
55        .await
56        .map(|d: Devices| d.devices)
57}
58
59pub async fn get_currently_playing_track(
60    market: Option<&str>,
61    spotify: &Client<impl AuthFlow + Authorised>,
62) -> Result<CurrentlyPlayingItem> {
63    let market = market.map(|m| [("market", m)]);
64    spotify
65        .get::<Option<[(&str, &str); 1]>, _>("/me/player/currently-playing".to_owned(), market)
66        .await
67}
68
69pub fn start_playback() -> StartPlaybackEndpoint {
70    StartPlaybackEndpoint::default()
71}
72
73pub async fn pause_playback(
74    device_id: Option<&str>,
75    spotify: &Client<impl AuthFlow + Authorised>,
76) -> Result<Nil> {
77    let device_id = device_id.map(|d| [("device_id", d)]);
78    spotify
79        .request(Method::PUT, "/me/player/pause".to_owned(), device_id, None)
80        .await
81}
82
83pub async fn skip_to_next(
84    device_id: Option<&str>,
85    spotify: &Client<impl AuthFlow + Authorised>,
86) -> Result<Nil> {
87    let device_id = device_id.map(|d| [("device_id", d)]);
88    spotify
89        .request(Method::POST, "/me/player/next".to_owned(), device_id, None)
90        .await
91}
92
93pub async fn skip_to_previous(
94    device_id: Option<&str>,
95    spotify: &Client<impl AuthFlow + Authorised>,
96) -> Result<Nil> {
97    let device_id = device_id.map(|d| [("device_id", d)]);
98    spotify
99        .request(
100            Method::POST,
101            "/me/player/previous".to_owned(),
102            device_id,
103            None,
104        )
105        .await
106}
107
108pub fn seek_to_position(position: u32) -> SeekToPositionEndpoint {
109    SeekToPositionEndpoint {
110        position_ms: position,
111        device_id: None,
112    }
113}
114
115pub fn set_repeat_mode(repeat_mode: RepeatMode) -> SetRepeatModeEndpoint {
116    SetRepeatModeEndpoint {
117        state: repeat_mode,
118        device_id: None,
119    }
120}
121
122pub fn set_playback_volume(volume: u32) -> SetPlaybackVolumeEndpoint {
123    SetPlaybackVolumeEndpoint {
124        volume_percent: volume,
125        device_id: None,
126    }
127}
128
129pub fn toggle_playback_shuffle(shuffle: bool) -> ToggleShuffleEndpoint {
130    ToggleShuffleEndpoint {
131        state: shuffle,
132        device_id: None,
133    }
134}
135
136pub fn recently_played_tracks() -> RecentlyPlayedTracksEndpoint {
137    RecentlyPlayedTracksEndpoint::default()
138}
139
140pub async fn get_user_queue(spotify: &Client<impl AuthFlow + Authorised>) -> Result<Queue> {
141    spotify
142        .get::<(), _>("/me/player/queue".to_owned(), None)
143        .await
144}
145
146pub fn add_item_to_queue(uri: impl Into<String>) -> AddItemToQueueEndpoint {
147    AddItemToQueueEndpoint {
148        uri: uri.into(),
149        device_id: None,
150    }
151}
152
153#[derive(Clone, Copy, Debug, Default, Serialize)]
154#[serde(rename_all = "snake_case")]
155pub enum RepeatMode {
156    Track,
157    Context,
158    #[default]
159    Off,
160}
161
162mod private {
163    use super::{After, Before, Unspecified};
164
165    pub trait Sealed {}
166
167    impl Sealed for After {}
168    impl Sealed for Before {}
169    impl Sealed for Unspecified {}
170}
171
172pub trait TimestampMarker: private::Sealed + Debug {}
173impl TimestampMarker for Before {}
174impl TimestampMarker for After {}
175impl TimestampMarker for Unspecified {}
176
177#[derive(Clone, Copy, Debug, Default)]
178pub struct After;
179
180#[derive(Clone, Copy, Debug, Default)]
181pub struct Before;
182
183#[derive(Clone, Copy, Debug, Default)]
184pub struct Unspecified;
185
186#[derive(Clone, Debug, Default, Serialize)]
187pub struct TransferPlaybackEndpoint {
188    pub(crate) device_ids: Vec<String>,
189    pub(crate) play: Option<bool>,
190}
191
192impl TransferPlaybackEndpoint {
193    /// If `true`, ensure playback happens on the new device.
194    /// Otherwise, keep the current playback state.
195    pub fn play(mut self, play: bool) -> Self {
196        self.play = Some(play);
197        self
198    }
199
200    #[doc = include_str!("../docs/send.md")]
201    pub async fn send(self, spotify: &Client<impl AuthFlow + Authorised>) -> Result<Nil> {
202        spotify.put("/me/player".to_owned(), Body::Json(self)).await
203    }
204}
205
206#[derive(Clone, Debug, Default, Serialize)]
207pub struct StartPlaybackEndpoint {
208    #[serde(skip)]
209    pub(crate) device_id: Option<String>,
210    pub(crate) context_uri: Option<String>,
211    pub(crate) uris: Option<Vec<String>>,
212    pub(crate) offset: Option<Value>,
213    pub(crate) position_ms: Option<u32>,
214}
215
216impl StartPlaybackEndpoint {
217    #[doc = include_str!("../docs/device_id.md")]
218    pub fn device_id(mut self, device_id: impl Into<String>) -> Self {
219        self.device_id = Some(device_id.into());
220        self
221    }
222
223    /// The *URI* of the context to play. Valid contexts are albums, artists and playlists.
224    pub fn context_uri(mut self, context_uri: impl Into<String>) -> Self {
225        self.context_uri = Some(context_uri.into());
226        self
227    }
228
229    /// The *URI*s of the tracks to play.
230    pub fn uris(mut self, uris: &[&str]) -> Self {
231        self.uris = Some(uris.iter().map(ToString::to_string).collect());
232        self
233    }
234
235    #[doc = include_str!("../docs/offset.md")]
236    pub fn offset(mut self, offset: u32) -> Self {
237        self.offset = Some(json!({ "position": offset }));
238        self
239    }
240
241    /// The *URI* of the track to start/resume playback.
242    /// The track must be in the context specified by `context_uri`.
243    pub fn offset_uri(mut self, uri: &str) -> Self {
244        self.offset = Some(json!({ "uri": uri }));
245        self
246    }
247
248    /// The position at which to start/resume the playback.
249    pub fn position_ms(mut self, position_ms: u32) -> Self {
250        self.position_ms = Some(position_ms);
251        self
252    }
253
254    #[doc = include_str!("../docs/send.md")]
255    pub async fn send(self, spotify: &Client<impl AuthFlow + Authorised>) -> Result<Nil> {
256        let endpoint = match self.device_id {
257            Some(ref id) => format!("/me/player/play?device_id={id}"),
258            None => "/me/player/play".to_owned(),
259        };
260
261        spotify.put(endpoint, Body::Json(self)).await
262    }
263}
264
265#[derive(Clone, Debug, Default, Serialize)]
266pub struct SeekToPositionEndpoint {
267    pub(crate) position_ms: u32,
268    pub(crate) device_id: Option<String>,
269}
270
271impl SeekToPositionEndpoint {
272    #[doc = include_str!("../docs/device_id.md")]
273    pub fn device_id(mut self, device_id: impl Into<String>) -> Self {
274        self.device_id = Some(device_id.into());
275        self
276    }
277
278    #[doc = include_str!("../docs/send.md")]
279    pub async fn send(self, spotify: &Client<impl AuthFlow + Authorised>) -> Result<Nil> {
280        spotify
281            .request(Method::PUT, "/me/player/seek".to_owned(), self.into(), None)
282            .await
283    }
284}
285
286#[derive(Clone, Debug, Default, Serialize)]
287pub struct SetRepeatModeEndpoint {
288    pub(crate) state: RepeatMode,
289    pub(crate) device_id: Option<String>,
290}
291
292impl SetRepeatModeEndpoint {
293    #[doc = include_str!("../docs/device_id.md")]
294    pub fn device_id(mut self, device_id: impl Into<String>) -> Self {
295        self.device_id = Some(device_id.into());
296        self
297    }
298
299    #[doc = include_str!("../docs/send.md")]
300    pub async fn send(self, spotify: &Client<impl AuthFlow + Authorised>) -> Result<Nil> {
301        spotify
302            .request(
303                Method::PUT,
304                "/me/player/repeat".to_owned(),
305                self.into(),
306                None,
307            )
308            .await
309    }
310}
311
312#[derive(Clone, Debug, Default, Serialize)]
313pub struct SetPlaybackVolumeEndpoint {
314    pub(crate) volume_percent: u32,
315    pub(crate) device_id: Option<String>,
316}
317
318impl SetPlaybackVolumeEndpoint {
319    #[doc = include_str!("../docs/device_id.md")]
320    pub fn device_id(mut self, device_id: impl Into<String>) -> Self {
321        self.device_id = Some(device_id.into());
322        self
323    }
324
325    #[doc = include_str!("../docs/send.md")]
326    pub async fn send(self, spotify: &Client<impl AuthFlow + Authorised>) -> Result<Nil> {
327        spotify
328            .request(
329                Method::PUT,
330                "/me/player/volume".to_owned(),
331                self.into(),
332                None,
333            )
334            .await
335    }
336}
337
338#[derive(Clone, Debug, Default, Serialize)]
339pub struct ToggleShuffleEndpoint {
340    pub(crate) state: bool,
341    pub(crate) device_id: Option<String>,
342}
343
344impl ToggleShuffleEndpoint {
345    #[doc = include_str!("../docs/device_id.md")]
346    pub fn device_id(mut self, device_id: impl Into<String>) -> Self {
347        self.device_id = Some(device_id.into());
348        self
349    }
350
351    #[doc = include_str!("../docs/send.md")]
352    pub async fn send(self, spotify: &Client<impl AuthFlow + Authorised>) -> Result<Nil> {
353        spotify
354            .request(
355                Method::PUT,
356                "/me/player/shuffle".to_owned(),
357                self.into(),
358                None,
359            )
360            .await
361    }
362}
363
364#[derive(Clone, Debug, Default, Serialize)]
365pub struct RecentlyPlayedTracksEndpoint<T: TimestampMarker = Unspecified> {
366    pub(crate) limit: Option<u32>,
367    pub(crate) after: Option<u64>,
368    pub(crate) before: Option<u64>,
369    marker: PhantomData<T>,
370}
371
372impl RecentlyPlayedTracksEndpoint<Unspecified> {
373    /// A Unix timestamp in miliseconds. Returns all items after (but not including) this cursor position.
374    pub fn after(self, after: u64) -> RecentlyPlayedTracksEndpoint<After> {
375        RecentlyPlayedTracksEndpoint {
376            limit: self.limit,
377            after: Some(after),
378            before: self.before,
379            marker: PhantomData,
380        }
381    }
382
383    /// A Unix timestamp in miliseconds. Returns all items before (but not including) this cursor position.
384    pub fn before(self, before: u64) -> RecentlyPlayedTracksEndpoint<Before> {
385        RecentlyPlayedTracksEndpoint {
386            limit: self.limit,
387            after: self.after,
388            before: Some(before),
389            marker: PhantomData,
390        }
391    }
392}
393
394impl<T: TimestampMarker + Default> RecentlyPlayedTracksEndpoint<T> {
395    #[doc = include_str!("../docs/limit.md")]
396    pub fn limit(mut self, limit: u32) -> Self {
397        self.limit = Some(limit);
398        self
399    }
400
401    #[doc = include_str!("../docs/send.md")]
402    pub async fn get(
403        self,
404        spotify: &Client<impl AuthFlow + Authorised>,
405    ) -> Result<CursorPage<PlayHistory, Self>> {
406        spotify
407            .get("/me/player/recently-played".to_owned(), self)
408            .await
409    }
410}
411
412#[derive(Clone, Debug, Default, Serialize)]
413pub struct AddItemToQueueEndpoint {
414    pub(crate) uri: String,
415    pub(crate) device_id: Option<String>,
416}
417
418impl AddItemToQueueEndpoint {
419    #[doc = include_str!("../docs/device_id.md")]
420    pub fn device_id(mut self, device_id: impl Into<String>) -> Self {
421        self.device_id = Some(device_id.into());
422        self
423    }
424
425    #[doc = include_str!("../docs/send.md")]
426    pub async fn send(self, spotify: &Client<impl AuthFlow + Authorised>) -> Result<Nil> {
427        spotify
428            .request(
429                Method::POST,
430                "/me/player/queue".to_owned(),
431                self.into(),
432                None,
433            )
434            .await
435    }
436}