mirror of
https://github.com/Dangoware/dango-music-player.git
synced 2025-04-19 18:12:54 -05:00
Updated Controller and added Player Trait
This commit is contained in:
parent
94e6c25219
commit
56040bfd28
7 changed files with 226 additions and 545 deletions
|
@ -14,7 +14,10 @@ pub mod music_controller{
|
||||||
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;
|
||||||
|
|
|
@ -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();
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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()?,
|
|
||||||
name: String::new(),
|
|
||||||
items: Vec::new(),
|
items: Vec::new(),
|
||||||
played: Vec::new(),
|
played: Vec::new(),
|
||||||
loop_: false,
|
loop_: false,
|
||||||
|
shuffle: 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();
|
|
||||||
}
|
|
||||||
|
|
|
@ -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
0
src/music_player/kira.rs
Normal file
57
src/music_player/player.rs
Normal file
57
src/music_player/player.rs
Normal 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>;
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in a new issue