mirror of
https://github.com/Dangoware/dango-music-player.git
synced 2025-04-19 10:02:53 -05:00
Added basic Queue functionality
This commit is contained in:
parent
93d6b059a0
commit
d8cf8eeb2b
10 changed files with 223 additions and 93 deletions
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
use kushi::{Queue, QueueItemType};
|
use kushi::{Queue, QueueItemType};
|
||||||
use kushi::{QueueError, QueueItem};
|
use kushi::{QueueError, QueueItem};
|
||||||
use serde::Serialize;
|
use serde::{Deserialize, Serialize};
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
|
@ -32,7 +32,7 @@ pub enum ControllerError {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum PlayerLocation {
|
pub enum PlayerLocation {
|
||||||
Test,
|
Test,
|
||||||
|
@ -103,17 +103,21 @@ enum InnerLibraryResponse<'a> {
|
||||||
AllSongs(&'a Vec<Song>),
|
AllSongs(&'a Vec<Song>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub enum QueueCommand {
|
pub enum QueueCommand {
|
||||||
Append(QueueItem<QueueSong, QueueAlbum>),
|
Append(QueueItem<QueueSong, QueueAlbum>),
|
||||||
Next,
|
Next,
|
||||||
Prev,
|
Prev,
|
||||||
GetIndex(usize),
|
GetIndex(usize),
|
||||||
NowPlaying,
|
NowPlaying,
|
||||||
|
Get
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub enum QueueResponse {
|
pub enum QueueResponse {
|
||||||
Ok,
|
Ok,
|
||||||
Item(QueueItem<QueueSong, QueueAlbum>),
|
Item(QueueItem<QueueSong, QueueAlbum>),
|
||||||
|
Get(Vec<QueueItem<QueueSong, QueueAlbum>>)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -123,6 +127,10 @@ pub struct ControllerInput {
|
||||||
MailMan<PlayerResponse, PlayerCommand>,
|
MailMan<PlayerResponse, PlayerCommand>,
|
||||||
),
|
),
|
||||||
lib_mail: MailMan<LibraryResponse, LibraryCommand>,
|
lib_mail: MailMan<LibraryResponse, LibraryCommand>,
|
||||||
|
queue_mail: (
|
||||||
|
MailMan<QueueCommand, QueueResponse>,
|
||||||
|
MailMan<QueueResponse, QueueCommand>
|
||||||
|
),
|
||||||
library: MusicLibrary,
|
library: MusicLibrary,
|
||||||
config: Arc<RwLock<Config>>,
|
config: Arc<RwLock<Config>>,
|
||||||
}
|
}
|
||||||
|
@ -130,21 +138,25 @@ pub struct ControllerInput {
|
||||||
pub struct ControllerHandle {
|
pub struct ControllerHandle {
|
||||||
pub lib_mail: MailMan<LibraryCommand, LibraryResponse>,
|
pub lib_mail: MailMan<LibraryCommand, LibraryResponse>,
|
||||||
pub player_mail: MailMan<PlayerCommand, PlayerResponse>,
|
pub player_mail: MailMan<PlayerCommand, PlayerResponse>,
|
||||||
|
pub queue_mail: MailMan<QueueCommand, QueueResponse>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ControllerHandle {
|
impl ControllerHandle {
|
||||||
pub fn new(library: MusicLibrary, config: Arc<RwLock<Config>>) -> (Self, ControllerInput) {
|
pub fn new(library: MusicLibrary, config: Arc<RwLock<Config>>) -> (Self, ControllerInput) {
|
||||||
let lib_mail = MailMan::double();
|
let lib_mail = MailMan::double();
|
||||||
let player_mail = MailMan::double();
|
let player_mail = MailMan::double();
|
||||||
|
let queue_mail = MailMan::double();
|
||||||
|
|
||||||
(
|
(
|
||||||
ControllerHandle {
|
ControllerHandle {
|
||||||
lib_mail: lib_mail.0,
|
lib_mail: lib_mail.0,
|
||||||
player_mail: player_mail.0.clone()
|
player_mail: player_mail.0.clone(),
|
||||||
|
queue_mail: queue_mail.0.clone()
|
||||||
},
|
},
|
||||||
ControllerInput {
|
ControllerInput {
|
||||||
player_mail,
|
player_mail,
|
||||||
lib_mail: lib_mail.1,
|
lib_mail: lib_mail.1,
|
||||||
|
queue_mail: queue_mail,
|
||||||
library,
|
library,
|
||||||
config
|
config
|
||||||
}
|
}
|
||||||
|
@ -158,6 +170,7 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
|
||||||
ControllerInput {
|
ControllerInput {
|
||||||
player_mail,
|
player_mail,
|
||||||
lib_mail,
|
lib_mail,
|
||||||
|
queue_mail,
|
||||||
mut library,
|
mut library,
|
||||||
config
|
config
|
||||||
}: ControllerInput
|
}: ControllerInput
|
||||||
|
@ -173,20 +186,20 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
|
||||||
shuffle: None,
|
shuffle: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
for song in &library.library {
|
// for song in &library.library {
|
||||||
queue.add_item(
|
// queue.add_item(
|
||||||
QueueSong {
|
// QueueSong {
|
||||||
song: song.clone(),
|
// song: song.clone(),
|
||||||
location: PlayerLocation::Test,
|
// location: PlayerLocation::Test,
|
||||||
},
|
// },
|
||||||
true,
|
// true,
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
let inner_lib_mail = MailMan::double();
|
let inner_lib_mail = MailMan::double();
|
||||||
let queue = queue;
|
let queue = queue;
|
||||||
|
|
||||||
std::thread::scope(|scope| {
|
std::thread::scope(|scope| {
|
||||||
let queue_mail = MailMan::double();
|
let queue_mail = queue_mail;
|
||||||
let a = scope.spawn(|| {
|
let a = scope.spawn(|| {
|
||||||
futures::executor::block_on(async {
|
futures::executor::block_on(async {
|
||||||
moro::async_scope!(|scope| {
|
moro::async_scope!(|scope| {
|
||||||
|
@ -243,14 +256,19 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
|
||||||
player_mail: MailMan<PlayerResponse, PlayerCommand>,
|
player_mail: MailMan<PlayerResponse, PlayerCommand>,
|
||||||
queue_mail: MailMan<QueueCommand, QueueResponse>,
|
queue_mail: MailMan<QueueCommand, QueueResponse>,
|
||||||
) -> Result<(), ()> {
|
) -> Result<(), ()> {
|
||||||
{
|
let mut first = true;
|
||||||
player.write().unwrap().set_volume(0.05);
|
|
||||||
}
|
|
||||||
while true {
|
while true {
|
||||||
let _mail = player_mail.recv().await;
|
let _mail = player_mail.recv().await;
|
||||||
if let Ok(mail) = _mail {
|
if let Ok(mail) = _mail {
|
||||||
match mail {
|
match mail {
|
||||||
PlayerCommand::Play => {
|
PlayerCommand::Play => {
|
||||||
|
if first {
|
||||||
|
queue_mail.send(QueueCommand::NowPlaying).await.unwrap();
|
||||||
|
let QueueResponse::Item(item) = queue_mail.recv().await.unwrap() else { unimplemented!() };
|
||||||
|
let QueueItemType::Single(song) = item.item else { unimplemented!("This is temporary, handle queueItemTypes at some point") };
|
||||||
|
player.write().unwrap().enqueue_next(song.song.primary_uri().unwrap().0).unwrap();
|
||||||
|
first = false
|
||||||
|
}
|
||||||
player.write().unwrap().play().unwrap();
|
player.write().unwrap().play().unwrap();
|
||||||
player_mail.send(PlayerResponse::Empty).await.unwrap();
|
player_mail.send(PlayerResponse::Empty).await.unwrap();
|
||||||
}
|
}
|
||||||
|
@ -272,8 +290,8 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
};
|
};
|
||||||
player.write().unwrap().enqueue_next(uri).unwrap();
|
player.write().unwrap().enqueue_next(uri).unwrap();
|
||||||
let QueueItemType::Single(x) = item.item else { panic!("This is temporary, handle queueItemTypes at some point")};
|
let QueueItemType::Single(np_song) = item.item else { panic!("This is temporary, handle queueItemTypes at some point")};
|
||||||
player_mail.send(PlayerResponse::NowPlaying(x.song.clone())).await.unwrap();
|
player_mail.send(PlayerResponse::NowPlaying(np_song.song.clone())).await.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PlayerCommand::PrevSong => {
|
PlayerCommand::PrevSong => {
|
||||||
|
@ -284,8 +302,8 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
|
||||||
QueueItemType::Single(song) => song.song.primary_uri().unwrap().0,
|
QueueItemType::Single(song) => song.song.primary_uri().unwrap().0,
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
};
|
};
|
||||||
player.write().unwrap().enqueue_next(uri).unwrap();
|
let QueueItemType::Single(np_song) = item.item else { panic!("This is temporary, handle queueItemTypes at some point")};
|
||||||
player_mail.send(PlayerResponse::Empty).await.unwrap();
|
player_mail.send(PlayerResponse::NowPlaying(np_song.song.clone())).await.unwrap();;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PlayerCommand::Enqueue(index) => {
|
PlayerCommand::Enqueue(index) => {
|
||||||
|
@ -319,16 +337,17 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
|
||||||
lib_mail: MailMan<LibraryResponse, LibraryCommand>,
|
lib_mail: MailMan<LibraryResponse, LibraryCommand>,
|
||||||
inner_lib_mail: MailMan<InnerLibraryCommand, InnerLibraryResponse<'c>>,
|
inner_lib_mail: MailMan<InnerLibraryCommand, InnerLibraryResponse<'c>>,
|
||||||
) -> Result<(), ()> {
|
) -> Result<(), ()> {
|
||||||
println!("outer lib loop");
|
|
||||||
while true {
|
while true {
|
||||||
match lib_mail.recv().await.unwrap() {
|
match lib_mail.recv().await.unwrap() {
|
||||||
LibraryCommand::Song(uuid) => {
|
LibraryCommand::Song(uuid) => {
|
||||||
println!("got song commandf");
|
|
||||||
inner_lib_mail
|
inner_lib_mail
|
||||||
.send(InnerLibraryCommand::Song(uuid))
|
.send(InnerLibraryCommand::Song(uuid))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let x = inner_lib_mail.recv().await.unwrap();
|
let InnerLibraryResponse::Song(song) = inner_lib_mail.recv().await.unwrap() else {
|
||||||
|
unimplemented!();
|
||||||
|
};
|
||||||
|
lib_mail.send(LibraryResponse::Song(song.clone())).await.unwrap();
|
||||||
}
|
}
|
||||||
LibraryCommand::AllSongs => {
|
LibraryCommand::AllSongs => {
|
||||||
inner_lib_mail
|
inner_lib_mail
|
||||||
|
@ -386,9 +405,15 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
|
||||||
) {
|
) {
|
||||||
while true {
|
while true {
|
||||||
match queue_mail.recv().await.unwrap() {
|
match queue_mail.recv().await.unwrap() {
|
||||||
QueueCommand::Append(item) => match item.item {
|
QueueCommand::Append(item) => {
|
||||||
QueueItemType::Single(song) => queue.add_item(song, true),
|
match item.item {
|
||||||
_ => unimplemented!(),
|
QueueItemType::Single(song) => queue.add_item(song, true),
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
queue_mail
|
||||||
|
.send(QueueResponse::Ok)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
},
|
},
|
||||||
QueueCommand::Next => {
|
QueueCommand::Next => {
|
||||||
let next = queue.next().unwrap();
|
let next = queue.next().unwrap();
|
||||||
|
@ -405,7 +430,7 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
QueueCommand::GetIndex(index) => {
|
QueueCommand::GetIndex(index) => {
|
||||||
let item = queue.items[index].clone();
|
let item = queue.items.get(index).expect("No item in the queue at index {index}").clone();
|
||||||
queue_mail.send(QueueResponse::Item(item)).await.unwrap();
|
queue_mail.send(QueueResponse::Item(item)).await.unwrap();
|
||||||
}
|
}
|
||||||
QueueCommand::NowPlaying => {
|
QueueCommand::NowPlaying => {
|
||||||
|
@ -415,6 +440,9 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
QueueCommand::Get => {
|
||||||
|
queue_mail.send(QueueResponse::Get(queue.items.clone())).await.unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -445,7 +473,7 @@ mod test_super {
|
||||||
new_config_lib();
|
new_config_lib();
|
||||||
|
|
||||||
let config = Config::read_file(PathBuf::from(std::env!("CONFIG-PATH"))).unwrap();
|
let config = Config::read_file(PathBuf::from(std::env!("CONFIG-PATH"))).unwrap();
|
||||||
let mut library = {
|
let library = {
|
||||||
MusicLibrary::init(
|
MusicLibrary::init(
|
||||||
config.libraries.get_default().unwrap().path.clone(),
|
config.libraries.get_default().unwrap().path.clone(),
|
||||||
config.libraries.get_default().unwrap().uuid,
|
config.libraries.get_default().unwrap().uuid,
|
||||||
|
|
|
@ -475,18 +475,23 @@ impl Song {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn album_art(&self, i: usize) -> Result<Vec<u8>, Box<dyn Error>> {
|
pub fn album_art(&self, i: usize) -> Result<Option<Vec<u8>>, Box<dyn Error>> {
|
||||||
match self.album_art.get(i).unwrap() {
|
if let Some(art) = self.album_art.get(i) {
|
||||||
AlbumArt::Embedded(j) => {
|
match art {
|
||||||
let file = lofty::read_from_path(self.primary_uri()?.0.path())?;
|
AlbumArt::Embedded(j) => {
|
||||||
Ok(file.tag(file.primary_tag_type()).unwrap().pictures()[*j].data().to_vec())
|
let file = lofty::read_from_path(self.primary_uri()?.0.path())?;
|
||||||
},
|
Ok(Some(file.tag(file.primary_tag_type()).unwrap().pictures()[*j].data().to_vec()))
|
||||||
AlbumArt::External(ref path) => {
|
},
|
||||||
let mut buf = vec![];
|
AlbumArt::External(ref path) => {
|
||||||
std::fs::File::open(path.path())?.read_to_end(&mut buf)?;
|
let mut buf = vec![];
|
||||||
Ok(buf)
|
std::fs::File::open(path.path())?.read_to_end(&mut buf)?;
|
||||||
|
Ok(Some(buf))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -57,7 +57,7 @@ QueueItem<T, U> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct Queue<
|
pub struct Queue<
|
||||||
T: Debug + Clone + PartialEq, // T: The Singular Item Type
|
T: Debug + Clone + PartialEq, // T: The Singular Item Type
|
||||||
U: Debug + PartialEq + Clone + IntoIterator, // U: The Multi-Item Type. Needs to be tracked as multiple items
|
U: Debug + PartialEq + Clone + IntoIterator, // U: The Multi-Item Type. Needs to be tracked as multiple items
|
||||||
|
|
|
@ -19,6 +19,7 @@ tauri-build = { version = "2", features = [] }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
dmp-core = { path = "../dmp-core" }
|
dmp-core = { path = "../dmp-core" }
|
||||||
|
kushi = { path = "../kushi-queue" }
|
||||||
tauri = { version = "2", features = [ "protocol-asset", "unstable"] }
|
tauri = { version = "2", features = [ "protocol-asset", "unstable"] }
|
||||||
tauri-plugin-shell = "2"
|
tauri-plugin-shell = "2"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
@ -26,11 +27,12 @@ serde_json = "1"
|
||||||
futures = "0.3.31"
|
futures = "0.3.31"
|
||||||
crossbeam = "0.8.4"
|
crossbeam = "0.8.4"
|
||||||
directories = "5.0.1"
|
directories = "5.0.1"
|
||||||
uuid = { version = "1.11.0", features = ["v4"] }
|
uuid = { version = "1.11.0", features = ["v4", "serde"] }
|
||||||
ciborium = "0.2.2"
|
ciborium = "0.2.2"
|
||||||
mime = "0.3.17"
|
mime = "0.3.17"
|
||||||
file-format = "0.26.0"
|
file-format = "0.26.0"
|
||||||
chrono = { version = "0.4.38", features = ["serde"] }
|
chrono = { version = "0.4.38", features = ["serde"] }
|
||||||
|
itertools = "0.13.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = [ "custom-protocol" ]
|
default = [ "custom-protocol" ]
|
||||||
|
|
20
src-tauri/src/commands.rs
Normal file
20
src-tauri/src/commands.rs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
use dmp_core::music_controller::{controller::{ControllerHandle, LibraryResponse, PlayerLocation, QueueResponse}, queue::QueueSong};
|
||||||
|
use kushi::QueueItem;
|
||||||
|
use tauri::{AppHandle, Emitter, State, Wry};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn add_song_to_queue(app: AppHandle<Wry>, ctrl_handle: State<'_, ControllerHandle>, uuid: Uuid, location: PlayerLocation) -> Result<(), String> {
|
||||||
|
ctrl_handle.lib_mail.send(dmp_core::music_controller::controller::LibraryCommand::Song(uuid)).await.unwrap();
|
||||||
|
let LibraryResponse::Song(song) = ctrl_handle.lib_mail.recv().await.unwrap() else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
ctrl_handle.queue_mail.send(dmp_core::music_controller::controller::QueueCommand::Append(QueueItem::from_item_type(kushi::QueueItemType::Single(QueueSong { song, location })))).await.unwrap();
|
||||||
|
let QueueResponse::Ok = ctrl_handle.queue_mail.recv().await.unwrap() else {
|
||||||
|
panic!()
|
||||||
|
};
|
||||||
|
app.emit("queue_updated", ()).unwrap();
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -1,21 +1,21 @@
|
||||||
use std::{fs, io::Read, path::PathBuf, str::FromStr, thread::spawn, time::Duration};
|
use std::{fs, path::PathBuf, str::FromStr, thread::spawn};
|
||||||
|
|
||||||
|
use commands::add_song_to_queue;
|
||||||
use crossbeam::channel::{unbounded, Receiver, Sender};
|
use crossbeam::channel::{unbounded, Receiver, Sender};
|
||||||
use dmp_core::{config::{Config, ConfigLibrary}, music_controller::controller::{Controller, ControllerHandle}, music_player::gstreamer::GStreamer, music_storage::library::{AlbumArt, MusicLibrary}};
|
use dmp_core::{config::{Config, ConfigLibrary}, music_controller::controller::{Controller, ControllerHandle, LibraryResponse}, music_player::gstreamer::GStreamer, music_storage::library::{AlbumArt, MusicLibrary}};
|
||||||
use tauri::{http::Response, Manager, State, Url, WebviewWindowBuilder, Wry};
|
use tauri::{http::Response, Manager, State, Url, WebviewWindowBuilder, Wry};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use wrappers::ArtworkRx;
|
|
||||||
|
|
||||||
use crate::wrappers::{get_library, play, pause, prev, set_volume, get_song, next};
|
use crate::wrappers::{get_library, play, pause, prev, set_volume, get_song, next, get_queue};
|
||||||
|
|
||||||
pub mod wrappers;
|
pub mod wrappers;
|
||||||
|
pub mod commands;
|
||||||
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
let (rx, tx) = unbounded::<Config>();
|
let (rx, tx) = unbounded::<Config>();
|
||||||
let (lib_rx, lib_tx) = unbounded::<Option<PathBuf>>();
|
let (lib_rx, lib_tx) = unbounded::<Option<PathBuf>>();
|
||||||
let (handle_rx, handle_tx) = unbounded::<ControllerHandle>();
|
let (handle_rx, handle_tx) = unbounded::<ControllerHandle>();
|
||||||
let (art_rx, art_tx) = unbounded::<Vec<u8>>();
|
|
||||||
|
|
||||||
let controller_thread = spawn(move || {
|
let controller_thread = spawn(move || {
|
||||||
let mut config = { tx.recv().unwrap() } ;
|
let mut config = { tx.recv().unwrap() } ;
|
||||||
|
@ -32,10 +32,10 @@ pub fn run() {
|
||||||
).unwrap();
|
).unwrap();
|
||||||
|
|
||||||
let scan_path = scan_path.unwrap_or_else(|| config.libraries.get_default().unwrap().scan_folders.as_ref().unwrap()[0].clone());
|
let scan_path = scan_path.unwrap_or_else(|| config.libraries.get_default().unwrap().scan_folders.as_ref().unwrap()[0].clone());
|
||||||
library.scan_folder(&scan_path).unwrap();
|
// library.scan_folder(&scan_path).unwrap();
|
||||||
|
|
||||||
if config.libraries.get_default().is_err() {
|
if config.libraries.get_default().is_err() {
|
||||||
config.push_library( ConfigLibrary::new(save_path, String::from("Library"), Some(vec![scan_path.clone()])));
|
config.push_library( ConfigLibrary::new(save_path.clone(), String::from("Library"), Some(vec![scan_path.clone()])));
|
||||||
}
|
}
|
||||||
if library.library.is_empty() {
|
if library.library.is_empty() {
|
||||||
println!("library is empty");
|
println!("library is empty");
|
||||||
|
@ -44,6 +44,8 @@ pub fn run() {
|
||||||
}
|
}
|
||||||
println!("scan_path: {}", scan_path.display());
|
println!("scan_path: {}", scan_path.display());
|
||||||
|
|
||||||
|
library.save(save_path).unwrap();
|
||||||
|
|
||||||
let (handle, input) = ControllerHandle::new(
|
let (handle, input) = ControllerHandle::new(
|
||||||
library,
|
library,
|
||||||
std::sync::Arc::new(std::sync::RwLock::new(config))
|
std::sync::Arc::new(std::sync::RwLock::new(config))
|
||||||
|
@ -51,9 +53,8 @@ pub fn run() {
|
||||||
|
|
||||||
handle_rx.send(handle).unwrap();
|
handle_rx.send(handle).unwrap();
|
||||||
|
|
||||||
let controller = futures::executor::block_on(Controller::<GStreamer>::start(input)).unwrap();
|
let _controller = futures::executor::block_on(Controller::<GStreamer>::start(input)).unwrap();
|
||||||
});
|
});
|
||||||
|
|
||||||
let app = tauri::Builder::default()
|
let app = tauri::Builder::default()
|
||||||
.plugin(tauri_plugin_shell::init())
|
.plugin(tauri_plugin_shell::init())
|
||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
|
@ -68,20 +69,37 @@ pub fn run() {
|
||||||
prev,
|
prev,
|
||||||
get_song,
|
get_song,
|
||||||
lib_already_created,
|
lib_already_created,
|
||||||
|
get_queue,
|
||||||
|
add_song_to_queue,
|
||||||
]).manage(ConfigRx(rx))
|
]).manage(ConfigRx(rx))
|
||||||
.manage(LibRx(lib_rx))
|
.manage(LibRx(lib_rx))
|
||||||
.manage(HandleTx(handle_tx))
|
.manage(HandleTx(handle_tx))
|
||||||
.manage(ArtworkRx(art_rx))
|
.register_asynchronous_uri_scheme_protocol("asset", move |ctx, req, res| {
|
||||||
.register_asynchronous_uri_scheme_protocol("asset", move |_, req, res| {
|
let query = req
|
||||||
dbg!(req);
|
.clone()
|
||||||
let buf = art_tx.recv().unwrap_or_else(|_| Vec::new());
|
.uri()
|
||||||
|
.clone()
|
||||||
|
.into_parts()
|
||||||
|
.path_and_query
|
||||||
|
.unwrap()
|
||||||
|
.query()
|
||||||
|
.unwrap()
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let bytes = futures::executor::block_on(async move {
|
||||||
|
let controller = ctx.app_handle().state::<ControllerHandle>();
|
||||||
|
controller.lib_mail.send(dmp_core::music_controller::controller::LibraryCommand::Song(Uuid::parse_str(query.as_str()).unwrap())).await.unwrap();
|
||||||
|
let LibraryResponse::Song(song) = controller.lib_mail.recv().await.unwrap() else { unreachable!() };
|
||||||
|
song.album_art(0).unwrap_or_else(|_| None).unwrap_or_default()
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
res.respond(
|
res.respond(
|
||||||
Response::builder()
|
Response::builder()
|
||||||
.header("Origin", "*")
|
.header("Origin", "*")
|
||||||
.header("Content-Length", buf.len())
|
.header("Content-Length", bytes.len())
|
||||||
.status(200)
|
.status(200)
|
||||||
.body(buf)
|
.body(bytes)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
);
|
);
|
||||||
println!("res sent")
|
println!("res sent")
|
||||||
|
@ -96,7 +114,7 @@ pub fn run() {
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
});
|
});
|
||||||
// controller_thread.join().unwrap();
|
std::mem::drop(controller_thread)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ConfigRx(Sender<Config>);
|
struct ConfigRx(Sender<Config>);
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||||
|
|
||||||
pub mod wrappers;
|
|
||||||
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
dango_music_player_lib::run()
|
dango_music_player_lib::run()
|
||||||
|
|
|
@ -2,7 +2,9 @@ use std::collections::BTreeMap;
|
||||||
|
|
||||||
use chrono::{DateTime, Utc, serde::ts_milliseconds_option};
|
use chrono::{DateTime, Utc, serde::ts_milliseconds_option};
|
||||||
use crossbeam::channel::Sender;
|
use crossbeam::channel::Sender;
|
||||||
use dmp_core::{music_controller::controller::{ControllerHandle, LibraryCommand, LibraryResponse, PlayerResponse}, music_storage::library::{BannedType, Song, URI}};
|
use dmp_core::{music_controller::controller::{ControllerHandle, LibraryCommand, LibraryResponse, PlayerResponse, QueueCommand, QueueResponse}, music_storage::library::{BannedType, Song, URI}};
|
||||||
|
use itertools::Itertools;
|
||||||
|
use kushi::QueueItemType;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use tauri::{ipc::Response, AppHandle, Emitter, State, Wry};
|
use tauri::{ipc::Response, AppHandle, Emitter, State, Wry};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
@ -44,24 +46,26 @@ pub async fn get_volume(ctrl_handle: State<'_, ControllerHandle>) -> Result<(),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn next(app: AppHandle<Wry>, ctrl_handle: State<'_, ControllerHandle>, art_rx: State<'_, ArtworkRx>) -> Result<(), String> {
|
pub async fn next(app: AppHandle<Wry>, ctrl_handle: State<'_, ControllerHandle>) -> Result<(), String> {
|
||||||
ctrl_handle.player_mail.send(dmp_core::music_controller::controller::PlayerCommand::NextSong).await.unwrap();
|
ctrl_handle.player_mail.send(dmp_core::music_controller::controller::PlayerCommand::NextSong).await.unwrap();
|
||||||
let PlayerResponse::NowPlaying(song) = ctrl_handle.player_mail.recv().await.unwrap() else {
|
let PlayerResponse::NowPlaying(song) = ctrl_handle.player_mail.recv().await.unwrap() else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
let _song = _Song::from(&song);
|
|
||||||
art_rx.0.send(song.album_art(0).unwrap()).unwrap();
|
|
||||||
println!("next");
|
println!("next");
|
||||||
app.emit("now_playing_change", _song).unwrap();
|
app.emit("now_playing_change", _Song::from(&song)).unwrap();
|
||||||
|
app.emit("queue_updated", ()).unwrap();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn prev(ctrl_handle: State<'_, ControllerHandle>) -> Result<(), String> {
|
pub async fn prev(app: AppHandle<Wry>, ctrl_handle: State<'_, ControllerHandle>) -> Result<(), String> {
|
||||||
ctrl_handle.player_mail.send(dmp_core::music_controller::controller::PlayerCommand::PrevSong).await.unwrap();
|
ctrl_handle.player_mail.send(dmp_core::music_controller::controller::PlayerCommand::PrevSong).await.unwrap();
|
||||||
let PlayerResponse::Empty = ctrl_handle.player_mail.recv().await.unwrap() else {
|
let PlayerResponse::NowPlaying(song) = ctrl_handle.player_mail.recv().await.unwrap() else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
println!("prev");
|
||||||
|
app.emit("now_playing_change", _Song::from(&song)).unwrap();
|
||||||
|
app.emit("queue_updated", ()).unwrap();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,6 +75,17 @@ pub async fn now_playing(ctrl_handle: State<'_, ControllerHandle>) -> Result<(),
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn get_queue(ctrl_handle: State<'_, ControllerHandle>) -> Result<Vec<_Song>, String> {
|
||||||
|
ctrl_handle.queue_mail.send(QueueCommand::Get).await.unwrap();
|
||||||
|
let QueueResponse::Get(queue) = ctrl_handle.queue_mail.recv().await.unwrap() else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
Ok(queue.into_iter().map(|item| {
|
||||||
|
let QueueItemType::Single(song) = item.item else { unreachable!("There should be no albums in the queue right now") };
|
||||||
|
_Song::from(&song.song)
|
||||||
|
}).collect_vec())
|
||||||
|
}
|
||||||
|
|
||||||
//Grab Album art from custom protocol
|
//Grab Album art from custom protocol
|
||||||
#[derive(Serialize, Debug, Clone)]
|
#[derive(Serialize, Debug, Clone)]
|
||||||
|
@ -122,10 +137,3 @@ pub async fn get_song(ctrl_handle: State<'_, ControllerHandle>) -> Result<(), St
|
||||||
println!("got songs");
|
println!("got songs");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Debug)]
|
|
||||||
pub struct NowPlaying {
|
|
||||||
title: String,
|
|
||||||
artist: String,
|
|
||||||
album: String,
|
|
||||||
}
|
|
29
src/App.css
29
src/App.css
|
@ -11,7 +11,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.leftSide {
|
.leftSide {
|
||||||
width: 85%;
|
width: 80%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
@ -19,7 +19,7 @@
|
||||||
.rightSide {
|
.rightSide {
|
||||||
position: relative;
|
position: relative;
|
||||||
align-self:flex-end;
|
align-self:flex-end;
|
||||||
width: 15%;
|
width: 20%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: #c1bcd1;
|
background-color: #c1bcd1;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -71,6 +71,31 @@
|
||||||
bottom: -25%;
|
bottom: -25%;
|
||||||
background-color: burlywood;
|
background-color: burlywood;
|
||||||
height: 50%;
|
height: 50%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
.queueSongButton {
|
||||||
|
height: 15%;
|
||||||
|
padding: 0%;
|
||||||
|
margin: 0%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.queueSong {
|
||||||
|
height: 15%;
|
||||||
|
width: 90%;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.queueSongCoverArt {
|
||||||
|
width: 25%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.queueSongTags {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.song {
|
.song {
|
||||||
|
|
62
src/App.tsx
62
src/App.tsx
|
@ -10,7 +10,8 @@ import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
|
||||||
const appWindow = getCurrentWebviewWindow();
|
const appWindow = getCurrentWebviewWindow();
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const library = useState<JSX.Element[]>();
|
const library = useState<JSX.Element[]>([]);
|
||||||
|
const [queue, setQueue] = useState<JSX.Element[]>([]);
|
||||||
|
|
||||||
const [nowPlaying, setNowPlaying] = useState<JSX.Element>(
|
const [nowPlaying, setNowPlaying] = useState<JSX.Element>(
|
||||||
<NowPlaying
|
<NowPlaying
|
||||||
|
@ -24,7 +25,6 @@ function App() {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unlisten = appWindow.listen<any>("now_playing_change", ({ event, payload }) => {
|
const unlisten = appWindow.listen<any>("now_playing_change", ({ event, payload }) => {
|
||||||
// console.log(event);
|
// console.log(event);
|
||||||
|
|
||||||
setNowPlaying(
|
setNowPlaying(
|
||||||
<NowPlaying
|
<NowPlaying
|
||||||
title={ payload.tags.TrackTitle }
|
title={ payload.tags.TrackTitle }
|
||||||
|
@ -35,7 +35,19 @@ function App() {
|
||||||
)
|
)
|
||||||
|
|
||||||
})
|
})
|
||||||
|
return () => { unlisten.then((f) => f()) }
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const unlisten = appWindow.listen<any>("queue_updated", (_) => {
|
||||||
|
// console.log(event);
|
||||||
|
invoke('get_queue').then((_songs) => {
|
||||||
|
let songs = _songs as any[]
|
||||||
|
setQueue(
|
||||||
|
songs.filter((_, i) => i != 0).map((song) => <QueueSong song={ song } key={ song.uuid + '_' + Math.floor((Math.random() * 100_000) + 1) + '_' + Date.now() } />)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
return () => { unlisten.then((f) => f()) }
|
return () => { unlisten.then((f) => f()) }
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
@ -52,20 +64,13 @@ function App() {
|
||||||
</div>
|
</div>
|
||||||
<div className="rightSide">
|
<div className="rightSide">
|
||||||
{ nowPlaying }
|
{ nowPlaying }
|
||||||
<Queue />
|
<Queue songs={queue} setSongs={ setQueue } />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface L {
|
|
||||||
uuid: number,
|
|
||||||
}
|
|
||||||
function LI({uuid}: L) {
|
|
||||||
return ( <img src={convertFileSrc("abc") + "?" + uuid } id="nowPlayingArtwork" alt="Some Image" key={uuid} /> )
|
|
||||||
}
|
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|
||||||
function getConfig(): any {
|
function getConfig(): any {
|
||||||
|
@ -99,7 +104,7 @@ function PlaylistHead() {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MainViewProps {
|
interface MainViewProps {
|
||||||
lib_ref: [JSX.Element[] | undefined, React.Dispatch<React.SetStateAction<JSX.Element[] | undefined>>],
|
lib_ref: [JSX.Element[], React.Dispatch<React.SetStateAction<JSX.Element[]>>],
|
||||||
}
|
}
|
||||||
|
|
||||||
function MainView({ lib_ref }: MainViewProps) {
|
function MainView({ lib_ref }: MainViewProps) {
|
||||||
|
@ -145,7 +150,7 @@ interface SongProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
function Song(props: SongProps) {
|
function Song(props: SongProps) {
|
||||||
console.log(props.tags);
|
// console.log(props.tags);
|
||||||
|
|
||||||
return(
|
return(
|
||||||
<div className="song">
|
<div className="song">
|
||||||
|
@ -153,6 +158,10 @@ function Song(props: SongProps) {
|
||||||
<p className="album">{ props.tags.Album }</p>
|
<p className="album">{ props.tags.Album }</p>
|
||||||
<p className="artist">{ props.tags.AlbumArtist }</p>
|
<p className="artist">{ props.tags.AlbumArtist }</p>
|
||||||
<p className="duration">{ props.duration }</p>
|
<p className="duration">{ props.duration }</p>
|
||||||
|
<button onClick={(_) => {
|
||||||
|
invoke('add_song_to_queue', { uuid: props.uuid, location: 'Library' }).then(() => {} )
|
||||||
|
}}
|
||||||
|
>Add to Queue</button>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -208,17 +217,34 @@ function NowPlaying({ title, artist, album, artwork }: NowPlayingProps) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function Queue() {
|
interface QueueProps {
|
||||||
|
songs: JSX.Element[],
|
||||||
|
setSongs: React.Dispatch<React.SetStateAction<JSX.Element[]>>
|
||||||
|
}
|
||||||
|
function Queue({ songs, setSongs }: QueueProps) {
|
||||||
return (
|
return (
|
||||||
<section className="Queue">
|
<section className="Queue">
|
||||||
This is where the Queue be
|
{ songs }
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CurrentArtProps {
|
interface QueueSongProps {
|
||||||
uuid: number,
|
song: any
|
||||||
}
|
}
|
||||||
function CurrentArt({uuid}: CurrentArtProps) {
|
|
||||||
return <img src={convertFileSrc("abc") + "?" + uuid } id="nowPlayingArtwork" alt="Now Playing Artwork" key={uuid} />
|
function QueueSong({ song }: QueueSongProps) {
|
||||||
|
console.log(song.tags);
|
||||||
|
|
||||||
|
return (
|
||||||
|
// <button className="queueSongButton">
|
||||||
|
<div className="queueSong">
|
||||||
|
<img className="queueSongCoverArt" src={ convertFileSrc('abc') + '?' + song.uuid } key={ 'coverArt_' + song.uuid }/>
|
||||||
|
<div className="queueSongTags">
|
||||||
|
<h3 className="queueSongTitle">{ song.tags.TrackTitle }</h3>
|
||||||
|
<h4 className="queueSongArtist">{ song.tags.TrackArtist }</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
// </button>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue