@@ -16,7 +16,7 @@ use tokio_stream::wrappers::UnboundedReceiverStream;
1616
1717use crate :: {
1818 config:: ConnectConfig ,
19- context:: { PageContext , StationContext } ,
19+ context:: PageContext ,
2020 core:: {
2121 authentication:: Credentials , mercury:: MercurySender , session:: UserAttributes ,
2222 util:: SeqGenerator , version, Error , Session , SpotifyId ,
@@ -100,7 +100,7 @@ struct SpircTask {
100100 session : Session ,
101101 resolve_context : Option < String > ,
102102 autoplay_context : bool ,
103- context : Option < StationContext > ,
103+ context : Option < PageContext > ,
104104
105105 spirc_id : usize ,
106106}
@@ -124,7 +124,6 @@ pub enum SpircCommand {
124124 SetVolume ( u16 ) ,
125125}
126126
127- const CONTEXT_TRACKS_COUNT : usize = 50 ;
128127const CONTEXT_TRACKS_HISTORY : usize = 10 ;
129128const CONTEXT_FETCH_THRESHOLD : u32 = 5 ;
130129
@@ -184,6 +183,7 @@ fn initial_device_state(config: ConnectConfig) -> DeviceState {
184183 } ;
185184 {
186185 let msg = repeated. push_default ( ) ;
186+ // TODO: implement logout
187187 msg. set_typ ( protocol:: spirc:: CapabilityType :: kSupportsLogout) ;
188188 {
189189 let repeated = msg. mut_intValue ( ) ;
@@ -224,17 +224,51 @@ fn initial_device_state(config: ConnectConfig) -> DeviceState {
224224 } ;
225225 {
226226 let msg = repeated. push_default ( ) ;
227- msg. set_typ ( protocol:: spirc:: CapabilityType :: kSupportedContexts ) ;
227+ msg. set_typ ( protocol:: spirc:: CapabilityType :: kSupportsExternalEpisodes ) ;
228228 {
229- let repeated = msg. mut_stringValue ( ) ;
230- repeated. push ( :: std:: convert:: Into :: into ( "album" ) ) ;
231- repeated. push ( :: std:: convert:: Into :: into ( "playlist" ) ) ;
232- repeated. push ( :: std:: convert:: Into :: into ( "search" ) ) ;
233- repeated. push ( :: std:: convert:: Into :: into ( "inbox" ) ) ;
234- repeated. push ( :: std:: convert:: Into :: into ( "toplist" ) ) ;
235- repeated. push ( :: std:: convert:: Into :: into ( "starred" ) ) ;
236- repeated. push ( :: std:: convert:: Into :: into ( "publishedstarred" ) ) ;
237- repeated. push ( :: std:: convert:: Into :: into ( "track" ) )
229+ let repeated = msg. mut_intValue ( ) ;
230+ repeated. push ( 1 )
231+ } ;
232+ msg
233+ } ;
234+ {
235+ let msg = repeated. push_default ( ) ;
236+ // TODO: how would such a rename command be triggered? Handle it.
237+ msg. set_typ ( protocol:: spirc:: CapabilityType :: kSupportsRename) ;
238+ {
239+ let repeated = msg. mut_intValue ( ) ;
240+ repeated. push ( 1 )
241+ } ;
242+ msg
243+ } ;
244+ {
245+ let msg = repeated. push_default ( ) ;
246+ msg. set_typ ( protocol:: spirc:: CapabilityType :: kCommandAcks) ;
247+ {
248+ let repeated = msg. mut_intValue ( ) ;
249+ repeated. push ( 0 )
250+ } ;
251+ msg
252+ } ;
253+ {
254+ let msg = repeated. push_default ( ) ;
255+ // TODO: does this mean local files or the local network?
256+ // LAN may be an interesting privacy toggle.
257+ msg. set_typ ( protocol:: spirc:: CapabilityType :: kRestrictToLocal) ;
258+ {
259+ let repeated = msg. mut_intValue ( ) ;
260+ repeated. push ( 0 )
261+ } ;
262+ msg
263+ } ;
264+ {
265+ let msg = repeated. push_default ( ) ;
266+ // TODO: what does this hide, or who do we hide from?
267+ // May be an interesting privacy toggle.
268+ msg. set_typ ( protocol:: spirc:: CapabilityType :: kHidden) ;
269+ {
270+ let repeated = msg. mut_intValue ( ) ;
271+ repeated. push ( 0 )
238272 } ;
239273 msg
240274 } ;
@@ -243,9 +277,15 @@ fn initial_device_state(config: ConnectConfig) -> DeviceState {
243277 msg. set_typ ( protocol:: spirc:: CapabilityType :: kSupportedTypes) ;
244278 {
245279 let repeated = msg. mut_stringValue ( ) ;
246- repeated. push ( :: std:: convert:: Into :: into ( "audio/track" ) ) ;
247- repeated. push ( :: std:: convert:: Into :: into ( "audio/episode" ) ) ;
248- repeated. push ( :: std:: convert:: Into :: into ( "track" ) )
280+ repeated. push ( "audio/episode" . to_string ( ) ) ;
281+ repeated. push ( "audio/episode+track" . to_string ( ) ) ;
282+ repeated. push ( "audio/track" . to_string ( ) ) ;
283+ // other known types:
284+ // - "audio/ad"
285+ // - "audio/interruption"
286+ // - "audio/local"
287+ // - "video/ad"
288+ // - "video/episode"
249289 } ;
250290 msg
251291 } ;
@@ -498,35 +538,30 @@ impl SpircTask {
498538 break ;
499539 } ,
500540 context_uri = async { self . resolve_context. take( ) } , if self . resolve_context. is_some( ) => {
501- let context_uri = context_uri. unwrap( ) ;
502- let is_next_page = context_uri. starts_with( "hm://" ) ;
541+ let context_uri = context_uri. unwrap( ) ; // guaranteed above
542+ if context_uri. contains( "spotify:show:" ) || context_uri. contains( "spotify:episode:" ) {
543+ continue ; // not supported by apollo stations
544+ }
503545
504- let context = if is_next_page {
546+ let context = if context_uri . starts_with ( "hm://" ) {
505547 self . session. spclient( ) . get_next_page( & context_uri) . await
506548 } else {
507- let previous_tracks = self . state. get_track( ) . iter( ) . filter_map( |t| SpotifyId :: try_from( t) . ok( ) ) . collect( ) ;
508- self . session. spclient( ) . get_apollo_station( & context_uri, CONTEXT_TRACKS_COUNT , previous_tracks, self . autoplay_context) . await
549+ // only send previous tracks that were before the current playback position
550+ let current_position = self . state. get_playing_track_index( ) as usize ;
551+ let previous_tracks = self . state. get_track( ) [ ..current_position] . iter( ) . filter_map( |t| SpotifyId :: try_from( t) . ok( ) ) . collect( ) ;
552+
553+ let scope = if self . autoplay_context {
554+ "stations" // this returns a `StationContext` but we deserialize it into a `PageContext`
555+ } else {
556+ "tracks" // this returns a `PageContext`
557+ } ;
558+
559+ self . session. spclient( ) . get_apollo_station( scope, & context_uri, None , previous_tracks, self . autoplay_context) . await
509560 } ;
510561
511562 match context {
512563 Ok ( value) => {
513- let r_context = if is_next_page {
514- match serde_json:: from_slice:: <PageContext >( & value) {
515- Ok ( page_context) => {
516- // page contexts don't have the stations full metadata, so decorate it
517- let mut station_context = self . context. clone( ) . unwrap_or_default( ) ;
518- station_context. tracks = page_context. tracks;
519- station_context. next_page_url = page_context. next_page_url;
520- station_context. correlation_id = page_context. correlation_id;
521- Ok ( station_context)
522- } ,
523- Err ( e) => Err ( e) ,
524- }
525- } else {
526- serde_json:: from_slice:: <StationContext >( & value)
527- } ;
528-
529- self . context = match r_context {
564+ self . context = match serde_json:: from_slice:: <PageContext >( & value) {
530565 Ok ( context) => {
531566 info!(
532567 "Resolved {:?} tracks from <{:?}>" ,
@@ -829,7 +864,7 @@ impl SpircTask {
829864
830865 for entry in update. get_device_state ( ) . get_metadata ( ) . iter ( ) {
831866 match entry. get_field_type ( ) {
832- "client-id " => self . session . set_client_id ( entry. get_metadata ( ) ) ,
867+ "client_id " => self . session . set_client_id ( entry. get_metadata ( ) ) ,
833868 "brand_display_name" => self . session . set_client_brand_name ( entry. get_metadata ( ) ) ,
834869 "model_display_name" => self . session . set_client_model_name ( entry. get_metadata ( ) ) ,
835870 _ => ( ) ,
@@ -1207,17 +1242,23 @@ impl SpircTask {
12071242
12081243 // When in autoplay, keep topping up the playlist when it nears the end
12091244 if update_tracks {
1210- self . update_tracks_from_context ( ) ;
1211- new_index = self . state . get_playing_track_index ( ) ;
1212- tracks_len = self . state . get_track ( ) . len ( ) as u32 ;
1245+ if let Some ( ref context) = self . context {
1246+ self . resolve_context = Some ( context. next_page_url . to_owned ( ) ) ;
1247+ self . update_tracks_from_context ( ) ;
1248+ tracks_len = self . state . get_track ( ) . len ( ) as u32 ;
1249+ }
12131250 }
12141251
12151252 // When not in autoplay, either start autoplay or loop back to the start
12161253 if new_index >= tracks_len {
1217- if self . session . autoplay ( ) {
1254+ // for some contexts there is no autoplay, such as shows and episodes
1255+ // in such cases there is no context in librespot.
1256+ if self . context . is_some ( ) && self . session . autoplay ( ) {
12181257 // Extend the playlist
12191258 debug ! ( "Starting autoplay for <{}>" , context_uri) ;
1259+ // force reloading the current context with an autoplay context
12201260 self . autoplay_context = true ;
1261+ self . resolve_context = Some ( self . state . get_context_uri ( ) . to_owned ( ) ) ;
12211262 self . update_tracks_from_context ( ) ;
12221263 self . player . set_auto_normalise_as_album ( false ) ;
12231264 } else {
@@ -1306,17 +1347,10 @@ impl SpircTask {
13061347
13071348 fn update_tracks_from_context ( & mut self ) {
13081349 if let Some ( ref context) = self . context {
1309- self . resolve_context =
1310- if !self . autoplay_context || context. next_page_url . contains ( "autoplay=true" ) {
1311- Some ( context. next_page_url . to_owned ( ) )
1312- } else {
1313- // this arm means: we need to resolve for autoplay,
1314- // and were previously resolving for the original context
1315- Some ( context. uri . to_owned ( ) )
1316- } ;
1317-
13181350 let new_tracks = & context. tracks ;
1351+
13191352 debug ! ( "Adding {:?} tracks from context to frame" , new_tracks. len( ) ) ;
1353+
13201354 let mut track_vec = self . state . take_track ( ) . into_vec ( ) ;
13211355 if let Some ( head) = track_vec. len ( ) . checked_sub ( CONTEXT_TRACKS_HISTORY ) {
13221356 track_vec. drain ( 0 ..head) ;
@@ -1342,22 +1376,22 @@ impl SpircTask {
13421376 trace ! ( "State: {:#?}" , frame. get_state( ) ) ;
13431377
13441378 let index = frame. get_state ( ) . get_playing_track_index ( ) ;
1345- let context_uri = frame. get_state ( ) . get_context_uri ( ) . to_owned ( ) ;
1379+ let context_uri = frame. get_state ( ) . get_context_uri ( ) ;
13461380 let tracks = frame. get_state ( ) . get_track ( ) ;
13471381
13481382 trace ! ( "Frame has {:?} tracks" , tracks. len( ) ) ;
13491383
13501384 // First the tracks from the requested context, without autoplay.
13511385 // We will transition into autoplay after the latest track of this context.
13521386 self . autoplay_context = false ;
1353- self . resolve_context = Some ( context_uri. clone ( ) ) ;
1387+ self . resolve_context = Some ( context_uri. to_owned ( ) ) ;
13541388
13551389 self . player
13561390 . set_auto_normalise_as_album ( context_uri. starts_with ( "spotify:album:" ) ) ;
13571391
13581392 self . state . set_playing_track_index ( index) ;
13591393 self . state . set_track ( tracks. iter ( ) . cloned ( ) . collect ( ) ) ;
1360- self . state . set_context_uri ( context_uri) ;
1394+ self . state . set_context_uri ( context_uri. to_owned ( ) ) ;
13611395 // has_shuffle/repeat seem to always be true in these replace msgs,
13621396 // but to replicate the behaviour of the Android client we have to
13631397 // ignore false values.
@@ -1517,8 +1551,11 @@ struct CommandSender<'a> {
15171551impl < ' a > CommandSender < ' a > {
15181552 fn new ( spirc : & ' a mut SpircTask , cmd : MessageType ) -> CommandSender < ' _ > {
15191553 let mut frame = protocol:: spirc:: Frame :: new ( ) ;
1554+ // frame version
15201555 frame. set_version ( 1 ) ;
1521- frame. set_protocol_version ( :: std:: convert:: Into :: into ( "2.0.0" ) ) ;
1556+ // Latest known Spirc version is 3.2.6, but we need another interface to announce support for Spirc V3.
1557+ // Setting anything higher than 2.0.0 here just seems to limit it to 2.0.0.
1558+ frame. set_protocol_version ( "2.0.0" . to_string ( ) ) ;
15221559 frame. set_ident ( spirc. ident . clone ( ) ) ;
15231560 frame. set_seq_nr ( spirc. sequence . get ( ) ) ;
15241561 frame. set_typ ( cmd) ;
0 commit comments