Song fetching and covert art fetching boiler

This commit is contained in:
Muaz Ahmad 2024-11-28 13:20:22 +05:00
parent 35184a76b9
commit 2a894bdd83
8 changed files with 51 additions and 31 deletions

View file

@ -1,10 +1,8 @@
use std::ops::Deref;
use std::process::exit; use std::process::exit;
use std::sync::mpsc::{channel, Receiver}; use std::sync::mpsc::{channel, Receiver};
use player::errors::PlayerError; use player::errors::PlayerError;
use ssonic::response::PlayQueue;
mod audio; mod audio;
mod config; mod config;

View file

@ -1,6 +1,5 @@
#[derive(Debug)] #[derive(Debug)]
pub enum PlayerError { pub enum PlayerError {
PlaylistEmpty,
UserQuit, UserQuit,
} }
@ -9,7 +8,6 @@ impl std::error::Error for PlayerError {}
impl std::fmt::Display for PlayerError { impl std::fmt::Display for PlayerError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
Self::PlaylistEmpty => write!(f, "Playlist empty"),
Self::UserQuit => write!(f, "User quit signalled"), Self::UserQuit => write!(f, "User quit signalled"),
} }
} }

View file

@ -18,6 +18,7 @@ use crate::{
pub enum PlayerEvent { pub enum PlayerEvent {
AddSongList(Vec<Song>), AddSongList(Vec<Song>),
UserQuit, UserQuit,
PlayNext,
} }
pub struct Player { pub struct Player {

View file

@ -1,4 +1,4 @@
use std::{sync::mpsc::TryRecvError, time::Duration}; use std::{sync::mpsc::TryRecvError, thread, time::Duration};
use crossterm::event::{poll, read, Event, KeyCode}; use crossterm::event::{poll, read, Event, KeyCode};
@ -16,6 +16,7 @@ impl Player {
fn init_playlist(&self) -> Result<(), Error> { fn init_playlist(&self) -> Result<(), Error> {
self.api_chan self.api_chan
.send(APIEvent::FetchRandom(self.settings.subsonic.random_limit))?; .send(APIEvent::FetchRandom(self.settings.subsonic.random_limit))?;
self.player_chan_in.send(PlayerEvent::PlayNext)?;
Ok(()) Ok(())
} }
@ -37,6 +38,7 @@ impl Player {
match e { match e {
PlayerEvent::UserQuit => return Err(Box::new(PlayerError::UserQuit)), PlayerEvent::UserQuit => return Err(Box::new(PlayerError::UserQuit)),
PlayerEvent::AddSongList(list) => self.playlist.append(list), PlayerEvent::AddSongList(list) => self.playlist.append(list),
PlayerEvent::PlayNext => self.play_next()?,
_ => unimplemented!(), _ => unimplemented!(),
} }
} }
@ -60,4 +62,19 @@ impl Player {
fn update(&mut self) -> Result<(), Error> { fn update(&mut self) -> Result<(), Error> {
Ok(()) Ok(())
} }
fn play_next(&mut self) -> Result<(), Error> {
let song = match self.playlist.get_next() {
None => {
// no song exists, requeue the event
self.player_chan_in.send(PlayerEvent::PlayNext)?;
return Ok(());
}
Some(s) => s,
};
if let Some(cover_id) = song.cover_art {
self.api_chan.send(APIEvent::FetchCoverArt(cover_id))?;
}
Ok(())
}
} }

View file

@ -1,19 +1,14 @@
use std::{borrow::BorrowMut, collections::VecDeque}; use std::{borrow::BorrowMut, collections::VecDeque};
use crate::{ssonic::response::Song, utils::Error}; use crate::ssonic::response::Song;
use super::errors::PlayerError;
pub struct Playlist { pub struct Playlist {
song_list: VecDeque<Song>, song_list: VecDeque<Song>,
} }
impl Playlist { impl Playlist {
pub fn get_next(&mut self) -> Result<Song, Error> { pub fn get_next(&mut self) -> Option<Song> {
match self.song_list.pop_front() { self.song_list.pop_front()
Some(song) => Ok(song),
None => Err(Box::new(PlayerError::PlaylistEmpty)),
}
} }
pub fn append(&mut self, list: Vec<Song>) { pub fn append(&mut self, list: Vec<Song>) {

View file

@ -1,4 +1,7 @@
use reqwest::{blocking::Response, header::ACCEPT}; use reqwest::{
blocking::Response,
header::{ACCEPT, CONTENT_TYPE},
};
use serde::Serialize; use serde::Serialize;
use crate::{ use crate::{
@ -18,21 +21,31 @@ impl APIClient {
loop { loop {
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(String) => self.get_cover_art(String)?,
_ => unimplemented!(),
}; };
self.player_chan.send(player_resp)?; self.player_chan.send(player_resp)?;
} }
} }
fn get_random(&mut self, n: i32) -> Result<PlayerEvent, Error> { fn get_cover_art(&mut self, cover_id: String) -> Result<PlayerEvent, Error> {
let response = self.request("/getRandomSongs", Some(&[("size", n.to_string())]))?; let response = self.request("/getCoverArt", Some(&[("id", cover_id)]))?;
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(),
"/getRandomSongs", "/getCoverArt",
))); )));
} };
if response.headers()[CONTENT_TYPE].to_str()? == "application/json" {
self.validate(response, "/getCoverArt")?;
};
unimplemented!()
}
fn get_random(&mut self, n: i32) -> Result<PlayerEvent, Error> {
let response = self.request("/getRandomSongs", Some(&[("size", n.to_string())]))?;
let song_list = self let song_list = self
.validate(response)? .validate(response, "/getRandomSongs")?
.subsonic_response .subsonic_response
.random_songs .random_songs
.unwrap() .unwrap()
@ -43,16 +56,17 @@ 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)?;
self.validate(response)?; self.validate(response, "/ping.view")?;
Ok(()) Ok(())
} }
fn validate(&mut self, response: Response) -> Result<response::Response, Error> { fn validate(
&mut self,
response: Response,
path: &'static str,
) -> Result<response::Response, Error> {
if !response.status().is_success() { if !response.status().is_success() {
return Err(Box::new(APIError::StatusError( return Err(Box::new(APIError::StatusError(response.status(), path)));
response.status(),
"/ping.view",
)));
} }
let ssonic_response: response::Response = response.json()?; let ssonic_response: response::Response = response.json()?;
if ssonic_response.subsonic_response.status != "ok" { if ssonic_response.subsonic_response.status != "ok" {

View file

@ -12,6 +12,8 @@ use crate::{config::Settings, player::PlayerEvent, utils::Error};
pub enum APIEvent { pub enum APIEvent {
FetchRandom(i32), FetchRandom(i32),
FetchCoverArt(String),
StreamSong(String),
} }
pub struct APIClient { pub struct APIClient {

View file

@ -14,8 +14,6 @@ pub struct SSonicResponse {
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
#[serde(alias = "randomSongs")] #[serde(alias = "randomSongs")]
pub random_songs: Option<SongList>, pub random_songs: Option<SongList>,
#[serde(skip_serializing_if = "Option::is_none")]
pub play_queue: Option<PlayQueue>,
} }
#[derive(Deserialize)] #[derive(Deserialize)]
@ -35,10 +33,7 @@ pub struct Song {
pub title: String, pub title: String,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub artist: Option<String>, pub artist: Option<String>,
#[serde(alias = "coverArt")]
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
#[serde(alias = "coverArt")]
pub cover_art: Option<String>, pub cover_art: Option<String>,
} }
#[derive(Deserialize)]
pub struct PlayQueue {}