Chunked song download boiler
This commit is contained in:
parent
120d991360
commit
71cc75feff
5 changed files with 50 additions and 10 deletions
|
@ -6,9 +6,11 @@ pub struct Metadata {
|
|||
pub name: String,
|
||||
pub artist: Option<String>,
|
||||
pub playing: bool,
|
||||
pub duration: i32,
|
||||
pub current_time: i32,
|
||||
pub duration: u32,
|
||||
pub current_time: u32,
|
||||
pub cover: DynamicImage,
|
||||
pub size: u32,
|
||||
pub bytes_received: u32,
|
||||
}
|
||||
|
||||
impl Metadata {
|
||||
|
@ -20,6 +22,8 @@ impl Metadata {
|
|||
duration: 0,
|
||||
current_time: 0,
|
||||
cover: default_cover(),
|
||||
size: 0,
|
||||
bytes_received: 0,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -32,5 +36,7 @@ impl Metadata {
|
|||
self.artist = song.artist;
|
||||
self.current_time = 0;
|
||||
self.duration = song.duration;
|
||||
self.bytes_received = 0;
|
||||
self.size = song.size;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,8 @@ use crossterm::event::{poll, read, Event, KeyCode};
|
|||
use image::DynamicImage;
|
||||
|
||||
use crate::{
|
||||
ssonic::{response::Song, APIEvent},
|
||||
audio::AudioEvent,
|
||||
ssonic::{response::Song, APIEvent, MAX_CHUNK_SIZE},
|
||||
utils::{default_cover, Error},
|
||||
};
|
||||
|
||||
|
@ -79,8 +80,20 @@ impl Player {
|
|||
self.player_chan_in
|
||||
.send(PlayerEvent::UpdateCover(default_cover()))?;
|
||||
}
|
||||
self.fetch_audio_chunk(song.id.clone())?;
|
||||
self.tui_root.metadata.update_metadata(song);
|
||||
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ use std::io::{BufReader, Read};
|
|||
|
||||
use reqwest::{
|
||||
blocking::Response,
|
||||
header::{ACCEPT, CONTENT_TYPE},
|
||||
header::{HeaderMap, HeaderValue, ACCEPT, CONTENT_TYPE, RANGE},
|
||||
};
|
||||
use serde::Serialize;
|
||||
|
||||
|
@ -24,14 +24,29 @@ impl APIClient {
|
|||
let player_resp = match self.api_requests.recv()? {
|
||||
super::APIEvent::FetchRandom(n) => self.get_random(n)?,
|
||||
super::APIEvent::FetchCoverArt(id) => self.get_cover_art(id)?,
|
||||
super::APIEvent::StreamSong(id, start, end) => self.stream_song(id, start, end)?,
|
||||
_ => unimplemented!(),
|
||||
};
|
||||
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> {
|
||||
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() {
|
||||
return Err(Box::new(APIError::StatusError(
|
||||
response.status(),
|
||||
|
@ -54,7 +69,7 @@ impl APIClient {
|
|||
}
|
||||
|
||||
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
|
||||
.validate(response, "/getRandomSongs")?
|
||||
.subsonic_response
|
||||
|
@ -66,7 +81,7 @@ impl APIClient {
|
|||
}
|
||||
|
||||
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")?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -98,6 +113,7 @@ impl APIClient {
|
|||
&mut self,
|
||||
uri: &str,
|
||||
extra_params: Option<&T>,
|
||||
extra_headers: Option<HeaderMap>,
|
||||
) -> Result<Response, Error> {
|
||||
let (token, salt) = self.generate_random_token();
|
||||
let mut req = self
|
||||
|
@ -114,7 +130,9 @@ impl APIClient {
|
|||
if let Some(params) = extra_params {
|
||||
req = req.query(params);
|
||||
}
|
||||
req = req.header(ACCEPT, "application/json");
|
||||
if let Some(headers) = extra_headers {
|
||||
req = req.headers(headers);
|
||||
}
|
||||
Ok(req.send()?)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,10 +10,12 @@ use reqwest::blocking::Client;
|
|||
|
||||
use crate::{config::Settings, player::PlayerEvent, utils::Error};
|
||||
|
||||
pub const MAX_CHUNK_SIZE: u32 = 1000000;
|
||||
|
||||
pub enum APIEvent {
|
||||
FetchRandom(i32),
|
||||
FetchCoverArt(String),
|
||||
StreamSong(String),
|
||||
StreamSong(String, u32, u32),
|
||||
}
|
||||
|
||||
pub struct APIClient {
|
||||
|
|
|
@ -36,5 +36,6 @@ pub struct Song {
|
|||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(alias = "coverArt")]
|
||||
pub cover_art: Option<String>,
|
||||
pub duration: i32,
|
||||
pub duration: u32,
|
||||
pub size: u32,
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue