From 180ffacde19c696e140d07f86fb2fe78f8137bb9 Mon Sep 17 00:00:00 2001 From: Joey Eamigh <55670930+JoeyEamigh@users.noreply.github.com> Date: Sat, 15 Jun 2024 23:36:47 -0400 Subject: [PATCH] adding flag to show connect device as group --- connect/src/config.rs | 2 ++ discovery/examples/discovery_group.rs | 22 ++++++++++++++++++++++ discovery/src/lib.rs | 7 +++++++ discovery/src/server.rs | 10 +++++++++- src/main.rs | 9 +++++++++ 5 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 discovery/examples/discovery_group.rs diff --git a/connect/src/config.rs b/connect/src/config.rs index 473fa173..278ecf17 100644 --- a/connect/src/config.rs +++ b/connect/src/config.rs @@ -4,6 +4,7 @@ use crate::core::config::DeviceType; pub struct ConnectConfig { pub name: String, pub device_type: DeviceType, + pub is_group: bool, pub initial_volume: Option, pub has_volume_ctrl: bool, } @@ -13,6 +14,7 @@ impl Default for ConnectConfig { ConnectConfig { name: "Librespot".to_string(), device_type: DeviceType::default(), + is_group: false, initial_volume: Some(50), has_volume_ctrl: true, } diff --git a/discovery/examples/discovery_group.rs b/discovery/examples/discovery_group.rs new file mode 100644 index 00000000..e98b1536 --- /dev/null +++ b/discovery/examples/discovery_group.rs @@ -0,0 +1,22 @@ +use futures::StreamExt; +use librespot_core::SessionConfig; +use librespot_discovery::DeviceType; +use sha1::{Digest, Sha1}; + +#[tokio::main(flavor = "current_thread")] +async fn main() { + let name = "Librespot Group"; + let device_id = hex::encode(Sha1::digest(name.as_bytes())); + + let mut server = + librespot_discovery::Discovery::builder(device_id, SessionConfig::default().client_id) + .name(name) + .device_type(DeviceType::Speaker) + .is_group(true) + .launch() + .unwrap(); + + while let Some(x) = server.next().await { + println!("Received {:?}", x); + } +} diff --git a/discovery/src/lib.rs b/discovery/src/lib.rs index 8caeadfb..f1f0f692 100644 --- a/discovery/src/lib.rs +++ b/discovery/src/lib.rs @@ -84,6 +84,7 @@ impl Builder { server_config: server::Config { name: "Librespot".into(), device_type: DeviceType::default(), + is_group: false, device_id: device_id.into(), client_id: client_id.into(), }, @@ -104,6 +105,12 @@ impl Builder { self } + /// Sets whether the device is a group. This affects the icon in Spotify clients. Default is `false`. + pub fn is_group(mut self, is_group: bool) -> Self { + self.server_config.is_group = is_group; + self + } + /// Set the ip addresses on which it should listen to incoming connections. The default is all interfaces. pub fn zeroconf_ip(mut self, zeroconf_ip: Vec) -> Self { self.zeroconf_ip = zeroconf_ip; diff --git a/discovery/src/server.rs b/discovery/src/server.rs index 6167275d..35f3993b 100644 --- a/discovery/src/server.rs +++ b/discovery/src/server.rs @@ -39,6 +39,7 @@ pub struct Config { pub name: Cow<'static, str>, pub device_type: DeviceType, pub device_id: String, + pub is_group: bool, pub client_id: String, } @@ -70,6 +71,12 @@ impl RequestHandler { if let Some(username) = &self.username { active_user = username.to_string(); } + // options based on zeroconf guide, search for `groupStatus` on page + let group_status = if self.config.is_group { + "GROUP" + } else { + "NONE" + }; // See: https://developer.spotify.com/documentation/commercial-hardware/implementation/guides/zeroconf/ let body = json!({ @@ -87,7 +94,8 @@ impl RequestHandler { "modelDisplayName": "librespot", "libraryVersion": crate::core::version::SEMVER, "resolverVersion": "1", - "groupStatus": "NONE", + // valid values are "GROUP" and "NONE" + "groupStatus": group_status, // valid value documented & seen in the wild: "accesstoken" // Using it will cause clients to fail to connect. "tokenType": "default", diff --git a/src/main.rs b/src/main.rs index 22c3abec..3e656be2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -203,6 +203,7 @@ fn get_setup() -> Setup { const CACHE_SIZE_LIMIT: &str = "cache-size-limit"; const DEVICE: &str = "device"; const DEVICE_TYPE: &str = "device-type"; + const DEVICE_IS_GROUP: &str = "group"; const DISABLE_AUDIO_CACHE: &str = "disable-audio-cache"; const DISABLE_CREDENTIAL_CACHE: &str = "disable-credential-cache"; const DISABLE_DISCOVERY: &str = "disable-discovery"; @@ -409,6 +410,10 @@ fn get_setup() -> Setup { DEVICE_TYPE, "Displayed device type. Defaults to speaker.", "TYPE", + ).optflag( + "", + DEVICE_IS_GROUP, + "Whether the device represents a group. Defaults to false.", ) .optopt( TEMP_DIR_SHORT, @@ -1312,11 +1317,14 @@ fn get_setup() -> Setup { }) .unwrap_or_default(); + let is_group = opt_present(DEVICE_IS_GROUP); + let has_volume_ctrl = !matches!(mixer_config.volume_ctrl, VolumeCtrl::Fixed); ConnectConfig { name, device_type, + is_group, initial_volume, has_volume_ctrl, } @@ -1685,6 +1693,7 @@ async fn main() { match librespot::discovery::Discovery::builder(device_id, client_id) .name(setup.connect_config.name.clone()) .device_type(setup.connect_config.device_type) + .is_group(setup.connect_config.is_group) .port(setup.zeroconf_port) .zeroconf_ip(setup.zeroconf_ip.clone()) .launch()