Grabbing songs from the backend almost works + other minor changes

This commit is contained in:
MrDulfin 2024-12-08 14:08:30 -05:00
parent ba01bb4a7c
commit efca53b4bd
10 changed files with 484 additions and 60 deletions

View file

@ -5,6 +5,7 @@
use kushi::{Queue, QueueItemType}; use kushi::{Queue, QueueItemType};
use kushi::{QueueError, QueueItem}; use kushi::{QueueError, QueueItem};
use serde::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};
@ -74,9 +75,10 @@ pub enum PlayerCommand {
SetVolume(f64), SetVolume(f64),
} }
#[derive(Debug, PartialEq, PartialOrd, Clone)] #[derive(Debug, PartialEq, Clone)]
pub enum PlayerResponse { pub enum PlayerResponse {
Empty, Empty,
NowPlaying(Song)
} }
pub enum LibraryCommand { pub enum LibraryCommand {
@ -270,7 +272,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();
player_mail.send(PlayerResponse::Empty).await.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();
} }
} }
PlayerCommand::PrevSong => { PlayerCommand::PrevSong => {
@ -328,14 +331,11 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
let x = inner_lib_mail.recv().await.unwrap(); let x = inner_lib_mail.recv().await.unwrap();
} }
LibraryCommand::AllSongs => { LibraryCommand::AllSongs => {
println!("got command");
inner_lib_mail inner_lib_mail
.send(InnerLibraryCommand::AllSongs) .send(InnerLibraryCommand::AllSongs)
.await .await
.unwrap(); .unwrap();
println!("sent");
let x = inner_lib_mail.recv().await.unwrap(); let x = inner_lib_mail.recv().await.unwrap();
println!("recieved");
if let InnerLibraryResponse::AllSongs(songs) = x { if let InnerLibraryResponse::AllSongs(songs) = x {
lib_mail.send(LibraryResponse::AllSongs(songs.clone())).await.unwrap(); lib_mail.send(LibraryResponse::AllSongs(songs.clone())).await.unwrap();
} else { } else {
@ -497,3 +497,4 @@ mod test_super {
a.join().unwrap(); a.join().unwrap();
} }
} }

View file

@ -7,6 +7,7 @@ use std::cmp::Ordering;
// Various std things // Various std things
use std::collections::{BTreeMap, HashMap}; use std::collections::{BTreeMap, HashMap};
use std::error::Error; use std::error::Error;
use std::io::Read;
use std::ops::ControlFlow::{Break, Continue}; use std::ops::ControlFlow::{Break, Continue};
use std::vec::IntoIter; use std::vec::IntoIter;
@ -473,6 +474,24 @@ impl Song {
None => Err("No valid URIs for this song".into()), None => Err("No valid URIs for this song".into()),
} }
} }
pub fn album_art(&self, i: usize) -> Result<Vec<u8>, Box<dyn Error>> {
match self.album_art[i] {
AlbumArt::Embedded(j) => {
let file = lofty::read_from_path(self.primary_uri()?.0.path())?;
if file.contains_tag_type(TagType::Id3v2) {
Ok(file.tag(TagType::Id3v2).unwrap().pictures()[j].data().to_vec())
} else {
unimplemented!()
}
},
AlbumArt::External(ref path) => {
let mut buf = vec![];
std::fs::File::open(path.path())?.read_to_end(&mut buf)?;
Ok(buf)
}
}
}
} }
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]

224
package-lock.json generated
View file

@ -8,8 +8,12 @@
"name": "dango-music-player", "name": "dango-music-player",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@jprochazk/cbor": "github:jprochazk/cbor",
"@tauri-apps/api": "^2", "@tauri-apps/api": "^2",
"@tauri-apps/plugin-shell": "^2", "@tauri-apps/plugin-shell": "^2",
"cbor": "github:jprochazk/cbor",
"cbor-x": "^1.6.0",
"node-fetch": "^3.3.2",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0" "react-dom": "^18.2.0"
}, },
@ -19,7 +23,7 @@
"@types/react-dom": "^18.2.7", "@types/react-dom": "^18.2.7",
"@vitejs/plugin-react": "^4.2.1", "@vitejs/plugin-react": "^4.2.1",
"typescript": "^5.2.2", "typescript": "^5.2.2",
"vite": "^5.3.1" "vite": "^5.4.11"
} }
}, },
"node_modules/@ampproject/remapping": { "node_modules/@ampproject/remapping": {
@ -289,6 +293,78 @@
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@cbor-extract/cbor-extract-darwin-arm64": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-darwin-arm64/-/cbor-extract-darwin-arm64-2.2.0.tgz",
"integrity": "sha512-P7swiOAdF7aSi0H+tHtHtr6zrpF3aAq/W9FXx5HektRvLTM2O89xCyXF3pk7pLc7QpaY7AoaE8UowVf9QBdh3w==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@cbor-extract/cbor-extract-darwin-x64": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-darwin-x64/-/cbor-extract-darwin-x64-2.2.0.tgz",
"integrity": "sha512-1liF6fgowph0JxBbYnAS7ZlqNYLf000Qnj4KjqPNW4GViKrEql2MgZnAsExhY9LSy8dnvA4C0qHEBgPrll0z0w==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@cbor-extract/cbor-extract-linux-arm": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-arm/-/cbor-extract-linux-arm-2.2.0.tgz",
"integrity": "sha512-QeBcBXk964zOytiedMPQNZr7sg0TNavZeuUCD6ON4vEOU/25+pLhNN6EDIKJ9VLTKaZ7K7EaAriyYQ1NQ05s/Q==",
"cpu": [
"arm"
],
"optional": true,
"os": [
"linux"
]
},
"node_modules/@cbor-extract/cbor-extract-linux-arm64": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-arm64/-/cbor-extract-linux-arm64-2.2.0.tgz",
"integrity": "sha512-rQvhNmDuhjTVXSPFLolmQ47/ydGOFXtbR7+wgkSY0bdOxCFept1hvg59uiLPT2fVDuJFuEy16EImo5tE2x3RsQ==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
]
},
"node_modules/@cbor-extract/cbor-extract-linux-x64": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-x64/-/cbor-extract-linux-x64-2.2.0.tgz",
"integrity": "sha512-cWLAWtT3kNLHSvP4RKDzSTX9o0wvQEEAj4SKvhWuOVZxiDAeQazr9A+PSiRILK1VYMLeDml89ohxCnUNQNQNCw==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
]
},
"node_modules/@cbor-extract/cbor-extract-win32-x64": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-win32-x64/-/cbor-extract-win32-x64-2.2.0.tgz",
"integrity": "sha512-l2M+Z8DO2vbvADOBNLbbh9y5ST1RY5sqkWOg/58GkUPBYou/cuNZ68SGQ644f1CvZ8kcOxyZtw06+dxWHIoN/w==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"win32"
]
},
"node_modules/@esbuild/aix-ppc64": { "node_modules/@esbuild/aix-ppc64": {
"version": "0.21.5", "version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
@ -657,6 +733,10 @@
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/@jprochazk/cbor": {
"version": "0.5.0",
"resolved": "git+ssh://git@github.com/jprochazk/cbor.git#4824b43c60f8a1c38fd8ef3d51d1f24e30a55743"
},
"node_modules/@jridgewell/gen-mapping": { "node_modules/@jridgewell/gen-mapping": {
"version": "0.3.5", "version": "0.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
@ -1287,6 +1367,40 @@
} }
] ]
}, },
"node_modules/cbor": {
"name": "@jprochazk/cbor",
"version": "0.5.0",
"resolved": "git+ssh://git@github.com/jprochazk/cbor.git#4824b43c60f8a1c38fd8ef3d51d1f24e30a55743"
},
"node_modules/cbor-extract": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/cbor-extract/-/cbor-extract-2.2.0.tgz",
"integrity": "sha512-Ig1zM66BjLfTXpNgKpvBePq271BPOvu8MR0Jl080yG7Jsl+wAZunfrwiwA+9ruzm/WEdIV5QF/bjDZTqyAIVHA==",
"hasInstallScript": true,
"optional": true,
"dependencies": {
"node-gyp-build-optional-packages": "5.1.1"
},
"bin": {
"download-cbor-prebuilds": "bin/download-prebuilds.js"
},
"optionalDependencies": {
"@cbor-extract/cbor-extract-darwin-arm64": "2.2.0",
"@cbor-extract/cbor-extract-darwin-x64": "2.2.0",
"@cbor-extract/cbor-extract-linux-arm": "2.2.0",
"@cbor-extract/cbor-extract-linux-arm64": "2.2.0",
"@cbor-extract/cbor-extract-linux-x64": "2.2.0",
"@cbor-extract/cbor-extract-win32-x64": "2.2.0"
}
},
"node_modules/cbor-x": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/cbor-x/-/cbor-x-1.6.0.tgz",
"integrity": "sha512-0kareyRwHSkL6ws5VXHEf8uY1liitysCVJjlmhaLG+IXLqhSaOO+t63coaso7yjwEzWZzLy8fJo06gZDVQM9Qg==",
"optionalDependencies": {
"cbor-extract": "^2.2.0"
}
},
"node_modules/convert-source-map": { "node_modules/convert-source-map": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
@ -1299,6 +1413,14 @@
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"dev": true "dev": true
}, },
"node_modules/data-uri-to-buffer": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
"integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
"engines": {
"node": ">= 12"
}
},
"node_modules/debug": { "node_modules/debug": {
"version": "4.3.7", "version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
@ -1316,6 +1438,15 @@
} }
} }
}, },
"node_modules/detect-libc": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
"integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
"optional": true,
"engines": {
"node": ">=8"
}
},
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.5.65", "version": "1.5.65",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.65.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.65.tgz",
@ -1369,6 +1500,39 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/fetch-blob": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
"integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "paypal",
"url": "https://paypal.me/jimmywarting"
}
],
"dependencies": {
"node-domexception": "^1.0.0",
"web-streams-polyfill": "^3.0.3"
},
"engines": {
"node": "^12.20 || >= 14.13"
}
},
"node_modules/formdata-polyfill": {
"version": "4.0.10",
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
"dependencies": {
"fetch-blob": "^3.1.2"
},
"engines": {
"node": ">=12.20.0"
}
},
"node_modules/fsevents": { "node_modules/fsevents": {
"version": "2.3.3", "version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@ -1474,6 +1638,55 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
} }
}, },
"node_modules/node-domexception": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "github",
"url": "https://paypal.me/jimmywarting"
}
],
"engines": {
"node": ">=10.5.0"
}
},
"node_modules/node-fetch": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
"integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
"dependencies": {
"data-uri-to-buffer": "^4.0.0",
"fetch-blob": "^3.1.4",
"formdata-polyfill": "^4.0.10"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/node-fetch"
}
},
"node_modules/node-gyp-build-optional-packages": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.1.1.tgz",
"integrity": "sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw==",
"optional": true,
"dependencies": {
"detect-libc": "^2.0.1"
},
"bin": {
"node-gyp-build-optional-packages": "bin.js",
"node-gyp-build-optional-packages-optional": "optional.js",
"node-gyp-build-optional-packages-test": "build-test.js"
}
},
"node_modules/node-releases": { "node_modules/node-releases": {
"version": "2.0.18", "version": "2.0.18",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz",
@ -1657,6 +1870,7 @@
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz",
"integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==", "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"esbuild": "^0.21.3", "esbuild": "^0.21.3",
"postcss": "^8.4.43", "postcss": "^8.4.43",
@ -1711,6 +1925,14 @@
} }
} }
}, },
"node_modules/web-streams-polyfill": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
"integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
"engines": {
"node": ">= 8"
}
},
"node_modules/yallist": { "node_modules/yallist": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",

View file

@ -25,6 +25,6 @@
"@types/react-dom": "^18.2.7", "@types/react-dom": "^18.2.7",
"@vitejs/plugin-react": "^4.2.1", "@vitejs/plugin-react": "^4.2.1",
"typescript": "^5.2.2", "typescript": "^5.2.2",
"vite": "^5.3.1" "vite": "^5.4.11"
} }
} }

View file

@ -18,17 +18,19 @@ crate-type = ["staticlib", "cdylib", "rlib"]
tauri-build = { version = "2", features = [] } tauri-build = { version = "2", features = [] }
[dependencies] [dependencies]
tauri = { version = "2", features = ["unstable"] } dmp-core = { path = "../dmp-core" }
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"] }
serde_json = "1" serde_json = "1"
dmp-core = { path = "../dmp-core" }
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"] }
ciborium = "0.2.2" ciborium = "0.2.2"
mime = "0.3.17" mime = "0.3.17"
file-format = "0.26.0"
chrono = { version = "0.4.38", features = ["serde"] }
[features] [features]
default = [ "custom-protocol" ] default = [ "custom-protocol" ]

View file

@ -1,9 +1,10 @@
use std::{fs, path::PathBuf, str::FromStr, thread::spawn}; use std::{fs, io::Read, path::PathBuf, str::FromStr, thread::spawn, time::Duration};
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::MusicLibrary}; use dmp_core::{config::{Config, ConfigLibrary}, music_controller::controller::{Controller, ControllerHandle}, music_player::gstreamer::GStreamer, music_storage::library::{AlbumArt, MusicLibrary}};
use tauri::{Manager, State, 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};
@ -12,13 +13,14 @@ pub mod wrappers;
#[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::<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 t1 = spawn(move || { let controller_thread = spawn(move || {
let mut config = { tx.recv().unwrap() } ; let mut config = { tx.recv().unwrap() } ;
let scan_path = { lib_tx.recv().unwrap() }; let scan_path = { lib_tx.recv().unwrap() };
let save_path = config.libraries.library_folder.join("library.dlib"); let save_path = dbg!(config.libraries.library_folder.join("library.dlib"));
let mut library = MusicLibrary::init( let mut library = MusicLibrary::init(
save_path.clone(), save_path.clone(),
@ -28,19 +30,25 @@ pub fn run() {
Uuid::new_v4() Uuid::new_v4()
} }
).unwrap(); ).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();
config.push_library( ConfigLibrary::new(save_path, String::from("Library"), Some(vec![scan_path.clone()]))); if config.libraries.get_default().is_err() {
// config.write_file().unwrap(); config.push_library( ConfigLibrary::new(save_path, String::from("Library"), Some(vec![scan_path.clone()])));
}
if library.library.is_empty() {
println!("library is empty");
} else {
config.write_file().unwrap();
}
println!("scan_path: {}", scan_path.display());
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))
); );
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();
@ -59,10 +67,25 @@ pub fn run() {
next, next,
prev, prev,
get_song, get_song,
lib_already_created,
]).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 |_, req, res| {
dbg!(req);
let buf = art_tx.recv().unwrap_or_else(|_| Vec::new());
res.respond(
Response::builder()
.header("Origin", "*")
.header("Content-Length", buf.len())
.status(200)
.body(buf)
.unwrap()
);
println!("res sent")
})
.build(tauri::generate_context!()) .build(tauri::generate_context!())
.expect("error while building tauri application"); .expect("error while building tauri application");
@ -73,19 +96,21 @@ pub fn run() {
} }
_ => {} _ => {}
}); });
t1.join().unwrap(); // controller_thread.join().unwrap();
} }
struct ConfigRx(Sender<Config>); struct ConfigRx(Sender<Config>);
struct LibRx(Sender<PathBuf>); struct LibRx(Sender<Option<PathBuf>>);
struct HandleTx(Receiver<ControllerHandle>); struct HandleTx(Receiver<ControllerHandle>);
#[tauri::command] #[tauri::command]
async fn get_config(state: State<'_, ConfigRx>) -> Result<Config, String> { async fn get_config(state: State<'_, ConfigRx>) -> Result<Config, String> {
if let Some(dir) = directories::ProjectDirs::from("", "Dangoware", "dmp") { if let Some(dir) = directories::ProjectDirs::from("", "Dangoware", "dmp") {
let path = dir.config_dir(); let path = dir.config_dir();
fs::create_dir(path).or_else(|err| { // dbg!(&path);
fs::create_dir_all(path).or_else(|err| {
if err.kind() == std::io::ErrorKind::AlreadyExists { if err.kind() == std::io::ErrorKind::AlreadyExists {
Ok(()) Ok(())
} else { } else {
@ -139,7 +164,7 @@ async fn create_library(
path: String path: String
) -> Result<(), String> { ) -> Result<(), String> {
println!("{path}"); println!("{path}");
let path = PathBuf::from(path); let path = PathBuf::from(path.trim().trim_matches('"'));
if !path.exists() { if !path.exists() {
panic!("Path {} does not exist!", path.display()) panic!("Path {} does not exist!", path.display())
@ -147,9 +172,17 @@ async fn create_library(
panic!("Path {} is not a directory!", path.display()) panic!("Path {} is not a directory!", path.display())
} }
lib_rx.inner().0.send(path).unwrap(); lib_rx.inner().0.send(Some(path)).unwrap();
app.manage(handle_tx.inner().0.recv().unwrap()); app.manage(handle_tx.inner().0.recv().unwrap());
window.close().unwrap(); window.close().unwrap();
Ok(()) Ok(())
} }
#[tauri::command]
async fn lib_already_created(app: tauri::AppHandle<Wry>, lib_rx: State<'_, LibRx>, handle_tx: State<'_, HandleTx>) -> Result<(), String> {
println!("lib already created");
lib_rx.inner().0.send(None);
app.manage(handle_tx.inner().0.recv().unwrap());
Ok(())
}

View file

@ -1,7 +1,14 @@
use dmp_core::{music_controller::controller::{ControllerHandle, LibraryCommand, LibraryResponse, PlayerResponse}, music_storage::library::Song}; use std::collections::BTreeMap;
use tauri::{ipc::Response, State};
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 serde::Serialize;
use tauri::{ipc::Response, AppHandle, Emitter, State, Wry};
use uuid::Uuid; use uuid::Uuid;
pub struct ArtworkRx(pub Sender<Vec<u8>>);
#[tauri::command] #[tauri::command]
pub async fn play(ctrl_handle: State<'_, ControllerHandle>) -> Result<(), String> { pub async fn play(ctrl_handle: State<'_, ControllerHandle>) -> Result<(), String> {
ctrl_handle.player_mail.send(dmp_core::music_controller::controller::PlayerCommand::Play).await.unwrap(); ctrl_handle.player_mail.send(dmp_core::music_controller::controller::PlayerCommand::Play).await.unwrap();
@ -21,7 +28,8 @@ pub async fn pause(ctrl_handle: State<'_, ControllerHandle>) -> Result<(), Strin
} }
#[tauri::command] #[tauri::command]
pub async fn set_volume(ctrl_handle: State<'_, ControllerHandle>, volume: f64) -> Result<(), String> { pub async fn set_volume(ctrl_handle: State<'_, ControllerHandle>, volume: String) -> Result<(), String> {
let volume = volume.parse::<f64>().unwrap() / 1000.0;
ctrl_handle.player_mail.send(dmp_core::music_controller::controller::PlayerCommand::SetVolume(volume)).await.unwrap(); ctrl_handle.player_mail.send(dmp_core::music_controller::controller::PlayerCommand::SetVolume(volume)).await.unwrap();
let PlayerResponse::Empty = ctrl_handle.player_mail.recv().await.unwrap() else { let PlayerResponse::Empty = ctrl_handle.player_mail.recv().await.unwrap() else {
unreachable!() unreachable!()
@ -36,11 +44,14 @@ pub async fn get_volume(ctrl_handle: State<'_, ControllerHandle>) -> Result<(),
} }
#[tauri::command] #[tauri::command]
pub async fn next(ctrl_handle: State<'_, ControllerHandle>) -> Result<(), String> { pub async fn next(app: AppHandle<Wry>, ctrl_handle: State<'_, ControllerHandle>, art_rx: State<'_, ArtworkRx>) -> 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::Empty = 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();
app.emit("now_playing_change", _song).unwrap();
Ok(()) Ok(())
} }
@ -59,16 +70,48 @@ pub async fn now_playing(ctrl_handle: State<'_, ControllerHandle>) -> Result<(),
Ok(()) Ok(())
} }
//Grab Album art from custom protocol
#[derive(Serialize, Debug, Clone)]
pub struct _Song {
pub location: Vec<URI>,
pub uuid: Uuid,
pub plays: i32,
pub format: Option<String>,
pub duration: String,
#[serde(with = "ts_milliseconds_option")]
pub last_played: Option<DateTime<Utc>>,
#[serde(with = "ts_milliseconds_option")]
pub date_added: Option<DateTime<Utc>>,
#[serde(with = "ts_milliseconds_option")]
pub date_modified: Option<DateTime<Utc>>,
pub tags: BTreeMap<String, String>,
}
impl From<&Song> for _Song {
fn from(value: &Song) -> Self {
_Song {
location: value.location.clone(),
uuid: value.uuid.clone(),
plays: value.plays.clone(),
duration: value.duration.as_secs().to_string(),
format: value.format.map(|format| format.to_string()),
last_played: value.last_played,
date_added: value.date_added,
date_modified: value.date_modified,
tags: value.tags.iter().map(|(k, v)| (k.to_string(), v.clone())).collect()
}
}
}
#[tauri::command] #[tauri::command]
pub async fn get_library(ctrl_handle: State<'_, ControllerHandle>) -> Result<Response, String> { pub async fn get_library(ctrl_handle: State<'_, ControllerHandle>) -> Result<Vec<_Song>, String> {
println!("getting songs");
ctrl_handle.lib_mail.send(LibraryCommand::AllSongs).await.unwrap(); ctrl_handle.lib_mail.send(LibraryCommand::AllSongs).await.unwrap();
let LibraryResponse::AllSongs(songs) = ctrl_handle.lib_mail.recv().await.unwrap() else { unreachable!("It has been reached") }; let LibraryResponse::AllSongs(songs) = ctrl_handle.lib_mail.recv().await.unwrap() else { unreachable!("It has been reached") };
println!("got songs");
let mut buf = vec![]; let _songs = songs.iter().map(|song| _Song::from(song)).collect::<Vec<_>>();
ciborium::into_writer(&songs, &mut buf);
Ok(Response::new(buf)) Ok(_songs)
} }
#[tauri::command] #[tauri::command]
@ -78,3 +121,10 @@ 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,
}

View file

@ -18,7 +18,11 @@
} }
], ],
"security": { "security": {
"csp": null "assetProtocol": {
"enable": true,
"scope": { "allow": ["asset://localhost*"] }
},
"csp": "default-src 'self'; img-src 'self'; asset: asset://localhost/*"
} }
}, },
"bundle": { "bundle": {

View file

@ -36,6 +36,7 @@
.mainView { .mainView {
background-color: #f5eab2; background-color: #f5eab2;
height: 91.5%; height: 91.5%;
overflow-y: scroll;
} }
#playBar { #playBar {
@ -71,3 +72,9 @@
background-color: burlywood; background-color: burlywood;
height: 50%; height: 50%;
} }
.song {
display: flex;
justify-content: left;
gap: 1%;
}

View file

@ -1,29 +1,40 @@
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import reactLogo from "./assets/react.svg"; import { convertFileSrc, invoke } from "@tauri-apps/api/core";
import { invoke } from "@tauri-apps/api/core";
import "./App.css"; import "./App.css";
import { Config, Song } from "./types"; import { Config } from "./types";
import { EventEmitter } from "@tauri-apps/plugin-shell";
import { decode, encode } from 'cbor-x'; import { listen } from "@tauri-apps/api/event";
import CBOR from "cbor";
function App() { function App() {
const library = useState<JSX.Element[]>();
const [artwork, setArtwork] = useState<JSX.Element>(<></>);
const [nowPlaying, setNowPlaying] = useState<JSX.Element>(<NowPlaying title="blank" album="blank" artist="blank" artwork={ artwork }/>);
listen<any>("now_playing_change", (event) => {
console.log(event.payload);
setNowPlaying( <NowPlaying
title={ event.payload.tags.TrackTitle }
album={ event.payload.tags.AlbumTitle }
artist={ event.payload.tags["DISPLAY ARTIST"]}
artwork={ artwork } />)
setArtwork( <img src="asset://localhost" id="nowPlayingArtwork" /> )
})
useEffect(() => { useEffect(() => {
getConfig(); getConfig();
invoke('set_volume', { volume: 0.04 }).then( () => {} ) invoke('set_volume', { volume: "1" }).then( () => {} )
}, []) }, [])
return ( return (
<main className="container"> <main className="container">
<div className="leftSide"> <div className="leftSide">
<PlaylistHead /> <PlaylistHead />
<MainView /> <MainView lib_ref={ library } />
<PlayBar /> <PlayBar />
</div> </div>
<div className="rightSide"> <div className="rightSide">
<NowPlaying /> { nowPlaying }
<Queue /> <Queue />
</div> </div>
@ -38,6 +49,9 @@ function getConfig(): any {
let config = _config as Config; let config = _config as Config;
if (config.libraries.libraries.length == 0) { if (config.libraries.libraries.length == 0) {
newWindow() newWindow()
} else {
console.log("else");
invoke('lib_already_created').then(() => {})
} }
}) })
} }
@ -60,17 +74,78 @@ function PlaylistHead() {
) )
} }
function MainView() { interface MainViewProps {
lib_ref: [JSX.Element[] | undefined, React.Dispatch<React.SetStateAction<JSX.Element[] | undefined>>],
}
function MainView({ lib_ref }: MainViewProps) {
const [library, setLibrary] = lib_ref;
// useEffect(() => {
// console.log(lib_ref);
// console.log(typeof lib_ref);
// if (typeof lib_ref.current !== "undefined") {
// setLibrary(lib_ref.current.map((song) => {
// <Song
// location={ song.location }
// uuid={ song.uuid }
// plays={ song.plays }
// duration={ song.duration }
// tags={ song.tags }
// />
// }))
// }
// }, [lib_ref])
return ( return (
<div className="mainView"> <div className="mainView">
main view main view
<button onClick={ () => invoke('get_library').then((bytes) => { <button onClick={ (e) => {
console.log(bytes); e.preventDefault();
let arr = new Uint8Array(bytes as ArrayBuffer);
let a: any = CBOR.decode(arr);
console.log(a);
}) }>get library</button> invoke('get_library').then((lib) => {
setLibrary([...(lib as any[]).map((song) => {
console.log(song);
return (
<Song
key={ song.uuid }
location={ song.location }
uuid={ song.uuid }
plays={ song.plays }
duration={ song.duration }
tags={ song.tags }
/>
)
})])
})} }>get library</button>
<div>{ library }</div>
</div>
)
}
interface SongProps {
location: any,
uuid: string,
plays: number,
format?: string,
duration: string,
last_played?: string,
date_added?: string,
date_modified?: string,
tags: any
}
function Song(props: SongProps) {
console.log(props.tags);
return(
<div className="song">
<p className="title">{ props.tags.TrackTitle }</p>
<p className="album">{ props.tags.Album }</p>
<p className="artist">{ props.tags.AlbumArtist }</p>
<p className="duration">{ props.duration }</p>
</div> </div>
) )
} }
@ -95,20 +170,31 @@ function PlayBar() {
} }
}}>{ playing }</button> }}>{ playing }</button>
<button onClick={ () => invoke('next').then(() => {}) }>next</button> <button onClick={ () => invoke('next').then(() => {}) }>next</button>
<input type="range" name="volume" id="volumeSlider" /> <input type="range" name="volume" id="volumeSlider" onChange={ (volume) => {
invoke('set_volume', { volume: volume.target.value }).then(() => {})
}} />
</div> </div>
<input type="range" name="seek" id="seekBar" /> <input type="range" name="seek" id="seekBar" />
</section> </section>
) )
} }
function NowPlaying() { interface NowPlayingProps {
title: string,
artist: string,
album: string,
artwork: JSX.Element
}
function NowPlaying({ title, artist, album, artwork }: NowPlayingProps) {
console.log(convertFileSrc("abc"));
return ( return (
<section className="nowPlaying"> <section className="nowPlaying">
<img id="nowPlayingArtwork" src="https://images.genius.com/22648cfc4f618884df6d6082962b34d2.1000x1000x1.png" /> { artwork }
<h2></h2> <h2>{ title }</h2>
<p>t+pazolite; </p> <p>{ artist }</p>
<p>Heartache Debug</p> <p>{ album }</p>
</section> </section>
) )
} }