- Playstate and next handling

- Handle chunk queueing better (directly from api response)
- Fetch more songs if playlist is empty
This commit is contained in:
Muaz Ahmad 2024-12-09 16:44:36 +05:00
parent d0c5a8f61f
commit 10e88b511d
13 changed files with 97 additions and 38 deletions

View file

@ -13,7 +13,7 @@ pub struct DecoderContext {
encoded_in: Sender<Vec<u8>>, encoded_in: Sender<Vec<u8>>,
frames_out: Receiver<Vec<u8>>, frames_out: Receiver<Vec<u8>>,
sample_buf: VecDeque<Vec<u8>>, sample_buf: VecDeque<Vec<u8>>,
input_done: bool, pub input_done: bool,
} }
pub fn init(stride: usize, sample_rate: u32) -> Result<DecoderContext, Error> { pub fn init(stride: usize, sample_rate: u32) -> Result<DecoderContext, Error> {
@ -54,6 +54,12 @@ pub fn init(stride: usize, sample_rate: u32) -> Result<DecoderContext, Error> {
}) })
} }
impl Drop for DecoderContext {
fn drop(&mut self) {
self.process.kill().unwrap();
}
}
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)?;
@ -70,13 +76,17 @@ impl DecoderContext {
} }
} }
pub fn next_sample(&mut self) -> (Vec<u8>, bool) { pub fn next_sample(&mut self) -> (Vec<u8>, SampleReturnState) {
let curr_n_samples = self.sample_buf.len(); let curr_n_samples = self.sample_buf.len();
let mut fetch_more_file = false; let mut fetch_more_file = SampleReturnState::BufferPending;
if curr_n_samples < 1000 { if curr_n_samples < 1000 {
self.fetch_samples(); self.fetch_samples();
if self.sample_buf.len() == curr_n_samples && !self.input_done { if self.sample_buf.len() == curr_n_samples {
fetch_more_file = true; if !self.input_done {
fetch_more_file = SampleReturnState::FetchMore;
} else if curr_n_samples < 50 {
fetch_more_file = SampleReturnState::FileDone;
}
} }
} }
@ -86,3 +96,9 @@ impl DecoderContext {
); );
} }
} }
pub enum SampleReturnState {
BufferPending,
FetchMore,
FileDone,
}

View file

@ -16,6 +16,7 @@ pub enum AudioEvent {
DecodeChunk(Vec<u8>), DecodeChunk(Vec<u8>),
DecoderInit(u32, u32), DecoderInit(u32, u32),
FinalChunkRecvd, FinalChunkRecvd,
TogglePlaying,
} }
pub struct SoundManager { pub struct SoundManager {

View file

@ -3,7 +3,6 @@ use std::{process::exit, sync::Arc};
use pipewire::{ use pipewire::{
channel::Receiver, channel::Receiver,
context::Context, context::Context,
core::Core,
keys, keys,
main_loop::MainLoop, main_loop::MainLoop,
properties::properties, properties::properties,
@ -11,7 +10,7 @@ use pipewire::{
stream::{Stream, StreamFlags}, stream::{Stream, StreamFlags},
}; };
use crate::utils::{new_pw_pod, Error}; use crate::utils::Error;
pub fn init( pub fn init(
samples: std::sync::mpsc::Receiver<Vec<u8>>, samples: std::sync::mpsc::Receiver<Vec<u8>>,

View file

@ -45,7 +45,7 @@ impl SoundManager {
sample_in, sample_in,
pw_signal_in, pw_signal_in,
decoder_context, decoder_context,
playing: true, playing: false,
}) })
} }
pub fn begin(&mut self) -> Result<(), Error> { pub fn begin(&mut self) -> Result<(), Error> {
@ -64,8 +64,12 @@ impl SoundManager {
fn push_samples(&mut self) -> Result<(), Error> { fn push_samples(&mut self) -> Result<(), Error> {
let (frame, fetch_more) = self.decoder_context.next_sample(); let (frame, fetch_more) = self.decoder_context.next_sample();
self.sample_in.send(frame)?; self.sample_in.send(frame)?;
if fetch_more { match fetch_more {
self.player_chan.send(PlayerEvent::FetchChunk)?; codec::SampleReturnState::BufferPending => (),
codec::SampleReturnState::FetchMore => {
self.player_chan.send(PlayerEvent::FetchChunk)?
}
codec::SampleReturnState::FileDone => self.player_chan.send(PlayerEvent::PlayNext)?,
} }
Ok(()) Ok(())
} }
@ -82,6 +86,8 @@ impl SoundManager {
self.decoder_context = codec::init(channel_count as usize * 2, sample_rate)?; self.decoder_context = codec::init(channel_count as usize * 2, sample_rate)?;
self.update_pw_stream(channel_count, sample_rate)? self.update_pw_stream(channel_count, sample_rate)?
} }
AudioEvent::FinalChunkRecvd => self.decoder_context.input_done = true,
AudioEvent::TogglePlaying => self.playing = !self.playing,
_ => unimplemented!(), _ => unimplemented!(),
} }
Ok(()) Ok(())

View file

@ -25,10 +25,16 @@ pub struct Subsonic {
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct Controls { pub struct Controls {
pub quit: char, pub quit: char,
pub play_state_toggle: char,
pub next: char,
} }
fn default_controls() -> Controls { fn default_controls() -> Controls {
Controls { quit: 'q' } Controls {
quit: 'q',
play_state_toggle: ' ',
next: 'n',
}
} }
fn default_random_limit() -> i32 { fn default_random_limit() -> i32 {

View file

@ -16,8 +16,12 @@ fn init() -> Result<Receiver<utils::Error>, utils::Error> {
let (player_events_in, player_events_out) = channel(); let (player_events_in, player_events_out) = channel();
let audio_event_chan = let audio_event_chan =
audio::init(settings.clone(), error_in.clone(), player_events_in.clone())?; audio::init(settings.clone(), error_in.clone(), player_events_in.clone())?;
let api_event_chan = let api_event_chan = ssonic::init(
ssonic::init(settings.clone(), error_in.clone(), player_events_in.clone())?; settings.clone(),
error_in.clone(),
player_events_in.clone(),
audio_event_chan.clone(),
)?;
player::init( player::init(
settings.clone(), settings.clone(),
audio_event_chan, audio_event_chan,

View file

@ -8,10 +8,10 @@ pub struct Metadata {
pub artist: Option<String>, pub artist: Option<String>,
pub playing: bool, pub playing: bool,
pub duration: u32, pub duration: u32,
pub current_time: u32,
pub cover: DynamicImage, pub cover: DynamicImage,
pub size: u32, pub size: u32,
pub bytes_received: u32, pub bytes_received: u32,
pub last_byte_range_start: u32,
} }
impl Metadata { impl Metadata {
@ -22,13 +22,17 @@ impl Metadata {
artist: None, artist: None,
playing: false, playing: false,
duration: 0, duration: 0,
current_time: 0,
cover: default_cover(), cover: default_cover(),
size: 0, size: 0,
bytes_received: 0, bytes_received: 0,
last_byte_range_start: 0,
} }
} }
pub fn request_pending(&self) -> bool {
self.last_byte_range_start == self.bytes_received
}
pub fn bytes_pending(&self) -> bool { pub fn bytes_pending(&self) -> bool {
self.bytes_received < self.size self.bytes_received < self.size
} }
@ -41,7 +45,6 @@ impl Metadata {
self.id = song.id; self.id = song.id;
self.name = song.title; self.name = song.title;
self.artist = song.artist; self.artist = song.artist;
self.current_time = 0;
self.duration = song.duration; self.duration = song.duration;
self.bytes_received = 0; self.bytes_received = 0;
self.size = song.size; self.size = song.size;

View file

@ -21,7 +21,7 @@ pub enum PlayerEvent {
UpdateCover(DynamicImage), UpdateCover(DynamicImage),
UserQuit, UserQuit,
PlayNext, PlayNext,
AddAudioChunk(Vec<u8>), AddAudioChunk(usize),
FetchChunk, FetchChunk,
} }

View file

@ -1,4 +1,8 @@
use std::{sync::mpsc::TryRecvError, thread, time::Duration}; use std::{
sync::mpsc::TryRecvError,
thread,
time::{Duration, SystemTime},
};
use crossterm::event::{poll, read, Event, KeyCode}; use crossterm::event::{poll, read, Event, KeyCode};
use image::DynamicImage; use image::DynamicImage;
@ -45,22 +49,33 @@ impl Player {
PlayerEvent::AddSongList(list) => self.playlist.append(list), PlayerEvent::AddSongList(list) => self.playlist.append(list),
PlayerEvent::PlayNext => self.play_next()?, PlayerEvent::PlayNext => self.play_next()?,
PlayerEvent::UpdateCover(cover) => self.tui_root.update_cover(cover), PlayerEvent::UpdateCover(cover) => self.tui_root.update_cover(cover),
PlayerEvent::AddAudioChunk(chunk) => self.recv_chunk(chunk)?, PlayerEvent::AddAudioChunk(chunk_len) => self.recv_chunk(chunk_len)?,
PlayerEvent::FetchChunk => self.fetch_audio_chunk()?, PlayerEvent::FetchChunk => self.fetch_audio_chunk(false)?,
_ => unimplemented!(), _ => unimplemented!(),
} }
} }
if self.playlist.last_song() {
self.api_chan
.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(100))? { if poll(Duration::from_millis(40))? {
match read()? { match read()? {
Event::Key(e) => { Event::Key(e) => match e.code {
if e.code == KeyCode::Char(self.settings.controls.quit) { x if x == KeyCode::Char(self.settings.controls.quit) => {
self.player_chan_in.send(PlayerEvent::UserQuit)? self.player_chan_in.send(PlayerEvent::UserQuit)?
} }
x if x == KeyCode::Char(self.settings.controls.play_state_toggle) => {
self.audio_chan.send(AudioEvent::TogglePlaying)?
} }
x if x == KeyCode::Char(self.settings.controls.next) => {
self.player_chan_in.send(PlayerEvent::PlayNext)?
}
_ => (),
},
_ => (), _ => (),
} }
} }
@ -84,12 +99,16 @@ impl Player {
} }
self.audio_chan.send(AudioEvent::DecoderInit(48000, 2))?; self.audio_chan.send(AudioEvent::DecoderInit(48000, 2))?;
self.tui_root.metadata.update_metadata(song); self.tui_root.metadata.update_metadata(song);
self.fetch_audio_chunk()?; self.fetch_audio_chunk(true)?;
Ok(()) Ok(())
} }
fn fetch_audio_chunk(&mut self) -> Result<(), Error> { fn fetch_audio_chunk(&mut self, initial_fetch: bool) -> Result<(), Error> {
if self.tui_root.metadata.request_pending() && !initial_fetch {
return Ok(());
}
let start = self.tui_root.metadata.bytes_received; let start = self.tui_root.metadata.bytes_received;
let diff = self.tui_root.metadata.size - self.tui_root.metadata.bytes_received; let diff = self.tui_root.metadata.size - self.tui_root.metadata.bytes_received;
if diff <= MAX_CHUNK_SIZE { if diff <= MAX_CHUNK_SIZE {
@ -101,12 +120,12 @@ impl Player {
start, start,
end, end,
))?; ))?;
self.tui_root.metadata.last_byte_range_start = start;
Ok(()) Ok(())
} }
fn recv_chunk(&mut self, chunk: Vec<u8>) -> Result<(), Error> { fn recv_chunk(&mut self, chunk_len: usize) -> Result<(), Error> {
self.tui_root.metadata.bytes_received += chunk.len() as u32; self.tui_root.metadata.bytes_received += chunk_len as u32;
self.audio_chan.send(AudioEvent::DecodeChunk(chunk))?;
Ok(()) Ok(())
} }
} }

View file

@ -7,6 +7,10 @@ pub struct Playlist {
} }
impl Playlist { impl Playlist {
pub fn last_song(&self) -> bool {
self.song_list.len() == 1
}
pub fn get_next(&mut self) -> Option<Song> { pub fn get_next(&mut self) -> Option<Song> {
self.song_list.pop_front() self.song_list.pop_front()
} }

View file

@ -83,12 +83,7 @@ impl Root {
} }
} }
fn render_time(&self, area: Rect, buf: &mut Buffer) { fn render_time(&self, area: Rect, buf: &mut Buffer) {
Paragraph::new(format!( Paragraph::new(format!("{}", format_duration(self.metadata.duration),)).render(area, buf);
"{} / {}",
format_duration(self.metadata.current_time),
format_duration(self.metadata.duration),
))
.render(area, buf);
} }
pub fn update_cover(&mut self, cover: DynamicImage) { pub fn update_cover(&mut self, cover: DynamicImage) {
self.metadata.set_cover(cover); self.metadata.set_cover(cover);

View file

@ -7,6 +7,7 @@ use reqwest::{
use serde::Serialize; use serde::Serialize;
use crate::{ use crate::{
audio::AudioEvent,
player::PlayerEvent, player::PlayerEvent,
ssonic::{errors::APIError, response}, ssonic::{errors::APIError, response},
utils::{generate_random_salt, Error}, utils::{generate_random_salt, Error},
@ -46,7 +47,9 @@ impl APIClient {
let mut audio_chunk = Vec::new(); let mut audio_chunk = Vec::new();
response.read_to_end(&mut audio_chunk)?; response.read_to_end(&mut audio_chunk)?;
return Ok(PlayerEvent::AddAudioChunk(audio_chunk)); let chunk_len = audio_chunk.len();
self.audio_chan.send(AudioEvent::DecodeChunk(audio_chunk))?;
return Ok(PlayerEvent::AddAudioChunk(chunk_len));
} }
fn validate_media( fn validate_media(

View file

@ -8,9 +8,9 @@ use std::{
use reqwest::blocking::Client; use reqwest::blocking::Client;
use crate::{config::Settings, player::PlayerEvent, utils::Error}; use crate::{audio::AudioEvent, config::Settings, player::PlayerEvent, utils::Error};
pub const MAX_CHUNK_SIZE: u32 = 1000000; pub const MAX_CHUNK_SIZE: u32 = 1_000_000;
pub enum APIEvent { pub enum APIEvent {
FetchRandom(i32), FetchRandom(i32),
@ -24,12 +24,14 @@ pub struct APIClient {
api_requests: Receiver<APIEvent>, api_requests: Receiver<APIEvent>,
player_chan: Sender<PlayerEvent>, player_chan: Sender<PlayerEvent>,
ssonic_client: Client, ssonic_client: Client,
audio_chan: Sender<AudioEvent>,
} }
pub fn init( pub fn init(
settings: Arc<Settings>, settings: Arc<Settings>,
error_chan: Sender<Error>, error_chan: Sender<Error>,
player_chan: Sender<PlayerEvent>, player_chan: Sender<PlayerEvent>,
audio_chan: Sender<AudioEvent>,
) -> Result<Sender<APIEvent>, Error> { ) -> Result<Sender<APIEvent>, Error> {
let (api_requests_in, api_requests_out) = channel(); let (api_requests_in, api_requests_out) = channel();
thread::spawn(move || { thread::spawn(move || {
@ -39,6 +41,7 @@ pub fn init(
api_requests: api_requests_out, api_requests: api_requests_out,
player_chan, player_chan,
ssonic_client: Client::new(), ssonic_client: Client::new(),
audio_chan,
}; };
match client.begin() { match client.begin() {
Ok(_) => (), Ok(_) => (),