From d40c0f50db02db7ec3cdd652d66820dbff9a0c87 Mon Sep 17 00:00:00 2001 From: Brice <33697112+bricedp@users.noreply.github.com> Date: Wed, 16 May 2018 21:15:17 -0400 Subject: [PATCH] Cache volume across restarts (#220) * create Volume struct for use with Cache * add "volume" file to Cache * load cached volume on start, intial overrides cached overrides default * amend volume_to_mixer function to cache the volume on every change * pass cache to Spirc and SpircTask so volume_to_mixer has access * rustfmt changes * revert volume_to_mixer function and Spirc/SpircTask cache variable * Volume implements Copy, pass by value instead of reference * clamp volume to 100 if cached value exceeds limit * convert Volume to u16 internally, use float and round to convert hex->dec * convert initial_volume and ConnectConfig.volume to u16 as well * add cache_volume function to SpircTask * remove conversion to/from percentage on cached volume * consolidate device.set_volume, mixer.set_volume, and caching * streamline intial volume logic --- connect/src/spirc.rs | 32 ++++++++++++++----------- core/src/cache.rs | 18 ++++++++++++++ core/src/config.rs | 2 +- core/src/lib.rs | 1 + core/src/volume.rs | 31 ++++++++++++++++++++++++ examples/play.rs | 2 +- playback/src/audio_backend/jackaudio.rs | 6 +++-- src/main.rs | 18 +++++++------- 8 files changed, 84 insertions(+), 26 deletions(-) create mode 100644 core/src/volume.rs diff --git a/connect/src/spirc.rs b/connect/src/spirc.rs index 6c766a4c..87bf4645 100644 --- a/connect/src/spirc.rs +++ b/connect/src/spirc.rs @@ -9,6 +9,7 @@ use core::session::Session; use core::spotify_id::SpotifyId; use core::util::SeqGenerator; use core::version; +use core::volume::Volume; use protocol; use protocol::spirc::{DeviceState, Frame, MessageType, PlayStatus, State}; @@ -74,13 +75,13 @@ fn initial_state() -> State { frame } -fn initial_device_state(config: ConnectConfig, volume: u16) -> DeviceState { +fn initial_device_state(config: ConnectConfig) -> DeviceState { { let mut msg = DeviceState::new(); msg.set_sw_version(version::version_string()); msg.set_is_active(false); msg.set_can_play(true); - msg.set_volume(volume as u32); + msg.set_volume(0); msg.set_name(config.name); { let repeated = msg.mut_capabilities(); @@ -233,11 +234,10 @@ impl Spirc { let (cmd_tx, cmd_rx) = mpsc::unbounded(); - let volume = config.volume as u16; + let volume = config.volume; let linear_volume = config.linear_volume; - let device = initial_device_state(config, volume); - mixer.set_volume(volume_to_mixer(volume as u16, linear_volume)); + let device = initial_device_state(config); let mut task = SpircTask { player: player, @@ -260,6 +260,8 @@ impl Spirc { session: session.clone(), }; + task.set_volume(volume); + let spirc = Spirc { commands: cmd_tx }; task.hello(); @@ -532,9 +534,7 @@ impl SpircTask { } MessageType::kMessageTypeVolume => { - self.device.set_volume(frame.get_volume()); - self.mixer - .set_volume(volume_to_mixer(frame.get_volume() as u16, self.linear_volume)); + self.set_volume(frame.get_volume() as u16); self.notify(None); } @@ -657,9 +657,7 @@ impl SpircTask { if volume > 0xFFFF { volume = 0xFFFF; } - self.device.set_volume(volume); - self.mixer - .set_volume(volume_to_mixer(volume as u16, self.linear_volume)); + self.set_volume(volume as u16); } fn handle_volume_down(&mut self) { @@ -667,9 +665,7 @@ impl SpircTask { if volume < 0 { volume = 0; } - self.device.set_volume(volume as u32); - self.mixer - .set_volume(volume_to_mixer(volume as u16, self.linear_volume)); + self.set_volume(volume as u16); } fn handle_end_of_track(&mut self) { @@ -724,6 +720,14 @@ impl SpircTask { } cs.send(); } + + fn set_volume(&mut self, volume: u16) { + self.device.set_volume(volume as u32); + self.mixer.set_volume(volume_to_mixer(volume, self.linear_volume)); + if let Some(cache) = self.session.cache() { + cache.save_volume(Volume { volume }) + } + } } impl Drop for SpircTask { diff --git a/core/src/cache.rs b/core/src/cache.rs index a5f89d5e..908ba294 100644 --- a/core/src/cache.rs +++ b/core/src/cache.rs @@ -7,6 +7,7 @@ use std::path::PathBuf; use authentication::Credentials; use spotify_id::FileId; +use volume::Volume; #[derive(Clone)] pub struct Cache { @@ -52,6 +53,23 @@ impl Cache { } } +// cache volume to root/volume +impl Cache { + fn volume_path(&self) -> PathBuf { + self.root.join("volume") + } + + pub fn volume(&self) -> Option { + let path = self.volume_path(); + Volume::from_file(path) + } + + pub fn save_volume(&self, volume: Volume) { + let path = self.volume_path(); + volume.save_to_file(&path); + } +} + impl Cache { fn file_path(&self, file: FileId) -> PathBuf { let name = file.to_base16(); diff --git a/core/src/config.rs b/core/src/config.rs index 3511ee3f..7baff40b 100644 --- a/core/src/config.rs +++ b/core/src/config.rs @@ -81,6 +81,6 @@ impl Default for DeviceType { pub struct ConnectConfig { pub name: String, pub device_type: DeviceType, - pub volume: i32, + pub volume: u16, pub linear_volume: bool, } diff --git a/core/src/lib.rs b/core/src/lib.rs index 053e4191..ba3756d1 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -52,3 +52,4 @@ pub mod session; pub mod spotify_id; pub mod util; pub mod version; +pub mod volume; diff --git a/core/src/volume.rs b/core/src/volume.rs new file mode 100644 index 00000000..24a3d3f9 --- /dev/null +++ b/core/src/volume.rs @@ -0,0 +1,31 @@ +use std::fs::File; +use std::io::{Read, Write}; +use std::path::Path; + +#[derive(Clone, Copy, Debug)] +pub struct Volume { + pub volume: u16, +} + +impl Volume { + // read volume from file + fn from_reader(mut reader: R) -> u16 { + let mut contents = String::new(); + reader.read_to_string(&mut contents).unwrap(); + contents.trim().parse::().unwrap() + } + + pub(crate) fn from_file>(path: P) -> Option { + File::open(path).ok().map(Volume::from_reader) + } + + // write volume to file + fn save_to_writer(&self, writer: &mut W) { + writer.write_all(self.volume.to_string().as_bytes()).unwrap(); + } + + pub(crate) fn save_to_file>(&self, path: P) { + let mut file = File::create(path).unwrap(); + self.save_to_writer(&mut file) + } +} diff --git a/examples/play.rs b/examples/play.rs index c7785e15..ac261978 100644 --- a/examples/play.rs +++ b/examples/play.rs @@ -36,7 +36,7 @@ fn main() { let session = core.run(Session::connect(session_config, credentials, None, handle)) .unwrap(); - let (player, _)= Player::new(player_config, session.clone(), None, move || (backend)(None)); + let (player, _) = Player::new(player_config, session.clone(), None, move || (backend)(None)); println!("Playing..."); core.run(player.load(track, true, 0)).unwrap(); diff --git a/playback/src/audio_backend/jackaudio.rs b/playback/src/audio_backend/jackaudio.rs index cda62206..3b118f38 100644 --- a/playback/src/audio_backend/jackaudio.rs +++ b/playback/src/audio_backend/jackaudio.rs @@ -1,6 +1,8 @@ use super::{Open, Sink}; -use jack::prelude::{client_options, AsyncClient, AudioOutPort, AudioOutSpec, Client, JackControl, Port, - ProcessHandler, ProcessScope}; +use jack::prelude::{ + client_options, AsyncClient, AudioOutPort, AudioOutSpec, Client, JackControl, Port, ProcessHandler, + ProcessScope, +}; use std::io; use std::sync::mpsc::{sync_channel, Receiver, SyncSender}; diff --git a/src/main.rs b/src/main.rs index b949c93a..3665e615 100644 --- a/src/main.rs +++ b/src/main.rs @@ -204,15 +204,22 @@ fn setup(args: &[String]) -> Setup { let mixer_name = matches.opt_str("mixer"); let mixer = mixer::find(mixer_name.as_ref()).expect("Invalid mixer"); + let use_audio_cache = !matches.opt_present("disable-audio-cache"); + + let cache = matches + .opt_str("c") + .map(|cache_location| Cache::new(PathBuf::from(cache_location), use_audio_cache)); + let initial_volume = matches .opt_str("initial-volume") .map(|volume| { - let volume = volume.parse::().unwrap(); - if volume < 0 || volume > 100 { + let volume = volume.parse::().unwrap(); + if volume > 100 { panic!("Initial volume must be in the range 0-100"); } - volume * 0xFFFF / 100 + (volume as i32 * 0xFFFF / 100) as u16 }) + .or_else(|| cache.as_ref().and_then(Cache::volume)) .unwrap_or(0x8000); let zeroconf_port = matches @@ -221,11 +228,6 @@ fn setup(args: &[String]) -> Setup { .unwrap_or(0); let name = matches.opt_str("name").unwrap(); - let use_audio_cache = !matches.opt_present("disable-audio-cache"); - - let cache = matches - .opt_str("c") - .map(|cache_location| Cache::new(PathBuf::from(cache_location), use_audio_cache)); let credentials = { let cached_credentials = cache.as_ref().and_then(Cache::credentials);