diff --git a/CHANGELOG.md b/CHANGELOG.md index 6db058bd..d93b636c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [main] Add a `-q`, `--quiet` option that changes the logging level to warn. - [main] Add a short name for every flag and option. - [main] Add the ability to parse environment variables. +- [playback] `pulseaudio`: set the PulseAudio name to match librespot's device name via `PULSE_PROP_application.name` environment variable (user set env var value takes precedence). (breaking) +- [playback] `pulseaudio`: set icon to `audio-x-generic` so we get an icon instead of a placeholder via `PULSE_PROP_application.icon_name` environment variable (user set env var value takes precedence). (breaking) +- [playback] `pulseaudio`: set values to: `PULSE_PROP_application.version`, `PULSE_PROP_application.process.binary`, `PULSE_PROP_stream.description`, `PULSE_PROP_media.software` and `PULSE_PROP_media.role` environment variables (user set env var values take precedence). (breaking) ### Fixed - [main] Prevent hang when discovery is disabled and there are no credentials or when bad credentials are given. diff --git a/core/src/spclient.rs b/core/src/spclient.rs index 37a125ad..820b2182 100644 --- a/core/src/spclient.rs +++ b/core/src/spclient.rs @@ -257,7 +257,7 @@ impl SpClient { let mut tries: usize = 0; let mut last_response; - let body = body.unwrap_or_else(String::new); + let body = body.unwrap_or_default(); loop { tries += 1; diff --git a/discovery/src/lib.rs b/discovery/src/lib.rs index b4e95737..02686e3e 100644 --- a/discovery/src/lib.rs +++ b/discovery/src/lib.rs @@ -16,7 +16,6 @@ use std::{ task::{Context, Poll}, }; -use cfg_if::cfg_if; use futures_core::Stream; use thiserror::Error; @@ -117,29 +116,23 @@ impl Builder { let name = self.server_config.name.clone().into_owned(); let server = DiscoveryServer::new(self.server_config, &mut port)??; - let svc; + #[cfg(feature = "with-dns-sd")] + let svc = dns_sd::DNSService::register( + Some(name.as_ref()), + "_spotify-connect._tcp", + None, + None, + port, + &["VERSION=1.0", "CPath=/"], + )?; - cfg_if! { - if #[cfg(feature = "with-dns-sd")] { - svc = dns_sd::DNSService::register( - Some(name.as_ref()), - "_spotify-connect._tcp", - None, - None, - port, - &["VERSION=1.0", "CPath=/"], - )?; - - } else { - let responder = libmdns::Responder::spawn(&tokio::runtime::Handle::current())?; - svc = responder.register( - "_spotify-connect._tcp".to_owned(), - name, - port, - &["VERSION=1.0", "CPath=/"], - ) - } - }; + #[cfg(not(feature = "with-dns-sd"))] + let svc = libmdns::Responder::spawn(&tokio::runtime::Handle::current())?.register( + "_spotify-connect._tcp".to_owned(), + name, + port, + &["VERSION=1.0", "CPath=/"], + ); Ok(Discovery { server, _svc: svc }) } diff --git a/playback/src/audio_backend/alsa.rs b/playback/src/audio_backend/alsa.rs index 16aa420d..c639228c 100644 --- a/playback/src/audio_backend/alsa.rs +++ b/playback/src/audio_backend/alsa.rs @@ -144,7 +144,7 @@ fn list_compatible_devices() -> SinkResult<()> { println!( "\tDescription:\n\n\t\t{}\n", - a.desc.unwrap_or_default().replace("\n", "\n\t\t") + a.desc.unwrap_or_default().replace('\n', "\n\t\t") ); println!( diff --git a/playback/src/audio_backend/pulseaudio.rs b/playback/src/audio_backend/pulseaudio.rs index 7487517f..b92acefa 100644 --- a/playback/src/audio_backend/pulseaudio.rs +++ b/playback/src/audio_backend/pulseaudio.rs @@ -5,11 +5,9 @@ use crate::decoder::AudioPacket; use crate::{NUM_CHANNELS, SAMPLE_RATE}; use libpulse_binding::{self as pulse, error::PAErr, stream::Direction}; use libpulse_simple_binding::Simple; +use std::env; use thiserror::Error; -const APP_NAME: &str = "librespot"; -const STREAM_NAME: &str = "Spotify endpoint"; - #[derive(Debug, Error)] enum PulseError { #[error(" Unsupported Pulseaudio Sample Spec, Format {pulse_format:?} ({format:?}), Channels {channels}, Rate {rate}")] @@ -47,13 +45,18 @@ impl From for SinkError { } pub struct PulseAudioSink { - s: Option, + sink: Option, device: Option, + app_name: String, + stream_desc: String, format: AudioFormat, } impl Open for PulseAudioSink { fn open(device: Option, format: AudioFormat) -> Self { + let app_name = env::var("PULSE_PROP_application.name").unwrap_or_default(); + let stream_desc = env::var("PULSE_PROP_stream.description").unwrap_or_default(); + let mut actual_format = format; if actual_format == AudioFormat::F64 { @@ -64,8 +67,10 @@ impl Open for PulseAudioSink { info!("Using PulseAudioSink with format: {:?}", actual_format); Self { - s: None, + sink: None, device, + app_name, + stream_desc, format: actual_format, } } @@ -73,7 +78,7 @@ impl Open for PulseAudioSink { impl Sink for PulseAudioSink { fn start(&mut self) -> SinkResult<()> { - if self.s.is_none() { + if self.sink.is_none() { // PulseAudio calls S24 and S24_3 different from the rest of the world let pulse_format = match self.format { AudioFormat::F32 => pulse::sample::Format::FLOAT32NE, @@ -84,13 +89,13 @@ impl Sink for PulseAudioSink { _ => unreachable!(), }; - let ss = pulse::sample::Spec { + let sample_spec = pulse::sample::Spec { format: pulse_format, channels: NUM_CHANNELS, rate: SAMPLE_RATE, }; - if !ss.is_valid() { + if !sample_spec.is_valid() { let pulse_error = PulseError::InvalidSampleSpec { pulse_format, format: self.format, @@ -101,30 +106,28 @@ impl Sink for PulseAudioSink { return Err(SinkError::from(pulse_error)); } - let s = Simple::new( + let sink = Simple::new( None, // Use the default server. - APP_NAME, // Our application's name. + &self.app_name, // Our application's name. Direction::Playback, // Direction. self.device.as_deref(), // Our device (sink) name. - STREAM_NAME, // Description of our stream. - &ss, // Our sample format. + &self.stream_desc, // Description of our stream. + &sample_spec, // Our sample format. None, // Use default channel map. None, // Use default buffering attributes. ) .map_err(PulseError::ConnectionRefused)?; - self.s = Some(s); + self.sink = Some(sink); } Ok(()) } fn stop(&mut self) -> SinkResult<()> { - let s = self.s.as_mut().ok_or(PulseError::NotConnected)?; + let sink = self.sink.take().ok_or(PulseError::NotConnected)?; - s.drain().map_err(PulseError::DrainFailure)?; - - self.s = None; + sink.drain().map_err(PulseError::DrainFailure)?; Ok(()) } @@ -133,9 +136,9 @@ impl Sink for PulseAudioSink { impl SinkAsBytes for PulseAudioSink { fn write_bytes(&mut self, data: &[u8]) -> SinkResult<()> { - let s = self.s.as_mut().ok_or(PulseError::NotConnected)?; + let sink = self.sink.as_mut().ok_or(PulseError::NotConnected)?; - s.write(data).map_err(PulseError::OnWrite)?; + sink.write(data).map_err(PulseError::OnWrite)?; Ok(()) } diff --git a/src/main.rs b/src/main.rs index f81bd1c0..a194ec0a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -596,7 +596,7 @@ fn get_setup() -> Setup { let stripped_env_key = |k: &str| { k.trim_start_matches("LIBRESPOT_") - .replace("_", "-") + .replace('_', "-") .to_lowercase() }; @@ -1143,6 +1143,43 @@ fn get_setup() -> Setup { exit(1); } + #[cfg(feature = "pulseaudio-backend")] + { + if env::var("PULSE_PROP_application.name").is_err() { + let pulseaudio_name = if name != connect_default_config.name { + format!("{} - {}", connect_default_config.name, name) + } else { + name.clone() + }; + + env::set_var("PULSE_PROP_application.name", pulseaudio_name); + } + + if env::var("PULSE_PROP_application.version").is_err() { + env::set_var("PULSE_PROP_application.version", version::SEMVER); + } + + if env::var("PULSE_PROP_application.icon_name").is_err() { + env::set_var("PULSE_PROP_application.icon_name", "audio-x-generic"); + } + + if env::var("PULSE_PROP_application.process.binary").is_err() { + env::set_var("PULSE_PROP_application.process.binary", "librespot"); + } + + if env::var("PULSE_PROP_stream.description").is_err() { + env::set_var("PULSE_PROP_stream.description", "Spotify Connect endpoint"); + } + + if env::var("PULSE_PROP_media.software").is_err() { + env::set_var("PULSE_PROP_media.software", "Spotify"); + } + + if env::var("PULSE_PROP_media.role").is_err() { + env::set_var("PULSE_PROP_media.role", "music"); + } + } + let initial_volume = opt_str(INITIAL_VOLUME) .map(|initial_volume| { let volume = match initial_volume.parse::() {