Browse Source

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
Brice 6 years ago
parent
commit
d40c0f50db
8 changed files with 84 additions and 26 deletions
  1. 18 14
      connect/src/spirc.rs
  2. 18 0
      core/src/cache.rs
  3. 1 1
      core/src/config.rs
  4. 1 0
      core/src/lib.rs
  5. 31 0
      core/src/volume.rs
  6. 1 1
      examples/play.rs
  7. 4 2
      playback/src/audio_backend/jackaudio.rs
  8. 10 8
      src/main.rs

+ 18 - 14
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 {

+ 18 - 0
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<u16> {
+        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();

+ 1 - 1
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,
 }

+ 1 - 0
core/src/lib.rs

@@ -52,3 +52,4 @@ pub mod session;
 pub mod spotify_id;
 pub mod util;
 pub mod version;
+pub mod volume;

+ 31 - 0
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<R: Read>(mut reader: R) -> u16 {
+        let mut contents = String::new();
+        reader.read_to_string(&mut contents).unwrap();
+        contents.trim().parse::<u16>().unwrap()
+    }
+
+    pub(crate) fn from_file<P: AsRef<Path>>(path: P) -> Option<u16> {
+        File::open(path).ok().map(Volume::from_reader)
+    }
+
+    // write volume to file
+    fn save_to_writer<W: Write>(&self, writer: &mut W) {
+        writer.write_all(self.volume.to_string().as_bytes()).unwrap();
+    }
+
+    pub(crate) fn save_to_file<P: AsRef<Path>>(&self, path: P) {
+        let mut file = File::create(path).unwrap();
+        self.save_to_writer(&mut file)
+    }
+}

+ 1 - 1
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();

+ 4 - 2
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};
 

+ 10 - 8
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::<i32>().unwrap();
-            if volume < 0 || volume > 100 {
+            let volume = volume.parse::<u16>().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);