Skip to content

Commit 33bf3a7

Browse files
ralphCopilotphotovoltex
authored
Emit SetQueue event (#1677)
* Emit `Player::SetQueue` event * Remve AddToQueue, switch to SetQueue * Add CHANGELOG, reformat * Emit `SetQueue` once when a track collection is added * Clarify wording Co-authored-by: Copilot <[email protected]> * Just call `emit_set_queue_event()` instead of duplicating * Use QueueTrack struct instead of `(String, String)` * Make SetQueue an opt-in event Other expensive events might be added to `OptInPlayerEvents` in the future. * Refactor to use `ConnectConfig` for opting in to `SetQueue` events * Use explicit initializer value Co-authored-by: Felix Prillwitz <[email protected]> * Move variable closer to usage * Fix formatting --------- Co-authored-by: Ralph von der Heyden <> Co-authored-by: Copilot <[email protected]> Co-authored-by: Felix Prillwitz <[email protected]>
1 parent 5440c4d commit 33bf3a7

6 files changed

Lines changed: 146 additions & 27 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Added
1111

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

1515
### Changed
1616

connect/src/spirc.rs

Lines changed: 56 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ 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},
@@ -95,6 +95,8 @@ struct SpircTask {
9595

9696
context_resolver: ContextResolver,
9797

98+
emit_set_queue_events: bool,
99+
98100
shutdown: bool,
99101
session: Session,
100102

@@ -173,6 +175,7 @@ impl Spirc {
173175
let spirc_id = SPIRC_COUNTER.fetch_add(1, Ordering::AcqRel);
174176
debug!("new Spirc[{spirc_id}]");
175177

178+
let emit_set_queue_events = config.emit_set_queue_events;
176179
let connect_state = ConnectState::new(config, &session);
177180

178181
let connection_id_update = session
@@ -249,6 +252,8 @@ impl Spirc {
249252

250253
context_resolver: ContextResolver::new(session.clone()),
251254

255+
emit_set_queue_events,
256+
252257
shutdown: false,
253258
session,
254259

@@ -636,10 +641,52 @@ impl SpircTask {
636641
false
637642
};
638643

644+
// Fire set queue event if context was successfully loaded
645+
if update_state {
646+
self.emit_set_queue_event();
647+
}
648+
639649
self.context_resolver.remove_used_and_invalid();
640650
update_state
641651
}
642652

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+
643690
// todo: is the time_delta still necessary?
644691
fn now_ms(&self) -> i64 {
645692
let dur = SystemTime::now()
@@ -1084,13 +1131,13 @@ impl SpircTask {
10841131
}
10851132
SetRepeatingTrack(repeat_track) => self.handle_repeat_track(repeat_track.value),
10861133
AddToQueue(add_to_queue) => {
1087-
let track = add_to_queue.track.clone();
10881134
self.connect_state.add_to_queue(add_to_queue.track, true);
1089-
if let Ok(uri) = SpotifyUri::from_uri(&track.uri) {
1090-
self.player.emit_added_to_queue_event(uri);
1091-
}
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();
10921140
}
1093-
SetQueue(set_queue) => self.connect_state.handle_set_queue(set_queue),
10941141
SetOptions(set_options) => {
10951142
if let Some(repeat_context) = set_options.repeating_context {
10961143
self.handle_repeat_context(repeat_context)?
@@ -1460,6 +1507,8 @@ impl SpircTask {
14601507
.connect_state
14611508
.update_context(ctx, ContextType::Default)?;
14621509

1510+
self.emit_set_queue_event();
1511+
14631512
Ok(())
14641513
}
14651514

@@ -1598,11 +1647,8 @@ impl SpircTask {
15981647
..Default::default()
15991648
};
16001649
self.connect_state.add_to_queue(track, true);
1601-
1602-
if let Ok(uri) = SpotifyUri::from_uri(&track_uri) {
1603-
self.player.emit_added_to_queue_event(uri);
1604-
}
16051650
}
1651+
self.emit_set_queue_event();
16061652
}
16071653

16081654
fn handle_preload_next_track(&mut self) {

connect/src/state.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ pub struct ConnectConfig {
9090
pub disable_volume: bool,
9191
/// Number of incremental steps (default: 64)
9292
pub volume_steps: u16,
93+
/// Emit `SetQueue` player events when the queue changes (default: false)
94+
pub emit_set_queue_events: bool,
9395
}
9496

9597
impl Default for ConnectConfig {
@@ -101,6 +103,7 @@ impl Default for ConnectConfig {
101103
initial_volume: u16::MAX / 2,
102104
disable_volume: false,
103105
volume_steps: 64,
106+
emit_set_queue_events: false,
104107
}
105108
}
106109
}

playback/src/player.rs

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,19 @@ enum PlayerCommand {
138138
track: bool,
139139
},
140140
EmitAutoPlayChangedEvent(bool),
141-
EmitAddedToQueueEvent(SpotifyUri),
141+
EmitSetQueueEvent {
142+
context_uri: String,
143+
current_track: Option<QueueTrack>,
144+
next_tracks: Vec<QueueTrack>,
145+
prev_tracks: Vec<QueueTrack>,
146+
},
147+
}
148+
149+
/// Represents a track in the queue with its URI and provider.
150+
#[derive(Clone, Debug, PartialEq, Eq)]
151+
pub struct QueueTrack {
152+
pub uri: String,
153+
pub provider: String,
142154
}
143155

144156
#[derive(Debug, Clone)]
@@ -147,9 +159,6 @@ pub enum PlayerEvent {
147159
PlayRequestIdChanged {
148160
play_request_id: u64,
149161
},
150-
AddedToQueue {
151-
track_id: SpotifyUri,
152-
},
153162
// Fired when the player is stopped (e.g. by issuing a "stop" command to the player).
154163
Stopped {
155164
play_request_id: u64,
@@ -252,6 +261,13 @@ pub enum PlayerEvent {
252261
FilterExplicitContentChanged {
253262
filter: bool,
254263
},
264+
/// Fired when the queue is set or context is loaded with its track list.
265+
SetQueue {
266+
context_uri: String,
267+
current_track: Option<QueueTrack>,
268+
next_tracks: Vec<QueueTrack>,
269+
prev_tracks: Vec<QueueTrack>,
270+
},
255271
}
256272

257273
impl PlayerEvent {
@@ -652,8 +668,19 @@ impl Player {
652668
self.command(PlayerCommand::EmitAutoPlayChangedEvent(auto_play));
653669
}
654670

655-
pub fn emit_added_to_queue_event(&self, track_id: SpotifyUri) {
656-
self.command(PlayerCommand::EmitAddedToQueueEvent(track_id));
671+
pub fn emit_set_queue_event(
672+
&self,
673+
context_uri: String,
674+
current_track: Option<QueueTrack>,
675+
next_tracks: Vec<QueueTrack>,
676+
prev_tracks: Vec<QueueTrack>,
677+
) {
678+
self.command(PlayerCommand::EmitSetQueueEvent {
679+
context_uri,
680+
current_track,
681+
next_tracks,
682+
prev_tracks,
683+
});
657684
}
658685
}
659686

@@ -2343,9 +2370,17 @@ impl PlayerInternal {
23432370
self.auto_normalise_as_album = setting
23442371
}
23452372

2346-
PlayerCommand::EmitAddedToQueueEvent(track_id) => {
2347-
self.send_event(PlayerEvent::AddedToQueue { track_id })
2348-
}
2373+
PlayerCommand::EmitSetQueueEvent {
2374+
context_uri,
2375+
current_track,
2376+
next_tracks,
2377+
prev_tracks,
2378+
} => self.send_event(PlayerEvent::SetQueue {
2379+
context_uri,
2380+
current_track,
2381+
next_tracks,
2382+
prev_tracks,
2383+
}),
23492384

23502385
PlayerCommand::EmitFilterExplicitContentChangedEvent(filter) => {
23512386
self.send_event(PlayerEvent::FilterExplicitContentChanged { filter });
@@ -2548,9 +2583,16 @@ impl fmt::Debug for PlayerCommand {
25482583
.debug_tuple("EmitAutoPlayChangedEvent")
25492584
.field(&auto_play)
25502585
.finish(),
2551-
PlayerCommand::EmitAddedToQueueEvent(track_id) => f
2552-
.debug_tuple("EmitAddedToQueueEvent")
2553-
.field(&track_id)
2586+
PlayerCommand::EmitSetQueueEvent {
2587+
context_uri,
2588+
next_tracks,
2589+
prev_tracks,
2590+
..
2591+
} => f
2592+
.debug_tuple("EmitSetQueueEvent")
2593+
.field(&context_uri)
2594+
.field(&next_tracks.len())
2595+
.field(&prev_tracks.len())
25542596
.finish(),
25552597
}
25562598
}

src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1534,6 +1534,7 @@ async fn get_setup() -> Setup {
15341534
initial_volume,
15351535
disable_volume,
15361536
volume_steps,
1537+
emit_set_queue_events: false,
15371538
}
15381539
};
15391540

src/player_event_handler.rs

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,6 @@ impl EventHandler {
2727
.insert("PLAYER_EVENT", "play_request_id_changed".to_string());
2828
env_vars.insert("PLAY_REQUEST_ID", play_request_id.to_string());
2929
}
30-
PlayerEvent::AddedToQueue { track_id } => {
31-
env_vars.insert("PLAYER_EVENT", "added_to_queue".to_string());
32-
env_vars.insert("TRACK_ID", track_id.to_id());
33-
}
3430
PlayerEvent::TrackChanged { audio_item } => {
3531
let id = audio_item.track_id.to_id();
3632
env_vars.insert("PLAYER_EVENT", "track_changed".to_string());
@@ -234,6 +230,37 @@ impl EventHandler {
234230
);
235231
env_vars.insert("FILTER", filter.to_string());
236232
}
233+
PlayerEvent::SetQueue {
234+
context_uri,
235+
current_track,
236+
next_tracks,
237+
prev_tracks,
238+
} => {
239+
env_vars.insert("PLAYER_EVENT", "set_queue".to_string());
240+
env_vars.insert("CONTEXT_URI", context_uri);
241+
if let Some(track) = current_track {
242+
env_vars.insert(
243+
"CURRENT_TRACK",
244+
format!("{}\t{}", track.uri, track.provider),
245+
);
246+
}
247+
env_vars.insert(
248+
"NEXT_TRACKS",
249+
next_tracks
250+
.into_iter()
251+
.map(|t| format!("{}\t{}", t.uri, t.provider))
252+
.collect::<Vec<String>>()
253+
.join("\n"),
254+
);
255+
env_vars.insert(
256+
"PREV_TRACKS",
257+
prev_tracks
258+
.into_iter()
259+
.map(|t| format!("{}\t{}", t.uri, t.provider))
260+
.collect::<Vec<String>>()
261+
.join("\n"),
262+
);
263+
}
237264
// Ignore event irrelevant for standalone binary like PositionChanged
238265
_ => {}
239266
}

0 commit comments

Comments
 (0)