- 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>>,
frames_out: Receiver<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> {
@ -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 {
pub fn append_chunk(&mut self, chunk: Vec<u8>) -> Result<(), Error> {
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 mut fetch_more_file = false;
let mut fetch_more_file = SampleReturnState::BufferPending;
if curr_n_samples < 1000 {
self.fetch_samples();
if self.sample_buf.len() == curr_n_samples && !self.input_done {
fetch_more_file = true;
if self.sample_buf.len() == curr_n_samples {
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>),
DecoderInit(u32, u32),
FinalChunkRecvd,
TogglePlaying,
}
pub struct SoundManager {

View file

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

View file

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

View file

@ -25,10 +25,16 @@ pub struct Subsonic {
#[derive(Deserialize)]
pub struct Controls {
pub quit: char,
pub play_state_toggle: char,
pub next: char,
}
fn default_controls() -> Controls {
Controls { quit: 'q' }
Controls {
quit: 'q',
play_state_toggle: ' ',
next: 'n',
}
}
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 audio_event_chan =
audio::init(settings.clone(), error_in.clone(), player_events_in.clone())?;
let api_event_chan =
ssonic::init(settings.clone(), error_in.clone(), player_events_in.clone())?;
let api_event_chan = ssonic::init(
settings.clone(),
error_in.clone(),
player_events_in.clone(),
audio_event_chan.clone(),
)?;
player::init(
settings.clone(),
audio_event_chan,

View file

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

View file

@ -21,7 +21,7 @@ pub enum PlayerEvent {
UpdateCover(DynamicImage),
UserQuit,
PlayNext,
AddAudioChunk(Vec<u8>),
AddAudioChunk(usize),
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 image::DynamicImage;
@ -45,22 +49,33 @@ impl Player {
PlayerEvent::AddSongList(list) => self.playlist.append(list),
PlayerEvent::PlayNext => self.play_next()?,
PlayerEvent::UpdateCover(cover) => self.tui_root.update_cover(cover),
PlayerEvent::AddAudioChunk(chunk) => self.recv_chunk(chunk)?,
PlayerEvent::FetchChunk => self.fetch_audio_chunk()?,
PlayerEvent::AddAudioChunk(chunk_len) => self.recv_chunk(chunk_len)?,
PlayerEvent::FetchChunk => self.fetch_audio_chunk(false)?,
_ => unimplemented!(),
}
}
if self.playlist.last_song() {
self.api_chan
.send(APIEvent::FetchRandom(self.settings.subsonic.random_limit))?;
}
Ok(())
}
fn handle_inputs(&mut self) -> Result<(), Error> {
if poll(Duration::from_millis(100))? {
if poll(Duration::from_millis(40))? {
match read()? {
Event::Key(e) => {
if e.code == KeyCode::Char(self.settings.controls.quit) {
Event::Key(e) => match e.code {
x if x == KeyCode::Char(self.settings.controls.quit) => {
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.tui_root.metadata.update_metadata(song);
self.fetch_audio_chunk()?;
self.fetch_audio_chunk(true)?;
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 diff = self.tui_root.metadata.size - self.tui_root.metadata.bytes_received;
if diff <= MAX_CHUNK_SIZE {
@ -101,12 +120,12 @@ impl Player {
start,
end,
))?;
self.tui_root.metadata.last_byte_range_start = start;
Ok(())
}
fn recv_chunk(&mut self, chunk: Vec<u8>) -> Result<(), Error> {
self.tui_root.metadata.bytes_received += chunk.len() as u32;
self.audio_chan.send(AudioEvent::DecodeChunk(chunk))?;
fn recv_chunk(&mut self, chunk_len: usize) -> Result<(), Error> {
self.tui_root.metadata.bytes_received += chunk_len as u32;
Ok(())
}
}

View file

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

View file

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

View file

@ -7,6 +7,7 @@ use reqwest::{
use serde::Serialize;
use crate::{
audio::AudioEvent,
player::PlayerEvent,
ssonic::{errors::APIError, response},
utils::{generate_random_salt, Error},
@ -46,7 +47,9 @@ impl APIClient {
let mut audio_chunk = Vec::new();
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(

View file

@ -8,9 +8,9 @@ use std::{
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 {
FetchRandom(i32),
@ -24,12 +24,14 @@ pub struct APIClient {
api_requests: Receiver<APIEvent>,
player_chan: Sender<PlayerEvent>,
ssonic_client: Client,
audio_chan: Sender<AudioEvent>,
}
pub fn init(
settings: Arc<Settings>,
error_chan: Sender<Error>,
player_chan: Sender<PlayerEvent>,
audio_chan: Sender<AudioEvent>,
) -> Result<Sender<APIEvent>, Error> {
let (api_requests_in, api_requests_out) = channel();
thread::spawn(move || {
@ -39,6 +41,7 @@ pub fn init(
api_requests: api_requests_out,
player_chan,
ssonic_client: Client::new(),
audio_chan,
};
match client.begin() {
Ok(_) => (),