Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### 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`

### Changed

- [core] Made `SpotifyId::to_base62`, `SpotifyId::to_base16`, `FileId::to_base16`, `SpotifyUri::to_id`, `SpotifyUri::to_uri` infallible (breaking)
Expand Down
62 changes: 61 additions & 1 deletion connect/src/spirc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use crate::{
connect::{Cluster, ClusterUpdate, LogoutCommand, SetVolumeCommand},
context::Context,
explicit_content_pubsub::UserAttributesUpdate,
player::ProvidedTrack,
playlist4_external::PlaylistModificationInfo,
social_connect_v2::SessionUpdate,
transfer_state::TransferState,
Expand Down Expand Up @@ -132,6 +133,7 @@ enum SpircCommand {
Activate,
Transfer(Option<TransferRequest>),
Load(LoadRequest),
AddToQueue(SpotifyUri),
}

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

/// Adds a track, episode, album or playlist to the queue.
///
/// Does nothing if we are not the active device.
///
/// For albums and playlists, all tracks/episodes are resolved and added to the queue.
pub fn add_to_queue(&self, uri: SpotifyUri) -> Result<(), Error> {
if !matches!(
uri,
SpotifyUri::Track { .. }
| SpotifyUri::Episode { .. }
| SpotifyUri::Album { .. }
| SpotifyUri::Playlist { .. }
) {
return Err(Error::invalid_argument("uri"));
}
Ok(self.commands.send(SpircCommand::AddToQueue(uri))?)
}

/// Disconnects the current device and pauses the playback according the value.
///
/// Does nothing if we are not the active device.
Expand Down Expand Up @@ -679,6 +699,7 @@ impl SpircTask {
SpircCommand::SetPosition(position) => self.handle_seek(position),
SpircCommand::SetVolume(volume) => self.set_volume(volume),
SpircCommand::Load(command) => self.handle_load(command, None, None).await?,
SpircCommand::AddToQueue(uri) => self.handle_add_to_queue(uri).await,
};

self.notify().await
Expand Down Expand Up @@ -1062,7 +1083,13 @@ impl SpircTask {
self.handle_repeat_context(repeat_context.value)?
}
SetRepeatingTrack(repeat_track) => self.handle_repeat_track(repeat_track.value),
AddToQueue(add_to_queue) => self.connect_state.add_to_queue(add_to_queue.track, true),
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);
}
}
SetQueue(set_queue) => self.connect_state.handle_set_queue(set_queue),
SetOptions(set_options) => {
if let Some(repeat_context) = set_options.repeating_context {
Expand Down Expand Up @@ -1545,6 +1572,39 @@ impl SpircTask {
self.connect_state.set_repeat_track(repeat);
}

async fn handle_add_to_queue(&mut self, uri: SpotifyUri) {
let track_uris: Vec<String> = match uri {
SpotifyUri::Track { .. } | SpotifyUri::Episode { .. } => vec![uri.to_uri()],
SpotifyUri::Album { .. } | SpotifyUri::Playlist { .. } => {
match self.session.spclient().get_context(&uri.to_uri()).await {
Ok(context) => context
.pages
.iter()
.flat_map(|page| page.tracks.iter())
.filter_map(|track| track.uri.clone())
.collect(),
Err(e) => {
error!("failed to resolve context for {}: {e}", uri.item_type());
return;
}
}
}
_ => return,
};

for track_uri in track_uris {
let track = ProvidedTrack {
uri: track_uri.clone(),
..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);
}
}
}

fn handle_preload_next_track(&mut self) {
// Requests the player thread to preload the next track
match self.play_status {
Expand Down
16 changes: 16 additions & 0 deletions playback/src/player.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ enum PlayerCommand {
track: bool,
},
EmitAutoPlayChangedEvent(bool),
EmitAddedToQueueEvent(SpotifyUri),
}

#[derive(Debug, Clone)]
Expand All @@ -146,6 +147,9 @@ 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 @@ -647,6 +651,10 @@ impl Player {
pub fn emit_auto_play_changed_event(&self, auto_play: bool) {
self.command(PlayerCommand::EmitAutoPlayChangedEvent(auto_play));
}

pub fn emit_added_to_queue_event(&self, track_id: SpotifyUri) {
self.command(PlayerCommand::EmitAddedToQueueEvent(track_id));
}
}

impl Drop for Player {
Expand Down Expand Up @@ -2335,6 +2343,10 @@ impl PlayerInternal {
self.auto_normalise_as_album = setting
}

PlayerCommand::EmitAddedToQueueEvent(track_id) => {
self.send_event(PlayerEvent::AddedToQueue { track_id })
}

PlayerCommand::EmitFilterExplicitContentChangedEvent(filter) => {
self.send_event(PlayerEvent::FilterExplicitContentChanged { filter });

Expand Down Expand Up @@ -2536,6 +2548,10 @@ impl fmt::Debug for PlayerCommand {
.debug_tuple("EmitAutoPlayChangedEvent")
.field(&auto_play)
.finish(),
PlayerCommand::EmitAddedToQueueEvent(track_id) => f
.debug_tuple("EmitAddedToQueueEvent")
.field(&track_id)
.finish(),
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions src/player_event_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ 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