Looping fetch of audio file chunks

This commit is contained in:
Muaz Ahmad 2024-11-29 14:43:37 +05:00
parent 6b0a53e96f
commit 3122e2559e
6 changed files with 60 additions and 43 deletions

View file

@ -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>,

View file

@ -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);
}

View file

@ -21,7 +21,7 @@ pub enum PlayerEvent {
UpdateCover(DynamicImage),
UserQuit,
PlayNext,
AddAudioChunks(Vec<u8>),
AddAudioChunk(Vec<u8>),
}
pub struct Player {

View file

@ -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;
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;
end = std::cmp::min(diff, MAX_CHUNK_SIZE);
self.api_chan.send(APIEvent::StreamSong(id, start, end))?;
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(())
}

View file

@ -42,46 +42,42 @@ 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));
return Ok(PlayerEvent::AddAudioChunk(audio_chunk));
}
unreachable!();
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)));
}
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 {
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(),
image::ImageFormat::from_mime_type(response.headers()[CONTENT_TYPE].to_str()?).unwrap(),
)?;
return Ok(PlayerEvent::UpdateCover(cover));
}
unreachable!();
}
fn get_random(&mut self, n: i32) -> Result<PlayerEvent, Error> {
let response = self.request("/getRandomSongs", Some(&[("size", n.to_string())]), None)?;

View file

@ -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)
}
}
}
}