- Volume control

- Less aggressive chunk queuing and next signalling
This commit is contained in:
Muaz Ahmad 2024-12-10 14:09:28 +05:00
parent 021d765d8a
commit 013b06a3e2
9 changed files with 77 additions and 28 deletions

View file

@ -27,4 +27,4 @@ next = 'n' # Skip current song
## Don't use this ## 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.

View file

@ -14,6 +14,8 @@ pub struct DecoderContext {
frames_out: Receiver<Vec<u8>>, frames_out: Receiver<Vec<u8>>,
sample_buf: VecDeque<Vec<u8>>, sample_buf: VecDeque<Vec<u8>>,
pub input_done: bool, pub input_done: bool,
done_signalled: bool,
fetch_queued: bool,
} }
pub fn init(stride: usize, sample_rate: u32) -> Result<DecoderContext, Error> { 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, frames_out,
sample_buf: VecDeque::new(), sample_buf: VecDeque::new(),
input_done: false, input_done: false,
done_signalled: false,
fetch_queued: true,
}) })
} }
@ -63,6 +67,7 @@ impl Drop for DecoderContext {
impl DecoderContext { impl DecoderContext {
pub fn append_chunk(&mut self, chunk: Vec<u8>) -> Result<(), Error> { pub fn append_chunk(&mut self, chunk: Vec<u8>) -> Result<(), Error> {
self.encoded_in.send(chunk)?; self.encoded_in.send(chunk)?;
self.fetch_queued = false;
Ok(()) Ok(())
} }
@ -83,9 +88,13 @@ impl DecoderContext {
self.fetch_samples(); self.fetch_samples();
if self.sample_buf.len() == curr_n_samples { if self.sample_buf.len() == curr_n_samples {
if !self.input_done { if !self.input_done {
if !self.fetch_queued {
fetch_more_file = SampleReturnState::FetchMore; 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; fetch_more_file = SampleReturnState::FileDone;
self.done_signalled = true;
} }
} }
} }

View file

@ -17,6 +17,8 @@ pub enum AudioEvent {
DecoderInit(u32, u32), DecoderInit(u32, u32),
FinalChunkRecvd, FinalChunkRecvd,
TogglePlaying, TogglePlaying,
VolumeUp,
VolumeDown,
} }
pub struct SoundManager { pub struct SoundManager {
@ -28,6 +30,7 @@ pub struct SoundManager {
pw_signal_in: pipewire::channel::Sender<Vec<u8>>, pw_signal_in: pipewire::channel::Sender<Vec<u8>>,
decoder_context: DecoderContext, decoder_context: DecoderContext,
playing: bool, playing: bool,
volume: f64,
} }
pub fn init( pub fn init(

View file

@ -10,9 +10,11 @@ use std::{
use crate::{ use crate::{
config::Settings, config::Settings,
player::PlayerEvent, 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}; use super::{codec, errors::AudioError, pw, AudioEvent, SoundManager};
impl SoundManager { impl SoundManager {
@ -46,6 +48,7 @@ impl SoundManager {
pw_signal_in, pw_signal_in,
decoder_context, decoder_context,
playing: false, playing: false,
volume: 1.0,
}) })
} }
pub fn begin(&mut self) -> Result<(), Error> { pub fn begin(&mut self) -> Result<(), Error> {
@ -55,14 +58,32 @@ impl SoundManager {
if self.playing { if self.playing {
self.push_samples()?; self.push_samples()?;
} }
let time_elapsed = SystemTime::now().duration_since(start)?; let time_left_cycle = time_rem(start, Duration::from_millis(9))?;
let time_rem = Duration::from_millis(9).saturating_sub(time_elapsed); thread::sleep(time_left_cycle);
thread::sleep(time_rem); }
}
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> { 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)?; self.sample_in.send(frame)?;
match fetch_more { match fetch_more {
codec::SampleReturnState::BufferPending => (), codec::SampleReturnState::BufferPending => (),
@ -88,6 +109,8 @@ impl SoundManager {
} }
AudioEvent::FinalChunkRecvd => self.decoder_context.input_done = true, AudioEvent::FinalChunkRecvd => self.decoder_context.input_done = true,
AudioEvent::TogglePlaying => self.playing = !self.playing, AudioEvent::TogglePlaying => self.playing = !self.playing,
AudioEvent::VolumeUp => self.set_volume(VOLUME_CHANGE_INTERVAL),
AudioEvent::VolumeDown => self.set_volume(-VOLUME_CHANGE_INTERVAL),
_ => unimplemented!(), _ => unimplemented!(),
} }
Ok(()) Ok(())

View file

@ -18,8 +18,6 @@ pub struct Subsonic {
pub username: String, pub username: String,
#[serde(default)] #[serde(default)]
pub password: String, // Only optional in file, will otherwise pull from Env var pub password: String, // Only optional in file, will otherwise pull from Env var
#[serde(default = "default_random_limit")]
pub random_limit: i32,
} }
#[derive(Deserialize)] #[derive(Deserialize)]
@ -27,6 +25,8 @@ pub struct Controls {
pub quit: char, pub quit: char,
pub play_state_toggle: char, pub play_state_toggle: char,
pub next: char, pub next: char,
pub vol_up: char,
pub vol_down: char,
} }
fn default_controls() -> Controls { fn default_controls() -> Controls {
@ -34,13 +34,11 @@ fn default_controls() -> Controls {
quit: 'q', quit: 'q',
play_state_toggle: ' ', play_state_toggle: ' ',
next: 'n', next: 'n',
vol_up: '+',
vol_down: '-',
} }
} }
fn default_random_limit() -> i32 {
10
}
pub fn init() -> Result<Arc<Settings>, Error> { pub fn init() -> Result<Arc<Settings>, Error> {
let settings = parse_config()?; let settings = parse_config()?;
Ok(Arc::new(settings)) Ok(Arc::new(settings))

View file

@ -10,7 +10,7 @@ use image::DynamicImage;
use crate::{ use crate::{
audio::AudioEvent, audio::AudioEvent,
ssonic::{response::Song, APIEvent, MAX_CHUNK_SIZE}, ssonic::{response::Song, APIEvent, MAX_CHUNK_SIZE},
utils::{default_cover, Error}, utils::{default_cover, time_rem, Error},
}; };
use super::{errors::PlayerError, Player, PlayerEvent}; use super::{errors::PlayerError, Player, PlayerEvent};
@ -23,17 +23,19 @@ impl Player {
} }
fn init_playlist(&self) -> Result<(), Error> { fn init_playlist(&self) -> Result<(), Error> {
self.api_chan self.api_chan.send(APIEvent::FetchRandom)?;
.send(APIEvent::FetchRandom(self.settings.subsonic.random_limit))?;
self.player_chan_in.send(PlayerEvent::PlayNext)?; self.player_chan_in.send(PlayerEvent::PlayNext)?;
Ok(()) Ok(())
} }
fn run(&mut self) -> Result<(), Error> { fn run(&mut self) -> Result<(), Error> {
loop { loop {
let st = SystemTime::now();
self.handle_events()?; self.handle_events()?;
self.handle_inputs()?; self.handle_inputs()?;
self.update()?; 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() { if self.playlist.last_song() {
self.api_chan self.api_chan.send(APIEvent::FetchRandom)?;
.send(APIEvent::FetchRandom(self.settings.subsonic.random_limit))?;
} }
Ok(()) Ok(())
} }
fn handle_inputs(&mut self) -> Result<(), Error> { fn handle_inputs(&mut self) -> Result<(), Error> {
if poll(Duration::from_millis(40))? { if poll(Duration::from_millis(10))? {
match read()? { match read()? {
Event::Key(e) => match e.code { Event::Key(e) => match e.code {
x if x == KeyCode::Char(self.settings.controls.quit) => { x if x == KeyCode::Char(self.settings.controls.quit) => {
@ -74,6 +75,12 @@ impl Player {
x if x == KeyCode::Char(self.settings.controls.next) => { x if x == KeyCode::Char(self.settings.controls.next) => {
self.player_chan_in.send(PlayerEvent::PlayNext)? 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> { fn play_next(&mut self) -> Result<(), Error> {
dbg!("playing next");
let song = match self.playlist.get_next() { let song = match self.playlist.get_next() {
None => { None => {
// no song exists, requeue the event // no song exists, requeue the event

View file

@ -23,7 +23,7 @@ impl APIClient {
self.ping()?; self.ping()?;
loop { loop {
let player_resp = match self.api_requests.recv()? { 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::FetchCoverArt(id) => self.get_cover_art(id)?,
super::APIEvent::StreamSong(id, start, end) => self.stream_song(id, start, end)?, super::APIEvent::StreamSong(id, start, end) => self.stream_song(id, start, end)?,
_ => unimplemented!(), _ => unimplemented!(),
@ -82,8 +82,8 @@ impl APIClient {
return Ok(PlayerEvent::UpdateCover(cover)); return Ok(PlayerEvent::UpdateCover(cover));
} }
fn get_random(&mut self, n: i32) -> Result<PlayerEvent, Error> { fn get_random(&mut self) -> Result<PlayerEvent, Error> {
let response = self.request("/getRandomSongs", Some(&[("size", n.to_string())]), None)?; let response = self.request("/getRandomSongs", None, None)?;
let song_list = self let song_list = self
.validate(response, "/getRandomSongs")? .validate(response, "/getRandomSongs")?
.subsonic_response .subsonic_response
@ -95,7 +95,7 @@ impl APIClient {
} }
fn ping(&mut self) -> Result<(), Error> { 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")?; self.validate(response, "/ping.view")?;
Ok(()) Ok(())
} }
@ -123,10 +123,10 @@ impl APIClient {
(format!("{:x}", hash), salt) (format!("{:x}", hash), salt)
} }
fn request<T: Serialize + ?Sized>( fn request(
&mut self, &mut self,
uri: &str, uri: &str,
extra_params: Option<&T>, extra_params: Option<&[(&str, String)]>,
extra_headers: Option<HeaderMap>, extra_headers: Option<HeaderMap>,
) -> Result<Response, Error> { ) -> Result<Response, Error> {
let (token, salt) = self.generate_random_token(); let (token, salt) = self.generate_random_token();

View file

@ -13,7 +13,7 @@ use crate::{audio::AudioEvent, config::Settings, player::PlayerEvent, utils::Err
pub const MAX_CHUNK_SIZE: u32 = 1_000_000; pub const MAX_CHUNK_SIZE: u32 = 1_000_000;
pub enum APIEvent { pub enum APIEvent {
FetchRandom(i32), FetchRandom,
FetchCoverArt(String), FetchCoverArt(String),
StreamSong(String, u32, u32), StreamSong(String, u32, u32),
} }

View file

@ -8,7 +8,10 @@ use pipewire::spa::{
}, },
}; };
use rand::{distributions::Alphanumeric, thread_rng, Rng}; 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>; 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 .0
.into_inner() .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))
}