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
This commit is contained in:
Brice 2018-05-16 21:15:17 -04:00 committed by Paul Liétar
parent 21f1ccfb5a
commit d40c0f50db
8 changed files with 84 additions and 26 deletions

View file

@ -9,6 +9,7 @@ use core::session::Session;
use core::spotify_id::SpotifyId; use core::spotify_id::SpotifyId;
use core::util::SeqGenerator; use core::util::SeqGenerator;
use core::version; use core::version;
use core::volume::Volume;
use protocol; use protocol;
use protocol::spirc::{DeviceState, Frame, MessageType, PlayStatus, State}; use protocol::spirc::{DeviceState, Frame, MessageType, PlayStatus, State};
@ -74,13 +75,13 @@ fn initial_state() -> State {
frame frame
} }
fn initial_device_state(config: ConnectConfig, volume: u16) -> DeviceState { fn initial_device_state(config: ConnectConfig) -> DeviceState {
{ {
let mut msg = DeviceState::new(); let mut msg = DeviceState::new();
msg.set_sw_version(version::version_string()); msg.set_sw_version(version::version_string());
msg.set_is_active(false); msg.set_is_active(false);
msg.set_can_play(true); msg.set_can_play(true);
msg.set_volume(volume as u32); msg.set_volume(0);
msg.set_name(config.name); msg.set_name(config.name);
{ {
let repeated = msg.mut_capabilities(); let repeated = msg.mut_capabilities();
@ -233,11 +234,10 @@ impl Spirc {
let (cmd_tx, cmd_rx) = mpsc::unbounded(); let (cmd_tx, cmd_rx) = mpsc::unbounded();
let volume = config.volume as u16; let volume = config.volume;
let linear_volume = config.linear_volume; let linear_volume = config.linear_volume;
let device = initial_device_state(config, volume); let device = initial_device_state(config);
mixer.set_volume(volume_to_mixer(volume as u16, linear_volume));
let mut task = SpircTask { let mut task = SpircTask {
player: player, player: player,
@ -260,6 +260,8 @@ impl Spirc {
session: session.clone(), session: session.clone(),
}; };
task.set_volume(volume);
let spirc = Spirc { commands: cmd_tx }; let spirc = Spirc { commands: cmd_tx };
task.hello(); task.hello();
@ -532,9 +534,7 @@ impl SpircTask {
} }
MessageType::kMessageTypeVolume => { MessageType::kMessageTypeVolume => {
self.device.set_volume(frame.get_volume()); self.set_volume(frame.get_volume() as u16);
self.mixer
.set_volume(volume_to_mixer(frame.get_volume() as u16, self.linear_volume));
self.notify(None); self.notify(None);
} }
@ -657,9 +657,7 @@ impl SpircTask {
if volume > 0xFFFF { if volume > 0xFFFF {
volume = 0xFFFF; volume = 0xFFFF;
} }
self.device.set_volume(volume); self.set_volume(volume as u16);
self.mixer
.set_volume(volume_to_mixer(volume as u16, self.linear_volume));
} }
fn handle_volume_down(&mut self) { fn handle_volume_down(&mut self) {
@ -667,9 +665,7 @@ impl SpircTask {
if volume < 0 { if volume < 0 {
volume = 0; volume = 0;
} }
self.device.set_volume(volume as u32); self.set_volume(volume as u16);
self.mixer
.set_volume(volume_to_mixer(volume as u16, self.linear_volume));
} }
fn handle_end_of_track(&mut self) { fn handle_end_of_track(&mut self) {
@ -724,6 +720,14 @@ impl SpircTask {
} }
cs.send(); 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 { impl Drop for SpircTask {

View file

@ -7,6 +7,7 @@ use std::path::PathBuf;
use authentication::Credentials; use authentication::Credentials;
use spotify_id::FileId; use spotify_id::FileId;
use volume::Volume;
#[derive(Clone)] #[derive(Clone)]
pub struct Cache { 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 { impl Cache {
fn file_path(&self, file: FileId) -> PathBuf { fn file_path(&self, file: FileId) -> PathBuf {
let name = file.to_base16(); let name = file.to_base16();

View file

@ -81,6 +81,6 @@ impl Default for DeviceType {
pub struct ConnectConfig { pub struct ConnectConfig {
pub name: String, pub name: String,
pub device_type: DeviceType, pub device_type: DeviceType,
pub volume: i32, pub volume: u16,
pub linear_volume: bool, pub linear_volume: bool,
} }

View file

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

31
core/src/volume.rs Normal file
View file

@ -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)
}
}

View file

@ -36,7 +36,7 @@ fn main() {
let session = core.run(Session::connect(session_config, credentials, None, handle)) let session = core.run(Session::connect(session_config, credentials, None, handle))
.unwrap(); .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..."); println!("Playing...");
core.run(player.load(track, true, 0)).unwrap(); core.run(player.load(track, true, 0)).unwrap();

View file

@ -1,6 +1,8 @@
use super::{Open, Sink}; use super::{Open, Sink};
use jack::prelude::{client_options, AsyncClient, AudioOutPort, AudioOutSpec, Client, JackControl, Port, use jack::prelude::{
ProcessHandler, ProcessScope}; client_options, AsyncClient, AudioOutPort, AudioOutSpec, Client, JackControl, Port, ProcessHandler,
ProcessScope,
};
use std::io; use std::io;
use std::sync::mpsc::{sync_channel, Receiver, SyncSender}; use std::sync::mpsc::{sync_channel, Receiver, SyncSender};

View file

@ -204,15 +204,22 @@ fn setup(args: &[String]) -> Setup {
let mixer_name = matches.opt_str("mixer"); let mixer_name = matches.opt_str("mixer");
let mixer = mixer::find(mixer_name.as_ref()).expect("Invalid 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 let initial_volume = matches
.opt_str("initial-volume") .opt_str("initial-volume")
.map(|volume| { .map(|volume| {
let volume = volume.parse::<i32>().unwrap(); let volume = volume.parse::<u16>().unwrap();
if volume < 0 || volume > 100 { if volume > 100 {
panic!("Initial volume must be in the range 0-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); .unwrap_or(0x8000);
let zeroconf_port = matches let zeroconf_port = matches
@ -221,11 +228,6 @@ fn setup(args: &[String]) -> Setup {
.unwrap_or(0); .unwrap_or(0);
let name = matches.opt_str("name").unwrap(); 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 credentials = {
let cached_credentials = cache.as_ref().and_then(Cache::credentials); let cached_credentials = cache.as_ref().and_then(Cache::credentials);