diff --git a/src/player/current.rs b/src/player/current.rs index 6986bbb..ac9e055 100644 --- a/src/player/current.rs +++ b/src/player/current.rs @@ -6,9 +6,11 @@ pub struct Metadata { pub name: String, pub artist: Option, 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; } } diff --git a/src/player/player.rs b/src/player/player.rs index e9c0fac..769a449 100644 --- a/src/player/player.rs +++ b/src/player/player.rs @@ -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(()) + } } diff --git a/src/ssonic/client.rs b/src/ssonic/client.rs index 301ca52..c4d0821 100644 --- a/src/ssonic/client.rs +++ b/src/ssonic/client.rs @@ -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 { + 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 { - 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 { - 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, ) -> Result { 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()?) } } diff --git a/src/ssonic/mod.rs b/src/ssonic/mod.rs index f98d82d..ffd28a9 100644 --- a/src/ssonic/mod.rs +++ b/src/ssonic/mod.rs @@ -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 { diff --git a/src/ssonic/response.rs b/src/ssonic/response.rs index bcfa6ca..0e47dce 100644 --- a/src/ssonic/response.rs +++ b/src/ssonic/response.rs @@ -36,5 +36,6 @@ pub struct Song { #[serde(skip_serializing_if = "Option::is_none")] #[serde(alias = "coverArt")] pub cover_art: Option, - pub duration: i32, + pub duration: u32, + pub size: u32, }