@@ -14,12 +14,13 @@ use crate::{
1414 model:: { LoadRequest , PlayingTrack , SpircPlayStatus } ,
1515 playback:: {
1616 mixer:: Mixer ,
17- player:: { Player , PlayerEvent , PlayerEventChannel } ,
17+ player:: { Player , PlayerEvent , PlayerEventChannel , QueueTrack } ,
1818 } ,
1919 protocol:: {
2020 connect:: { Cluster , ClusterUpdate , LogoutCommand , SetVolumeCommand } ,
2121 context:: Context ,
2222 explicit_content_pubsub:: UserAttributesUpdate ,
23+ player:: ProvidedTrack ,
2324 playlist4_external:: PlaylistModificationInfo ,
2425 social_connect_v2:: SessionUpdate ,
2526 transfer_state:: TransferState ,
@@ -94,6 +95,8 @@ struct SpircTask {
9495
9596 context_resolver : ContextResolver ,
9697
98+ emit_set_queue_events : bool ,
99+
97100 shutdown : bool ,
98101 session : Session ,
99102
@@ -132,6 +135,7 @@ enum SpircCommand {
132135 Activate ,
133136 Transfer ( Option < TransferRequest > ) ,
134137 Load ( LoadRequest ) ,
138+ AddToQueue ( SpotifyUri ) ,
135139}
136140
137141const CONTEXT_FETCH_THRESHOLD : usize = 2 ;
@@ -171,6 +175,7 @@ impl Spirc {
171175 let spirc_id = SPIRC_COUNTER . fetch_add ( 1 , Ordering :: AcqRel ) ;
172176 debug ! ( "new Spirc[{spirc_id}]" ) ;
173177
178+ let emit_set_queue_events = config. emit_set_queue_events ;
174179 let connect_state = ConnectState :: new ( config, & session) ;
175180
176181 let connection_id_update = session
@@ -247,6 +252,8 @@ impl Spirc {
247252
248253 context_resolver : ContextResolver :: new ( session. clone ( ) ) ,
249254
255+ emit_set_queue_events,
256+
250257 shutdown : false ,
251258 session,
252259
@@ -388,6 +395,24 @@ impl Spirc {
388395 Ok ( self . commands . send ( SpircCommand :: Load ( command) ) ?)
389396 }
390397
398+ /// Adds a track, episode, album or playlist to the queue.
399+ ///
400+ /// Does nothing if we are not the active device.
401+ ///
402+ /// For albums and playlists, all tracks/episodes are resolved and added to the queue.
403+ pub fn add_to_queue ( & self , uri : SpotifyUri ) -> Result < ( ) , Error > {
404+ if !matches ! (
405+ uri,
406+ SpotifyUri :: Track { .. }
407+ | SpotifyUri :: Episode { .. }
408+ | SpotifyUri :: Album { .. }
409+ | SpotifyUri :: Playlist { .. }
410+ ) {
411+ return Err ( Error :: invalid_argument ( "uri" ) ) ;
412+ }
413+ Ok ( self . commands . send ( SpircCommand :: AddToQueue ( uri) ) ?)
414+ }
415+
391416 /// Disconnects the current device and pauses the playback according the value.
392417 ///
393418 /// Does nothing if we are not the active device.
@@ -616,10 +641,52 @@ impl SpircTask {
616641 false
617642 } ;
618643
644+ // Fire set queue event if context was successfully loaded
645+ if update_state {
646+ self . emit_set_queue_event ( ) ;
647+ }
648+
619649 self . context_resolver . remove_used_and_invalid ( ) ;
620650 update_state
621651 }
622652
653+ /// Emit set queue event via PlayerEvent
654+ fn emit_set_queue_event ( & self ) {
655+ if !self . emit_set_queue_events {
656+ return ;
657+ }
658+
659+ let state_player = self . connect_state . player ( ) ;
660+
661+ let current_track = state_player. track . as_ref ( ) . map ( |t| QueueTrack {
662+ uri : t. uri . clone ( ) ,
663+ provider : t. provider . clone ( ) ,
664+ } ) ;
665+
666+ let next_tracks: Vec < _ > = state_player
667+ . next_tracks
668+ . iter ( )
669+ . map ( |t| QueueTrack {
670+ uri : t. uri . clone ( ) ,
671+ provider : t. provider . clone ( ) ,
672+ } )
673+ . collect ( ) ;
674+
675+ let prev_tracks: Vec < _ > = state_player
676+ . prev_tracks
677+ . iter ( )
678+ . map ( |t| QueueTrack {
679+ uri : t. uri . clone ( ) ,
680+ provider : t. provider . clone ( ) ,
681+ } )
682+ . collect ( ) ;
683+
684+ let context_uri = self . connect_state . context_uri ( ) . clone ( ) ;
685+
686+ self . player
687+ . emit_set_queue_event ( context_uri, current_track, next_tracks, prev_tracks) ;
688+ }
689+
623690 // todo: is the time_delta still necessary?
624691 fn now_ms ( & self ) -> i64 {
625692 let dur = SystemTime :: now ( )
@@ -679,6 +746,7 @@ impl SpircTask {
679746 SpircCommand :: SetPosition ( position) => self . handle_seek ( position) ,
680747 SpircCommand :: SetVolume ( volume) => self . set_volume ( volume) ,
681748 SpircCommand :: Load ( command) => self . handle_load ( command, None , None ) . await ?,
749+ SpircCommand :: AddToQueue ( uri) => self . handle_add_to_queue ( uri) . await ,
682750 } ;
683751
684752 self . notify ( ) . await
@@ -1062,8 +1130,14 @@ impl SpircTask {
10621130 self . handle_repeat_context ( repeat_context. value ) ?
10631131 }
10641132 SetRepeatingTrack ( repeat_track) => self . handle_repeat_track ( repeat_track. value ) ,
1065- AddToQueue ( add_to_queue) => self . connect_state . add_to_queue ( add_to_queue. track , true ) ,
1066- SetQueue ( set_queue) => self . connect_state . handle_set_queue ( set_queue) ,
1133+ AddToQueue ( add_to_queue) => {
1134+ self . connect_state . add_to_queue ( add_to_queue. track , true ) ;
1135+ self . emit_set_queue_event ( ) ;
1136+ }
1137+ SetQueue ( set_queue) => {
1138+ self . connect_state . handle_set_queue ( set_queue) ;
1139+ self . emit_set_queue_event ( ) ;
1140+ }
10671141 SetOptions ( set_options) => {
10681142 if let Some ( repeat_context) = set_options. repeating_context {
10691143 self . handle_repeat_context ( repeat_context) ?
@@ -1433,6 +1507,8 @@ impl SpircTask {
14331507 . connect_state
14341508 . update_context ( ctx, ContextType :: Default ) ?;
14351509
1510+ self . emit_set_queue_event ( ) ;
1511+
14361512 Ok ( ( ) )
14371513 }
14381514
@@ -1545,6 +1621,36 @@ impl SpircTask {
15451621 self . connect_state . set_repeat_track ( repeat) ;
15461622 }
15471623
1624+ async fn handle_add_to_queue ( & mut self , uri : SpotifyUri ) {
1625+ let track_uris: Vec < String > = match uri {
1626+ SpotifyUri :: Track { .. } | SpotifyUri :: Episode { .. } => vec ! [ uri. to_uri( ) ] ,
1627+ SpotifyUri :: Album { .. } | SpotifyUri :: Playlist { .. } => {
1628+ match self . session . spclient ( ) . get_context ( & uri. to_uri ( ) ) . await {
1629+ Ok ( context) => context
1630+ . pages
1631+ . iter ( )
1632+ . flat_map ( |page| page. tracks . iter ( ) )
1633+ . filter_map ( |track| track. uri . clone ( ) )
1634+ . collect ( ) ,
1635+ Err ( e) => {
1636+ error ! ( "failed to resolve context for {}: {e}" , uri. item_type( ) ) ;
1637+ return ;
1638+ }
1639+ }
1640+ }
1641+ _ => return ,
1642+ } ;
1643+
1644+ for track_uri in track_uris {
1645+ let track = ProvidedTrack {
1646+ uri : track_uri. clone ( ) ,
1647+ ..Default :: default ( )
1648+ } ;
1649+ self . connect_state . add_to_queue ( track, true ) ;
1650+ }
1651+ self . emit_set_queue_event ( ) ;
1652+ }
1653+
15481654 fn handle_preload_next_track ( & mut self ) {
15491655 // Requests the player thread to preload the next track
15501656 match self . play_status {
0 commit comments