diff --git a/CHANGELOG.md b/CHANGELOG.md index e49cb47..dbe04ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/). ### Fixed - [remote] Prevent duplicate remotes appearing in older Deezer apps +- [remote] Initial volume not being set when controller reconnects - [track] Infinite loop loading track that is not available yet or anymore ## [0.6.1] - 2024-12-13 diff --git a/src/player.rs b/src/player.rs index de432eb..46e00c3 100644 --- a/src/player.rs +++ b/src/player.rs @@ -219,14 +219,21 @@ pub struct Player { } impl Player { + /// Default volume level. + /// + /// Constant value of 100% (1.0) used as initial volume setting. + const DEFAULT_VOLUME: Percentage = Percentage::from_ratio_f32(1.0); + /// Logarithmic volume scale factor for a dynamic range of 60 dB. /// /// Equal to 10^(60/20) = 1000.0 + /// Constant used in volume scaling calculations. const LOG_VOLUME_SCALE_FACTOR: f32 = 1000.0; /// Logarithmic volume growth rate for a dynamic range of 60 dB. /// /// Equal to ln(1000) ≈ 6.907755279 + /// Constant used in volume scaling calculations. const LOG_VOLUME_GROWTH_RATE: f32 = 6.907_755_4; /// Creates a new player instance. @@ -275,7 +282,7 @@ impl Player { repeat_mode: RepeatMode::default(), normalization: config.normalization, gain_target_db, - volume: Percentage::from_ratio_f32(1.0), + volume: Self::DEFAULT_VOLUME, event_tx: None, playing_since: Duration::ZERO, deferred_seek: None, @@ -436,7 +443,8 @@ impl Player { let sink = rodio::Sink::try_new(&handle)?; // Set the volume to the last known value. - sink.set_volume(self.volume.as_ratio_f32()); + let target_volume = std::mem::replace(&mut self.volume, Self::DEFAULT_VOLUME); + sink.set_volume(target_volume.as_ratio_f32()); // The output source will output silence when the queue is empty. // That will cause the sink to report as "playing", so we need to pause it. diff --git a/src/protocol/connect/contents.rs b/src/protocol/connect/contents.rs index e393949..75decb3 100644 --- a/src/protocol/connect/contents.rs +++ b/src/protocol/connect/contents.rs @@ -1229,23 +1229,34 @@ impl FromStr for AudioQuality { /// Values are stored internally as `f64` ratios between 0.0 and 1.0, but the /// type provides methods to work with both ratio and percentage formats. /// +/// # Constants +/// +/// Provides common values as const: +/// ```rust +/// const ZERO: Percentage = Percentage::ZERO; // 0% +/// const MAX: Percentage = Percentage::ONE_HUNDRED; // 100% +/// ``` +/// /// # Examples /// +/// Creating percentages: /// ```rust -/// // Volume control -/// let max_volume = Percentage::ONE_HUNDRED; // 100% volume -/// let half_volume = Percentage::from_percent_f32(50.0); // 50% volume -/// let muted = Percentage::ZERO; // 0% volume +/// // Using const constructors +/// const HALF: Percentage = Percentage::from_ratio_f32(0.5); +/// const QUARTER: Percentage = Percentage::from_percent_f32(25.0); +/// +/// // Using runtime constructors +/// let progress = Percentage::from_ratio_f64(0.75); +/// let volume = Percentage::from_percent_f64(80.0); /// ``` /// -/// Using in playback messages: +/// Using in messages: /// ```rust /// let body = Body::PlaybackProgress { -/// message_id: "msg123".to_string(), -/// // ... other fields ... +/// // ... /// progress: Some(Percentage::from_ratio_f32(0.25)), // 25% through track /// volume: Percentage::from_ratio_f32(0.8), // 80% volume -/// // ... other fields ... +/// // ... /// }; /// ``` /// @@ -1269,60 +1280,102 @@ pub struct Percentage(f64); impl Percentage { /// Represents 0% (0.0) + /// + /// # Examples + /// + /// ```rust + /// const MUTED: Percentage = Percentage::ZERO; + /// assert_eq!(MUTED.as_percent_f32(), 0.0); + /// ``` pub const ZERO: Self = Self(0.0); /// Represents 100% (1.0) + /// + /// # Examples + /// + /// ```rust + /// const MAX_VOLUME: Percentage = Percentage::ONE_HUNDRED; + /// assert_eq!(MAX_VOLUME.as_percent_f32(), 100.0); + /// ``` pub const ONE_HUNDRED: Self = Self(1.0); /// Creates a new percentage from a 32-bit floating point ratio. /// + /// Can be used in const contexts. + /// /// # Examples /// /// ```rust + /// // Const context + /// const HALF: Percentage = Percentage::from_ratio_f32(0.5); + /// assert_eq!(HALF.as_percent_f32(), 50.0); + /// + /// // Runtime context /// let p = Percentage::from_ratio_f32(0.75); /// assert_eq!(p.as_percent_f32(), 75.0); /// ``` #[must_use] - pub fn from_ratio_f32(ratio: f32) -> Self { - Self(ratio.into()) + pub const fn from_ratio_f32(ratio: f32) -> Self { + Self(ratio as f64) } /// Creates a new percentage from a 64-bit floating point ratio. /// + /// Can be used in const contexts. + /// /// # Examples /// /// ```rust - /// let p = Percentage::from_ratio_f64(0.333); - /// assert_eq!(p.as_percent_f64(), 33.3); + /// // Const context + /// const THIRD: Percentage = Percentage::from_ratio_f64(0.333); + /// assert_eq!(THIRD.as_percent_f64(), 33.3); + /// + /// // Runtime context + /// let p = Percentage::from_ratio_f64(0.5); + /// assert_eq!(p.as_percent_f64(), 50.0); /// ``` #[must_use] - pub fn from_ratio_f64(ratio: f64) -> Self { + pub const fn from_ratio_f64(ratio: f64) -> Self { Self(ratio) } /// Creates a new percentage from a 32-bit floating point percentage value. /// + /// Can be used in const contexts. + /// /// # Examples /// /// ```rust + /// // Const context + /// const HALF: Percentage = Percentage::from_percent_f32(50.0); + /// assert_eq!(HALF.as_ratio_f32(), 0.5); + /// + /// // Runtime context /// let p = Percentage::from_percent_f32(75.0); /// assert_eq!(p.as_ratio_f32(), 0.75); /// ``` #[must_use] - pub fn from_percent_f32(percent: f32) -> Self { - Self(f64::from(percent) / 100.0) + pub const fn from_percent_f32(percent: f32) -> Self { + Self(percent as f64 / 100.0) } /// Creates a new percentage from a 64-bit floating point percentage value. /// + /// Can be used in const contexts. + /// /// # Examples /// /// ```rust - /// let p = Percentage::from_percent_f64(33.3); - /// assert_eq!(p.as_ratio_f64(), 0.333); + /// // Const context + /// const THIRD: Percentage = Percentage::from_percent_f64(33.3); + /// assert_eq!(THIRD.as_ratio_f64(), 0.333); + /// + /// // Runtime context + /// let p = Percentage::from_percent_f64(75.0); + /// assert_eq!(p.as_ratio_f64(), 0.75); /// ``` #[must_use] - pub fn from_percent_f64(percent: f64) -> Self { + pub const fn from_percent_f64(percent: f64) -> Self { Self(percent / 100.0) } @@ -1344,14 +1397,17 @@ impl Percentage { /// Returns the value as a 64-bit floating point ratio (0.0 to 1.0). /// + /// Can be used in const contexts. + /// /// # Examples /// /// ```rust - /// let p = Percentage::from_ratio_f64(0.333); - /// assert_eq!(p.as_ratio_f64(), 0.333); + /// const P: Percentage = Percentage::from_ratio_f64(0.333); + /// const RATIO: f64 = P.as_ratio_f64(); + /// assert_eq!(RATIO, 0.333); /// ``` #[must_use] - pub fn as_ratio_f64(&self) -> f64 { + pub const fn as_ratio_f64(&self) -> f64 { self.0 } @@ -1373,14 +1429,17 @@ impl Percentage { /// Returns the value as a 64-bit floating point percentage (0.0 to 100.0). /// + /// Can be used in const contexts. + /// /// # Examples /// /// ```rust - /// let p = Percentage::from_ratio_f64(0.333); - /// assert_eq!(p.as_percent_f64(), 33.3); + /// const P: Percentage = Percentage::from_ratio_f64(0.333); + /// const PERCENT: f64 = P.as_percent_f64(); + /// assert_eq!(PERCENT, 33.3); /// ``` #[must_use] - pub fn as_percent_f64(&self) -> f64 { + pub const fn as_percent_f64(&self) -> f64 { self.0 * 100.0 } } diff --git a/src/remote.rs b/src/remote.rs index ea5a772..45cb419 100644 --- a/src/remote.rs +++ b/src/remote.rs @@ -233,11 +233,15 @@ enum ShuffleAction { /// * Active - Set volume and remain active until client sets below maximum /// * Inactive - Initial volume has been superseded by client control /// * Disabled - No initial volume configured +/// +/// The state transitions from Active to Inactive when the client takes control by setting +/// a volume below maximum. When a connection ends, it transitions back to Active to ensure +/// the initial volume is reapplied on reconnection. #[derive(Copy, Clone, Debug, PartialEq)] enum InitialVolume { - /// Initial volume is active and will be applied + /// Initial volume is active and will be applied on connection/reconnection Active(Percentage), - /// Initial volume is stored but inactive + /// Initial volume is stored but inactive (client has taken control) Inactive(Percentage), /// No initial volume configured Disabled, @@ -1202,9 +1206,12 @@ impl Client { /// * Clear controller association /// * Reset connection state /// * Reset discovery state - /// * Restore initial volume activation + /// * Restore initial volume for next connection /// * Flush cached tokens /// * Emit disconnect event + /// + /// The initial volume is reactivated during reset to ensure it will be + /// applied again when a new controller connects. fn reset_states(&mut self) { if let Some(controller) = self.controller() { info!("disconnected from {controller}");