use serde::{Deserialize, Serialize}; /// Volume normalization levels matching Spotify's presets #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Default)] #[serde(rename_all = "lowercase")] pub enum VolumeLevel { /// Louder output (-11 LUFS) Loud, /// Default level (-14 LUFS) #[default] Normal, /// Quieter output (-23 LUFS) Quiet, } impl VolumeLevel { /// Get the target LUFS value for this volume level pub fn target_lufs(&self) -> f32 { match self { VolumeLevel::Loud => -11.0, VolumeLevel::Normal => -14.0, VolumeLevel::Quiet => -23.0, } } } /// Audio playback settings #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct AudioSettings { /// Crossfade duration in seconds (0 = disabled, max 12) pub crossfade_duration: f32, /// Enable gapless playback between tracks pub gapless_playback: bool, /// Enable volume normalization pub normalize_volume: bool, /// Target volume level for normalization pub volume_level: VolumeLevel, } impl Default for AudioSettings { fn default() -> Self { Self { crossfade_duration: 0.0, gapless_playback: true, normalize_volume: false, volume_level: VolumeLevel::Normal, } } } impl AudioSettings { /// Clamp crossfade duration to valid range (0-12 seconds) pub fn with_crossfade_clamped(mut self) -> Self { self.crossfade_duration = self.crossfade_duration.clamp(0.0, 12.0); self } } /// Video playback settings #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct VideoSettings { /// Enable auto-play of next episode (with countdown) pub auto_play_next_episode: bool, /// Countdown duration in seconds before auto-play (5-30 seconds) pub auto_play_countdown_seconds: u32, } impl Default for VideoSettings { fn default() -> Self { Self { auto_play_next_episode: true, auto_play_countdown_seconds: 10, } } } impl VideoSettings { /// Clamp countdown duration to valid range (5-30 seconds) pub fn with_countdown_clamped(mut self) -> Self { self.auto_play_countdown_seconds = self.auto_play_countdown_seconds.clamp(5, 30); self } } #[cfg(test)] mod tests { use super::*; #[test] fn test_default_settings() { let settings = AudioSettings::default(); assert_eq!(settings.crossfade_duration, 0.0); assert!(settings.gapless_playback); assert!(!settings.normalize_volume); assert_eq!(settings.volume_level, VolumeLevel::Normal); } #[test] fn test_volume_level_lufs() { assert_eq!(VolumeLevel::Loud.target_lufs(), -11.0); assert_eq!(VolumeLevel::Normal.target_lufs(), -14.0); assert_eq!(VolumeLevel::Quiet.target_lufs(), -23.0); } #[test] fn test_crossfade_clamping() { let settings = AudioSettings { crossfade_duration: 20.0, ..Default::default() } .with_crossfade_clamped(); assert_eq!(settings.crossfade_duration, 12.0); let settings = AudioSettings { crossfade_duration: -5.0, ..Default::default() } .with_crossfade_clamped(); assert_eq!(settings.crossfade_duration, 0.0); } #[test] fn test_settings_serialization() { let settings = AudioSettings { crossfade_duration: 5.0, gapless_playback: true, normalize_volume: true, volume_level: VolumeLevel::Loud, }; let json = serde_json::to_string(&settings).unwrap(); assert!(json.contains("\"crossfadeDuration\":5.0")); assert!(json.contains("\"gaplessPlayback\":true")); assert!(json.contains("\"normalizeVolume\":true")); assert!(json.contains("\"volumeLevel\":\"loud\"")); let parsed: AudioSettings = serde_json::from_str(&json).unwrap(); assert_eq!(parsed.crossfade_duration, 5.0); assert_eq!(parsed.volume_level, VolumeLevel::Loud); } #[test] fn test_video_default_settings() { let settings = VideoSettings::default(); assert!(settings.auto_play_next_episode); assert_eq!(settings.auto_play_countdown_seconds, 10); } #[test] fn test_video_countdown_clamping() { let settings = VideoSettings { auto_play_countdown_seconds: 60, ..Default::default() } .with_countdown_clamped(); assert_eq!(settings.auto_play_countdown_seconds, 30); let settings = VideoSettings { auto_play_countdown_seconds: 2, ..Default::default() } .with_countdown_clamped(); assert_eq!(settings.auto_play_countdown_seconds, 5); } #[test] fn test_video_settings_serialization() { let settings = VideoSettings { auto_play_next_episode: false, auto_play_countdown_seconds: 15, }; let json = serde_json::to_string(&settings).unwrap(); assert!(json.contains("\"autoPlayNextEpisode\":false")); assert!(json.contains("\"autoPlayCountdownSeconds\":15")); let parsed: VideoSettings = serde_json::from_str(&json).unwrap(); assert!(!parsed.auto_play_next_episode); assert_eq!(parsed.auto_play_countdown_seconds, 15); } }