From c0416972b6492ebc693cb924e162d8298fc35467 Mon Sep 17 00:00:00 2001 From: ashthespy Date: Wed, 13 Mar 2019 20:35:46 +0100 Subject: [PATCH] Support Dailymixes and refactor dynamic playlists --- connect/src/context.rs | 86 +++++++++++++++++++++++++++++++++++ connect/src/lib.rs | 1 + connect/src/spirc.rs | 101 +++++++++++++++++++---------------------- 3 files changed, 133 insertions(+), 55 deletions(-) create mode 100644 connect/src/context.rs diff --git a/connect/src/context.rs b/connect/src/context.rs new file mode 100644 index 00000000..36e55711 --- /dev/null +++ b/connect/src/context.rs @@ -0,0 +1,86 @@ +use core::spotify_id::SpotifyId; +use protocol::spirc::TrackRef; + +use serde; + +#[derive(Deserialize, Debug)] +pub struct StationContext { + pub uri: Option, + pub next_page_url: String, + #[serde(deserialize_with = "deserialize_protobuf_TrackRef")] + pub tracks: Vec, + // Not required for core functionality + // pub seeds: Vec, + // #[serde(rename = "imageUri")] + // pub image_uri: String, + // pub subtitle: Option, + // pub subtitles: Vec, + // #[serde(rename = "subtitleUri")] + // pub subtitle_uri: Option, + // pub title: String, + // #[serde(rename = "titleUri")] + // pub title_uri: String, + // pub related_artists: Vec, +} + +#[derive(Deserialize, Debug)] +pub struct PageContext { + pub uri: String, + pub next_page_url: String, + #[serde(deserialize_with = "deserialize_protobuf_TrackRef")] + pub tracks: Vec, + // Not required for core functionality + // pub url: String, + // // pub restrictions: +} + +#[derive(Deserialize, Debug)] +pub struct TrackContext { + #[serde(rename = "original_gid")] + pub gid: String, + pub uri: String, + pub uid: String, + // Not required for core functionality + // pub album_uri: String, + // pub artist_uri: String, + // pub metadata: MetadataContext, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct ArtistContext { + artist_name: String, + artist_uri: String, + image_uri: String, +} + +#[derive(Deserialize, Debug)] +pub struct MetadataContext { + album_title: String, + artist_name: String, + artist_uri: String, + image_url: String, + title: String, + uid: String, +} + +#[allow(non_snake_case)] +fn deserialize_protobuf_TrackRef(de: D) -> Result, D::Error> +where + D: serde::Deserializer, +{ + let v: Vec = try!(serde::Deserialize::deserialize(de)); + let track_vec = v + .iter() + .map(|v| { + let mut t = TrackRef::new(); + // This has got to be the most round about way of doing this. + t.set_gid(SpotifyId::from_base62(&v.gid).unwrap().to_raw().to_vec()); + t.set_uri(v.uri.to_owned()); + + t + }) + .collect::>(); + + Ok(track_vec) +} diff --git a/connect/src/lib.rs b/connect/src/lib.rs index f1eb9972..9dad97f1 100644 --- a/connect/src/lib.rs +++ b/connect/src/lib.rs @@ -26,5 +26,6 @@ extern crate librespot_core as core; extern crate librespot_playback as playback; extern crate librespot_protocol as protocol; +pub mod context; pub mod discovery; pub mod spirc; diff --git a/connect/src/spirc.rs b/connect/src/spirc.rs index 54be3534..e810ef9a 100644 --- a/connect/src/spirc.rs +++ b/connect/src/spirc.rs @@ -12,60 +12,18 @@ use core::version; use core::volume::Volume; use protocol; -use protocol::spirc::{DeviceState, Frame, MessageType, PlayStatus, State, TrackRef}; +use protocol::spirc::{DeviceState, Frame, MessageType, PlayStatus, State}; use playback::mixer::Mixer; use playback::player::Player; -use serde; use serde_json; +use context::StationContext; use rand; use rand::seq::SliceRandom; use std; use std::time::{SystemTime, UNIX_EPOCH}; -// Keep this here for now - -#[derive(Deserialize, Debug)] -struct TrackContext { - album_uri: String, - artist_uri: String, - // metadata: String, - #[serde(rename = "original_gid")] - gid: String, - uid: String, - uri: String, -} -#[derive(Deserialize, Debug)] -struct StationContext { - uri: String, - next_page_url: String, - seeds: Vec, - #[serde(deserialize_with = "deserialize_protobuf_TrackRef")] - tracks: Vec, -} - -#[allow(non_snake_case)] -fn deserialize_protobuf_TrackRef(de: D) -> Result, D::Error> -where - D: serde::Deserializer, -{ - let v: Vec = try!(serde::Deserialize::deserialize(de)); - let track_vec = v - .iter() - .map(|v| { - let mut t = TrackRef::new(); - // This has got to be the most round about way of doing this. - t.set_gid(SpotifyId::from_base62(&v.gid).unwrap().to_raw().to_vec()); - t.set_uri(v.uri.to_owned()); - - t - }) - .collect::>(); - - Ok(track_vec) -} - pub struct SpircTask { player: Player, mixer: Box, @@ -396,12 +354,26 @@ impl Future for SpircTask { match self.context_fut.poll() { Ok(Async::Ready(value)) => { - let r_context = serde_json::from_value::(value).ok(); - debug!("Radio Context: {:#?}", r_context); - if let Some(ref context) = r_context { - warn!("Got {:?} tracks from <{}>", context.tracks.len(), context.uri); - } - self.context = r_context; + let r_context = serde_json::from_value::(value.clone()); + self.context = match r_context { + Ok(context) => { + info!( + "Resolved {:?} tracks from <{:?}>", + context.tracks.len(), + self.state.get_context_uri(), + ); + Some(context) + } + Err(e) => { + error!("Unable to parse JSONContext {:?}\n{:?}", e, value); + None + } + }; + // It needn't be so verbose - can be as simple as + // if let Some(ref context) = r_context { + // info!("Got {:?} tracks from <{}>", context.tracks.len(), context.uri); + // } + // self.context = r_context; progress = true; self.context_fut = Box::new(future::empty()); @@ -409,7 +381,7 @@ impl Future for SpircTask { Ok(Async::NotReady) => (), Err(err) => { self.context_fut = Box::new(future::empty()); - error!("Error: {:?}", err) + error!("ContextError: {:?}", err) } } } @@ -686,7 +658,9 @@ impl SpircTask { self.state.get_track().len() as u32 - new_index < 5 ); let context_uri = self.state.get_context_uri().to_owned(); - if context_uri.contains("station") && ((self.state.get_track().len() as u32) - new_index) < 5 { + if (context_uri.starts_with("spotify:station:") || context_uri.starts_with("spotify:dailymix:")) + && ((self.state.get_track().len() as u32) - new_index) < 5 + { self.context_fut = self.resolve_station(&context_uri); self.update_tracks_from_context(); } @@ -808,7 +782,7 @@ impl SpircTask { let context_uri = frame.get_state().get_context_uri().to_owned(); let tracks = frame.get_state().get_track(); debug!("Frame has {:?} tracks", tracks.len()); - if context_uri.contains("station") { + if context_uri.starts_with("spotify:station:") || context_uri.starts_with("spotify:dailymix:") { self.context_fut = self.resolve_station(&context_uri); } @@ -820,9 +794,26 @@ impl SpircTask { } fn load_track(&mut self, play: bool) { - let index = self.state.get_playing_track_index(); + let mut index = self.state.get_playing_track_index(); let track = { - let gid = self.state.get_track()[index as usize].get_gid(); + // let gid = self.state.get_track()[index as usize].get_gid(); + let track_ref = self.state.get_track()[index as usize].to_owned(); + let mut gid = track_ref.get_gid(); + if gid.len() != 16 { + let track_len = self.state.get_track().len() as u32; + if index < track_len - 1 { + index = 0; + } else { + index = index + 1; + } + warn!( + "Skipping track {:?} at position [{}] of {}", + track_ref.get_uri(), + index, + track_len + ); + gid = self.state.get_track()[index as usize].get_gid(); + } SpotifyId::from_raw(gid).unwrap() }; let position = self.state.get_position_ms();