Updated Controller and added Player Trait

This commit is contained in:
MrDulfin 2024-05-19 18:48:29 -04:00
parent 94e6c25219
commit 56040bfd28
7 changed files with 226 additions and 545 deletions

View file

@ -8,13 +8,16 @@ pub mod music_storage {
pub mod db_reader; pub mod db_reader;
} }
pub mod music_controller{ pub mod music_controller {
pub mod controller; pub mod controller;
pub mod connections; pub mod connections;
pub mod queue; pub mod queue;
} }
pub mod music_player; pub mod music_player {
pub mod gstreamer;
pub mod player;
}
#[allow(clippy::module_inception)] #[allow(clippy::module_inception)]
pub mod config { pub mod config {
pub mod config; pub mod config;

View file

@ -1,103 +1,103 @@
use std::{ // use std::{
sync::{Arc, RwLock}, // sync::{Arc, RwLock},
error::Error, // error::Error,
}; // };
use discord_rpc_client::Client; // use discord_rpc_client::Client;
use listenbrainz::ListenBrainz; // use listenbrainz::ListenBrainz;
use uuid::Uuid; // use uuid::Uuid;
use crate::{ // use crate::{
config::config::Config, music_controller::controller::{Controller, QueueCmd, QueueResponse}, music_storage::library::{MusicLibrary, Song, Tag} // config::config::Config, music_controller::controller::{Controller, QueueCmd, QueueResponse}, music_storage::library::{MusicLibrary, Song, Tag}
}; // };
use super::controller::DatabaseResponse; // use super::controller::DatabaseResponse;
impl Controller { // impl Controller {
pub fn listenbrainz_authenticate(&mut self) -> Result<ListenBrainz, Box<dyn Error>> { // pub fn listenbrainz_authenticate(&mut self) -> Result<ListenBrainz, Box<dyn Error>> {
let config = &self.config.read().unwrap(); // let config = &self.config.read().unwrap();
let mut client = ListenBrainz::new(); // let mut client = ListenBrainz::new();
let lbz_token = match &config.connections.listenbrainz_token { // let lbz_token = match &config.connections.listenbrainz_token {
Some(token) => token, // Some(token) => token,
None => todo!("No ListenBrainz token in config") // None => todo!("No ListenBrainz token in config")
}; // };
if !client.is_authenticated() { // if !client.is_authenticated() {
client.authenticate(lbz_token)?; // client.authenticate(lbz_token)?;
} // }
Ok(client) // Ok(client)
} // }
pub fn lbz_scrobble(&self, client: ListenBrainz, uuid: Uuid) -> Result<(), Box<dyn Error>> { // pub fn lbz_scrobble(&self, client: ListenBrainz, uuid: Uuid) -> Result<(), Box<dyn Error>> {
let config = &self.config.read().unwrap(); // let config = &self.config.read().unwrap();
&self.db_mail.send(super::controller::DatabaseCmd::QueryUuid(uuid)); // &self.db_mail.send(super::controller::DatabaseCmd::QueryUuid(uuid));
let res = &self.db_mail.recv()?; // let res = &self.db_mail.recv()?;
let song = match res { // let song = match res {
DatabaseResponse::Song(song) => song, // DatabaseResponse::Song(song) => song,
_ => todo!() // _ => todo!()
}; // };
let unknown = &"unknown".to_string(); // let unknown = &"unknown".to_string();
let artist = song.get_tag(&Tag::Artist).unwrap_or(unknown); // let artist = song.get_tag(&Tag::Artist).unwrap_or(unknown);
let track = song.get_tag(&Tag::Title).unwrap_or(unknown); // let track = song.get_tag(&Tag::Title).unwrap_or(unknown);
let release = song.get_tag(&Tag::Album).map(|rel| rel.as_str()); // let release = song.get_tag(&Tag::Album).map(|rel| rel.as_str());
client.listen(artist, track, release)?; // client.listen(artist, track, release)?;
Ok(()) // Ok(())
} // }
pub fn lbz_now_playing(&self, client: ListenBrainz, uuid: Uuid) -> Result<(), Box<dyn Error>> { // pub fn lbz_now_playing(&self, client: ListenBrainz, uuid: Uuid) -> Result<(), Box<dyn Error>> {
let config = &self.config.read().unwrap(); // let config = &self.config.read().unwrap();
&self.db_mail.send(super::controller::DatabaseCmd::QueryUuid(uuid)); // &self.db_mail.send(super::controller::DatabaseCmd::QueryUuid(uuid));
let res = &self.db_mail.recv()?; // let res = &self.db_mail.recv()?;
let song = match res { // let song = match res {
DatabaseResponse::Song(song) => song, // DatabaseResponse::Song(song) => song,
_ => todo!() // _ => todo!()
}; // };
let unknown = &"unknown".to_string(); // let unknown = &"unknown".to_string();
let artist = song.get_tag(&Tag::Artist).unwrap_or(unknown); // let artist = song.get_tag(&Tag::Artist).unwrap_or(unknown);
let track = song.get_tag(&Tag::Title).unwrap_or(unknown); // let track = song.get_tag(&Tag::Title).unwrap_or(unknown);
let release = song.get_tag(&Tag::Album).map(|rel| rel.as_str()); // let release = song.get_tag(&Tag::Album).map(|rel| rel.as_str());
client.listen(artist, track, release)?; // client.listen(artist, track, release)?;
Ok(()) // Ok(())
} // }
pub fn discord_song_change(client: &mut Client,song: Song) { // pub fn discord_song_change(client: &mut Client,song: Song) {
client.set_activity(|a| { // client.set_activity(|a| {
a.state(format!("Listening to {}", song.get_tag(&Tag::Title).unwrap())) // a.state(format!("Listening to {}", song.get_tag(&Tag::Title).unwrap()))
.into() // .into()
}); // });
} // }
} // }
#[cfg(test)] // #[cfg(test)]
mod test_super { // mod test_super {
use std::{thread::sleep, time::Duration}; // use std::{thread::sleep, time::Duration};
use super::*; // use super::*;
use crate::config::config::tests::read_config_lib; // use crate::config::config::tests::read_config_lib;
#[test] // #[test]
fn listenbrainz() { // fn listenbrainz() {
let mut c = Controller::start(".\\test-config\\config_test.json").unwrap(); // let mut c = Controller::start(".\\test-config\\config_test.json").unwrap();
let client = c.listenbrainz_authenticate().unwrap(); // let client = c.listenbrainz_authenticate().unwrap();
c.q_new().unwrap(); // c.q_new().unwrap();
c.queue_mail[0].send(QueueCmd::SetVolume(0.04)).unwrap(); // c.queue_mail[0].send(QueueCmd::SetVolume(0.04)).unwrap();
let songs = c.lib_get_songs(); // let songs = c.lib_get_songs();
c.q_enqueue(0, songs[1].location.to_owned()).unwrap(); // c.q_enqueue(0, songs[1].location.to_owned()).unwrap();
c.q_play(0).unwrap(); // c.q_play(0).unwrap();
sleep(Duration::from_secs(100)); // sleep(Duration::from_secs(100));
c.lbz_scrobble(client, songs[1].uuid).unwrap(); // c.lbz_scrobble(client, songs[1].uuid).unwrap();
} // }
} // }

View file

@ -13,7 +13,8 @@ use std::error::Error;
use crossbeam_channel::unbounded; use crossbeam_channel::unbounded;
use uuid::Uuid; use uuid::Uuid;
use crate::music_controller::queue::{QueueItem, QueueItemType}; use crate::music_controller::queue::QueueItem;
use crate::music_player::gstreamer::GStreamer;
use crate::music_storage::library::{Tag, URI}; use crate::music_storage::library::{Tag, URI};
use crate::{ use crate::{
music_storage::library::{MusicLibrary, Song}, music_storage::library::{MusicLibrary, Song},
@ -22,63 +23,10 @@ use crate::{
}; };
pub struct Controller { pub struct Controller {
// queues: Vec<Queue>, pub queue: Queue,
pub config: Arc<RwLock<Config>>, pub config: Arc<RwLock<Config>>,
// library: MusicLibrary, pub library: MusicLibrary,
pub(super) controller_mail: MailMan<ControllerCmd, ControllerResponse>, player_mail: MailMan<PlayerCmd, PlayerRes>
pub(super) db_mail: MailMan<DatabaseCmd, DatabaseResponse>,
pub(super) queue_mail: Vec<MailMan<QueueCmd, QueueResponse>>,
}
#[derive(Debug)]
pub(super) enum ControllerCmd {
Default,
Test
}
#[derive(Debug)]
pub(super) enum ControllerResponse {
Empty,
QueueMailMan(MailMan<QueueCmd, QueueResponse>),
}
#[derive(Debug)]
pub(super) enum DatabaseCmd {
Default,
Test,
SaveLibrary,
GetSongs,
QueryUuid(Uuid),
QueryUuids(Vec<Uuid>),
ReadFolder(String),
}
#[derive(Debug)]
pub(super) enum DatabaseResponse {
Empty,
Song(Song),
Songs(Vec<Song>),
Library(MusicLibrary),
}
#[derive(Debug)]
pub(super) enum QueueCmd {
Default,
Test,
Play,
Pause,
// SetSongs(Vec<QueueItem<QueueState>>),
// SetLocation(URI),
Enqueue(URI),
SetVolume(f64),
}
#[derive(Debug)]
pub(super) enum QueueResponse {
Default,
Test,
Index(i32),
Uuid(Uuid),
} }
#[derive(Debug)] #[derive(Debug)]
@ -115,227 +63,72 @@ impl<T: Send, U: Send> MailMan<T, U> {
} }
} }
enum PlayerCmd {
Test(URI)
}
enum PlayerRes {
Test
}
#[allow(unused_variables)] #[allow(unused_variables)]
impl Controller { impl Controller {
pub fn start<P>(config_path: P) -> Result<Self, Box<dyn Error>> pub fn start<P>(config_path: P) -> Result<Self, Box<dyn Error>>
where std::path::PathBuf: std::convert::From<P> where std::path::PathBuf: std::convert::From<P>
{ {
let config_path = PathBuf::from(config_path); let config_path = PathBuf::from(config_path);
let config = Config::read_file(config_path)?; let config = Config::read_file(config_path)?;
let uuid = config.libraries.get_default()?.uuid; let uuid = config.libraries.get_default()?.uuid;
let config_ = Arc::new(RwLock::from(config)); let config_ = Arc::new(RwLock::from(config));
let mut lib = MusicLibrary::init(config_.clone(), uuid)?; let library = MusicLibrary::init(config_.clone(), uuid)?;
let config = config_.clone(); let (player_mail, in_thread) = MailMan::<PlayerCmd, PlayerRes>::double();
let (out_thread_controller, in_thread) = MailMan::double();
let monitor_thread = spawn(move || {
use ControllerCmd::*;
loop {
let command = in_thread.recv().unwrap();
match command { spawn(move || {
Default => (), let mut player = GStreamer::new().unwrap();
Test => {
in_thread.send(ControllerResponse::Empty).unwrap();
},
}
}
});
let config = config_.clone(); while true {
let (out_thread_db, in_thread) = MailMan::double(); match in_thread.recv().unwrap() {
let db_monitor = spawn(move || { PlayerCmd::Test(uri) => {
use DatabaseCmd::*; &player.set_volume(0.04);
loop { _ = &player.enqueue_next(&uri).unwrap();
let command = in_thread.recv().unwrap(); _ = &player.play();
in_thread.send(PlayerRes::Test).unwrap();
match command {
Default => {},
Test => {
in_thread.send(DatabaseResponse::Empty).unwrap();
},
GetSongs => {
let songs = lib.query_tracks(&String::from(""), &(vec![Tag::Title]), &(vec![Tag::Title])).unwrap().iter().cloned().cloned().collect();
in_thread.send(DatabaseResponse::Songs(songs)).unwrap();
},
SaveLibrary => {
//TODO: make this send lib ref to the function to save instead
lib.save(config.read().unwrap().to_owned()).unwrap();
},
QueryUuid(uuid) => {
match lib.query_uuid(&uuid) {
Some(song) => in_thread.send(DatabaseResponse::Song(song.0.clone())).unwrap(),
None => in_thread.send(DatabaseResponse::Empty).unwrap(),
}
},
QueryUuids(uuids) => {
let mut vec = Vec::new();
for uuid in uuids {
match lib.query_uuid(&uuid) {
Some(song) => vec.push(song.0.clone()),
None => unimplemented!()
}
}
in_thread.send(DatabaseResponse::Songs(vec)).unwrap();
},
ReadFolder(folder) => {
lib.scan_folder(&folder).unwrap();
in_thread.send(DatabaseResponse::Empty).unwrap();
} }
} }
} }
}); });
Ok( Ok(
Controller { Controller {
// queues: Vec::new(), queue: Queue::new(),
config: config_.clone(), config: config_.clone(),
controller_mail: out_thread_controller, library,
db_mail: out_thread_db, player_mail
queue_mail: Vec::new(),
} }
) )
} }
pub fn lib_get_songs(&self) -> Vec<Song> { pub fn q_add(&self, item: Uuid, source:super::queue::PlayerLocation , by_human: bool) {
self.db_mail.send(DatabaseCmd::GetSongs); self.queue.add_item(item, source, by_human)
match self.db_mail.recv().unwrap() {
DatabaseResponse::Songs(songs) => songs,
_ => Vec::new()
}
} }
pub fn lib_scan_folder(&self, folder: String) -> Result<(), Box<dyn Error>> {
let mail = &self.db_mail;
mail.send(DatabaseCmd::ReadFolder(folder))?;
dbg!(mail.recv()?);
Ok(())
}
pub fn lib_save(&self) -> Result<(), Box<dyn Error>> {
self.db_mail.send(DatabaseCmd::SaveLibrary);
Ok(())
}
pub fn q_new(&mut self) -> Result<usize, Box<dyn Error>> {
let (out_thread_queue, in_thread) = MailMan::<QueueCmd, QueueResponse>::double();
let queues_monitor = spawn(move || {
use QueueCmd::*;
let mut queue = Queue::new().unwrap();
loop {
let command = in_thread.recv().unwrap();
match command {
Default => {},
Test => { in_thread.send(QueueResponse::Test).unwrap() },
Play => {
match queue.player.play() {
Ok(_) => in_thread.send(QueueResponse::Default).unwrap(),
Err(_) => todo!()
};
},
Pause => {
match queue.player.pause() {
Ok(_) => in_thread.send(QueueResponse::Default).unwrap(),
Err(_) => todo!()
}
},
// SetSongs(songs) => {
// queue.set_tracks(songs);
// in_thread.send(QueueResponse::Default).unwrap();
// },
Enqueue(uri) => {
queue.player.enqueue_next(&uri).unwrap();
// in_thread.send(QueueResponse::Default).unwrap();
},
SetVolume(vol) => {
queue.player.set_volume(vol);
}
}
}
});
self.queue_mail.push(out_thread_queue);
Ok(self.queue_mail.len() - 1)
}
pub fn q_play(&self, index: usize) -> Result<(), Box<dyn Error>> {
let mail = &self.queue_mail[index];
mail.send(QueueCmd::Play)?;
dbg!(mail.recv()?);
Ok(())
}
pub fn q_pause(&self, index: usize) -> Result<(), Box<dyn Error>> {
let mail = &self.queue_mail[index];
mail.send(QueueCmd::Pause)?;
dbg!(mail.recv()?);
Ok(())
}
pub fn q_set_volume(&self, index: usize, volume: f64) -> Result<(), Box<dyn Error>> {
let mail = &self.queue_mail[index];
mail.send(QueueCmd::SetVolume(volume))?;
Ok(())
}
// fn q_set_songs(&self, index: usize, songs: Vec<QueueItem<QueueState>>) -> Result<(), Box<dyn Error>> {
// let mail = &self.queue_mail[index];
// mail.send(QueueCmd::SetSongs(songs))?;
// dbg!(mail.recv()?);
// Ok(())
// }
pub fn q_enqueue(&self, index: usize, uri: URI) -> Result<(), Box<dyn Error>> {
let mail = &self.queue_mail[index];
mail.send(QueueCmd::Enqueue(uri))?;
// dbg!(mail.recv()?);
Ok(())
}
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod test_super {
use std::{thread::sleep, time::Duration}; use std::{thread::sleep, time::Duration};
use super::Controller; use super::*;
#[test]
fn play_test() {
let mut a = match Controller::start("test-config/config_test.json".to_string()) {
Ok(c) => c,
Err(e) => panic!("{e}")
};
sleep(Duration::from_millis(500));
let i = a.q_new().unwrap();
a.q_set_volume(i, 0.04);
// a.new_queue();
let songs = a.lib_get_songs();
a.q_enqueue(i, songs[2].location.clone());
// a.enqueue(1, songs[2].location.clone());
a.q_play(i).unwrap();
// a.play(1).unwrap();
sleep(Duration::from_secs(10));
a.q_pause(i);
sleep(Duration::from_secs(10));
a.q_play(i);
sleep(Duration::from_secs(1000));
}
#[test] #[test]
fn test_() { fn test_() {
let a = match Controller::start("test-config/config_test.json".to_string()) { let c = Controller::start("F:\\Dangoware\\Dango Music Player\\dmp-core\\test-config\\config_test.json").unwrap();
Ok(c) => c,
Err(e) => panic!("{e}") sleep(Duration::from_secs(60));
};
a.lib_scan_folder("F:/Music/Mp3".to_string());
a.lib_save();
} }
} }

View file

@ -1,8 +1,5 @@
use uuid::Uuid; use uuid::Uuid;
use crate::{ use crate::music_storage::library::{MusicLibrary, Song, URI};
music_player::{Player, PlayerError},
music_storage::library::{Album, MusicLibrary, URI}
};
use std::{ use std::{
error::Error, error::Error,
sync::{Arc, RwLock} sync::{Arc, RwLock}
@ -27,54 +24,6 @@ pub enum QueueState {
NoState, NoState,
} }
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum QueueItemType<'a> {
Song(Uuid),
ExternalSong(URI),
Album{
album: Album<'a>,
shuffled: bool,
order: Option<Vec<Uuid>>,
// disc #, track #
current: (i32, i32)
},
Playlist {
uuid: Uuid,
shuffled: bool,
order: Option<Vec<Uuid>>,
current: Uuid
},
None,
Test
}
impl QueueItemType<'_> {
fn get_uri(&self, lib: Arc<RwLock<MusicLibrary>>) -> Option<URI> {
use QueueItemType::*;
let lib = lib.read().unwrap();
match self {
Song(uuid) => {
if let Some((song, _)) = lib.query_uuid(uuid) {
Some(song.location.clone())
}else {
Option::None
}
},
Album{album, shuffled, current: (disc, index), ..} => {
if !shuffled {
Some(album.track(*disc as usize, *index as usize).unwrap().location.clone())
}else {
todo!() //what to do for non shuffled album
}
},
ExternalSong(uri) => { Some(uri.clone()) },
_ => { Option::None }
}
}
}
// TODO: move this to a different location to be used elsewhere // TODO: move this to a different location to be used elsewhere
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
#[non_exhaustive] #[non_exhaustive]
@ -88,16 +37,16 @@ pub enum PlayerLocation {
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
#[non_exhaustive] #[non_exhaustive]
pub struct QueueItem<'a> { pub struct QueueItem {
pub(super) item: QueueItemType<'a>, pub(super) item: Song,
pub(super) state: QueueState, pub(super) state: QueueState,
pub(super) source: PlayerLocation, pub(super) source: PlayerLocation,
pub(super) by_human: bool pub(super) by_human: bool
} }
impl QueueItem<'_> { impl From<Song> for QueueItem {
fn new() -> Self { fn from(song: Song) -> Self {
QueueItem { QueueItem {
item: QueueItemType::None, item: song,
state: QueueState::NoState, state: QueueState::NoState,
source: PlayerLocation::Library, source: PlayerLocation::Library,
by_human: false by_human: false
@ -107,15 +56,14 @@ impl QueueItem<'_> {
#[derive(Debug)] #[derive(Debug)]
pub struct Queue<'a> { pub struct Queue {
pub player: Player, pub items: Vec<QueueItem>,
pub name: String, pub played: Vec<QueueItem>,
pub items: Vec<QueueItem<'a>>, pub loop_: bool,
pub played: Vec<QueueItem<'a>>, pub shuffle: bool
pub loop_: bool
} }
impl<'a> Queue<'a> { impl Queue {
fn has_addhere(&self) -> bool { fn has_addhere(&self) -> bool {
for item in &self.items { for item in &self.items {
if item.state == QueueState::AddHere { if item.state == QueueState::AddHere {
@ -126,28 +74,26 @@ impl<'a> Queue<'a> {
} }
fn dbg_items(&self) { fn dbg_items(&self) {
dbg!(self.items.iter().map(|item| item.item.clone() ).collect::<Vec<QueueItemType>>(), self.items.len()); dbg!(self.items.iter().map(|item| item.item.clone() ).collect::<Vec<Song>>(), self.items.len());
} }
pub fn new() -> Result<Self, PlayerError> { pub fn new() -> Self {
Ok( //TODO: Make the queue take settings from config/state if applicable
Queue { Queue {
player: Player::new()?, items: Vec::new(),
name: String::new(), played: Vec::new(),
items: Vec::new(), loop_: false,
played: Vec::new(), shuffle: false,
loop_: false, }
}
)
} }
pub fn set_items(&mut self, tracks: Vec<QueueItem<'a>>) { pub fn set_items(&mut self, tracks: Vec<QueueItem>) {
let mut tracks = tracks; let mut tracks = tracks;
self.items.clear(); self.items.clear();
self.items.append(&mut tracks); self.items.append(&mut tracks);
} }
pub fn add_item(&mut self, item: QueueItemType<'a>, source: PlayerLocation, by_human: bool) { pub fn add_item(&mut self, item: Song, source: PlayerLocation, by_human: bool) {
let mut i: usize = 0; let mut i: usize = 0;
self.items = self.items.iter().enumerate().map(|(j, item_)| { self.items = self.items.iter().enumerate().map(|(j, item_)| {
@ -168,7 +114,7 @@ impl<'a> Queue<'a> {
}); });
} }
pub fn add_item_next(&mut self, item: QueueItemType<'a>, source: PlayerLocation) { pub fn add_item_next(&mut self, item: Song, source: PlayerLocation) {
use QueueState::*; use QueueState::*;
let empty = self.items.is_empty(); let empty = self.items.is_empty();
@ -176,14 +122,14 @@ impl<'a> Queue<'a> {
(if empty { 0 } else { 1 }), (if empty { 0 } else { 1 }),
QueueItem { QueueItem {
item, item,
state: if (self.items.get(1).is_none() || (!self.has_addhere() && self.items.get(1).is_some()) || empty) { AddHere } else { NoState }, state: if (self.items.get(1).is_none() || !self.has_addhere() && self.items.get(1).is_some()) || empty { AddHere } else { NoState },
source, source,
by_human: true by_human: true
} }
) )
} }
pub fn add_multi(&mut self, items: Vec<QueueItemType>, source: PlayerLocation, by_human: bool) { pub fn add_multi(&mut self, items: Vec<Song>, source: PlayerLocation, by_human: bool) {
} }
@ -239,14 +185,10 @@ impl<'a> Queue<'a> {
use QueueState::*; use QueueState::*;
let empty = self.items.is_empty(); let empty = self.items.is_empty();
let nothing_error = Err(QueueError::EmptyQueue);
let index = if !empty { index } else { return nothing_error; }; let index = if !empty { index } else { return Err(QueueError::EmptyQueue); };
if !empty && index < self.items.len() { if !empty && index < self.items.len() {
let position = self.player.position();
if position.is_some_and(|dur| !dur.is_zero() ) {
self.played.push(self.items[0].clone());
}
let to_item = self.items[index].clone(); let to_item = self.items[index].clone();
@ -263,13 +205,13 @@ impl<'a> Queue<'a> {
} }
// dbg!(&to_item.item, &self.items[ind].item); // dbg!(&to_item.item, &self.items[ind].item);
}else if empty { }else if empty {
return nothing_error; return Err(QueueError::EmptyQueue);
}else { }else {
break; break;
} }
} }
}else { }else {
return Err(QueueError::EmptyQueue.into()); return Err(QueueError::EmptyQueue);
} }
Ok(()) Ok(())
} }
@ -287,9 +229,7 @@ impl<'a> Queue<'a> {
} }
#[allow(clippy::should_implement_trait)] #[allow(clippy::should_implement_trait)]
pub fn next(&mut self, lib: Arc<RwLock<MusicLibrary>>) -> Result<OutQueue, Box<dyn Error>> { pub fn next(&mut self) -> Result<&QueueItem, Box<dyn Error>> {
if self.items.is_empty() { if self.items.is_empty() {
if self.loop_ { if self.loop_ {
@ -300,119 +240,21 @@ impl<'a> Queue<'a> {
} }
// TODO: add an algorithm to detect if the song should be skipped // TODO: add an algorithm to detect if the song should be skipped
let item = self.items[0].clone(); let item = self.items[0].clone();
let uri: URI = match &self.items[1].item {
QueueItemType::Song(uuid) => {
// TODO: Refactor later for multiple URIs
match &lib.read().unwrap().query_uuid(uuid) {
Some(song) => song.0.location.clone(),
None => return Err("Uuid does not exist!".into()),
}
},
QueueItemType::Album { album, current, ..} => {
let (disc, track) = (current.0 as usize, current.1 as usize);
match album.track(disc, track) {
Some(track) => track.location.clone(),
None => return Err(format!("Track in Album {} at disc {} track {} does not exist!", album.title(), disc, track).into())
}
},
QueueItemType::Playlist { current, .. } => {
// TODO: Refactor later for multiple URIs
match &lib.read().unwrap().query_uuid(current) {
Some(song) => song.0.location.clone(),
None => return Err("Uuid does not exist!".into()),
}
},
_ => todo!()
};
if !self.player.is_paused() {
self.player.enqueue_next(&uri)?;
self.player.play()?
}
if self.items[0].state == QueueState::AddHere || !self.has_addhere() { if self.items[0].state == QueueState::AddHere || !self.has_addhere() {
self.items[1].state = QueueState::AddHere; self.items[1].state = QueueState::AddHere;
} }
self.played.push(item); self.played.push(item);
self.items.remove(0); self.items.remove(0);
Ok(todo!()) Ok(&self.items[1])
} }
pub fn prev() {} pub fn prev() {}
pub fn enqueue_item(&mut self, item: QueueItem, lib: Arc<RwLock<MusicLibrary>>) -> Result<(), Box<dyn Error>> {
if let Some(uri) = item.item.get_uri(lib) {
self.player.enqueue_next(&uri)?;
}else {
return Err("this item does not exist!".into());
}
Ok(())
}
pub fn check_played(&mut self) { pub fn check_played(&mut self) {
while self.played.len() > 50 { while self.played.len() > 50 {
self.played.remove(0); self.played.remove(0);
} }
} }
} }
pub struct OutQueue {
}
pub enum OutQueueItem {
}
#[test]
fn item_add_test() {
let mut q = Queue::new().unwrap();
for _ in 0..5 {
// dbg!("tick!");
q.add_item(QueueItemType::Song(Uuid::new_v4()), PlayerLocation::Library, true);
// dbg!(&q.items, &q.items.len());
}
for _ in 0..1 {
q.remove_item(0).inspect_err(|e| println!("{e:?}"));
}
for _ in 0..2 {
q.items.push(QueueItem { item: QueueItemType::Test, state: QueueState::NoState, source: PlayerLocation::Library, by_human: false });
}
dbg!(5);
q.add_item_next(QueueItemType::Test, PlayerLocation::Test);
dbg!(6);
dbg!(&q.items, &q.items.len());
}
#[test]
fn test_() {
let mut q = Queue::new().unwrap();
for _ in 0..400 {
q.items.push(QueueItem { item: QueueItemType::Song(Uuid::new_v4()), state: QueueState::NoState, source: PlayerLocation::File, by_human: false });
}
for _ in 0..50000 {
q.add_item(QueueItemType::Song(Uuid::new_v4()), PlayerLocation::Library, true);
}
// q.add_item_next(QueueItemType::Test, PlayerLocation::File);
// dbg!(&q.items, &q.items.len());
}
#[test]
fn move_test() {
let mut q = Queue::new().unwrap();
for _ in 0..5 {
q.add_item(QueueItemType::Song(Uuid::new_v4()), PlayerLocation::Library, true);
}
// q.add_item(QueueItemType::Test, QueueSource::Library, true).unwrap();
dbg!(&q.items, &q.items.len());
q.move_to(3).inspect_err(|e| {dbg!(e);});
dbg!(&q.items, &q.items.len());
// q.dbg_items();
}

View file

@ -15,8 +15,10 @@ use gstreamer::prelude::*;
use chrono::Duration; use chrono::Duration;
use thiserror::Error; use thiserror::Error;
use super::player::{Player, PlayerError};
#[derive(Debug)] #[derive(Debug)]
pub enum PlayerCmd { pub enum GstCmd {
Play, Play,
Pause, Pause,
Eos, Eos,
@ -24,7 +26,7 @@ pub enum PlayerCmd {
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub enum PlayerState { pub enum GstState {
Playing, Playing,
Paused, Paused,
Ready, Ready,
@ -33,7 +35,7 @@ pub enum PlayerState {
VoidPending, VoidPending,
} }
impl From<gst::State> for PlayerState { impl From<gst::State> for GstState {
fn from(value: gst::State) -> Self { fn from(value: gst::State) -> Self {
match value { match value {
gst::State::VoidPending => Self::VoidPending, gst::State::VoidPending => Self::VoidPending,
@ -45,7 +47,7 @@ impl From<gst::State> for PlayerState {
} }
} }
impl TryInto<gst::State> for PlayerState { impl TryInto<gst::State> for GstState {
fn try_into(self) -> Result<gst::State, Box<dyn Error>> { fn try_into(self) -> Result<gst::State, Box<dyn Error>> {
match self { match self {
Self::VoidPending => Ok(gst::State::VoidPending), Self::VoidPending => Ok(gst::State::VoidPending),
@ -60,24 +62,6 @@ impl TryInto<gst::State> for PlayerState {
type Error = Box<dyn Error>; type Error = Box<dyn Error>;
} }
#[derive(Error, Debug)]
pub enum PlayerError {
#[error("player initialization failed")]
Init(#[from] glib::Error),
#[error("element factory failed to create playbin3")]
Factory(#[from] glib::BoolError),
#[error("could not change playback state")]
StateChange(#[from] gst::StateChangeError),
#[error("the file or source is not found")]
NotFound,
#[error("failed to build gstreamer item")]
Build,
#[error("poison error")]
Poison,
#[error("general player error")]
General,
}
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
enum PlaybackStats { enum PlaybackStats {
Idle, Idle,
@ -91,10 +75,10 @@ enum PlaybackStats {
/// An instance of a music player with a GStreamer backend /// An instance of a music player with a GStreamer backend
#[derive(Debug)] #[derive(Debug)]
pub struct Player { pub struct GStreamer {
source: Option<URI>, source: Option<URI>,
//pub message_tx: Sender<PlayerCmd>, //pub message_tx: Sender<PlayerCmd>,
pub message_rx: crossbeam::channel::Receiver<PlayerCmd>, pub message_rx: crossbeam::channel::Receiver<GstCmd>,
playback_tx: crossbeam::channel::Sender<PlaybackStats>, playback_tx: crossbeam::channel::Sender<PlaybackStats>,
playbin: Arc<RwLock<Element>>, playbin: Arc<RwLock<Element>>,
@ -105,7 +89,7 @@ pub struct Player {
position: Arc<RwLock<Option<Duration>>>, position: Arc<RwLock<Option<Duration>>>,
} }
impl Player { impl GStreamer {
pub fn new() -> Result<Self, PlayerError> { pub fn new() -> Result<Self, PlayerError> {
// Initialize GStreamer, maybe figure out how to nicely fail here // Initialize GStreamer, maybe figure out how to nicely fail here
gst::init()?; gst::init()?;
@ -162,14 +146,14 @@ impl Player {
// Check if the current playback position is close to the end // Check if the current playback position is close to the end
let finish_point = end - Duration::milliseconds(250); let finish_point = end - Duration::milliseconds(250);
if pos_temp.unwrap() >= end { if pos_temp.unwrap() >= end {
let _ = playback_tx.try_send(PlayerCmd::Eos); let _ = playback_tx.try_send(GstCmd::Eos);
playbin_arc playbin_arc
.write() .write()
.unwrap() .unwrap()
.set_state(gst::State::Ready) .set_state(gst::State::Ready)
.expect("Unable to set the pipeline state"); .expect("Unable to set the pipeline state");
} else if pos_temp.unwrap() >= finish_point { } else if pos_temp.unwrap() >= finish_point {
let _ = playback_tx.try_send(PlayerCmd::AboutToFinish); let _ = playback_tx.try_send(GstCmd::AboutToFinish);
} }
// This has to be done AFTER the current time in the file // This has to be done AFTER the current time in the file
@ -468,7 +452,7 @@ impl Player {
} }
/// Get the current state of the playback /// Get the current state of the playback
pub fn state(&mut self) -> PlayerState { pub fn state(&mut self) -> GstState {
self.playbin().unwrap().current_state().into() self.playbin().unwrap().current_state().into()
/* /*
match *self.buffer.read().unwrap() { match *self.buffer.read().unwrap() {
@ -498,7 +482,9 @@ impl Player {
} }
} }
impl Drop for Player { // impl Player for GStreamer {}
impl Drop for GStreamer {
/// Cleans up the `GStreamer` pipeline and the monitoring /// Cleans up the `GStreamer` pipeline and the monitoring
/// thread when [Player] is dropped. /// thread when [Player] is dropped.
fn drop(&mut self) { fn drop(&mut self) {

0
src/music_player/kira.rs Normal file
View file

View file

@ -0,0 +1,57 @@
use chrono::Duration;
use thiserror::Error;
use gstreamer as gst;
use crate::music_storage::library::URI;
#[derive(Error, Debug)]
pub enum PlayerError {
#[error("player initialization failed")]
Init(#[from] glib::Error),
#[error("element factory failed to create playbin3")]
Factory(#[from] glib::BoolError),
#[error("could not change playback state")]
StateChange(#[from] gst::StateChangeError),
#[error("the file or source is not found")]
NotFound,
#[error("failed to build gstreamer item")]
Build,
#[error("poison error")]
Poison,
#[error("general player error")]
General,
}
pub trait Player {
fn source(&self) -> &Option<URI>;
fn enqueue_next(&mut self, next_track: &URI) -> Result<(), PlayerError>;
fn set_volume(&mut self, volume: f64);
fn volume(&mut self) -> f64;
fn ready(&mut self) -> Result<(), PlayerError>;
fn play(&mut self) -> Result<(), PlayerError>;
fn resume(&mut self) -> Result<(), PlayerError>;
fn pause(&mut self) -> Result<(), PlayerError>;
fn stop(&mut self) -> Result<(), PlayerError>;
fn is_paused(&mut self) -> bool;
fn position(&mut self) -> Option<Duration>;
fn duration(&mut self) -> Option<Duration>;
fn raw_duration(&self) -> Option<Duration>;
fn seek_by(&mut self, seek_amount: Duration) -> Result<(), PlayerError>;
fn seek_to(&mut self, target_pos: Duration) -> Result<(), PlayerError>;
}