mirror of
https://github.com/librespot-org/librespot.git
synced 2024-12-18 17:11:53 +00:00
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:
parent
21f1ccfb5a
commit
d40c0f50db
8 changed files with 84 additions and 26 deletions
|
@ -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 {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
31
core/src/volume.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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};
|
||||||
|
|
||||||
|
|
18
src/main.rs
18
src/main.rs
|
@ -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);
|
||||||
|
|
Loading…
Reference in a new issue