Looping fetch of audio file chunks
This commit is contained in:
parent
6b0a53e96f
commit
3122e2559e
6 changed files with 60 additions and 43 deletions
|
@ -12,7 +12,9 @@ use pipewire::PipewireContext;
|
|||
|
||||
use crate::{config::Settings, player::PlayerEvent, utils::Error};
|
||||
|
||||
pub enum AudioEvent {}
|
||||
pub enum AudioEvent {
|
||||
DecodeChunk(Vec<u8>),
|
||||
}
|
||||
|
||||
pub struct SoundManager {
|
||||
settings: Arc<Settings>,
|
||||
|
|
|
@ -29,6 +29,10 @@ impl Metadata {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn bytes_pending(&self) -> bool {
|
||||
self.bytes_received < self.size
|
||||
}
|
||||
|
||||
pub fn set_cover(&mut self, cover: DynamicImage) {
|
||||
self.cover = cover.thumbnail(200, 200);
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ pub enum PlayerEvent {
|
|||
UpdateCover(DynamicImage),
|
||||
UserQuit,
|
||||
PlayNext,
|
||||
AddAudioChunks(Vec<u8>),
|
||||
AddAudioChunk(Vec<u8>),
|
||||
}
|
||||
|
||||
pub struct Player {
|
||||
|
|
|
@ -45,6 +45,7 @@ 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)?,
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
@ -81,18 +82,28 @@ impl Player {
|
|||
.send(PlayerEvent::UpdateCover(default_cover()))?;
|
||||
}
|
||||
self.tui_root.metadata.update_metadata(song);
|
||||
self.fetch_audio_chunk(self.tui_root.metadata.id.clone())?;
|
||||
self.fetch_audio_chunk()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fetch_audio_chunk(&mut self, id: String) -> Result<(), Error> {
|
||||
let (mut start, mut end) = (0, 0);
|
||||
if self.tui_root.metadata.bytes_received < self.tui_root.metadata.size {
|
||||
start = self.tui_root.metadata.bytes_received;
|
||||
let diff = self.tui_root.metadata.size - self.tui_root.metadata.bytes_received;
|
||||
end = std::cmp::min(diff, MAX_CHUNK_SIZE);
|
||||
self.api_chan.send(APIEvent::StreamSong(id, start, end))?;
|
||||
fn fetch_audio_chunk(&mut self) -> Result<(), Error> {
|
||||
let start = self.tui_root.metadata.bytes_received;
|
||||
let diff = self.tui_root.metadata.size - self.tui_root.metadata.bytes_received;
|
||||
let end = std::cmp::min(diff, MAX_CHUNK_SIZE) + start - 1;
|
||||
self.api_chan.send(APIEvent::StreamSong(
|
||||
self.tui_root.metadata.id.clone(),
|
||||
start,
|
||||
end,
|
||||
))?;
|
||||
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))?;
|
||||
if self.tui_root.metadata.bytes_pending() {
|
||||
self.fetch_audio_chunk()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -42,45 +42,41 @@ impl APIClient {
|
|||
header.insert(RANGE, range_header.parse()?);
|
||||
let mut response = self.request("/stream", Some(&[("id", id)]), Some(header))?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
return Err(Box::new(APIError::StatusError(
|
||||
response.status(),
|
||||
"/stream",
|
||||
)));
|
||||
};
|
||||
response = self.validate_media(response, "/stream", "audio")?;
|
||||
|
||||
if response.headers()[CONTENT_TYPE].to_str()? == "application/json" {
|
||||
self.validate(response, "/stream")?; // will never error if not image
|
||||
} else {
|
||||
eprintln!("Fetched chunk");
|
||||
let mut audio_chunk = Vec::new();
|
||||
response.read_to_end(&mut audio_chunk)?;
|
||||
return Ok(PlayerEvent::AddAudioChunks(audio_chunk));
|
||||
let mut audio_chunk = Vec::new();
|
||||
response.read_to_end(&mut audio_chunk)?;
|
||||
return Ok(PlayerEvent::AddAudioChunk(audio_chunk));
|
||||
}
|
||||
|
||||
fn validate_media(
|
||||
&mut self,
|
||||
response: Response,
|
||||
path: &'static str,
|
||||
expected_mime_class: &'static str,
|
||||
) -> Result<Response, Error> {
|
||||
if !response.status().is_success() {
|
||||
return Err(Box::new(APIError::StatusError(response.status(), path)));
|
||||
}
|
||||
unreachable!();
|
||||
let mime = response.headers()[CONTENT_TYPE].to_str()?;
|
||||
match mime {
|
||||
"application/json" => drop(self.validate(response, path)?), // must error on binary endpoints
|
||||
_ if mime.starts_with(expected_mime_class) => return Ok(response),
|
||||
_ => return Err(Box::new(APIError::InvalidResponseType(String::from(mime)))),
|
||||
}
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn get_cover_art(&mut self, cover_id: String) -> Result<PlayerEvent, Error> {
|
||||
let mut response = self.request("/getCoverArt", Some(&[("id", cover_id)]), None)?;
|
||||
if !response.status().is_success() {
|
||||
return Err(Box::new(APIError::StatusError(
|
||||
response.status(),
|
||||
"/getCoverArt",
|
||||
)));
|
||||
};
|
||||
if response.headers()[CONTENT_TYPE].to_str()? == "application/json" {
|
||||
self.validate(response, "/getCoverArt")?; // will never error if not image
|
||||
} else {
|
||||
let mut image = Vec::new();
|
||||
response.read_to_end(&mut image)?;
|
||||
let cover = image::load_from_memory_with_format(
|
||||
&mut image,
|
||||
image::ImageFormat::from_mime_type(response.headers()[CONTENT_TYPE].to_str()?)
|
||||
.unwrap(),
|
||||
)?;
|
||||
return Ok(PlayerEvent::UpdateCover(cover));
|
||||
}
|
||||
unreachable!();
|
||||
response = self.validate_media(response, "/getCoverArt", "image")?;
|
||||
let mut image = Vec::new();
|
||||
response.read_to_end(&mut image)?;
|
||||
let cover = image::load_from_memory_with_format(
|
||||
&mut image,
|
||||
image::ImageFormat::from_mime_type(response.headers()[CONTENT_TYPE].to_str()?).unwrap(),
|
||||
)?;
|
||||
return Ok(PlayerEvent::UpdateCover(cover));
|
||||
}
|
||||
|
||||
fn get_random(&mut self, n: i32) -> Result<PlayerEvent, Error> {
|
||||
|
|
|
@ -3,6 +3,7 @@ use reqwest::StatusCode;
|
|||
#[derive(Debug)]
|
||||
pub enum APIError {
|
||||
StatusError(StatusCode, &'static str),
|
||||
InvalidResponseType(String),
|
||||
SubsonicError(String),
|
||||
}
|
||||
|
||||
|
@ -17,6 +18,9 @@ impl std::fmt::Display for APIError {
|
|||
code, path
|
||||
),
|
||||
Self::SubsonicError(message) => write!(f, "Subsonic API error: {}", message),
|
||||
Self::InvalidResponseType(mime) => {
|
||||
write!(f, "Expected media mime-type, found: {}", mime)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue