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/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 e9969f50..8d81834d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1124,6 +1124,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::() {