Song fetching and covert art fetching boiler
This commit is contained in:
parent
35184a76b9
commit
2a894bdd83
8 changed files with 51 additions and 31 deletions
|
@ -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;
|
||||
|
|
|
@ -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"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ use crate::{
|
|||
pub enum PlayerEvent {
|
||||
AddSongList(Vec<Song>),
|
||||
UserQuit,
|
||||
PlayNext,
|
||||
}
|
||||
|
||||
pub struct Player {
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>) {
|
||||
|
|
|
@ -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" {
|
||||
|
|
|
@ -12,6 +12,8 @@ use crate::{config::Settings, player::PlayerEvent, utils::Error};
|
|||
|
||||
pub enum APIEvent {
|
||||
FetchRandom(i32),
|
||||
FetchCoverArt(String),
|
||||
StreamSong(String),
|
||||
}
|
||||
|
||||
pub struct APIClient {
|
||||
|
|
|
@ -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 {}
|
||||
|
|
Loading…
Reference in a new issue