desktop: Persist volume to disk

This commit is contained in:
Nathan Adams 2024-03-05 21:14:47 +01:00
parent e18055d351
commit 6971a1aa5e
6 changed files with 183 additions and 14 deletions

View File

@ -87,9 +87,9 @@ pub struct Opt {
#[clap(long, short, default_value = "show-all")]
pub scale: StageScaleMode,
/// Audio volume as a number between 0 (muted) and 1 (full volume)
#[clap(long, short, default_value = "1.0")]
pub volume: f32,
/// Audio volume as a number between 0 (muted) and 1 (full volume). Default is 1.
#[clap(long, short)]
pub volume: Option<f32>,
/// Prevent movies from changing the stage scale mode.
#[clap(long, action)]

View File

@ -106,7 +106,7 @@ impl RuffleGui {
Self {
is_about_visible: false,
is_volume_visible: false,
volume_controls: VolumeControls::new(false, default_player_options.volume * 100.0),
volume_controls: VolumeControls::new(&preferences),
is_open_dialog_visible: false,
was_suspended_before_debug: false,
@ -481,6 +481,19 @@ impl RuffleGui {
if let Some(player) = player {
player.set_volume(self.volume_controls.get_volume());
}
// Don't update persisted volume if the CLI set it
if self.preferences.cli.volume.is_none() {
if let Err(e) = self.preferences.write_preferences(|writer| {
if changed_checkbox {
writer.set_mute(self.volume_controls.is_muted);
}
if changed_slider {
writer.set_volume(self.volume_controls.volume / 100.0);
}
}) {
tracing::warn!("Couldn't update volume preferences: {e}");
}
}
}
});
}
@ -568,8 +581,11 @@ pub struct VolumeControls {
}
impl VolumeControls {
fn new(is_muted: bool, volume: f32) -> Self {
Self { is_muted, volume }
fn new(preferences: &GlobalPreferences) -> Self {
Self {
is_muted: preferences.mute(),
volume: preferences.preferred_volume() * 100.0,
}
}
/// Returns the volume between 0 and 1 (calculated out of the

View File

@ -39,7 +39,6 @@ pub struct PlayerOptions {
pub align: StageAlign,
pub force_align: bool,
pub scale: StageScaleMode,
pub volume: f32,
pub force_scale: bool,
pub proxy: Option<Url>,
pub socket_allowed: HashSet<String>,
@ -68,7 +67,6 @@ impl From<&GlobalPreferences> for PlayerOptions {
align: value.cli.align.unwrap_or_default(),
force_align: value.cli.force_align,
scale: value.cli.scale,
volume: value.cli.volume,
force_scale: value.cli.force_scale,
proxy: value.cli.proxy.clone(),
upgrade_to_https: value.cli.upgrade_to_https,

View File

@ -62,23 +62,23 @@ impl GlobalPreferences {
}
pub fn graphics_backends(&self) -> GraphicsBackend {
self.cli.graphics.unwrap_or(
self.cli.graphics.unwrap_or_else(|| {
self.preferences
.lock()
.expect("Preferences is not reentrant")
.values
.graphics_backend,
)
.graphics_backend
})
}
pub fn graphics_power_preference(&self) -> PowerPreference {
self.cli.power.unwrap_or(
self.cli.power.unwrap_or_else(|| {
self.preferences
.lock()
.expect("Preferences is not reentrant")
.values
.graphics_power_preference,
)
.graphics_power_preference
})
}
pub fn language(&self) -> LanguageIdentifier {
@ -99,6 +99,24 @@ impl GlobalPreferences {
.clone()
}
pub fn mute(&self) -> bool {
self.preferences
.lock()
.expect("Preferences is not reentrant")
.values
.mute
}
pub fn preferred_volume(&self) -> f32 {
self.cli.volume.unwrap_or_else(|| {
self.preferences
.lock()
.expect("Preferences is not reentrant")
.values
.volume
})
}
pub fn write_preferences(&self, fun: impl FnOnce(&mut PreferencesWriter)) -> Result<(), Error> {
let mut preferences = self
.preferences
@ -138,6 +156,8 @@ pub struct SavedGlobalPreferences {
pub graphics_power_preference: PowerPreference,
pub language: LanguageIdentifier,
pub output_device: Option<String>,
pub mute: bool,
pub volume: f32,
}
impl Default for SavedGlobalPreferences {
@ -151,6 +171,8 @@ impl Default for SavedGlobalPreferences {
graphics_power_preference: Default::default(),
language: locale,
output_device: None,
mute: false,
volume: 1.0,
}
}
}

View File

@ -60,6 +60,18 @@ pub fn read_preferences(input: &str) -> (ParseResult, Document) {
Err(e) => result.add_warning(format!("Invalid output_device: {e}")),
};
match parse_item_from_float(document.get("volume")) {
Ok(Some(value)) => result.result.volume = value.clamp(0.0, 1.0) as f32,
Ok(None) => {}
Err(e) => result.add_warning(format!("Invalid volume: {e}")),
};
match parse_item_from_bool(document.get("mute")) {
Ok(Some(value)) => result.result.mute = value,
Ok(None) => {}
Err(e) => result.add_warning(format!("Invalid mute: {e}")),
};
(result, document)
}
@ -79,6 +91,30 @@ fn parse_item_from_str<T: FromStr + Default>(item: Option<&Item>) -> Result<Opti
}
}
fn parse_item_from_float(item: Option<&Item>) -> Result<Option<f64>, String> {
if let Some(item) = item {
if let Some(value) = item.as_float() {
Ok(Some(value))
} else {
Err(format!("expected float but found {}", item.type_name()))
}
} else {
Ok(None)
}
}
fn parse_item_from_bool(item: Option<&Item>) -> Result<Option<bool>, String> {
if let Some(item) = item {
if let Some(value) = item.as_bool() {
Ok(Some(value))
} else {
Err(format!("expected boolean but found {}", item.type_name()))
}
} else {
Ok(None)
}
}
#[cfg(test)]
mod tests {
use super::*;
@ -262,4 +298,76 @@ mod tests {
result
);
}
#[test]
fn mute() {
assert_eq!(
ParseResult {
result: SavedGlobalPreferences {
mute: false,
..Default::default()
},
warnings: vec!["Invalid mute: expected boolean but found string".to_string()]
},
read_preferences("mute = \"false\"").0
);
assert_eq!(
ParseResult {
result: SavedGlobalPreferences {
mute: true,
..Default::default()
},
warnings: vec![]
},
read_preferences("mute = true").0
);
assert_eq!(
ParseResult {
result: SavedGlobalPreferences {
mute: false,
..Default::default()
},
warnings: vec![]
},
read_preferences("").0
);
}
#[test]
fn volume() {
assert_eq!(
ParseResult {
result: SavedGlobalPreferences {
volume: 1.0,
..Default::default()
},
warnings: vec!["Invalid volume: expected float but found string".to_string()]
},
read_preferences("volume = \"0.5\"").0
);
assert_eq!(
ParseResult {
result: SavedGlobalPreferences {
volume: 0.5,
..Default::default()
},
warnings: vec![]
},
read_preferences("volume = 0.5").0
);
assert_eq!(
ParseResult {
result: SavedGlobalPreferences {
volume: 0.0,
..Default::default()
},
warnings: vec![]
},
read_preferences("volume = -1.0").0
);
}
}

View File

@ -33,6 +33,16 @@ impl<'a> PreferencesWriter<'a> {
}
self.0.values.output_device = name;
}
pub fn set_mute(&mut self, mute: bool) {
self.0.toml_document["mute"] = value(mute);
self.0.values.mute = mute;
}
pub fn set_volume(&mut self, volume: f32) {
self.0.toml_document["volume"] = value(volume as f64);
self.0.values.volume = volume;
}
}
#[cfg(test)]
@ -124,4 +134,19 @@ mod tests {
"",
);
}
#[test]
fn set_volume() {
test("", |writer| writer.set_volume(0.5), "volume = 0.5\n");
}
#[test]
fn set_mute() {
test("", |writer| writer.set_mute(true), "mute = true\n");
test(
"mute = true",
|writer| writer.set_mute(false),
"mute = false\n",
);
}
}