- Volume control
- Less aggressive chunk queuing and next signalling
This commit is contained in:
parent
021d765d8a
commit
013b06a3e2
9 changed files with 77 additions and 28 deletions
|
@ -27,4 +27,4 @@ next = 'n' # Skip current song
|
|||
|
||||
## Don't use this
|
||||
|
||||
I cannot stress enough how janky this is. Just don't use this.
|
||||
I cannot stress enough how janky this is. Just don't use this. The audio controls and samples are sent on the same queue ffs. I might remake all of this with libmpv or mpv-ipc for a saner player at some point.
|
||||
|
|
|
@ -14,6 +14,8 @@ pub struct DecoderContext {
|
|||
frames_out: Receiver<Vec<u8>>,
|
||||
sample_buf: VecDeque<Vec<u8>>,
|
||||
pub input_done: bool,
|
||||
done_signalled: bool,
|
||||
fetch_queued: bool,
|
||||
}
|
||||
|
||||
pub fn init(stride: usize, sample_rate: u32) -> Result<DecoderContext, Error> {
|
||||
|
@ -51,6 +53,8 @@ pub fn init(stride: usize, sample_rate: u32) -> Result<DecoderContext, Error> {
|
|||
frames_out,
|
||||
sample_buf: VecDeque::new(),
|
||||
input_done: false,
|
||||
done_signalled: false,
|
||||
fetch_queued: true,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -63,6 +67,7 @@ impl Drop for DecoderContext {
|
|||
impl DecoderContext {
|
||||
pub fn append_chunk(&mut self, chunk: Vec<u8>) -> Result<(), Error> {
|
||||
self.encoded_in.send(chunk)?;
|
||||
self.fetch_queued = false;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -83,9 +88,13 @@ impl DecoderContext {
|
|||
self.fetch_samples();
|
||||
if self.sample_buf.len() == curr_n_samples {
|
||||
if !self.input_done {
|
||||
if !self.fetch_queued {
|
||||
fetch_more_file = SampleReturnState::FetchMore;
|
||||
} else if curr_n_samples < 50 {
|
||||
self.fetch_queued = true;
|
||||
}
|
||||
} else if curr_n_samples < 50 && !self.done_signalled {
|
||||
fetch_more_file = SampleReturnState::FileDone;
|
||||
self.done_signalled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,8 @@ pub enum AudioEvent {
|
|||
DecoderInit(u32, u32),
|
||||
FinalChunkRecvd,
|
||||
TogglePlaying,
|
||||
VolumeUp,
|
||||
VolumeDown,
|
||||
}
|
||||
|
||||
pub struct SoundManager {
|
||||
|
@ -28,6 +30,7 @@ pub struct SoundManager {
|
|||
pw_signal_in: pipewire::channel::Sender<Vec<u8>>,
|
||||
decoder_context: DecoderContext,
|
||||
playing: bool,
|
||||
volume: f64,
|
||||
}
|
||||
|
||||
pub fn init(
|
||||
|
|
|
@ -10,9 +10,11 @@ use std::{
|
|||
use crate::{
|
||||
config::Settings,
|
||||
player::PlayerEvent,
|
||||
utils::{new_pw_pod, Error},
|
||||
utils::{new_pw_pod, time_rem, Error},
|
||||
};
|
||||
|
||||
const VOLUME_CHANGE_INTERVAL: f64 = 0.05;
|
||||
|
||||
use super::{codec, errors::AudioError, pw, AudioEvent, SoundManager};
|
||||
|
||||
impl SoundManager {
|
||||
|
@ -46,6 +48,7 @@ impl SoundManager {
|
|||
pw_signal_in,
|
||||
decoder_context,
|
||||
playing: false,
|
||||
volume: 1.0,
|
||||
})
|
||||
}
|
||||
pub fn begin(&mut self) -> Result<(), Error> {
|
||||
|
@ -55,14 +58,32 @@ impl SoundManager {
|
|||
if self.playing {
|
||||
self.push_samples()?;
|
||||
}
|
||||
let time_elapsed = SystemTime::now().duration_since(start)?;
|
||||
let time_rem = Duration::from_millis(9).saturating_sub(time_elapsed);
|
||||
thread::sleep(time_rem);
|
||||
let time_left_cycle = time_rem(start, Duration::from_millis(9))?;
|
||||
thread::sleep(time_left_cycle);
|
||||
}
|
||||
}
|
||||
|
||||
fn adjust_volume(&self, frame: &mut [u8]) {
|
||||
for i in 0..frame.len() / 2 {
|
||||
let mut val_bytes = [0; 2];
|
||||
val_bytes.copy_from_slice(&frame[2 * i..2 * i + 2]);
|
||||
let val = ((i16::from_le_bytes(val_bytes) as f64) * self.volume) as i16;
|
||||
frame[2 * i..2 * i + 2].copy_from_slice(&i16::to_le_bytes(val));
|
||||
}
|
||||
}
|
||||
|
||||
fn set_volume(&mut self, vol_change: f64) {
|
||||
self.volume += vol_change;
|
||||
if self.volume < 0.0 {
|
||||
self.volume = 0.0;
|
||||
} else if self.volume > 100.0 {
|
||||
self.volume = 100.0;
|
||||
}
|
||||
}
|
||||
|
||||
fn push_samples(&mut self) -> Result<(), Error> {
|
||||
let (frame, fetch_more) = self.decoder_context.next_sample();
|
||||
let (mut frame, fetch_more) = self.decoder_context.next_sample();
|
||||
self.adjust_volume(frame.as_mut_slice());
|
||||
self.sample_in.send(frame)?;
|
||||
match fetch_more {
|
||||
codec::SampleReturnState::BufferPending => (),
|
||||
|
@ -88,6 +109,8 @@ impl SoundManager {
|
|||
}
|
||||
AudioEvent::FinalChunkRecvd => self.decoder_context.input_done = true,
|
||||
AudioEvent::TogglePlaying => self.playing = !self.playing,
|
||||
AudioEvent::VolumeUp => self.set_volume(VOLUME_CHANGE_INTERVAL),
|
||||
AudioEvent::VolumeDown => self.set_volume(-VOLUME_CHANGE_INTERVAL),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
@ -18,8 +18,6 @@ pub struct Subsonic {
|
|||
pub username: String,
|
||||
#[serde(default)]
|
||||
pub password: String, // Only optional in file, will otherwise pull from Env var
|
||||
#[serde(default = "default_random_limit")]
|
||||
pub random_limit: i32,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
@ -27,6 +25,8 @@ pub struct Controls {
|
|||
pub quit: char,
|
||||
pub play_state_toggle: char,
|
||||
pub next: char,
|
||||
pub vol_up: char,
|
||||
pub vol_down: char,
|
||||
}
|
||||
|
||||
fn default_controls() -> Controls {
|
||||
|
@ -34,13 +34,11 @@ fn default_controls() -> Controls {
|
|||
quit: 'q',
|
||||
play_state_toggle: ' ',
|
||||
next: 'n',
|
||||
vol_up: '+',
|
||||
vol_down: '-',
|
||||
}
|
||||
}
|
||||
|
||||
fn default_random_limit() -> i32 {
|
||||
10
|
||||
}
|
||||
|
||||
pub fn init() -> Result<Arc<Settings>, Error> {
|
||||
let settings = parse_config()?;
|
||||
Ok(Arc::new(settings))
|
||||
|
|
|
@ -10,7 +10,7 @@ use image::DynamicImage;
|
|||
use crate::{
|
||||
audio::AudioEvent,
|
||||
ssonic::{response::Song, APIEvent, MAX_CHUNK_SIZE},
|
||||
utils::{default_cover, Error},
|
||||
utils::{default_cover, time_rem, Error},
|
||||
};
|
||||
|
||||
use super::{errors::PlayerError, Player, PlayerEvent};
|
||||
|
@ -23,17 +23,19 @@ impl Player {
|
|||
}
|
||||
|
||||
fn init_playlist(&self) -> Result<(), Error> {
|
||||
self.api_chan
|
||||
.send(APIEvent::FetchRandom(self.settings.subsonic.random_limit))?;
|
||||
self.api_chan.send(APIEvent::FetchRandom)?;
|
||||
self.player_chan_in.send(PlayerEvent::PlayNext)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run(&mut self) -> Result<(), Error> {
|
||||
loop {
|
||||
let st = SystemTime::now();
|
||||
self.handle_events()?;
|
||||
self.handle_inputs()?;
|
||||
self.update()?;
|
||||
let time_left_cycle = time_rem(st, Duration::from_millis(49))?;
|
||||
thread::sleep(time_left_cycle);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,14 +57,13 @@ impl Player {
|
|||
}
|
||||
}
|
||||
if self.playlist.last_song() {
|
||||
self.api_chan
|
||||
.send(APIEvent::FetchRandom(self.settings.subsonic.random_limit))?;
|
||||
self.api_chan.send(APIEvent::FetchRandom)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_inputs(&mut self) -> Result<(), Error> {
|
||||
if poll(Duration::from_millis(40))? {
|
||||
if poll(Duration::from_millis(10))? {
|
||||
match read()? {
|
||||
Event::Key(e) => match e.code {
|
||||
x if x == KeyCode::Char(self.settings.controls.quit) => {
|
||||
|
@ -74,6 +75,12 @@ impl Player {
|
|||
x if x == KeyCode::Char(self.settings.controls.next) => {
|
||||
self.player_chan_in.send(PlayerEvent::PlayNext)?
|
||||
}
|
||||
x if x == KeyCode::Char(self.settings.controls.vol_up) => {
|
||||
self.audio_chan.send(AudioEvent::VolumeUp)?
|
||||
}
|
||||
x if x == KeyCode::Char(self.settings.controls.vol_down) => {
|
||||
self.audio_chan.send(AudioEvent::VolumeDown)?
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
_ => (),
|
||||
|
@ -83,6 +90,7 @@ impl Player {
|
|||
}
|
||||
|
||||
fn play_next(&mut self) -> Result<(), Error> {
|
||||
dbg!("playing next");
|
||||
let song = match self.playlist.get_next() {
|
||||
None => {
|
||||
// no song exists, requeue the event
|
||||
|
|
|
@ -23,7 +23,7 @@ impl APIClient {
|
|||
self.ping()?;
|
||||
loop {
|
||||
let player_resp = match self.api_requests.recv()? {
|
||||
super::APIEvent::FetchRandom(n) => self.get_random(n)?,
|
||||
super::APIEvent::FetchRandom => self.get_random()?,
|
||||
super::APIEvent::FetchCoverArt(id) => self.get_cover_art(id)?,
|
||||
super::APIEvent::StreamSong(id, start, end) => self.stream_song(id, start, end)?,
|
||||
_ => unimplemented!(),
|
||||
|
@ -82,8 +82,8 @@ impl APIClient {
|
|||
return Ok(PlayerEvent::UpdateCover(cover));
|
||||
}
|
||||
|
||||
fn get_random(&mut self, n: i32) -> Result<PlayerEvent, Error> {
|
||||
let response = self.request("/getRandomSongs", Some(&[("size", n.to_string())]), None)?;
|
||||
fn get_random(&mut self) -> Result<PlayerEvent, Error> {
|
||||
let response = self.request("/getRandomSongs", None, None)?;
|
||||
let song_list = self
|
||||
.validate(response, "/getRandomSongs")?
|
||||
.subsonic_response
|
||||
|
@ -95,7 +95,7 @@ impl APIClient {
|
|||
}
|
||||
|
||||
fn ping(&mut self) -> Result<(), Error> {
|
||||
let response = self.request::<[(&str, String); 0]>("/ping.view", None, None)?;
|
||||
let response = self.request("/ping.view", None, None)?;
|
||||
self.validate(response, "/ping.view")?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -123,10 +123,10 @@ impl APIClient {
|
|||
(format!("{:x}", hash), salt)
|
||||
}
|
||||
|
||||
fn request<T: Serialize + ?Sized>(
|
||||
fn request(
|
||||
&mut self,
|
||||
uri: &str,
|
||||
extra_params: Option<&T>,
|
||||
extra_params: Option<&[(&str, String)]>,
|
||||
extra_headers: Option<HeaderMap>,
|
||||
) -> Result<Response, Error> {
|
||||
let (token, salt) = self.generate_random_token();
|
||||
|
|
|
@ -13,7 +13,7 @@ use crate::{audio::AudioEvent, config::Settings, player::PlayerEvent, utils::Err
|
|||
pub const MAX_CHUNK_SIZE: u32 = 1_000_000;
|
||||
|
||||
pub enum APIEvent {
|
||||
FetchRandom(i32),
|
||||
FetchRandom,
|
||||
FetchCoverArt(String),
|
||||
StreamSong(String, u32, u32),
|
||||
}
|
||||
|
|
10
src/utils.rs
10
src/utils.rs
|
@ -8,7 +8,10 @@ use pipewire::spa::{
|
|||
},
|
||||
};
|
||||
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
||||
use std::io::Cursor;
|
||||
use std::{
|
||||
io::Cursor,
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
|
||||
pub type Error = Box<dyn std::error::Error + Send + Sync>;
|
||||
|
||||
|
@ -67,3 +70,8 @@ pub fn new_pw_pod(sample_rate: u32, channels: u32) -> Vec<u8> {
|
|||
.0
|
||||
.into_inner()
|
||||
}
|
||||
|
||||
pub fn time_rem(start_time: SystemTime, target_dur: Duration) -> Result<Duration, Error> {
|
||||
let time_since = SystemTime::now().duration_since(start_time)?;
|
||||
Ok(target_dur.saturating_sub(time_since))
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue