use crate::*;
use serde::{Deserialize, Serialize};
use std::time::Duration;
pub async fn get_devices(token: &AccessToken) -> Result<Vec<Device>, EndpointError<PlayerError>> {
#[derive(Deserialize)]
struct Devices {
devices: Vec<Device>,
}
Ok(request!(
token,
GET "/v1/me/player/devices",
ret = Devices
)
.devices)
}
pub async fn get_playback(
token: &AccessToken,
market: Option<Market>,
) -> Result<Option<CurrentPlayback>, EndpointError<PlayerError>> {
Ok(Some(request!(
token,
GET "/v1/me/player",
optional_query_params = {"market": market.map(|m| m.as_str())},
ret = CurrentPlayback,
or_else = None
)))
}
pub async fn get_recently_played(
token: &AccessToken,
limit: usize,
after: Option<String>,
before: Option<String>,
) -> Result<Option<TwoWayCursorPage<PlayHistory>>, EndpointError<PlayerError>> {
Ok(Some(request!(
token,
GET "/v1/me/player/recently-played",
query_params = {"limit": limit.to_string()},
optional_query_params = {"after": after, "before": before},
ret = TwoWayCursorPage<PlayHistory>,
or_else = None
)))
}
pub async fn get_playing_track(
token: &AccessToken,
market: Option<Market>,
) -> Result<Option<CurrentlyPlaying>, EndpointError<PlayerError>> {
Ok(Some(request!(
token,
GET "/v1/me/player/currently-playing",
optional_query_params = {"market": market.map(|m| m.as_str())},
ret = CurrentlyPlaying,
or_else = None
)))
}
pub async fn pause(
token: &AccessToken,
device_id: Option<&str>,
) -> Result<(), EndpointError<PlayerError>> {
request!(
token,
PUT "/v1/me/player/pause",
optional_query_params = {"device_id": device_id},
body = "{}"
);
Ok(())
}
pub async fn seek(
token: &AccessToken,
position: Duration,
device_id: Option<&str>,
) -> Result<(), EndpointError<PlayerError>> {
request!(
token,
PUT "/v1/me/player/seek",
query_params = {"position_ms": position.as_millis().to_string()},
optional_query_params = {"device_id": device_id},
body = "{}"
);
Ok(())
}
pub async fn set_repeat(
token: &AccessToken,
state: RepeatState,
device_id: Option<&str>,
) -> Result<(), EndpointError<PlayerError>> {
request!(
token,
PUT "/v1/me/player/repeat",
query_params = {"state": state.as_str()},
optional_query_params = {"device_id": device_id},
body = "{}"
);
Ok(())
}
pub async fn set_volume(
token: &AccessToken,
volume_percent: i32,
device_id: Option<&str>,
) -> Result<(), EndpointError<PlayerError>> {
request!(
token,
PUT "/v1/me/player/volume",
query_params = {"volume_percent": volume_percent.to_string()},
optional_query_params = {"device_id": device_id},
body = "{}"
);
Ok(())
}
pub async fn skip_next(
token: &AccessToken,
device_id: Option<&str>,
) -> Result<(), EndpointError<PlayerError>> {
request!(
token,
POST "/v1/me/player/next",
optional_query_params = {"device_id": device_id},
body = "{}"
);
Ok(())
}
pub async fn skip_prev(
token: &AccessToken,
device_id: Option<&str>,
) -> Result<(), EndpointError<PlayerError>> {
request!(
token,
POST "/v1/me/player/previous",
optional_query_params = {"device_id": device_id},
body = "{}"
);
Ok(())
}
#[derive(Debug, Clone)]
pub enum Play<'s, 'i> {
Context(ItemType, &'i str, usize),
Tracks(&'s [&'s str]),
}
pub async fn play(
token: &AccessToken,
play: Option<Play<'_, '_>>,
position: Option<Duration>,
device_id: Option<&str>,
) -> Result<(), EndpointError<PlayerError>> {
#[derive(Serialize)]
struct Offset {
position: usize,
}
#[derive(Serialize)]
struct Body {
context_uri: Option<String>,
offset: Option<Offset>,
uris: Option<Vec<String>>,
position_ms: Option<u128>,
}
let mut body = Body {
context_uri: None,
offset: None,
uris: None,
position_ms: position.map(|duration| duration.as_millis()),
};
if let Some(play) = play {
match play {
Play::Context(context_type, id, position) => {
body.context_uri = Some(format!("spotify:{}:{}", context_type.as_str(), id));
body.offset = Some(Offset { position });
}
Play::Tracks(ids) => {
body.uris = Some(ids.iter().map(|s| format!("spotify:track:{}", s)).collect());
}
}
}
request!(
token,
PUT "/v1/me/player/play",
optional_query_params = {"device_id": device_id},
body = serde_json::to_string(&body)?
);
Ok(())
}
pub async fn set_shuffle(
token: &AccessToken,
shuffle: bool,
device_id: Option<&str>,
) -> Result<(), EndpointError<PlayerError>> {
request!(
token,
PUT "/v1/me/player/shuffle",
query_params = {"state": if shuffle {"true"} else {"false"}},
optional_query_params = {"deivce_id": device_id},
body = "{}"
);
Ok(())
}
pub async fn transfer(
token: &AccessToken,
id: &str,
play: bool,
) -> Result<(), EndpointError<PlayerError>> {
request!(
token,
PUT "/v1/me/player",
body = format!(r#"{{"device_ids":["{}"],"play":{}}}"#, id, play)
);
Ok(())
}
#[cfg(test)]
mod tests {
use crate::endpoints::token;
use crate::*;
use std::time::Duration;
use tokio::time;
#[tokio::test]
async fn test() {
let token = token().await;
let mut devices = get_devices(&token).await.unwrap().into_iter();
let device = loop {
let device = devices
.next()
.expect("You must have at least one usable device for this test to work.");
if !device.is_restricted && device.id.is_some() && !device.is_private_session {
break device;
}
};
let id = &device.id.as_ref().unwrap();
if !device.is_active {
println!("Transferring device to {}...", device.name);
transfer(&token, id, false).await.unwrap();
}
let wait_time = Duration::from_millis(300);
play(
&token,
Some(Play::Context(ItemType::Album, "3lBPyXvg1hhoJ1REnw80fZ", 2)),
Some(Duration::from_secs(10)),
None,
)
.await
.unwrap();
time::delay_for(wait_time).await;
let playback = get_playback(&token, Some(Market::FromToken))
.await
.unwrap()
.unwrap();
assert_eq!(playback.device.id, device.id);
assert_eq!(playback.device.name, device.name);
assert_eq!(playback.device.device_type, device.device_type);
assert_eq!(playback.device.volume_percent, device.volume_percent);
let context = playback.currently_playing.context.unwrap();
assert_eq!(context.context_type, ItemType::Album);
assert_eq!(context.id, "3lBPyXvg1hhoJ1REnw80fZ");
assert!(playback.currently_playing.progress.unwrap() >= Duration::from_secs(10));
assert!(playback.currently_playing.is_playing);
let track = &playback.currently_playing.item.unwrap();
assert_eq!(track.album.id, "3lBPyXvg1hhoJ1REnw80fZ");
assert_eq!(track.track_number, 3);
assert_eq!(
playback.currently_playing.currently_playing_type,
TrackType::Track
);
play(
&token,
Some(Play::Tracks(&[
"2Wbz0QcXCVYmuBgOwUV6KU",
"0vjYxBDAcflD0358arIVZG",
])),
None,
None,
)
.await
.unwrap();
time::delay_for(wait_time).await;
let playing = get_playing_track(&token, Some(Market::FromToken))
.await
.unwrap()
.unwrap();
assert!(playing.progress.unwrap() < Duration::from_secs(2));
assert!(playing.is_playing);
let track = &playing.item.unwrap();
assert_eq!(track.id, "2Wbz0QcXCVYmuBgOwUV6KU");
assert_eq!(playing.currently_playing_type, TrackType::Track);
seek(&token, Duration::from_millis(152106 - 2), None)
.await
.unwrap();
time::delay_for(wait_time).await;
let playing = get_playing_track(&token, Some(Market::FromToken))
.await
.unwrap()
.unwrap();
assert_eq!(playing.item.unwrap().id, "0vjYxBDAcflD0358arIVZG");
set_repeat(&token, RepeatState::Track, None).await.unwrap();
set_shuffle(&token, true, None).await.unwrap();
set_volume(&token, 17, None).await.unwrap();
time::delay_for(wait_time).await;
let playback = get_playback(&token, Some(Market::FromToken))
.await
.unwrap()
.unwrap();
assert_eq!(playback.repeat_state, RepeatState::Track);
assert_eq!(playback.shuffle_state, true);
assert_eq!(playback.device.volume_percent.unwrap(), 17);
set_repeat(&token, RepeatState::Context, None)
.await
.unwrap();
set_shuffle(&token, false, None).await.unwrap();
set_volume(&token, 73, None).await.unwrap();
time::delay_for(wait_time).await;
let playback = get_playback(&token, Some(Market::FromToken))
.await
.unwrap()
.unwrap();
assert_eq!(playback.repeat_state, RepeatState::Context);
assert_eq!(playback.shuffle_state, false);
assert_eq!(playback.device.volume_percent.unwrap(), 73);
skip_prev(&token, None).await.unwrap();
time::delay_for(wait_time).await;
let playing = get_playing_track(&token, Some(Market::FromToken))
.await
.unwrap()
.unwrap();
assert_eq!(playing.item.unwrap().id, "2Wbz0QcXCVYmuBgOwUV6KU");
skip_next(&token, None).await.unwrap();
time::delay_for(wait_time).await;
let playing = get_playing_track(&token, Some(Market::FromToken))
.await
.unwrap()
.unwrap();
assert_eq!(playing.item.unwrap().id, "0vjYxBDAcflD0358arIVZG");
pause(&token, None).await.unwrap();
time::delay_for(wait_time).await;
let playback = get_playback(&token, Some(Market::FromToken))
.await
.unwrap()
.unwrap();
assert!(!playback.currently_playing.is_playing);
}
#[tokio::test]
async fn test_recent() {
get_recently_played(&token().await, 3, None, None)
.await
.unwrap();
}
}