Skip to content

Commit 385e02c

Browse files
committed
feat: provide an easy transfer option to the spirc
1 parent 3a700f0 commit 385e02c

File tree

3 files changed

+62
-9
lines changed

3 files changed

+62
-9
lines changed

connect/src/spirc.rs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::{
44
authentication::Credentials,
55
dealer::{
66
manager::{BoxedStream, BoxedStreamResult, Reply, RequestReply},
7-
protocol::{Command, FallbackWrapper, Message, Request},
7+
protocol::{Command, FallbackWrapper, Message, Request, TransferOptions},
88
},
99
session::UserAttributes,
1010
Error, Session, SpotifyId,
@@ -128,6 +128,7 @@ enum SpircCommand {
128128
SetPosition(u32),
129129
SetVolume(u16),
130130
Activate,
131+
Transfer(Option<TransferOptions>),
131132
Load(LoadRequest),
132133
}
133134

@@ -393,10 +394,19 @@ impl Spirc {
393394

394395
/// Acquires the control as active connect device.
395396
///
396-
/// Does nothing if we are not the active device.
397+
/// Does not [Spirc::transfer] the playback. Does nothing if we are not the active device.
397398
pub fn activate(&self) -> Result<(), Error> {
398399
Ok(self.commands.send(SpircCommand::Activate)?)
399400
}
401+
402+
/// Acquires the control as active connect device over the transfer flow.
403+
///
404+
/// Does nothing if we are not the active device.
405+
pub fn transfer(&self, transfer_options: Option<TransferOptions>) -> Result<(), Error> {
406+
Ok(self
407+
.commands
408+
.send(SpircCommand::Transfer(transfer_options))?)
409+
}
400410
}
401411

402412
impl SpircTask {
@@ -617,12 +627,19 @@ impl SpircTask {
617627
rx.close()
618628
}
619629
}
630+
SpircCommand::Transfer(options) if !self.connect_state.is_active() => {
631+
self.session
632+
.spclient()
633+
.transfer(self.session.device_id(), self.session.device_id(), &options)
634+
.await?;
635+
return Ok(());
636+
}
620637
SpircCommand::Activate if !self.connect_state.is_active() => {
621638
trace!("Received SpircCommand::{:?}", cmd);
622639
self.handle_activate();
623640
return self.notify().await;
624641
}
625-
SpircCommand::Activate => warn!(
642+
SpircCommand::Transfer(..) | SpircCommand::Activate => warn!(
626643
"SpircCommand::{:?} will be ignored while already active",
627644
cmd
628645
),

core/src/dealer/protocol/request.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::{
77
transfer_state::TransferState,
88
},
99
};
10-
use serde::Deserialize;
10+
use serde::{Deserialize, Serialize};
1111
use serde_json::Value;
1212
use std::fmt::{Display, Formatter};
1313

@@ -165,12 +165,16 @@ pub struct GenericCommand {
165165
pub logging_params: LoggingParams,
166166
}
167167

168-
#[derive(Clone, Debug, Deserialize)]
168+
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
169169
pub struct TransferOptions {
170-
pub restore_paused: String,
171-
pub restore_position: String,
172-
pub restore_track: String,
173-
pub retain_session: String,
170+
#[serde(skip_serializing_if = "Option::is_none")]
171+
pub restore_paused: Option<String>,
172+
#[serde(skip_serializing_if = "Option::is_none")]
173+
pub restore_position: Option<String>,
174+
#[serde(skip_serializing_if = "Option::is_none")]
175+
pub restore_track: Option<String>,
176+
#[serde(skip_serializing_if = "Option::is_none")]
177+
pub retain_session: Option<String>,
174178
}
175179

176180
#[derive(Clone, Debug, Deserialize)]

core/src/spclient.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use crate::config::{os_version, OS};
77
use crate::{
88
apresolve::SocketAddress,
99
config::SessionConfig,
10+
dealer::protocol::TransferOptions,
1011
error::ErrorKind,
1112
protocol::{
1213
autoplay_context_request::AutoplayContextRequest,
@@ -882,4 +883,35 @@ impl SpClient {
882883

883884
self.request(&Method::GET, &endpoint, None, None).await
884885
}
886+
887+
/// Triggers the transfers of the playback from one device to another
888+
///
889+
/// Using the same `device_id` for `from_device_id` and `to_device_id`, initiates the transfer
890+
/// from the currently active device.
891+
pub async fn transfer(
892+
&self,
893+
from_device_id: &str,
894+
to_device_id: &str,
895+
transfer_options: &Option<TransferOptions>,
896+
) -> SpClientResult {
897+
let endpoint =
898+
format!("/connect-state/v1/connect/transfer/from/{from_device_id}/to/{to_device_id}");
899+
900+
let transfer_options = transfer_options
901+
.as_ref()
902+
.map(serde_json::to_string)
903+
.transpose()?;
904+
let body = transfer_options.map(|op| format!(r#"{{ "transfer_options": {op} }}"#));
905+
906+
debug!("{body:?}");
907+
908+
self.request_with_options(
909+
&Method::POST,
910+
&endpoint,
911+
None,
912+
body.as_deref().map(|s| s.as_bytes()),
913+
&NO_METRICS_AND_SALT,
914+
)
915+
.await
916+
}
885917
}

0 commit comments

Comments
 (0)