- 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
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>>,
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 {
fetch_more_file = SampleReturnState::FetchMore;
} else if curr_n_samples < 50 {
if !self.fetch_queued {
fetch_more_file = SampleReturnState::FetchMore;
self.fetch_queued = true;
}
} else if curr_n_samples < 50 && !self.done_signalled {
fetch_more_file = SampleReturnState::FileDone;
self.done_signalled = true;
}
}
}

View file

@ -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(

View file

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

View file

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

View file

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

View file

@ -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();

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 enum APIEvent {
FetchRandom(i32),
FetchRandom,
FetchCoverArt(String),
StreamSong(String, u32, u32),
}

View file

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