- Playstate and next handling
- Handle chunk queueing better (directly from api response) - Fetch more songs if playlist is empty
This commit is contained in:
parent
d0c5a8f61f
commit
10e88b511d
13 changed files with 97 additions and 38 deletions
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ pub enum AudioEvent {
|
|||
DecodeChunk(Vec<u8>),
|
||||
DecoderInit(u32, u32),
|
||||
FinalChunkRecvd,
|
||||
TogglePlaying,
|
||||
}
|
||||
|
||||
pub struct SoundManager {
|
||||
|
|
|
@ -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>>,
|
||||
|
|
|
@ -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(())
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -21,7 +21,7 @@ pub enum PlayerEvent {
|
|||
UpdateCover(DynamicImage),
|
||||
UserQuit,
|
||||
PlayNext,
|
||||
AddAudioChunk(Vec<u8>),
|
||||
AddAudioChunk(usize),
|
||||
FetchChunk,
|
||||
}
|
||||
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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(_) => (),
|
||||
|
|
Loading…
Reference in a new issue