- 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>>,
|
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,
|
||||||
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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>>,
|
||||||
|
|
|
@ -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(())
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -21,7 +21,7 @@ pub enum PlayerEvent {
|
||||||
UpdateCover(DynamicImage),
|
UpdateCover(DynamicImage),
|
||||||
UserQuit,
|
UserQuit,
|
||||||
PlayNext,
|
PlayNext,
|
||||||
AddAudioChunk(Vec<u8>),
|
AddAudioChunk(usize),
|
||||||
FetchChunk,
|
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 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(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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(_) => (),
|
||||||
|
|
Loading…
Reference in a new issue