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::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;
|
||||||
|
|
|
@ -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"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>) {
|
||||||
|
|
|
@ -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" {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {}
|
|
||||||
|
|
Loading…
Reference in a new issue