use std::time::Duration;
use serde::{Deserialize, Serialize};
use super::{id::PlayableContext, track::FullTrack, ExternalUrls, ItemType};
use crate::{prelude::IdTrait, util::duration_millis};
#[derive(Debug, Clone, Eq, Serialize, Deserialize)]
pub struct Device {
name: String,
id: String,
volume_percent: u8,
is_active: bool,
is_private_session: bool,
is_restricted: bool,
#[serde(rename = "type")]
device_type: DeviceType,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum DeviceType {
Computer,
Tablet,
Smartphone,
Speaker,
TV,
AVR,
STB,
AudioDongle,
GameConsole,
CastVideo,
CastAudio,
Automobile,
Unknown,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PlaybackState {
device: Device,
repeat_state: RepeatState,
shuffle_state: bool,
#[serde(flatten)]
currently_playing: CurrentlyPlayingItem,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CurrentlyPlayingItem {
timestamp: u64, is_playing: bool,
actions: Actions,
#[serde(flatten)]
public_playing_track: Option<PublicPlayingItem>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PublicPlayingItem {
context: Option<Context>,
#[serde(rename = "progress_ms", with = "duration_millis")]
progress: Duration,
#[serde(flatten)]
item: PlayingType,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Context {
#[serde(rename = "type")]
context_type: ItemType,
#[serde(default)]
external_urls: ExternalUrls,
uri: PlayableContext<'static>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct Actions {
pub disallows: Disallows,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct Disallows {
#[serde(default)]
pub interrupting_playback: bool,
#[serde(default)]
pub pausing: bool,
#[serde(default)]
pub resuming: bool,
#[serde(default)]
pub seeking: bool,
#[serde(default)]
pub skipping_next: bool,
#[serde(default)]
pub skipping_prev: bool,
#[serde(default)]
pub toggling_repeat_context: bool,
#[serde(default)]
pub toggling_shuffle: bool,
#[serde(default)]
pub toggling_repeat_track: bool,
#[serde(default)]
pub transferring_playback: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", tag = "currently_playing_type", content = "item")]
#[non_exhaustive]
pub enum PlayingType {
Track(FullTrack),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RepeatState {
Off,
Track,
Context,
}
impl Device {
pub fn name(&self) -> &str {
&self.name
}
pub fn id(&self) -> &str {
&self.id
}
pub fn volume_percent(&self) -> u8 {
self.volume_percent
}
pub fn is_active(&self) -> bool {
self.is_active
}
pub fn is_private_session(&self) -> bool {
self.is_private_session
}
pub fn is_restricted(&self) -> bool {
self.is_restricted
}
pub fn device_type(&self) -> DeviceType {
self.device_type
}
}
impl PartialEq for Device {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl PlaybackState {
pub fn device(&self) -> &Device {
&self.device
}
pub fn take_device(self) -> Device {
self.device
}
pub fn repeat_state(&self) -> RepeatState {
self.repeat_state
}
pub fn shuffle_state(&self) -> bool {
self.shuffle_state
}
pub fn currently_playing_item(&self) -> &CurrentlyPlayingItem {
&self.currently_playing
}
pub fn take_currently_playing_item(self) -> CurrentlyPlayingItem {
self.currently_playing
}
}
impl CurrentlyPlayingItem {
pub fn timestamp(&self) -> u64 {
self.timestamp
}
pub fn is_playing(&self) -> bool {
self.is_playing
}
pub fn actions(&self) -> Actions {
self.actions
}
pub fn public_playing_item(&self) -> Option<&PublicPlayingItem> {
self.public_playing_track.as_ref()
}
pub fn take_public_playing_item(self) -> Option<PublicPlayingItem> {
self.public_playing_track
}
}
impl PublicPlayingItem {
pub fn context(&self) -> Option<&Context> {
self.context.as_ref()
}
pub fn progress(&self) -> Duration {
self.progress
}
pub fn item(&self) -> &PlayingType {
&self.item
}
pub fn take_item(self) -> PlayingType {
self.item
}
}
impl RepeatState {
pub fn as_str(self) -> &'static str {
match self {
RepeatState::Off => "off",
RepeatState::Track => "track",
RepeatState::Context => "context",
}
}
}
impl Context {
pub fn external_urls(&self) -> &ExternalUrls {
&self.external_urls
}
pub fn id(&self) -> PlayableContext {
self.uri.as_borrowed()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn deserialize_context_for_playlist() {
let json = r#"{
"external_urls": {
"spotify": "https://open.spotify.com/playlist/37i9dQZF1DWZipvLjDtZYe"
},
"href": "https://api.spotify.com/v1/playlists/37i9dQZF1DWZipvLjDtZYe",
"type": "playlist",
"uri": "spotify:playlist:37i9dQZF1DWZipvLjDtZYe"
}"#;
let context: Context = serde_json::from_str(json).unwrap();
assert!(matches!(context.uri, PlayableContext::Playlist(_)));
assert_eq!("37i9dQZF1DWZipvLjDtZYe", context.uri.as_str());
}
#[test]
fn deserialize_context_for_collection() {
let json = r#"{
"external_urls": {
"spotify": "https://open.spotify.com/collection/tracks"
},
"href": "https://api.spotify.com/v1/me/tracks",
"type": "collection",
"uri": "spotify:user:1337420:collection"
}"#;
let context: Context = serde_json::from_str(json).unwrap();
assert!(matches!(context.uri, PlayableContext::Collection(_)));
assert_eq!("1337420", context.uri.as_str());
}
}