diff --git a/src/audio/codec.rs b/src/audio/codec.rs index d809eaf..d6b1073 100644 --- a/src/audio/codec.rs +++ b/src/audio/codec.rs @@ -13,7 +13,7 @@ pub struct DecoderContext { encoded_in: Sender>, frames_out: Receiver>, sample_buf: VecDeque>, - input_done: bool, + pub input_done: bool, } pub fn init(stride: usize, sample_rate: u32) -> Result { @@ -54,6 +54,12 @@ pub fn init(stride: usize, sample_rate: u32) -> Result { }) } +impl Drop for DecoderContext { + fn drop(&mut self) { + self.process.kill().unwrap(); + } +} + impl DecoderContext { pub fn append_chunk(&mut self, chunk: Vec) -> Result<(), Error> { self.encoded_in.send(chunk)?; @@ -70,13 +76,17 @@ impl DecoderContext { } } - pub fn next_sample(&mut self) -> (Vec, bool) { + pub fn next_sample(&mut self) -> (Vec, 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, +} diff --git a/src/audio/mod.rs b/src/audio/mod.rs index 7746ee5..ef19e6f 100644 --- a/src/audio/mod.rs +++ b/src/audio/mod.rs @@ -16,6 +16,7 @@ pub enum AudioEvent { DecodeChunk(Vec), DecoderInit(u32, u32), FinalChunkRecvd, + TogglePlaying, } pub struct SoundManager { diff --git a/src/audio/pw.rs b/src/audio/pw.rs index 5fc15fb..3788389 100644 --- a/src/audio/pw.rs +++ b/src/audio/pw.rs @@ -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>, diff --git a/src/audio/sound_mgr.rs b/src/audio/sound_mgr.rs index 3f7d8ac..b7ec65f 100644 --- a/src/audio/sound_mgr.rs +++ b/src/audio/sound_mgr.rs @@ -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(()) diff --git a/src/config/mod.rs b/src/config/mod.rs index 9686113..4f8725f 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -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 { diff --git a/src/main.rs b/src/main.rs index b5e2e87..2efc671 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,8 +16,12 @@ fn init() -> Result, 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, diff --git a/src/player/current.rs b/src/player/current.rs index 5e0e193..f1be98e 100644 --- a/src/player/current.rs +++ b/src/player/current.rs @@ -8,10 +8,10 @@ pub struct Metadata { pub artist: Option, 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; diff --git a/src/player/mod.rs b/src/player/mod.rs index 32dd3c8..fcb34ae 100644 --- a/src/player/mod.rs +++ b/src/player/mod.rs @@ -21,7 +21,7 @@ pub enum PlayerEvent { UpdateCover(DynamicImage), UserQuit, PlayNext, - AddAudioChunk(Vec), + AddAudioChunk(usize), FetchChunk, } diff --git a/src/player/player.rs b/src/player/player.rs index 8450e32..34c109b 100644 --- a/src/player/player.rs +++ b/src/player/player.rs @@ -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) -> 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(()) } } diff --git a/src/player/playlist.rs b/src/player/playlist.rs index f18af86..b9ea1b8 100644 --- a/src/player/playlist.rs +++ b/src/player/playlist.rs @@ -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 { self.song_list.pop_front() } diff --git a/src/player/tui.rs b/src/player/tui.rs index 0f43e11..f7e42e6 100644 --- a/src/player/tui.rs +++ b/src/player/tui.rs @@ -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); diff --git a/src/ssonic/client.rs b/src/ssonic/client.rs index be51ec8..7dcd5f2 100644 --- a/src/ssonic/client.rs +++ b/src/ssonic/client.rs @@ -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( diff --git a/src/ssonic/mod.rs b/src/ssonic/mod.rs index ffd28a9..72c7627 100644 --- a/src/ssonic/mod.rs +++ b/src/ssonic/mod.rs @@ -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, player_chan: Sender, ssonic_client: Client, + audio_chan: Sender, } pub fn init( settings: Arc, error_chan: Sender, player_chan: Sender, + audio_chan: Sender, ) -> Result, 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(_) => (),