Skip to content

Commit 87d37c3

Browse files
authored
Add add_to_queue to Spirc (#1676)
* Expose AddToQueue command * Add add_to_queue to Spirc * Change parameter type to SpotifyUri * Add support for other `SpotifyUri` types to `Spirc::add_to_queue` * Remove support for `SpotifyUri::Artist` / `SpotifyUri::Show` In order to reflect what the official apps are offering to the user. * EmitAddedToQueueEvent * Add CHANGELOG entry * Remove Artist/Show from handle_add_to_queue * D'oh 😬
1 parent 0a63d4b commit 87d37c3

4 files changed

Lines changed: 86 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- [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`
14+
1015
### Changed
1116

1217
- [core] Made `SpotifyId::to_base62`, `SpotifyId::to_base16`, `FileId::to_base16`, `SpotifyUri::to_id`, `SpotifyUri::to_uri` infallible (breaking)

connect/src/spirc.rs

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use crate::{
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,
@@ -132,6 +133,7 @@ enum SpircCommand {
132133
Activate,
133134
Transfer(Option<TransferRequest>),
134135
Load(LoadRequest),
136+
AddToQueue(SpotifyUri),
135137
}
136138

137139
const CONTEXT_FETCH_THRESHOLD: usize = 2;
@@ -388,6 +390,24 @@ impl Spirc {
388390
Ok(self.commands.send(SpircCommand::Load(command))?)
389391
}
390392

393+
/// Adds a track, episode, album or playlist to the queue.
394+
///
395+
/// Does nothing if we are not the active device.
396+
///
397+
/// For albums and playlists, all tracks/episodes are resolved and added to the queue.
398+
pub fn add_to_queue(&self, uri: SpotifyUri) -> Result<(), Error> {
399+
if !matches!(
400+
uri,
401+
SpotifyUri::Track { .. }
402+
| SpotifyUri::Episode { .. }
403+
| SpotifyUri::Album { .. }
404+
| SpotifyUri::Playlist { .. }
405+
) {
406+
return Err(Error::invalid_argument("uri"));
407+
}
408+
Ok(self.commands.send(SpircCommand::AddToQueue(uri))?)
409+
}
410+
391411
/// Disconnects the current device and pauses the playback according the value.
392412
///
393413
/// Does nothing if we are not the active device.
@@ -679,6 +699,7 @@ impl SpircTask {
679699
SpircCommand::SetPosition(position) => self.handle_seek(position),
680700
SpircCommand::SetVolume(volume) => self.set_volume(volume),
681701
SpircCommand::Load(command) => self.handle_load(command, None, None).await?,
702+
SpircCommand::AddToQueue(uri) => self.handle_add_to_queue(uri).await,
682703
};
683704

684705
self.notify().await
@@ -1062,7 +1083,13 @@ impl SpircTask {
10621083
self.handle_repeat_context(repeat_context.value)?
10631084
}
10641085
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),
1086+
AddToQueue(add_to_queue) => {
1087+
let track = add_to_queue.track.clone();
1088+
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+
}
1092+
}
10661093
SetQueue(set_queue) => self.connect_state.handle_set_queue(set_queue),
10671094
SetOptions(set_options) => {
10681095
if let Some(repeat_context) = set_options.repeating_context {
@@ -1545,6 +1572,39 @@ impl SpircTask {
15451572
self.connect_state.set_repeat_track(repeat);
15461573
}
15471574

1575+
async fn handle_add_to_queue(&mut self, uri: SpotifyUri) {
1576+
let track_uris: Vec<String> = match uri {
1577+
SpotifyUri::Track { .. } | SpotifyUri::Episode { .. } => vec![uri.to_uri()],
1578+
SpotifyUri::Album { .. } | SpotifyUri::Playlist { .. } => {
1579+
match self.session.spclient().get_context(&uri.to_uri()).await {
1580+
Ok(context) => context
1581+
.pages
1582+
.iter()
1583+
.flat_map(|page| page.tracks.iter())
1584+
.filter_map(|track| track.uri.clone())
1585+
.collect(),
1586+
Err(e) => {
1587+
error!("failed to resolve context for {}: {e}", uri.item_type());
1588+
return;
1589+
}
1590+
}
1591+
}
1592+
_ => return,
1593+
};
1594+
1595+
for track_uri in track_uris {
1596+
let track = ProvidedTrack {
1597+
uri: track_uri.clone(),
1598+
..Default::default()
1599+
};
1600+
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+
}
1605+
}
1606+
}
1607+
15481608
fn handle_preload_next_track(&mut self) {
15491609
// Requests the player thread to preload the next track
15501610
match self.play_status {

playback/src/player.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ enum PlayerCommand {
138138
track: bool,
139139
},
140140
EmitAutoPlayChangedEvent(bool),
141+
EmitAddedToQueueEvent(SpotifyUri),
141142
}
142143

143144
#[derive(Debug, Clone)]
@@ -146,6 +147,9 @@ pub enum PlayerEvent {
146147
PlayRequestIdChanged {
147148
play_request_id: u64,
148149
},
150+
AddedToQueue {
151+
track_id: SpotifyUri,
152+
},
149153
// Fired when the player is stopped (e.g. by issuing a "stop" command to the player).
150154
Stopped {
151155
play_request_id: u64,
@@ -647,6 +651,10 @@ impl Player {
647651
pub fn emit_auto_play_changed_event(&self, auto_play: bool) {
648652
self.command(PlayerCommand::EmitAutoPlayChangedEvent(auto_play));
649653
}
654+
655+
pub fn emit_added_to_queue_event(&self, track_id: SpotifyUri) {
656+
self.command(PlayerCommand::EmitAddedToQueueEvent(track_id));
657+
}
650658
}
651659

652660
impl Drop for Player {
@@ -2335,6 +2343,10 @@ impl PlayerInternal {
23352343
self.auto_normalise_as_album = setting
23362344
}
23372345

2346+
PlayerCommand::EmitAddedToQueueEvent(track_id) => {
2347+
self.send_event(PlayerEvent::AddedToQueue { track_id })
2348+
}
2349+
23382350
PlayerCommand::EmitFilterExplicitContentChangedEvent(filter) => {
23392351
self.send_event(PlayerEvent::FilterExplicitContentChanged { filter });
23402352

@@ -2536,6 +2548,10 @@ impl fmt::Debug for PlayerCommand {
25362548
.debug_tuple("EmitAutoPlayChangedEvent")
25372549
.field(&auto_play)
25382550
.finish(),
2551+
PlayerCommand::EmitAddedToQueueEvent(track_id) => f
2552+
.debug_tuple("EmitAddedToQueueEvent")
2553+
.field(&track_id)
2554+
.finish(),
25392555
}
25402556
}
25412557
}

src/player_event_handler.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ 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+
}
3034
PlayerEvent::TrackChanged { audio_item } => {
3135
let id = audio_item.track_id.to_id();
3236
env_vars.insert("PLAYER_EVENT", "track_changed".to_string());

0 commit comments

Comments
 (0)