- 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
|
## 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>>,
|
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 {
|
||||||
fetch_more_file = SampleReturnState::FetchMore;
|
if !self.fetch_queued {
|
||||||
} else if curr_n_samples < 50 {
|
fetch_more_file = SampleReturnState::FetchMore;
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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(())
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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),
|
||||||
}
|
}
|
||||||
|
|
10
src/utils.rs
10
src/utils.rs
|
@ -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))
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue