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::sync::mpsc::{channel, Receiver};
use player::errors::PlayerError;
use ssonic::response::PlayQueue;
mod audio;
mod config;

View file

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

View file

@ -18,6 +18,7 @@ use crate::{
pub enum PlayerEvent {
AddSongList(Vec<Song>),
UserQuit,
PlayNext,
}
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};
@ -16,6 +16,7 @@ impl Player {
fn init_playlist(&self) -> Result<(), Error> {
self.api_chan
.send(APIEvent::FetchRandom(self.settings.subsonic.random_limit))?;
self.player_chan_in.send(PlayerEvent::PlayNext)?;
Ok(())
}
@ -37,6 +38,7 @@ impl Player {
match e {
PlayerEvent::UserQuit => return Err(Box::new(PlayerError::UserQuit)),
PlayerEvent::AddSongList(list) => self.playlist.append(list),
PlayerEvent::PlayNext => self.play_next()?,
_ => unimplemented!(),
}
}
@ -60,4 +62,19 @@ impl Player {
fn update(&mut self) -> Result<(), Error> {
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 crate::{ssonic::response::Song, utils::Error};
use super::errors::PlayerError;
use crate::ssonic::response::Song;
pub struct Playlist {
song_list: VecDeque<Song>,
}
impl Playlist {
pub fn get_next(&mut self) -> Result<Song, Error> {
match self.song_list.pop_front() {
Some(song) => Ok(song),
None => Err(Box::new(PlayerError::PlaylistEmpty)),
}
pub fn get_next(&mut self) -> Option<Song> {
self.song_list.pop_front()
}
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 crate::{
@ -18,21 +21,31 @@ impl APIClient {
loop {
let player_resp = match self.api_requests.recv()? {
super::APIEvent::FetchRandom(n) => self.get_random(n)?,
super::APIEvent::FetchCoverArt(String) => self.get_cover_art(String)?,
_ => unimplemented!(),
};
self.player_chan.send(player_resp)?;
}
}
fn get_random(&mut self, n: i32) -> Result<PlayerEvent, Error> {
let response = self.request("/getRandomSongs", Some(&[("size", n.to_string())]))?;
fn get_cover_art(&mut self, cover_id: String) -> Result<PlayerEvent, Error> {
let response = self.request("/getCoverArt", Some(&[("id", cover_id)]))?;
if !response.status().is_success() {
return Err(Box::new(APIError::StatusError(
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
.validate(response)?
.validate(response, "/getRandomSongs")?
.subsonic_response
.random_songs
.unwrap()
@ -43,16 +56,17 @@ impl APIClient {
fn ping(&mut self) -> Result<(), Error> {
let response = self.request::<[(&str, String); 0]>("/ping.view", None)?;
self.validate(response)?;
self.validate(response, "/ping.view")?;
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() {
return Err(Box::new(APIError::StatusError(
response.status(),
"/ping.view",
)));
return Err(Box::new(APIError::StatusError(response.status(), path)));
}
let ssonic_response: response::Response = response.json()?;
if ssonic_response.subsonic_response.status != "ok" {

View file

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

View file

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