diff --git a/dmp-core/src/music_controller/controller.rs b/dmp-core/src/music_controller/controller.rs index f7bf664..41411eb 100644 --- a/dmp-core/src/music_controller/controller.rs +++ b/dmp-core/src/music_controller/controller.rs @@ -5,7 +5,7 @@ use kushi::{Queue, QueueItemType}; use kushi::{QueueError, QueueItem}; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use std::error::Error; use std::marker::PhantomData; use std::sync::{Arc, RwLock}; @@ -32,7 +32,7 @@ pub enum ControllerError { } // 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] pub enum PlayerLocation { Test, @@ -103,17 +103,21 @@ enum InnerLibraryResponse<'a> { AllSongs(&'a Vec), } +#[derive(Debug, Clone)] pub enum QueueCommand { Append(QueueItem), Next, Prev, GetIndex(usize), NowPlaying, + Get } +#[derive(Debug, Clone)] pub enum QueueResponse { Ok, Item(QueueItem), + Get(Vec>) } @@ -123,6 +127,10 @@ pub struct ControllerInput { MailMan, ), lib_mail: MailMan, + queue_mail: ( + MailMan, + MailMan + ), library: MusicLibrary, config: Arc>, } @@ -130,21 +138,25 @@ pub struct ControllerInput { pub struct ControllerHandle { pub lib_mail: MailMan, pub player_mail: MailMan, + pub queue_mail: MailMan, } impl ControllerHandle { pub fn new(library: MusicLibrary, config: Arc>) -> (Self, ControllerInput) { let lib_mail = MailMan::double(); let player_mail = MailMan::double(); + let queue_mail = MailMan::double(); ( ControllerHandle { lib_mail: lib_mail.0, - player_mail: player_mail.0.clone() + player_mail: player_mail.0.clone(), + queue_mail: queue_mail.0.clone() }, ControllerInput { player_mail, lib_mail: lib_mail.1, + queue_mail: queue_mail, library, config } @@ -158,6 +170,7 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> { ControllerInput { player_mail, lib_mail, + queue_mail, mut library, config }: ControllerInput @@ -173,20 +186,20 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> { shuffle: None, }; - for song in &library.library { - queue.add_item( - QueueSong { - song: song.clone(), - location: PlayerLocation::Test, - }, - true, - ); - } + // 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 queue_mail = queue_mail; let a = scope.spawn(|| { futures::executor::block_on(async { moro::async_scope!(|scope| { @@ -243,14 +256,19 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> { player_mail: MailMan, queue_mail: MailMan, ) -> Result<(), ()> { - { - player.write().unwrap().set_volume(0.05); - } + let mut first = true; while true { let _mail = player_mail.recv().await; if let Ok(mail) = _mail { match mail { 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_mail.send(PlayerResponse::Empty).await.unwrap(); } @@ -272,8 +290,8 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> { _ => unimplemented!(), }; player.write().unwrap().enqueue_next(uri).unwrap(); - let QueueItemType::Single(x) = item.item else { panic!("This is temporary, handle queueItemTypes at some point")}; - player_mail.send(PlayerResponse::NowPlaying(x.song.clone())).await.unwrap(); + let QueueItemType::Single(np_song) = item.item else { panic!("This is temporary, handle queueItemTypes at some point")}; + player_mail.send(PlayerResponse::NowPlaying(np_song.song.clone())).await.unwrap(); } } PlayerCommand::PrevSong => { @@ -284,8 +302,8 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> { QueueItemType::Single(song) => song.song.primary_uri().unwrap().0, _ => unimplemented!(), }; - player.write().unwrap().enqueue_next(uri).unwrap(); - player_mail.send(PlayerResponse::Empty).await.unwrap(); + let QueueItemType::Single(np_song) = item.item else { panic!("This is temporary, handle queueItemTypes at some point")}; + player_mail.send(PlayerResponse::NowPlaying(np_song.song.clone())).await.unwrap();; } } PlayerCommand::Enqueue(index) => { @@ -319,16 +337,17 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> { lib_mail: MailMan, inner_lib_mail: MailMan>, ) -> Result<(), ()> { - println!("outer lib loop"); while true { match lib_mail.recv().await.unwrap() { LibraryCommand::Song(uuid) => { - println!("got song commandf"); inner_lib_mail .send(InnerLibraryCommand::Song(uuid)) .await .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 => { inner_lib_mail @@ -386,9 +405,15 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> { ) { while true { match queue_mail.recv().await.unwrap() { - QueueCommand::Append(item) => match item.item { - QueueItemType::Single(song) => queue.add_item(song, true), - _ => unimplemented!(), + QueueCommand::Append(item) => { + match item.item { + QueueItemType::Single(song) => queue.add_item(song, true), + _ => unimplemented!(), + } + queue_mail + .send(QueueResponse::Ok) + .await + .unwrap(); }, QueueCommand::Next => { let next = queue.next().unwrap(); @@ -405,7 +430,7 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> { .unwrap(); } 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(); } QueueCommand::NowPlaying => { @@ -415,6 +440,9 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> { .await .unwrap(); } + QueueCommand::Get => { + queue_mail.send(QueueResponse::Get(queue.items.clone())).await.unwrap(); + } } } } @@ -445,7 +473,7 @@ mod test_super { new_config_lib(); let config = Config::read_file(PathBuf::from(std::env!("CONFIG-PATH"))).unwrap(); - let mut library = { + let library = { MusicLibrary::init( config.libraries.get_default().unwrap().path.clone(), config.libraries.get_default().unwrap().uuid, diff --git a/dmp-core/src/music_storage/library.rs b/dmp-core/src/music_storage/library.rs index 299a698..284f6d5 100644 --- a/dmp-core/src/music_storage/library.rs +++ b/dmp-core/src/music_storage/library.rs @@ -475,18 +475,23 @@ impl Song { } } - pub fn album_art(&self, i: usize) -> Result, Box> { - match self.album_art.get(i).unwrap() { - AlbumArt::Embedded(j) => { - let file = lofty::read_from_path(self.primary_uri()?.0.path())?; - Ok(file.tag(file.primary_tag_type()).unwrap().pictures()[*j].data().to_vec()) - }, - AlbumArt::External(ref path) => { - let mut buf = vec![]; - std::fs::File::open(path.path())?.read_to_end(&mut buf)?; - Ok(buf) + pub fn album_art(&self, i: usize) -> Result>, Box> { + if let Some(art) = self.album_art.get(i) { + match art { + AlbumArt::Embedded(j) => { + 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![]; + std::fs::File::open(path.path())?.read_to_end(&mut buf)?; + Ok(Some(buf)) + } } + } else { + Ok(None) } + } } diff --git a/kushi-queue/src/lib.rs b/kushi-queue/src/lib.rs index 6120c32..8bd2668 100644 --- a/kushi-queue/src/lib.rs +++ b/kushi-queue/src/lib.rs @@ -57,7 +57,7 @@ QueueItem { } } -#[derive(Debug, Default)] +#[derive(Debug, Clone, Default)] pub struct Queue< 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 diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 9d9d66f..fd0cc8a 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -19,6 +19,7 @@ tauri-build = { version = "2", features = [] } [dependencies] dmp-core = { path = "../dmp-core" } +kushi = { path = "../kushi-queue" } tauri = { version = "2", features = [ "protocol-asset", "unstable"] } tauri-plugin-shell = "2" serde = { version = "1", features = ["derive"] } @@ -26,11 +27,12 @@ serde_json = "1" futures = "0.3.31" crossbeam = "0.8.4" directories = "5.0.1" -uuid = { version = "1.11.0", features = ["v4"] } +uuid = { version = "1.11.0", features = ["v4", "serde"] } ciborium = "0.2.2" mime = "0.3.17" file-format = "0.26.0" chrono = { version = "0.4.38", features = ["serde"] } +itertools = "0.13.0" [features] default = [ "custom-protocol" ] diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs new file mode 100644 index 0000000..a26b677 --- /dev/null +++ b/src-tauri/src/commands.rs @@ -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, 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(()) +} \ No newline at end of file diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 94f313a..bebbf7b 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -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 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 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 commands; #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { let (rx, tx) = unbounded::(); let (lib_rx, lib_tx) = unbounded::>(); let (handle_rx, handle_tx) = unbounded::(); - let (art_rx, art_tx) = unbounded::>(); let controller_thread = spawn(move || { let mut config = { tx.recv().unwrap() } ; @@ -32,10 +32,10 @@ pub fn run() { ).unwrap(); 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() { - 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() { println!("library is empty"); @@ -44,6 +44,8 @@ pub fn run() { } println!("scan_path: {}", scan_path.display()); + library.save(save_path).unwrap(); + let (handle, input) = ControllerHandle::new( library, std::sync::Arc::new(std::sync::RwLock::new(config)) @@ -51,9 +53,8 @@ pub fn run() { handle_rx.send(handle).unwrap(); - let controller = futures::executor::block_on(Controller::::start(input)).unwrap(); + let _controller = futures::executor::block_on(Controller::::start(input)).unwrap(); }); - let app = tauri::Builder::default() .plugin(tauri_plugin_shell::init()) .invoke_handler(tauri::generate_handler![ @@ -68,20 +69,37 @@ pub fn run() { prev, get_song, lib_already_created, - + get_queue, + add_song_to_queue, ]).manage(ConfigRx(rx)) .manage(LibRx(lib_rx)) .manage(HandleTx(handle_tx)) - .manage(ArtworkRx(art_rx)) - .register_asynchronous_uri_scheme_protocol("asset", move |_, req, res| { - dbg!(req); - let buf = art_tx.recv().unwrap_or_else(|_| Vec::new()); + .register_asynchronous_uri_scheme_protocol("asset", move |ctx, req, res| { + let query = req + .clone() + .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::(); + 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( Response::builder() .header("Origin", "*") - .header("Content-Length", buf.len()) + .header("Content-Length", bytes.len()) .status(200) - .body(buf) + .body(bytes) .unwrap() ); println!("res sent") @@ -96,7 +114,7 @@ pub fn run() { } _ => {} }); - // controller_thread.join().unwrap(); + std::mem::drop(controller_thread) } struct ConfigRx(Sender); diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 183a698..0355cd9 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -1,8 +1,6 @@ // Prevents additional console window on Windows in release, DO NOT REMOVE!! #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] -pub mod wrappers; - fn main() { dango_music_player_lib::run() diff --git a/src-tauri/src/wrappers.rs b/src-tauri/src/wrappers.rs index 52751ed..cbbbfb2 100644 --- a/src-tauri/src/wrappers.rs +++ b/src-tauri/src/wrappers.rs @@ -2,7 +2,9 @@ use std::collections::BTreeMap; use chrono::{DateTime, Utc, serde::ts_milliseconds_option}; 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 tauri::{ipc::Response, AppHandle, Emitter, State, Wry}; use uuid::Uuid; @@ -44,24 +46,26 @@ pub async fn get_volume(ctrl_handle: State<'_, ControllerHandle>) -> Result<(), } #[tauri::command] -pub async fn next(app: AppHandle, ctrl_handle: State<'_, ControllerHandle>, art_rx: State<'_, ArtworkRx>) -> Result<(), String> { +pub async fn next(app: AppHandle, ctrl_handle: State<'_, ControllerHandle>) -> Result<(), String> { 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 { unreachable!() }; - let _song = _Song::from(&song); - art_rx.0.send(song.album_art(0).unwrap()).unwrap(); println!("next"); - app.emit("now_playing_change", _song).unwrap(); + app.emit("now_playing_change", _Song::from(&song)).unwrap(); + app.emit("queue_updated", ()).unwrap(); Ok(()) } #[tauri::command] -pub async fn prev(ctrl_handle: State<'_, ControllerHandle>) -> Result<(), String> { +pub async fn prev(app: AppHandle, ctrl_handle: State<'_, ControllerHandle>) -> Result<(), String> { 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!() }; + println!("prev"); + app.emit("now_playing_change", _Song::from(&song)).unwrap(); + app.emit("queue_updated", ()).unwrap(); Ok(()) } @@ -71,6 +75,17 @@ pub async fn now_playing(ctrl_handle: State<'_, ControllerHandle>) -> Result<(), Ok(()) } +#[tauri::command] +pub async fn get_queue(ctrl_handle: State<'_, ControllerHandle>) -> Result, 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 #[derive(Serialize, Debug, Clone)] @@ -121,11 +136,4 @@ pub async fn get_song(ctrl_handle: State<'_, ControllerHandle>) -> Result<(), St let LibraryResponse::Song(_) = ctrl_handle.lib_mail.recv().await.unwrap() else { unreachable!("It has been reached") }; println!("got songs"); Ok(()) -} - -#[derive(Serialize, Debug)] -pub struct NowPlaying { - title: String, - artist: String, - album: String, } \ No newline at end of file diff --git a/src/App.css b/src/App.css index 03a16ec..f890488 100644 --- a/src/App.css +++ b/src/App.css @@ -11,7 +11,7 @@ } .leftSide { - width: 85%; + width: 80%; height: 100%; display: flex; flex-direction: column; @@ -19,7 +19,7 @@ .rightSide { position: relative; align-self:flex-end; - width: 15%; + width: 20%; height: 100%; background-color: #c1bcd1; display: flex; @@ -71,6 +71,31 @@ bottom: -25%; background-color: burlywood; 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 { diff --git a/src/App.tsx b/src/App.tsx index c239b31..5f0ebe2 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -10,7 +10,8 @@ import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow"; const appWindow = getCurrentWebviewWindow(); function App() { - const library = useState(); + const library = useState([]); + const [queue, setQueue] = useState([]); const [nowPlaying, setNowPlaying] = useState( { const unlisten = appWindow.listen("now_playing_change", ({ event, payload }) => { // console.log(event); - setNowPlaying( { unlisten.then((f) => f()) } + }, []); + useEffect(() => { + const unlisten = appWindow.listen("queue_updated", (_) => { + // console.log(event); + invoke('get_queue').then((_songs) => { + let songs = _songs as any[] + setQueue( + songs.filter((_, i) => i != 0).map((song) => ) + ) + }) + }) return () => { unlisten.then((f) => f()) } }, []); @@ -52,20 +64,13 @@ function App() {
{ nowPlaying } - +
); } -interface L { - uuid: number, -} -function LI({uuid}: L) { - return ( Some Image ) -} - export default App; function getConfig(): any { @@ -99,7 +104,7 @@ function PlaylistHead() { } interface MainViewProps { - lib_ref: [JSX.Element[] | undefined, React.Dispatch>], + lib_ref: [JSX.Element[], React.Dispatch>], } function MainView({ lib_ref }: MainViewProps) { @@ -145,7 +150,7 @@ interface SongProps { } function Song(props: SongProps) { - console.log(props.tags); + // console.log(props.tags); return(
@@ -153,6 +158,10 @@ function Song(props: SongProps) {

{ props.tags.Album }

{ props.tags.AlbumArtist }

{ props.duration }

+
) } @@ -208,17 +217,34 @@ function NowPlaying({ title, artist, album, artwork }: NowPlayingProps) { ) } -function Queue() { +interface QueueProps { + songs: JSX.Element[], + setSongs: React.Dispatch> +} +function Queue({ songs, setSongs }: QueueProps) { return (
- This is where the Queue be + { songs }
) } -interface CurrentArtProps { - uuid: number, +interface QueueSongProps { + song: any } -function CurrentArt({uuid}: CurrentArtProps) { - return Now Playing Artwork + +function QueueSong({ song }: QueueSongProps) { + console.log(song.tags); + + return ( + // + ) }