mirror of
https://github.com/Dangoware/dango-music-player.git
synced 2025-04-19 10:02:53 -05:00
cobbled together some test code for controller and ran cargo fmt
This commit is contained in:
parent
be9f28e38f
commit
15988ae808
12 changed files with 515 additions and 227 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -3,6 +3,7 @@ target/
|
|||
test-config/
|
||||
# Rust configuration
|
||||
Cargo.lock
|
||||
.cargo/
|
||||
|
||||
# Database files
|
||||
*.db3*
|
||||
|
@ -15,4 +16,3 @@ music_database*
|
|||
*.json
|
||||
*.zip
|
||||
*.xml
|
||||
|
||||
|
|
|
@ -49,3 +49,9 @@ listenbrainz = "0.7.0"
|
|||
discord-rpc-client = "0.4.0"
|
||||
nestify = "0.3.3"
|
||||
kushi = "0.1.3"
|
||||
moro = "0.4.0"
|
||||
moro-local = "0.4.0"
|
||||
futures = "0.3.30"
|
||||
text_io = "0.1.12"
|
||||
tokio = { version = "1.40.0", features = ["macros", "rt"] }
|
||||
async-channel = "2.3.1"
|
||||
|
|
|
@ -193,10 +193,10 @@ pub mod tests {
|
|||
use crate::music_storage::library::MusicLibrary;
|
||||
use std::{
|
||||
path::PathBuf,
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
pub fn new_config_lib() -> (Config, MusicLibrary) {
|
||||
_ = std::fs::create_dir_all("test-config/music/");
|
||||
let lib = ConfigLibrary::new(
|
||||
PathBuf::from("test-config/library"),
|
||||
String::from("library"),
|
||||
|
@ -216,7 +216,8 @@ pub mod tests {
|
|||
)
|
||||
.unwrap();
|
||||
lib.scan_folder("test-config/music/").unwrap();
|
||||
lib.save(config.libraries.get_default().unwrap().path.clone()).unwrap();
|
||||
lib.save(config.libraries.get_default().unwrap().path.clone())
|
||||
.unwrap();
|
||||
|
||||
(config, lib)
|
||||
}
|
||||
|
@ -234,7 +235,8 @@ pub mod tests {
|
|||
|
||||
lib.scan_folder("test-config/music/").unwrap();
|
||||
|
||||
lib.save(config.libraries.get_default().unwrap().path.clone()).unwrap();
|
||||
lib.save(config.libraries.get_default().unwrap().path.clone())
|
||||
.unwrap();
|
||||
|
||||
(config, lib)
|
||||
}
|
||||
|
|
|
@ -1,20 +1,7 @@
|
|||
pub enum Setting {
|
||||
String {
|
||||
name: String,
|
||||
value: String
|
||||
},
|
||||
Int {
|
||||
name: String,
|
||||
value: i32
|
||||
},
|
||||
Bool {
|
||||
name: String,
|
||||
value: bool
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
pub struct Form {
|
||||
|
||||
String { name: String, value: String },
|
||||
Int { name: String, value: i32 },
|
||||
Bool { name: String, value: bool },
|
||||
}
|
||||
|
||||
pub struct Form {}
|
||||
|
|
|
@ -9,8 +9,8 @@ pub mod music_storage {
|
|||
}
|
||||
|
||||
pub mod music_controller {
|
||||
pub mod controller;
|
||||
pub mod connections;
|
||||
pub mod controller;
|
||||
pub mod queue;
|
||||
}
|
||||
|
||||
|
|
|
@ -13,8 +13,6 @@
|
|||
|
||||
// use super::controller::DatabaseResponse;
|
||||
|
||||
|
||||
|
||||
// impl Controller {
|
||||
// pub fn listenbrainz_authenticate(&mut self) -> Result<ListenBrainz, Box<dyn Error>> {
|
||||
// let config = &self.config.read().unwrap();
|
||||
|
@ -96,7 +94,6 @@
|
|||
// c.q_enqueue(0, songs[1].location.to_owned()).unwrap();
|
||||
// c.q_play(0).unwrap();
|
||||
|
||||
|
||||
// sleep(Duration::from_secs(100));
|
||||
// c.lbz_scrobble(client, songs[1].uuid).unwrap();
|
||||
// }
|
||||
|
|
|
@ -1,35 +1,24 @@
|
|||
//! The [Controller] is the input and output for the entire
|
||||
//! player. It manages queues, playback, library access, and
|
||||
//! other functions
|
||||
#![allow(while_true)]
|
||||
|
||||
use crossbeam_channel;
|
||||
use crossbeam_channel::{Receiver, Sender};
|
||||
use kushi::QueueError;
|
||||
use kushi::{Queue, QueueItemType};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
use std::thread::spawn;
|
||||
use thiserror::Error;
|
||||
|
||||
use crossbeam_channel::unbounded;
|
||||
use kushi::{QueueError, QueueItem};
|
||||
use std::error::Error;
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use thiserror::Error;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::config::ConfigError;
|
||||
use crate::music_player::player::{Player, PlayerCommand, PlayerError};
|
||||
use crate::{
|
||||
config::Config, music_storage::library::MusicLibrary,
|
||||
};
|
||||
use crate::music_player::player::{Player, PlayerError};
|
||||
use crate::music_storage::library::Song;
|
||||
use crate::{config::Config, music_storage::library::MusicLibrary};
|
||||
|
||||
use super::queue::{QueueAlbum, QueueSong};
|
||||
|
||||
|
||||
pub struct Controller<P: Player + Send + Sync> {
|
||||
pub queue: Arc<RwLock<Queue<QueueSong, QueueAlbum>>>,
|
||||
pub config: Arc<RwLock<Config>>,
|
||||
pub library: MusicLibrary,
|
||||
pub player: Arc<Mutex<P>>,
|
||||
}
|
||||
pub struct Controller<'a, P>(&'a PhantomData<P>);
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ControllerError {
|
||||
|
@ -52,146 +41,405 @@ pub enum PlayerLocation {
|
|||
Custom,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct MailMan<T: Send, U: Send> {
|
||||
pub tx: Sender<T>,
|
||||
rx: Receiver<U>,
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MailMan<Tx: Send, Rx: Send> {
|
||||
tx: async_channel::Sender<Tx>,
|
||||
rx: async_channel::Receiver<Rx>,
|
||||
}
|
||||
|
||||
impl<T: Send> MailMan<T, T> {
|
||||
pub fn new() -> Self {
|
||||
let (tx, rx) = unbounded::<T>();
|
||||
MailMan { tx, rx }
|
||||
}
|
||||
}
|
||||
impl<T: Send, U: Send> MailMan<T, U> {
|
||||
pub fn double() -> (MailMan<T, U>, MailMan<U, T>) {
|
||||
let (tx, rx) = unbounded::<T>();
|
||||
let (tx1, rx1) = unbounded::<U>();
|
||||
impl<Tx: Send, Rx: Send> MailMan<Tx, Rx> {
|
||||
pub fn double() -> (MailMan<Tx, Rx>, MailMan<Rx, Tx>) {
|
||||
let (tx, rx) = async_channel::unbounded::<Tx>();
|
||||
let (tx1, rx1) = async_channel::unbounded::<Rx>();
|
||||
|
||||
(MailMan { tx, rx: rx1 }, MailMan { tx: tx1, rx })
|
||||
}
|
||||
|
||||
pub fn send(&self, mail: T) -> Result<(), Box<dyn Error>> {
|
||||
self.tx.send(mail).unwrap();
|
||||
Ok(())
|
||||
pub async fn send(&self, mail: Tx) -> Result<(), async_channel::SendError<Tx>> {
|
||||
self.tx.send(mail).await
|
||||
}
|
||||
|
||||
pub fn recv(&self) -> Result<U, Box<dyn Error>> {
|
||||
let u = self.rx.recv()?;
|
||||
Ok(u)
|
||||
pub async fn recv(&self) -> Result<Rx, async_channel::RecvError> {
|
||||
self.rx.recv().await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, PartialOrd, Clone)]
|
||||
pub enum PlayerCommand {
|
||||
NextSong,
|
||||
PrevSong,
|
||||
Pause,
|
||||
Play,
|
||||
Enqueue(usize),
|
||||
SetVolume(f64),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, PartialOrd, Clone)]
|
||||
pub enum PlayerResponse {
|
||||
Empty,
|
||||
}
|
||||
|
||||
pub enum LibraryCommand {
|
||||
Song(Uuid),
|
||||
}
|
||||
|
||||
pub enum LibraryResponse {
|
||||
Songs(Song),
|
||||
}
|
||||
|
||||
enum InnerLibraryCommand {
|
||||
Song(Uuid),
|
||||
}
|
||||
|
||||
enum InnerLibraryResponse<'a> {
|
||||
Song(&'a Song),
|
||||
}
|
||||
|
||||
pub enum QueueCommand {
|
||||
Append(QueueItem<QueueSong, QueueAlbum>),
|
||||
Next,
|
||||
Prev,
|
||||
GetIndex(usize),
|
||||
NowPlaying,
|
||||
}
|
||||
|
||||
pub enum QueueResponse {
|
||||
Ok,
|
||||
Item(QueueItem<QueueSong, QueueAlbum>),
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
impl<P: Player + Send + Sync + Sized + 'static> Controller<P> {
|
||||
pub fn start<T>(config_path: T) -> Result <Self, Box<dyn Error>>
|
||||
impl<'c, P: Player + Send + Sync> Controller<'c, P> {
|
||||
pub async fn start(
|
||||
player_mail: (
|
||||
MailMan<PlayerCommand, PlayerResponse>,
|
||||
MailMan<PlayerResponse, PlayerCommand>,
|
||||
),
|
||||
lib_mail: MailMan<LibraryResponse, LibraryCommand>,
|
||||
mut library: MusicLibrary,
|
||||
config: Arc<RwLock<Config>>,
|
||||
) -> Result<(), Box<dyn Error>>
|
||||
where
|
||||
std::path::PathBuf: std::convert::From<T>,
|
||||
P: Player,
|
||||
{
|
||||
let config_path = PathBuf::from(config_path);
|
||||
|
||||
let config = Config::read_file(config_path)?;
|
||||
let uuid = config.libraries.get_default()?.uuid;
|
||||
|
||||
let library = MusicLibrary::init(config.libraries.get_default()?.path.clone(), uuid)?;
|
||||
let config_ = Arc::new(RwLock::from(config));
|
||||
|
||||
|
||||
let queue: Queue<QueueSong, QueueAlbum> = Queue {
|
||||
//TODO: make a separate event loop for sccessing library that clones borrowed values from inner library loop?
|
||||
let mut queue: Queue<QueueSong, QueueAlbum> = Queue {
|
||||
items: Vec::new(),
|
||||
played: Vec::new(),
|
||||
loop_: false,
|
||||
shuffle: None
|
||||
shuffle: None,
|
||||
};
|
||||
|
||||
let controller = Controller {
|
||||
queue: Arc::new(RwLock::from(queue)),
|
||||
config: config_.clone(),
|
||||
library,
|
||||
player: Arc::new(Mutex::new(P::new()?)),
|
||||
};
|
||||
for song in &library.library {
|
||||
queue.add_item(
|
||||
QueueSong {
|
||||
song: song.clone(),
|
||||
location: PlayerLocation::Test,
|
||||
},
|
||||
true,
|
||||
);
|
||||
}
|
||||
let inner_lib_mail = MailMan::double();
|
||||
let queue = queue;
|
||||
|
||||
std::thread::scope(|scope| {
|
||||
let queue_mail = MailMan::double();
|
||||
let a = scope.spawn(|| {
|
||||
futures::executor::block_on(async {
|
||||
moro::async_scope!(|scope| {
|
||||
println!("async scope created");
|
||||
let player = Arc::new(RwLock::new(P::new().unwrap()));
|
||||
|
||||
let player = controller.player.clone();
|
||||
let queue = controller.queue.clone();
|
||||
let controller_thread = spawn(move || {
|
||||
loop {
|
||||
let signal = { player.lock().unwrap().message_channel().recv().unwrap() };
|
||||
match signal {
|
||||
PlayerCommand::AboutToFinish => {
|
||||
println!("Switching songs!");
|
||||
|
||||
let mut queue = queue.write().unwrap();
|
||||
|
||||
let uri = queue
|
||||
.next()
|
||||
.unwrap()
|
||||
.clone();
|
||||
|
||||
player
|
||||
.lock()
|
||||
.unwrap()
|
||||
.enqueue_next(&{
|
||||
match uri.item {
|
||||
QueueItemType::Single(song) => song.song.primary_uri().unwrap().0.clone(),
|
||||
_ => unimplemented!()
|
||||
}
|
||||
let _player = player.clone();
|
||||
scope
|
||||
.spawn(async move {
|
||||
Controller::<P>::player_command_loop(
|
||||
_player,
|
||||
player_mail.1,
|
||||
queue_mail.0,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
},
|
||||
PlayerCommand::EndOfStream => {dbg!()}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
.await;
|
||||
scope
|
||||
.spawn(async move {
|
||||
Controller::<P>::player_event_loop(player, player_mail.0)
|
||||
.await
|
||||
.unwrap();
|
||||
})
|
||||
.await;
|
||||
scope
|
||||
.spawn(async {
|
||||
Controller::<P>::inner_library_loop(inner_lib_mail.1, &mut library)
|
||||
.await
|
||||
.unwrap()
|
||||
})
|
||||
.await;
|
||||
scope
|
||||
.spawn(async {
|
||||
Controller::<P>::outer_library_loop(lib_mail, inner_lib_mail.0)
|
||||
.await
|
||||
.unwrap();
|
||||
})
|
||||
.await
|
||||
})
|
||||
.await;
|
||||
})
|
||||
});
|
||||
|
||||
let b = scope.spawn(|| {
|
||||
futures::executor::block_on(async {
|
||||
Controller::<P>::queue_loop(queue, queue_mail.1).await;
|
||||
})
|
||||
});
|
||||
a.join().unwrap();
|
||||
b.join().unwrap();
|
||||
});
|
||||
|
||||
|
||||
Ok(controller)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn q_add(&mut self, item: &Uuid, source: PlayerLocation, by_human: bool) {
|
||||
let item = self.library.query_uuid(item).unwrap().0.to_owned();
|
||||
self.queue.write().unwrap().add_item(QueueSong { song: item, location: source }, by_human)
|
||||
async fn player_command_loop(
|
||||
player: Arc<RwLock<P>>,
|
||||
player_mail: MailMan<PlayerResponse, PlayerCommand>,
|
||||
queue_mail: MailMan<QueueCommand, QueueResponse>,
|
||||
) -> Result<(), ()> {
|
||||
{
|
||||
player.write().unwrap().set_volume(0.05);
|
||||
}
|
||||
while true {
|
||||
let _mail = player_mail.recv().await;
|
||||
if let Ok(mail) = _mail {
|
||||
match mail {
|
||||
PlayerCommand::Play => {
|
||||
player.write().unwrap().play().unwrap();
|
||||
player_mail.send(PlayerResponse::Empty).await.unwrap();
|
||||
}
|
||||
PlayerCommand::Pause => {
|
||||
player.write().unwrap().pause().unwrap();
|
||||
player_mail.send(PlayerResponse::Empty).await.unwrap();
|
||||
}
|
||||
PlayerCommand::SetVolume(volume) => {
|
||||
player.write().unwrap().set_volume(volume);
|
||||
println!("volume set to {volume}");
|
||||
player_mail.send(PlayerResponse::Empty).await.unwrap();
|
||||
}
|
||||
PlayerCommand::NextSong => {
|
||||
queue_mail.send(QueueCommand::Next).await.unwrap();
|
||||
|
||||
if let QueueResponse::Item(item) = queue_mail.recv().await.unwrap() {
|
||||
let uri = match &item.item {
|
||||
QueueItemType::Single(song) => song.song.primary_uri().unwrap().0,
|
||||
_ => unimplemented!(),
|
||||
};
|
||||
player.write().unwrap().enqueue_next(uri).unwrap();
|
||||
player_mail.send(PlayerResponse::Empty).await.unwrap();
|
||||
}
|
||||
}
|
||||
PlayerCommand::PrevSong => {
|
||||
queue_mail.send(QueueCommand::Prev).await.unwrap();
|
||||
|
||||
if let QueueResponse::Item(item) = queue_mail.recv().await.unwrap() {
|
||||
let uri = match &item.item {
|
||||
QueueItemType::Single(song) => song.song.primary_uri().unwrap().0,
|
||||
_ => unimplemented!(),
|
||||
};
|
||||
player.write().unwrap().enqueue_next(uri).unwrap();
|
||||
player_mail.send(PlayerResponse::Empty).await.unwrap();
|
||||
}
|
||||
}
|
||||
PlayerCommand::Enqueue(index) => {
|
||||
queue_mail
|
||||
.send(QueueCommand::GetIndex(index))
|
||||
.await
|
||||
.unwrap();
|
||||
if let QueueResponse::Item(item) = queue_mail.recv().await.unwrap() {
|
||||
match item.item {
|
||||
QueueItemType::Single(song) => {
|
||||
player
|
||||
.write()
|
||||
.unwrap()
|
||||
.enqueue_next(song.song.primary_uri().unwrap().0)
|
||||
.unwrap();
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
player_mail.send(PlayerResponse::Empty).await.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn outer_library_loop(
|
||||
lib_mail: MailMan<LibraryResponse, LibraryCommand>,
|
||||
inner_lib_mail: MailMan<InnerLibraryCommand, InnerLibraryResponse<'c>>,
|
||||
) -> Result<(), ()> {
|
||||
while true {
|
||||
match lib_mail.recv().await.unwrap() {
|
||||
LibraryCommand::Song(uuid) => {
|
||||
inner_lib_mail
|
||||
.send(InnerLibraryCommand::Song(uuid))
|
||||
.await
|
||||
.unwrap();
|
||||
let x = inner_lib_mail.recv().await.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn inner_library_loop(
|
||||
lib_mail: MailMan<InnerLibraryResponse<'c>, InnerLibraryCommand>,
|
||||
library: &'c mut MusicLibrary,
|
||||
) -> Result<(), ()> {
|
||||
while true {
|
||||
match lib_mail.recv().await.unwrap() {
|
||||
InnerLibraryCommand::Song(uuid) => {
|
||||
let song: &'c Song = library.query_uuid(&uuid).unwrap().0;
|
||||
lib_mail
|
||||
.send(InnerLibraryResponse::Song(song))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn player_event_loop(
|
||||
player: Arc<RwLock<P>>,
|
||||
player_mail: MailMan<PlayerCommand, PlayerResponse>,
|
||||
) -> Result<(), ()> {
|
||||
// just pretend this does something
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn queue_loop(
|
||||
mut queue: Queue<QueueSong, QueueAlbum>,
|
||||
queue_mail: MailMan<QueueResponse, QueueCommand>,
|
||||
) {
|
||||
while true {
|
||||
match queue_mail.recv().await.unwrap() {
|
||||
QueueCommand::Append(item) => match item.item {
|
||||
QueueItemType::Single(song) => queue.add_item(song, true),
|
||||
_ => unimplemented!(),
|
||||
},
|
||||
QueueCommand::Next => {
|
||||
let next = queue.next().unwrap();
|
||||
queue_mail
|
||||
.send(QueueResponse::Item(next.clone()))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
QueueCommand::Prev => {
|
||||
let next = queue.prev().unwrap();
|
||||
queue_mail
|
||||
.send(QueueResponse::Item(next.clone()))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
QueueCommand::GetIndex(index) => {
|
||||
let item = queue.items[index].clone();
|
||||
queue_mail.send(QueueResponse::Item(item)).await.unwrap();
|
||||
}
|
||||
QueueCommand::NowPlaying => {
|
||||
let item = queue.current().unwrap();
|
||||
queue_mail
|
||||
.send(QueueResponse::Item(item.clone()))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_super {
|
||||
use std::{thread::sleep, time::Duration};
|
||||
use std::{
|
||||
path::PathBuf,
|
||||
sync::{Arc, RwLock},
|
||||
thread::spawn,
|
||||
};
|
||||
|
||||
use crate::{config::tests::read_config_lib, music_controller::controller::{PlayerLocation, QueueSong}, music_player::{gstreamer::GStreamer, player::Player}};
|
||||
use crate::{
|
||||
config::{tests::new_config_lib, Config},
|
||||
music_controller::controller::{
|
||||
LibraryCommand, LibraryResponse, MailMan, PlayerCommand, PlayerResponse,
|
||||
},
|
||||
music_player::gstreamer::GStreamer,
|
||||
music_storage::library::MusicLibrary,
|
||||
};
|
||||
|
||||
use super::Controller;
|
||||
|
||||
#[test]
|
||||
fn construct_controller() {
|
||||
println!("starto!");
|
||||
let config = read_config_lib();
|
||||
#[tokio::test]
|
||||
async fn construct_controller() {
|
||||
// use if you don't have a config setup and add music to the music folder
|
||||
new_config_lib();
|
||||
|
||||
let next = config.1.library[2].clone();
|
||||
{
|
||||
let controller = Controller::<GStreamer>::start("test-config/config_test.json").unwrap();
|
||||
{
|
||||
let mut queue = controller.queue.write().unwrap();
|
||||
for x in config.1.library {
|
||||
queue.add_item(QueueSong { song: x, location: PlayerLocation::Library }, true);
|
||||
let lib_mail: (MailMan<LibraryCommand, LibraryResponse>, MailMan<_, _>) = MailMan::double();
|
||||
let player_mail: (MailMan<PlayerCommand, PlayerResponse>, MailMan<_, _>) =
|
||||
MailMan::double();
|
||||
|
||||
let _player_mail = player_mail.0.clone();
|
||||
let b = spawn(move || {
|
||||
futures::executor::block_on(async {
|
||||
_player_mail
|
||||
.send(PlayerCommand::SetVolume(0.01))
|
||||
.await
|
||||
.unwrap();
|
||||
loop {
|
||||
let buf: String = text_io::read!();
|
||||
dbg!(&buf);
|
||||
_player_mail
|
||||
.send(match buf.to_lowercase().as_str() {
|
||||
"next" => PlayerCommand::NextSong,
|
||||
"prev" => PlayerCommand::PrevSong,
|
||||
"pause" => PlayerCommand::Pause,
|
||||
"play" => PlayerCommand::Play,
|
||||
x if x.parse::<usize>().is_ok() => {
|
||||
PlayerCommand::Enqueue(x.parse::<usize>().unwrap())
|
||||
}
|
||||
_ => continue,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
println!("sent it");
|
||||
println!("{:?}", _player_mail.recv().await.unwrap())
|
||||
}
|
||||
}
|
||||
{
|
||||
controller.player.lock().unwrap().enqueue_next(next.primary_uri().unwrap().0).unwrap();
|
||||
}
|
||||
{
|
||||
controller.player.lock().unwrap().set_volume(0.1);
|
||||
}
|
||||
{
|
||||
controller.player.lock().unwrap().play().unwrap();
|
||||
}
|
||||
println!("I'm a tire");
|
||||
}
|
||||
sleep(Duration::from_secs(10))
|
||||
})
|
||||
});
|
||||
|
||||
let a = spawn(move || {
|
||||
futures::executor::block_on(async {
|
||||
let config = Config::read_file(PathBuf::from(std::env!("CONFIG-PATH"))).unwrap();
|
||||
let library = {
|
||||
MusicLibrary::init(
|
||||
config.libraries.get_default().unwrap().path.clone(),
|
||||
config.libraries.get_default().unwrap().uuid,
|
||||
)
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
Controller::<GStreamer>::start(
|
||||
player_mail,
|
||||
lib_mail.1,
|
||||
library,
|
||||
Arc::new(RwLock::new(config)),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
});
|
||||
});
|
||||
|
||||
b.join().unwrap();
|
||||
a.join().unwrap();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,30 +46,30 @@ impl TryInto<gst::State> for PlayerState {
|
|||
enum PlaybackInfo {
|
||||
Idle,
|
||||
Switching,
|
||||
Playing{
|
||||
Playing {
|
||||
start: Duration,
|
||||
end: Duration,
|
||||
end: Duration,
|
||||
},
|
||||
|
||||
/// When this is sent, the thread will die! Use it when the [Player] is
|
||||
/// done playing
|
||||
Finished
|
||||
Finished,
|
||||
}
|
||||
|
||||
/// An instance of a music player with a GStreamer backend
|
||||
#[derive(Debug)]
|
||||
pub struct GStreamer {
|
||||
source: Option<URI>,
|
||||
source: Option<URI>,
|
||||
|
||||
message_rx: crossbeam::channel::Receiver<PlayerCommand>,
|
||||
playback_tx: crossbeam::channel::Sender<PlaybackInfo>,
|
||||
|
||||
playbin: Arc<RwLock<Element>>,
|
||||
volume: f64,
|
||||
start: Option<Duration>,
|
||||
end: Option<Duration>,
|
||||
paused: Arc<RwLock<bool>>,
|
||||
position: Arc<RwLock<Option<Duration>>>,
|
||||
playbin: Arc<RwLock<Element>>,
|
||||
volume: f64,
|
||||
start: Option<Duration>,
|
||||
end: Option<Duration>,
|
||||
paused: Arc<RwLock<bool>>,
|
||||
position: Arc<RwLock<Option<Duration>>>,
|
||||
}
|
||||
|
||||
impl From<gst::StateChangeError> for PlayerError {
|
||||
|
@ -89,7 +89,7 @@ impl GStreamer {
|
|||
fn set_source(&mut self, source: &URI) -> Result<(), PlayerError> {
|
||||
if !source.exists().is_ok_and(|x| x) {
|
||||
// If the source doesn't exist, gstreamer will crash!
|
||||
return Err(PlayerError::NotFound)
|
||||
return Err(PlayerError::NotFound);
|
||||
}
|
||||
|
||||
// Make sure the playback tracker knows the stuff is stopped
|
||||
|
@ -110,10 +110,12 @@ impl GStreamer {
|
|||
self.end = Some(Duration::from_std(*end).unwrap());
|
||||
|
||||
// Send the updated position to the tracker
|
||||
self.playback_tx.send(PlaybackInfo::Playing{
|
||||
start: self.start.unwrap(),
|
||||
end: self.end.unwrap()
|
||||
}).unwrap();
|
||||
self.playback_tx
|
||||
.send(PlaybackInfo::Playing {
|
||||
start: self.start.unwrap(),
|
||||
end: self.end.unwrap(),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
// Wait for it to be ready, and then move to the proper position
|
||||
self.play().unwrap();
|
||||
|
@ -125,7 +127,9 @@ impl GStreamer {
|
|||
std::thread::sleep(std::time::Duration::from_millis(1));
|
||||
}
|
||||
//panic!("Couldn't seek to beginning of cue track in reasonable time (>20ms)");
|
||||
return Err(PlayerError::StateChange("Could not seek to beginning of CUE track".into()))
|
||||
return Err(PlayerError::StateChange(
|
||||
"Could not seek to beginning of CUE track".into(),
|
||||
));
|
||||
}
|
||||
_ => {
|
||||
self.playbin
|
||||
|
@ -145,10 +149,12 @@ impl GStreamer {
|
|||
self.end = self.raw_duration();
|
||||
|
||||
// Send the updated position to the tracker
|
||||
self.playback_tx.send(PlaybackInfo::Playing{
|
||||
start: self.start.unwrap(),
|
||||
end: self.end.unwrap()
|
||||
}).unwrap();
|
||||
self.playback_tx
|
||||
.send(PlaybackInfo::Playing {
|
||||
start: self.start.unwrap(),
|
||||
end: self.end.unwrap(),
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -223,7 +229,7 @@ impl Player for GStreamer {
|
|||
fn new() -> Result<Self, PlayerError> {
|
||||
// Initialize GStreamer, maybe figure out how to nicely fail here
|
||||
if let Err(err) = gst::init() {
|
||||
return Err(PlayerError::Init(err.to_string()))
|
||||
return Err(PlayerError::Init(err.to_string()));
|
||||
};
|
||||
let ctx = glib::MainContext::default();
|
||||
let _guard = ctx.acquire();
|
||||
|
@ -233,7 +239,7 @@ impl Player for GStreamer {
|
|||
match gst::ElementFactory::make("playbin3").build() {
|
||||
Ok(playbin) => playbin,
|
||||
Err(error) => return Err(PlayerError::Init(error.to_string())),
|
||||
}
|
||||
},
|
||||
));
|
||||
|
||||
let playbin = playbin_arc.clone();
|
||||
|
@ -252,7 +258,10 @@ impl Player for GStreamer {
|
|||
.build()
|
||||
.ok_or(PlayerError::Build)?;
|
||||
|
||||
playbin.write().unwrap().set_property_from_value("flags", &flags);
|
||||
playbin
|
||||
.write()
|
||||
.unwrap()
|
||||
.set_property_from_value("flags", &flags);
|
||||
//playbin.write().unwrap().set_property("instant-uri", true);
|
||||
|
||||
let position = Arc::new(RwLock::new(None));
|
||||
|
@ -262,7 +271,9 @@ impl Player for GStreamer {
|
|||
let (status_tx, status_rx) = unbounded::<PlaybackInfo>();
|
||||
let position_update = Arc::clone(&position);
|
||||
|
||||
std::thread::spawn(|| playback_monitor(playbin_arc, status_rx, playback_tx, position_update));
|
||||
std::thread::spawn(|| {
|
||||
playback_monitor(playbin_arc, status_rx, playback_tx, position_update)
|
||||
});
|
||||
|
||||
// Set up the thread to monitor bus messages
|
||||
let playbin_bus_ctrl = Arc::clone(&playbin);
|
||||
|
@ -279,11 +290,11 @@ impl Player for GStreamer {
|
|||
gst::MessageView::StreamStart(_) => println!("Stream start"),
|
||||
gst::MessageView::Error(err) => {
|
||||
println!("Error recieved: {}", err);
|
||||
return glib::ControlFlow::Break
|
||||
return glib::ControlFlow::Break;
|
||||
}
|
||||
gst::MessageView::Buffering(buffering) => {
|
||||
if *bus_paused.read().unwrap() == true {
|
||||
return glib::ControlFlow::Continue
|
||||
return glib::ControlFlow::Continue;
|
||||
}
|
||||
|
||||
// If the player is not paused, pause it
|
||||
|
@ -349,7 +360,7 @@ impl Player for GStreamer {
|
|||
|
||||
fn play(&mut self) -> Result<(), PlayerError> {
|
||||
if self.state() == PlayerState::Playing {
|
||||
return Ok(())
|
||||
return Ok(());
|
||||
}
|
||||
*self.paused.write().unwrap() = false;
|
||||
self.set_state(gst::State::Playing)?;
|
||||
|
@ -358,7 +369,7 @@ impl Player for GStreamer {
|
|||
|
||||
fn pause(&mut self) -> Result<(), PlayerError> {
|
||||
if self.state() == PlayerState::Paused || *self.paused.read().unwrap() {
|
||||
return Ok(())
|
||||
return Ok(());
|
||||
}
|
||||
*self.paused.write().unwrap() = true;
|
||||
self.set_state(gst::State::Paused)?;
|
||||
|
@ -472,7 +483,7 @@ fn playback_monitor(
|
|||
.map(|pos| Duration::nanoseconds(pos.nseconds() as i64));
|
||||
|
||||
match stats {
|
||||
PlaybackInfo::Playing{start, end} if pos_temp.is_some() => {
|
||||
PlaybackInfo::Playing { start, end } if pos_temp.is_some() => {
|
||||
// Check if the current playback position is close to the end
|
||||
let finish_point = end - Duration::milliseconds(2000);
|
||||
if pos_temp.unwrap().num_microseconds() >= end.num_microseconds() {
|
||||
|
@ -495,16 +506,14 @@ fn playback_monitor(
|
|||
// This has to be done AFTER the current time in the file
|
||||
// is calculated, or everything else is wrong
|
||||
pos_temp = Some(pos_temp.unwrap() - start)
|
||||
},
|
||||
}
|
||||
PlaybackInfo::Finished => {
|
||||
println!("MONITOR: Shutting down");
|
||||
*position.write().unwrap() = None;
|
||||
break
|
||||
},
|
||||
PlaybackInfo::Idle | PlaybackInfo::Switching => {
|
||||
sent_atf = false
|
||||
},
|
||||
_ => ()
|
||||
break;
|
||||
}
|
||||
PlaybackInfo::Idle | PlaybackInfo::Switching => sent_atf = false,
|
||||
_ => (),
|
||||
}
|
||||
|
||||
*position.write().unwrap() = pos_temp;
|
||||
|
|
|
@ -41,7 +41,9 @@ pub enum PlayerCommand {
|
|||
|
||||
pub trait Player {
|
||||
/// Create a new player.
|
||||
fn new() -> Result<Self, PlayerError> where Self: Sized;
|
||||
fn new() -> Result<Self, PlayerError>
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Get the currently playing [URI] from the player.
|
||||
fn source(&self) -> &Option<URI>;
|
||||
|
|
|
@ -2,13 +2,13 @@ use file_format::FileFormat;
|
|||
use lofty::{AudioFile, LoftyError, ParseOptions, Probe, TagType, TaggedFileExt};
|
||||
use quick_xml::events::Event;
|
||||
use quick_xml::reader::Reader;
|
||||
use uuid::Uuid;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::fs::File;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration as StdDur;
|
||||
use std::vec::Vec;
|
||||
use uuid::Uuid;
|
||||
|
||||
use chrono::prelude::*;
|
||||
|
||||
|
@ -118,7 +118,12 @@ impl ExternalLibrary for ITunesLibrary {
|
|||
buf.clear();
|
||||
}
|
||||
let elasped = now.elapsed();
|
||||
println!("\n\niTunesReader grabbed {} songs in {:#?} seconds\nIDs Skipped: {}", count3, elasped.as_secs(), count4);
|
||||
println!(
|
||||
"\n\niTunesReader grabbed {} songs in {:#?} seconds\nIDs Skipped: {}",
|
||||
count3,
|
||||
elasped.as_secs(),
|
||||
count4
|
||||
);
|
||||
let mut lib = ITunesLibrary::new();
|
||||
lib.tracks.append(converted_songs.as_mut());
|
||||
lib
|
||||
|
@ -175,10 +180,10 @@ impl ExternalLibrary for ITunesLibrary {
|
|||
skips: 0,
|
||||
favorited: track.favorited,
|
||||
banned: if track.banned {
|
||||
Some(BannedType::All)
|
||||
}else {
|
||||
None
|
||||
},
|
||||
Some(BannedType::All)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
rating: track.rating,
|
||||
format: match FileFormat::from_file(PathBuf::from(&loc)) {
|
||||
Ok(e) => Some(e),
|
||||
|
@ -334,25 +339,43 @@ impl ITunesSong {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{path::{Path, PathBuf}, sync::{Arc, RwLock}};
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
use crate::{config::{Config, ConfigLibrary}, music_storage::{db_reader::extern_library::ExternalLibrary, library::MusicLibrary}};
|
||||
use crate::{
|
||||
config::{Config, ConfigLibrary},
|
||||
music_storage::{db_reader::extern_library::ExternalLibrary, library::MusicLibrary},
|
||||
};
|
||||
|
||||
use super::ITunesLibrary;
|
||||
|
||||
#[test]
|
||||
fn itunes_lib_test() {
|
||||
let mut config = Config::read_file(PathBuf::from("test-config/config_test.json")).unwrap();
|
||||
let config_lib = ConfigLibrary::new(PathBuf::from("test-config/library2"), String::from("library2"), None);
|
||||
let config_lib = ConfigLibrary::new(
|
||||
PathBuf::from("test-config/library2"),
|
||||
String::from("library2"),
|
||||
None,
|
||||
);
|
||||
config.libraries.libraries.push(config_lib.clone());
|
||||
|
||||
let songs = ITunesLibrary::from_file(Path::new("test-config\\iTunesLib.xml")).to_songs();
|
||||
|
||||
let mut library = MusicLibrary::init(config.libraries.get_default().unwrap().path.clone(), config_lib.uuid).unwrap();
|
||||
let mut library = MusicLibrary::init(
|
||||
config.libraries.get_default().unwrap().path.clone(),
|
||||
config_lib.uuid,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
songs.iter().for_each(|song| library.add_song(song.to_owned()).unwrap());
|
||||
songs
|
||||
.iter()
|
||||
.for_each(|song| library.add_song(song.to_owned()).unwrap());
|
||||
|
||||
config.write_file().unwrap();
|
||||
library.save(config.libraries.get_default().unwrap().path.clone()).unwrap();
|
||||
library
|
||||
.save(config.libraries.get_default().unwrap().path.clone())
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -636,16 +636,12 @@ impl IntoIterator for Album {
|
|||
for (disc, mut tracks) in self.discs {
|
||||
tracks.par_sort_by(|a, b| a.0.cmp(&b.0));
|
||||
|
||||
let mut tracks = tracks.into_iter()
|
||||
.map(|(track, uuid)|
|
||||
AlbumTrack {
|
||||
disc,
|
||||
track,
|
||||
uuid
|
||||
})
|
||||
let mut tracks = tracks
|
||||
.into_iter()
|
||||
.map(|(track, uuid)| AlbumTrack { disc, track, uuid })
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
vec.append(&mut tracks);
|
||||
vec.append(&mut tracks);
|
||||
}
|
||||
vec.into_iter()
|
||||
}
|
||||
|
@ -654,7 +650,7 @@ impl IntoIterator for Album {
|
|||
pub struct AlbumTrack {
|
||||
disc: u16,
|
||||
track: u16,
|
||||
uuid: Uuid
|
||||
uuid: Uuid,
|
||||
}
|
||||
|
||||
impl AlbumTrack {
|
||||
|
@ -820,7 +816,10 @@ impl MusicLibrary {
|
|||
}
|
||||
|
||||
/// Finds all the audio files within a specified folder
|
||||
pub fn scan_folder<P: ?Sized + AsRef<Path>>(&mut self, target_path: &P) -> Result<i32, Box<dyn std::error::Error>> {
|
||||
pub fn scan_folder<P: ?Sized + AsRef<Path>>(
|
||||
&mut self,
|
||||
target_path: &P,
|
||||
) -> Result<i32, Box<dyn std::error::Error>> {
|
||||
let mut total = 0;
|
||||
let mut errors = 0;
|
||||
for target_file in WalkDir::new(target_path)
|
||||
|
@ -878,15 +877,21 @@ impl MusicLibrary {
|
|||
|
||||
pub fn remove_missing(&mut self) {
|
||||
let target_removals = Arc::new(Mutex::new(Vec::new()));
|
||||
self.library.par_iter().for_each(|t|{
|
||||
self.library.par_iter().for_each(|t| {
|
||||
for location in &t.location {
|
||||
if !location.exists().unwrap() {
|
||||
Arc::clone(&target_removals).lock().unwrap().push(location.clone());
|
||||
Arc::clone(&target_removals)
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push(location.clone());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let target_removals = Arc::try_unwrap(target_removals).unwrap().into_inner().unwrap();
|
||||
let target_removals = Arc::try_unwrap(target_removals)
|
||||
.unwrap()
|
||||
.into_inner()
|
||||
.unwrap();
|
||||
for location in target_removals {
|
||||
self.remove_uri(&location).unwrap();
|
||||
}
|
||||
|
@ -1119,16 +1124,19 @@ impl MusicLibrary {
|
|||
.unwrap_or(&String::new())
|
||||
.parse::<u16>()
|
||||
.unwrap_or_default(),
|
||||
song.uuid
|
||||
song.uuid,
|
||||
)),
|
||||
None => {
|
||||
album.discs.insert(disc_num, vec![(
|
||||
song.get_tag(&Tag::Track)
|
||||
.unwrap_or(&String::new())
|
||||
.parse::<u16>()
|
||||
.unwrap_or_default(),
|
||||
song.uuid
|
||||
)]);
|
||||
album.discs.insert(
|
||||
disc_num,
|
||||
vec![(
|
||||
song.get_tag(&Tag::Track)
|
||||
.unwrap_or(&String::new())
|
||||
.parse::<u16>()
|
||||
.unwrap_or_default(),
|
||||
song.uuid,
|
||||
)],
|
||||
);
|
||||
}
|
||||
},
|
||||
// If the album is not in the list, make it new one and add it
|
||||
|
@ -1144,13 +1152,13 @@ impl MusicLibrary {
|
|||
.unwrap_or(&String::new())
|
||||
.parse::<u16>()
|
||||
.unwrap_or_default(),
|
||||
song.uuid
|
||||
)])]),
|
||||
song.uuid,
|
||||
)],
|
||||
)]),
|
||||
cover: album_art.cloned(),
|
||||
};
|
||||
albums.insert(album_title, new_album);
|
||||
}
|
||||
|
||||
}
|
||||
paths.insert(song.uuid, song.primary_uri().unwrap());
|
||||
}
|
||||
|
@ -1162,19 +1170,18 @@ impl MusicLibrary {
|
|||
let num_a = a.0;
|
||||
let num_b = b.0;
|
||||
|
||||
if (num_a, num_b) != (0,0)
|
||||
{
|
||||
if (num_a, num_b) != (0, 0) {
|
||||
// If parsing the track numbers succeeds, compare as numbers
|
||||
num_a.cmp(&num_b)
|
||||
} else {
|
||||
// If parsing doesn't succeed, compare the locations
|
||||
let a = match paths.get_key_value(&a.1) {
|
||||
Some((_, (uri, _))) => uri,
|
||||
None => return Ordering::Equal
|
||||
None => return Ordering::Equal,
|
||||
};
|
||||
let b = match paths.get_key_value(&b.1) {
|
||||
Some((_, (uri, _))) => uri,
|
||||
None => return Ordering::Equal
|
||||
None => return Ordering::Equal,
|
||||
};
|
||||
|
||||
a.as_uri().cmp(&b.as_uri())
|
||||
|
@ -1217,13 +1224,20 @@ mod test {
|
|||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
use crate::{config::{tests::new_config_lib, Config}, music_storage::library::MusicLibrary};
|
||||
use crate::{
|
||||
config::{tests::new_config_lib, Config},
|
||||
music_storage::library::MusicLibrary,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn library_init() {
|
||||
let config = Config::read_file(PathBuf::from("test_config/config_test.json")).unwrap();
|
||||
let target_uuid = config.libraries.libraries[0].uuid;
|
||||
let a = MusicLibrary::init(config.libraries.get_default().unwrap().path.clone(), target_uuid).unwrap();
|
||||
let a = MusicLibrary::init(
|
||||
config.libraries.get_default().unwrap().path.clone(),
|
||||
target_uuid,
|
||||
)
|
||||
.unwrap();
|
||||
dbg!(a);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue