Skip to content
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- [connect] Add method `add_to_queue` to `Spirc` to add tracks, episodes, albums and playlists to the queue
- [playback] Add `AddedToQueue` player event, emitting when a track was added to the queue with `Spirc::add_to_queue`
- [playback] Add `SetQueue` player event, emitting when the queue changes (context loaded, track added to queue, or queue set via Spotify Connect)

### Changed

Expand Down
74 changes: 65 additions & 9 deletions connect/src/spirc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -636,10 +636,41 @@ impl SpircTask {
false
};

// Fire set queue event if context was successfully loaded
if update_state {
self.emit_set_queue_event();
}

self.context_resolver.remove_used_and_invalid();
update_state
}

/// Emit set queue event via PlayerEvent
fn emit_set_queue_event(&self) {
let context_uri = self.connect_state.context_uri().clone();
Comment thread
photovoltex marked this conversation as resolved.
Outdated
let state_player = self.connect_state.player();

let current_track = state_player
.track
.as_ref()
.map(|t| (t.uri.clone(), t.provider.clone()));

let next_tracks: Vec<_> = state_player
.next_tracks
.iter()
.map(|t| (t.uri.clone(), t.provider.clone()))
.collect();

let prev_tracks: Vec<_> = state_player
.prev_tracks
.iter()
.map(|t| (t.uri.clone(), t.provider.clone()))
.collect();

self.player
.emit_set_queue_event(context_uri, current_track, next_tracks, prev_tracks);
}

// todo: is the time_delta still necessary?
fn now_ms(&self) -> i64 {
let dur = SystemTime::now()
Expand Down Expand Up @@ -1084,13 +1115,39 @@ impl SpircTask {
}
SetRepeatingTrack(repeat_track) => self.handle_repeat_track(repeat_track.value),
AddToQueue(add_to_queue) => {
let track = add_to_queue.track.clone();
self.connect_state.add_to_queue(add_to_queue.track, true);
if let Ok(uri) = SpotifyUri::from_uri(&track.uri) {
self.player.emit_added_to_queue_event(uri);
}
self.emit_set_queue_event();
}
SetQueue(set_queue) => {
// Extract track data before consuming set_queue
Comment thread
ralph marked this conversation as resolved.
Outdated
let context_uri = self.connect_state.context_uri().clone();
let state_player = self.connect_state.player();

let current_track = state_player
.track
.as_ref()
.map(|t| (t.uri.clone(), t.provider.clone()));

let next_tracks: Vec<(String, String)> = set_queue
.next_tracks
.iter()
.map(|t| (t.uri.clone(), t.provider.clone()))
.collect();

let prev_tracks: Vec<(String, String)> = set_queue
.prev_tracks
.iter()
.map(|t| (t.uri.clone(), t.provider.clone()))
.collect();

self.connect_state.handle_set_queue(set_queue);
self.player.emit_set_queue_event(
Comment thread
ralph marked this conversation as resolved.
Outdated
context_uri,
current_track,
next_tracks,
prev_tracks,
);
}
SetQueue(set_queue) => self.connect_state.handle_set_queue(set_queue),
SetOptions(set_options) => {
if let Some(repeat_context) = set_options.repeating_context {
self.handle_repeat_context(repeat_context)?
Expand Down Expand Up @@ -1460,6 +1517,8 @@ impl SpircTask {
.connect_state
.update_context(ctx, ContextType::Default)?;

self.emit_set_queue_event();

Ok(())
}

Expand Down Expand Up @@ -1598,11 +1657,8 @@ impl SpircTask {
..Default::default()
};
self.connect_state.add_to_queue(track, true);

if let Ok(uri) = SpotifyUri::from_uri(&track_uri) {
self.player.emit_added_to_queue_event(uri);
}
}
self.emit_set_queue_event();
}

fn handle_preload_next_track(&mut self) {
Expand Down
59 changes: 47 additions & 12 deletions playback/src/player.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,12 @@ enum PlayerCommand {
track: bool,
},
EmitAutoPlayChangedEvent(bool),
EmitAddedToQueueEvent(SpotifyUri),
EmitSetQueueEvent {
context_uri: String,
current_track: Option<(String, String)>, // (uri, provider)
next_tracks: Vec<(String, String)>, // (uri, provider)
prev_tracks: Vec<(String, String)>, // (uri, provider)
},
}

#[derive(Debug, Clone)]
Expand All @@ -147,9 +152,6 @@ pub enum PlayerEvent {
PlayRequestIdChanged {
play_request_id: u64,
},
AddedToQueue {
track_id: SpotifyUri,
},
// Fired when the player is stopped (e.g. by issuing a "stop" command to the player).
Stopped {
play_request_id: u64,
Expand Down Expand Up @@ -252,6 +254,13 @@ pub enum PlayerEvent {
FilterExplicitContentChanged {
filter: bool,
},
/// Fired when the queue is set or context is loaded with its track list.
SetQueue {
context_uri: String,
current_track: Option<(String, String)>, // (uri, provider)
next_tracks: Vec<(String, String)>, // (uri, provider)
prev_tracks: Vec<(String, String)>, // (uri, provider)
Comment thread
ralph marked this conversation as resolved.
Outdated
},
}

impl PlayerEvent {
Expand Down Expand Up @@ -652,8 +661,19 @@ impl Player {
self.command(PlayerCommand::EmitAutoPlayChangedEvent(auto_play));
}

pub fn emit_added_to_queue_event(&self, track_id: SpotifyUri) {
self.command(PlayerCommand::EmitAddedToQueueEvent(track_id));
pub fn emit_set_queue_event(
&self,
context_uri: String,
current_track: Option<(String, String)>,
next_tracks: Vec<(String, String)>,
prev_tracks: Vec<(String, String)>,
) {
self.command(PlayerCommand::EmitSetQueueEvent {
context_uri,
current_track,
next_tracks,
prev_tracks,
});
}
}

Expand Down Expand Up @@ -2343,9 +2363,17 @@ impl PlayerInternal {
self.auto_normalise_as_album = setting
}

PlayerCommand::EmitAddedToQueueEvent(track_id) => {
self.send_event(PlayerEvent::AddedToQueue { track_id })
}
PlayerCommand::EmitSetQueueEvent {
context_uri,
current_track,
next_tracks,
prev_tracks,
} => self.send_event(PlayerEvent::SetQueue {
context_uri,
current_track,
next_tracks,
prev_tracks,
}),

PlayerCommand::EmitFilterExplicitContentChangedEvent(filter) => {
self.send_event(PlayerEvent::FilterExplicitContentChanged { filter });
Expand Down Expand Up @@ -2548,9 +2576,16 @@ impl fmt::Debug for PlayerCommand {
.debug_tuple("EmitAutoPlayChangedEvent")
.field(&auto_play)
.finish(),
PlayerCommand::EmitAddedToQueueEvent(track_id) => f
.debug_tuple("EmitAddedToQueueEvent")
.field(&track_id)
PlayerCommand::EmitSetQueueEvent {
context_uri,
next_tracks,
prev_tracks,
..
} => f
.debug_tuple("EmitSetQueueEvent")
.field(&context_uri)
.field(&next_tracks.len())
.field(&prev_tracks.len())
.finish(),
}
}
Expand Down
32 changes: 28 additions & 4 deletions src/player_event_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,6 @@ impl EventHandler {
.insert("PLAYER_EVENT", "play_request_id_changed".to_string());
env_vars.insert("PLAY_REQUEST_ID", play_request_id.to_string());
}
PlayerEvent::AddedToQueue { track_id } => {
env_vars.insert("PLAYER_EVENT", "added_to_queue".to_string());
env_vars.insert("TRACK_ID", track_id.to_id());
}
PlayerEvent::TrackChanged { audio_item } => {
let id = audio_item.track_id.to_id();
env_vars.insert("PLAYER_EVENT", "track_changed".to_string());
Expand Down Expand Up @@ -234,6 +230,34 @@ impl EventHandler {
);
env_vars.insert("FILTER", filter.to_string());
}
PlayerEvent::SetQueue {
context_uri,
current_track,
next_tracks,
prev_tracks,
} => {
env_vars.insert("PLAYER_EVENT", "set_queue".to_string());
env_vars.insert("CONTEXT_URI", context_uri);
if let Some((uri, provider)) = current_track {
env_vars.insert("CURRENT_TRACK", format!("{uri}\t{provider}"));
}
env_vars.insert(
"NEXT_TRACKS",
next_tracks
.into_iter()
.map(|(uri, provider)| format!("{uri}\t{provider}"))
.collect::<Vec<String>>()
.join("\n"),
);
env_vars.insert(
"PREV_TRACKS",
prev_tracks
.into_iter()
.map(|(uri, provider)| format!("{uri}\t{provider}"))
.collect::<Vec<String>>()
.join("\n"),
);
}
// Ignore event irrelevant for standalone binary like PositionChanged
_ => {}
}
Expand Down