Init player with random songs
This commit is contained in:
parent
4889026749
commit
d6c9bf4d3b
9 changed files with 119 additions and 32 deletions
|
@ -16,6 +16,12 @@ pub struct Subsonic {
|
||||||
pub username: String,
|
pub username: String,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub password: String, // Only optional in file, will otherwise pull from Env var
|
pub password: String, // Only optional in file, will otherwise pull from Env var
|
||||||
|
#[serde(default = "default_random_limit")]
|
||||||
|
pub random_limit: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_random_limit() -> i32 {
|
||||||
|
10
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init() -> Result<Arc<Settings>, Error> {
|
pub fn init() -> Result<Arc<Settings>, Error> {
|
||||||
|
|
|
@ -16,20 +16,20 @@ fn init() -> Result<Receiver<utils::Error>, utils::Error> {
|
||||||
audio::init(settings.clone(), error_in.clone(), player_events_in.clone())?;
|
audio::init(settings.clone(), error_in.clone(), player_events_in.clone())?;
|
||||||
let api_event_chan =
|
let api_event_chan =
|
||||||
ssonic::init(settings.clone(), error_in.clone(), player_events_in.clone())?;
|
ssonic::init(settings.clone(), error_in.clone(), player_events_in.clone())?;
|
||||||
let mut player = player::init(
|
player::init(
|
||||||
settings.clone(),
|
settings.clone(),
|
||||||
audio_event_chan,
|
audio_event_chan,
|
||||||
api_event_chan,
|
api_event_chan,
|
||||||
error_in.clone(),
|
error_in.clone(),
|
||||||
player_events_out,
|
player_events_out,
|
||||||
)?;
|
)?;
|
||||||
player.begin()?;
|
|
||||||
Ok(error_out)
|
Ok(error_out)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let err_chan = match init() {
|
let err_chan = match init() {
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
|
utils::restore();
|
||||||
eprintln!("{}", x);
|
eprintln!("{}", x);
|
||||||
exit(1)
|
exit(1)
|
||||||
}
|
}
|
||||||
|
@ -38,6 +38,7 @@ fn main() {
|
||||||
match err_chan.recv() {
|
match err_chan.recv() {
|
||||||
Err(_) => exit(0),
|
Err(_) => exit(0),
|
||||||
Ok(err) => {
|
Ok(err) => {
|
||||||
|
utils::restore();
|
||||||
eprintln!("{}", err);
|
eprintln!("{}", err);
|
||||||
exit(1)
|
exit(1)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
use crate::utils::Error;
|
|
||||||
|
|
||||||
use super::Player;
|
|
||||||
|
|
||||||
impl Player {
|
|
||||||
pub fn begin(&mut self) -> Result<(), Error> {
|
|
||||||
std::thread::sleep(std::time::Duration::from_secs(5));
|
|
||||||
ratatui::restore();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +1,31 @@
|
||||||
use std::sync::{
|
use std::{
|
||||||
mpsc::{Receiver, Sender},
|
sync::{
|
||||||
Arc,
|
mpsc::{Receiver, Sender},
|
||||||
|
Arc,
|
||||||
|
},
|
||||||
|
thread,
|
||||||
};
|
};
|
||||||
|
|
||||||
use ratatui::DefaultTerminal;
|
use ratatui::DefaultTerminal;
|
||||||
|
|
||||||
use crate::{audio::AudioEvent, config::Settings, ssonic::APIEvent, utils::Error};
|
use crate::{
|
||||||
|
audio::AudioEvent,
|
||||||
|
config::Settings,
|
||||||
|
ssonic::{response::Song, APIEvent},
|
||||||
|
utils::Error,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct PlayerEvent {}
|
pub enum PlayerEvent {
|
||||||
|
AddSongList(Vec<Song>),
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Player {
|
pub struct Player {
|
||||||
terminal: DefaultTerminal,
|
terminal: DefaultTerminal,
|
||||||
|
settings: Arc<Settings>,
|
||||||
|
audio_chan: Sender<AudioEvent>,
|
||||||
|
api_chan: Sender<APIEvent>,
|
||||||
|
error_chan: Sender<Error>,
|
||||||
|
player_chan: Receiver<PlayerEvent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(
|
pub fn init(
|
||||||
|
@ -19,9 +34,20 @@ pub fn init(
|
||||||
api_chan: Sender<APIEvent>,
|
api_chan: Sender<APIEvent>,
|
||||||
error_chan: Sender<Error>,
|
error_chan: Sender<Error>,
|
||||||
player_chan: Receiver<PlayerEvent>,
|
player_chan: Receiver<PlayerEvent>,
|
||||||
) -> Result<Player, Error> {
|
) -> Result<(), Error> {
|
||||||
let mut terminal = ratatui::init();
|
let terminal = ratatui::init();
|
||||||
Ok(Player { terminal })
|
thread::spawn(move || {
|
||||||
|
let mut player = Player {
|
||||||
|
terminal,
|
||||||
|
settings,
|
||||||
|
audio_chan,
|
||||||
|
api_chan,
|
||||||
|
error_chan,
|
||||||
|
player_chan,
|
||||||
|
};
|
||||||
|
player.begin();
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
mod begin;
|
mod player;
|
||||||
|
|
23
src/player/player.rs
Normal file
23
src/player/player.rs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
use crate::{ssonic::APIEvent, utils::Error};
|
||||||
|
|
||||||
|
use super::Player;
|
||||||
|
|
||||||
|
impl Player {
|
||||||
|
pub fn begin(&mut self) -> Result<(), Error> {
|
||||||
|
self.init_playlist()?;
|
||||||
|
self.run()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_playlist(&self) -> Result<(), Error> {
|
||||||
|
self.api_chan
|
||||||
|
.send(APIEvent::FetchRandom(self.settings.subsonic.random_limit))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&mut self) -> Result<(), Error> {
|
||||||
|
loop {
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ use reqwest::{blocking::Response, header::ACCEPT};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
player::PlayerEvent,
|
||||||
ssonic::{errors::APIError, response},
|
ssonic::{errors::APIError, response},
|
||||||
utils::{generate_random_salt, Error},
|
utils::{generate_random_salt, Error},
|
||||||
};
|
};
|
||||||
|
@ -13,15 +14,40 @@ use super::APIClient;
|
||||||
|
|
||||||
impl APIClient {
|
impl APIClient {
|
||||||
pub fn begin(&mut self) -> Result<(), Error> {
|
pub fn begin(&mut self) -> Result<(), Error> {
|
||||||
self.validate()?;
|
self.ping()?;
|
||||||
loop {
|
loop {
|
||||||
unimplemented!();
|
let player_resp = match self.api_requests.recv()? {
|
||||||
|
super::APIEvent::FetchRandom(n) => self.get_random(n)?,
|
||||||
|
};
|
||||||
|
self.player_chan.send(player_resp)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate(&mut self) -> Result<(), Error> {
|
fn get_random(&mut self, n: i32) -> Result<PlayerEvent, Error> {
|
||||||
let response = self.request::<[(&str, String); 0]>("/ping.view", None)?;
|
let response = self.request("/getRandomSongs", Some(&[("size", n.to_string())]))?;
|
||||||
|
if !response.status().is_success() {
|
||||||
|
return Err(Box::new(APIError::StatusError(
|
||||||
|
response.status(),
|
||||||
|
"/getRandomSongs",
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
let song_list = self
|
||||||
|
.validate(response)?
|
||||||
|
.subsonic_response
|
||||||
|
.random_songs
|
||||||
|
.unwrap()
|
||||||
|
.songs;
|
||||||
|
|
||||||
|
Ok(PlayerEvent::AddSongList(song_list))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ping(&mut self) -> Result<(), Error> {
|
||||||
|
let response = self.request::<[(&str, String); 0]>("/ping.view", None)?;
|
||||||
|
self.validate(response)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate(&mut self, response: Response) -> 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(),
|
response.status(),
|
||||||
|
@ -34,7 +60,7 @@ impl APIClient {
|
||||||
ssonic_response.subsonic_response.error.unwrap().message,
|
ssonic_response.subsonic_response.error.unwrap().message,
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(ssonic_response)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_random_token(&self) -> (String, String) {
|
fn generate_random_token(&self) -> (String, String) {
|
||||||
|
|
|
@ -10,7 +10,9 @@ use reqwest::blocking::Client;
|
||||||
|
|
||||||
use crate::{config::Settings, player::PlayerEvent, utils::Error};
|
use crate::{config::Settings, player::PlayerEvent, utils::Error};
|
||||||
|
|
||||||
pub enum APIEvent {}
|
pub enum APIEvent {
|
||||||
|
FetchRandom(i32),
|
||||||
|
}
|
||||||
|
|
||||||
pub struct APIClient {
|
pub struct APIClient {
|
||||||
settings: Arc<Settings>,
|
settings: Arc<Settings>,
|
||||||
|
@ -48,4 +50,4 @@ pub fn init(
|
||||||
|
|
||||||
mod client;
|
mod client;
|
||||||
mod errors;
|
mod errors;
|
||||||
mod response;
|
pub mod response;
|
||||||
|
|
|
@ -12,7 +12,8 @@ pub struct SSonicResponse {
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub error: Option<Error>,
|
pub error: Option<Error>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub random_songs: Option<Vec<Song>>,
|
#[serde(alias = "randomSongs")]
|
||||||
|
pub random_songs: Option<SongList>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub play_queue: Option<PlayQueue>,
|
pub play_queue: Option<PlayQueue>,
|
||||||
}
|
}
|
||||||
|
@ -22,12 +23,21 @@ pub struct Error {
|
||||||
pub message: String,
|
pub message: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct SongList {
|
||||||
|
#[serde(alias = "song")]
|
||||||
|
pub songs: Vec<Song>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct Song {
|
pub struct Song {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub artist: String,
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub cover_art: String,
|
pub artist: Option<String>,
|
||||||
|
#[serde(alias = "coverArt")]
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub cover_art: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
|
|
|
@ -9,3 +9,7 @@ pub fn generate_random_salt() -> String {
|
||||||
.map(|c| c as char)
|
.map(|c| c as char)
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn restore() {
|
||||||
|
ratatui::restore()
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue