Chunked song download boiler

This commit is contained in:
Muaz Ahmad 2024-11-29 13:17:40 +05:00
parent 120d991360
commit 71cc75feff
5 changed files with 50 additions and 10 deletions

View file

@ -6,9 +6,11 @@ pub struct Metadata {
pub name: String, pub name: String,
pub artist: Option<String>, pub artist: Option<String>,
pub playing: bool, pub playing: bool,
pub duration: i32, pub duration: u32,
pub current_time: i32, pub current_time: u32,
pub cover: DynamicImage, pub cover: DynamicImage,
pub size: u32,
pub bytes_received: u32,
} }
impl Metadata { impl Metadata {
@ -20,6 +22,8 @@ impl Metadata {
duration: 0, duration: 0,
current_time: 0, current_time: 0,
cover: default_cover(), cover: default_cover(),
size: 0,
bytes_received: 0,
} }
} }
@ -32,5 +36,7 @@ impl Metadata {
self.artist = song.artist; self.artist = song.artist;
self.current_time = 0; self.current_time = 0;
self.duration = song.duration; self.duration = song.duration;
self.bytes_received = 0;
self.size = song.size;
} }
} }

View file

@ -4,7 +4,8 @@ use crossterm::event::{poll, read, Event, KeyCode};
use image::DynamicImage; use image::DynamicImage;
use crate::{ use crate::{
ssonic::{response::Song, APIEvent}, audio::AudioEvent,
ssonic::{response::Song, APIEvent, MAX_CHUNK_SIZE},
utils::{default_cover, Error}, utils::{default_cover, Error},
}; };
@ -79,8 +80,20 @@ impl Player {
self.player_chan_in self.player_chan_in
.send(PlayerEvent::UpdateCover(default_cover()))?; .send(PlayerEvent::UpdateCover(default_cover()))?;
} }
self.fetch_audio_chunk(song.id.clone())?;
self.tui_root.metadata.update_metadata(song); self.tui_root.metadata.update_metadata(song);
Ok(()) 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))?;
}
Ok(())
}
} }

View file

@ -2,7 +2,7 @@ use std::io::{BufReader, Read};
use reqwest::{ use reqwest::{
blocking::Response, blocking::Response,
header::{ACCEPT, CONTENT_TYPE}, header::{HeaderMap, HeaderValue, ACCEPT, CONTENT_TYPE, RANGE},
}; };
use serde::Serialize; use serde::Serialize;
@ -24,14 +24,29 @@ impl APIClient {
let player_resp = match self.api_requests.recv()? { let player_resp = match self.api_requests.recv()? {
super::APIEvent::FetchRandom(n) => self.get_random(n)?, super::APIEvent::FetchRandom(n) => self.get_random(n)?,
super::APIEvent::FetchCoverArt(id) => self.get_cover_art(id)?, super::APIEvent::FetchCoverArt(id) => self.get_cover_art(id)?,
super::APIEvent::StreamSong(id, start, end) => self.stream_song(id, start, end)?,
_ => unimplemented!(), _ => unimplemented!(),
}; };
self.player_chan.send(player_resp)?; self.player_chan.send(player_resp)?;
} }
} }
fn stream_song(
&mut self,
id: String,
byte_start: u32,
byte_end: u32,
) -> Result<PlayerEvent, Error> {
let range_header = format!("bytes={}-{}", byte_start, byte_end);
let mut header = HeaderMap::new();
header.insert(RANGE, range_header.parse()?);
let mut response = self.request("/stream", Some(&[("id", id)]), Some(header))?;
unimplemented!()
}
fn get_cover_art(&mut self, cover_id: String) -> Result<PlayerEvent, Error> { fn get_cover_art(&mut self, cover_id: String) -> Result<PlayerEvent, Error> {
let mut response = self.request("/getCoverArt", Some(&[("id", cover_id)]))?; let mut response = self.request("/getCoverArt", Some(&[("id", cover_id)]), None)?;
if !response.status().is_success() { if !response.status().is_success() {
return Err(Box::new(APIError::StatusError( return Err(Box::new(APIError::StatusError(
response.status(), response.status(),
@ -54,7 +69,7 @@ impl APIClient {
} }
fn get_random(&mut self, n: i32) -> Result<PlayerEvent, Error> { fn get_random(&mut self, n: i32) -> Result<PlayerEvent, Error> {
let response = self.request("/getRandomSongs", Some(&[("size", n.to_string())]))?; let response = self.request("/getRandomSongs", Some(&[("size", n.to_string())]), None)?;
let song_list = self let song_list = self
.validate(response, "/getRandomSongs")? .validate(response, "/getRandomSongs")?
.subsonic_response .subsonic_response
@ -66,7 +81,7 @@ impl APIClient {
} }
fn ping(&mut self) -> Result<(), Error> { fn ping(&mut self) -> Result<(), Error> {
let response = self.request::<[(&str, String); 0]>("/ping.view", None)?; let response = self.request::<[(&str, String); 0]>("/ping.view", None, None)?;
self.validate(response, "/ping.view")?; self.validate(response, "/ping.view")?;
Ok(()) Ok(())
} }
@ -98,6 +113,7 @@ impl APIClient {
&mut self, &mut self,
uri: &str, uri: &str,
extra_params: Option<&T>, extra_params: Option<&T>,
extra_headers: Option<HeaderMap>,
) -> Result<Response, Error> { ) -> Result<Response, Error> {
let (token, salt) = self.generate_random_token(); let (token, salt) = self.generate_random_token();
let mut req = self let mut req = self
@ -114,7 +130,9 @@ impl APIClient {
if let Some(params) = extra_params { if let Some(params) = extra_params {
req = req.query(params); req = req.query(params);
} }
req = req.header(ACCEPT, "application/json"); if let Some(headers) = extra_headers {
req = req.headers(headers);
}
Ok(req.send()?) Ok(req.send()?)
} }
} }

View file

@ -10,10 +10,12 @@ use reqwest::blocking::Client;
use crate::{config::Settings, player::PlayerEvent, utils::Error}; use crate::{config::Settings, player::PlayerEvent, utils::Error};
pub const MAX_CHUNK_SIZE: u32 = 1000000;
pub enum APIEvent { pub enum APIEvent {
FetchRandom(i32), FetchRandom(i32),
FetchCoverArt(String), FetchCoverArt(String),
StreamSong(String), StreamSong(String, u32, u32),
} }
pub struct APIClient { pub struct APIClient {

View file

@ -36,5 +36,6 @@ pub struct Song {
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
#[serde(alias = "coverArt")] #[serde(alias = "coverArt")]
pub cover_art: Option<String>, pub cover_art: Option<String>,
pub duration: i32, pub duration: u32,
pub size: u32,
} }