diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md new file mode 100644 index 0000000..a6618b7 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md @@ -0,0 +1,9 @@ +## Describe your changes + +## Issue ticket number and link + +## Checklist before requesting a review +- [ ] I have performed a self-review of my code +- [ ] If it is a core feature, I have added thorough tests. +- [ ] Do we need to implement analytics? +- [ ] Will this be part of a product update? If yes, please write one phrase about this update. diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index b75eafd..db8d66a 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -88,7 +88,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: tagName: v__VERSION__ # the action automatically replaces \_\_VERSION\_\_ with the app version. - releaseName: 'v__VERSION__-Speeding things up(patch)' + releaseName: 'v__VERSION__-Audio Freedom Lab🥼' releaseBody: 'See the assets to download this version and install.' releaseDraft: true prerelease: false diff --git a/README.md b/README.md index ad04903..e7e4d65 100644 --- a/README.md +++ b/README.md @@ -42,11 +42,11 @@ A cross platform, local music player that is an offline(no streaming) version of # I am a developer ## Development Cycle 1. Download the pre-requisites for tauri only by following the pre-requisites page for your operating system. -2. If you are on linux OS(debian/ubuntu), run +2. If you are on a linux based OS, run ``` sudo apt-get install libasound2-dev ``` -3. Clone this repo, specifically the main-app-dev branch +3. Clone this repo or fork it and clone it. 4. run ``` cd muzik-offline/muzik-offline @@ -55,38 +55,37 @@ cd muzik-offline/muzik-offline ``` npm install ``` -6. This project uses discord rpc, so you will need a client id, otherwise the code won't compile +6. This application uses discord rpc, so you will need a client id, otherwise the application won't run 7. To get a client id, setup a new application on discord developer portal and then place your client id in an ```.env``` file in the src-tauri directory 8. The env file should look like this: ``` DISCORD_CLIENT_ID= ``` -9. If you are on linux/macos or any unix based OS, please navigate to tauri.config.json and under the ```windows``` object, change the ```decorations``` property to ```true``` -10. run +9. run ``` npm run tauri dev ``` -11. You can encrypt your env file with: +10. You can encrypt your env file with: ``` openssl enc -aes-256-cbc -salt -pbkdf2 -in src-tauri/.env -out src-tauri/.env.enc -pass pass:{pass-key} ``` ## Building -1. Clone the releases branch. It is the most stable and ready to go branch -2. Before you create a build, you will have to embed any env variables into the rust code otherwise the application will panic if you try to run it. The env variables are only meant to be used in the development cycle. -4. If you want to create a production build, run +1. Clone the releases branch. It is the most stable and "ready-to-go" branch +2. If you want to create a production build, run ``` npm run tauri build ``` -5. If you want to create a debug production build(one where you have access to devtools), run +3. If you want to create a debug production build(one where you have access to devtools), run ``` npm run tauri build -- --debug ``` -Please note that when you run ```npm run tauri dev```, ```npm run tauri build``` or ```npm run tauri build -- --debug``` for the first time, it may take a lengthy amount of minutes to compile everything. However this only occurs just on your first run. In subsequent runs, it will be faster. +> [!NOTE] +> Please note that when you run ```npm run tauri dev```, ```npm run tauri build``` or ```npm run tauri build -- --debug``` for the first time, it may take a lengthy amount of minutes to compile everything. However this only occurs just on your first run. In subsequent runs, it will be faster. # Node modules used -1. tauri-apps/api +1. tauri-apps 2. dexie 3. framer motion 4. react and react-dom @@ -94,6 +93,7 @@ Please note that when you run ```npm run tauri dev```, ```npm run tauri build``` 6. react viewport list 7. sass 8. zustand +9. react-loading-skeleton # Rust libraries used 1. tauri @@ -111,3 +111,14 @@ Please note that when you run ```npm run tauri dev```, ```npm run tauri build``` 13. dirs 14. discord-rich-presence 15. dotenv +16. souvlaki +17. warp +18. tauri-plugin-shell +19. tauri-plugin-notification +20. tauri-plugin-dialog +21. tauri-plugin-http +22. tauri-plugin-os +23. printpdf +24. tabled +25. walkdir +26. futures diff --git a/muzik-offline/.gitignore b/muzik-offline/.gitignore index 54f07af..95a2749 100644 --- a/muzik-offline/.gitignore +++ b/muzik-offline/.gitignore @@ -21,4 +21,7 @@ dist-ssr *.ntvs* *.njsproj *.sln -*.sw? \ No newline at end of file +*.sw? + +/src-airplay-cast/__pycache__/ +/src-airplay-cast/*.exe \ No newline at end of file diff --git a/muzik-offline/package-lock.json b/muzik-offline/package-lock.json index 483a292..e2d5cf1 100644 --- a/muzik-offline/package-lock.json +++ b/muzik-offline/package-lock.json @@ -1,12 +1,12 @@ { "name": "muzik-offline", - "version": "0.6.2", + "version": "0.7.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "muzik-offline", - "version": "0.6.2", + "version": "0.7.0", "dependencies": { "@hello-pangea/dnd": "^16.5.0", "@tauri-apps/api": "2.1.1", diff --git a/muzik-offline/package.json b/muzik-offline/package.json index 3a9ce81..4457ccd 100644 --- a/muzik-offline/package.json +++ b/muzik-offline/package.json @@ -1,7 +1,7 @@ { "name": "muzik-offline", "private": true, - "version": "0.6.2", + "version": "0.7.0", "type": "module", "scripts": { "dev": "vite", diff --git a/muzik-offline/src-tauri/Cargo.lock b/muzik-offline/src-tauri/Cargo.lock index 979e0b5..cc6733b 100644 --- a/muzik-offline/src-tauri/Cargo.lock +++ b/muzik-offline/src-tauri/Cargo.lock @@ -624,6 +624,12 @@ dependencies = [ "libloading 0.8.5", ] +[[package]] +name = "claxon" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bfbf56724aa9eca8afa4fcfadeb479e722935bb2a0900c2d37e0cc477af0688" + [[package]] name = "cocoa" version = "0.24.1" @@ -2036,6 +2042,12 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hound" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62adaabb884c94955b19907d60019f4e145d091c75345379e70d1ee696f7854f" + [[package]] name = "html5ever" version = "0.26.0" @@ -2637,6 +2649,17 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" +[[package]] +name = "lewton" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "777b48df9aaab155475a83a7df3070395ea1ac6902f5cd062b8f2b028075c030" +dependencies = [ + "byteorder", + "ogg", + "tinyvec", +] + [[package]] name = "libappindicator" version = "0.9.0" @@ -2957,7 +2980,7 @@ dependencies = [ [[package]] name = "muzik-offline" -version = "0.6.2" +version = "0.7.0" dependencies = [ "base64 0.21.7", "cocoa 0.26.0", @@ -2972,6 +2995,7 @@ dependencies = [ "printpdf", "rand 0.8.5", "rayon", + "rodio", "serde", "serde_json", "sled", @@ -2986,6 +3010,7 @@ dependencies = [ "tauri-plugin-os", "tauri-plugin-shell", "tokio", + "trash", "uuid 1.11.0", "walkdir", "warp", @@ -3420,6 +3445,15 @@ dependencies = [ "cc", ] +[[package]] +name = "ogg" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6951b4e8bf21c8193da321bcce9c9dd2e13c858fe078bf9054a288b419ae5d6e" +dependencies = [ + "byteorder", +] + [[package]] name = "ogg_pager" version = "0.6.1" @@ -4307,6 +4341,19 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "rodio" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ceb6607dd738c99bc8cb28eff249b7cd5c8ec88b9db96c0608c1480d140fb1" +dependencies = [ + "claxon", + "cpal", + "hound", + "lewton", + "symphonia", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -5828,6 +5875,23 @@ dependencies = [ "once_cell", ] +[[package]] +name = "trash" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defe1fdd4232e407b312377885a2c5396764972bddad87baf304753374a1bfc8" +dependencies = [ + "chrono", + "libc", + "log", + "objc2", + "objc2-foundation", + "once_cell", + "scopeguard", + "urlencoding", + "windows 0.56.0", +] + [[package]] name = "tray-icon" version = "0.19.2" @@ -5986,6 +6050,12 @@ dependencies = [ "serde", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "urlpattern" version = "0.3.0" diff --git a/muzik-offline/src-tauri/Cargo.toml b/muzik-offline/src-tauri/Cargo.toml index d6a81f6..5929502 100644 --- a/muzik-offline/src-tauri/Cargo.toml +++ b/muzik-offline/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "muzik-offline" -version = "0.6.2" +version = "0.7.0" description = "A desktop music player for listening to music offline." authors = ["Michael"] license = "MIT" @@ -40,6 +40,8 @@ printpdf = "0.7.0" tabled = "0.16.0" walkdir = "2.5.0" futures = "0.3.31" +rodio = "0.20.1" +trash = "5.2.0" [dependencies.uuid] version = "1.11.0" diff --git a/muzik-offline/src-tauri/src/app/setup.rs b/muzik-offline/src-tauri/src/app/setup.rs index 9df4b3a..fadb230 100644 --- a/muzik-offline/src-tauri/src/app/setup.rs +++ b/muzik-offline/src-tauri/src/app/setup.rs @@ -1,4 +1,6 @@ -use crate::components::audio_manager::BackendStateManager; +use crate::components::audio_manager::AppAudioManager; +use crate::components::kira_audio_manager::KiraManager; +use crate::components::rodio_audio_manager::RodioManager; use crate::database::db_api::{get_image_from_tree, get_null_cover_from_tree, get_thumbnail, get_wallpaper}; use crate::database::db_manager::DbManager; use kira::manager::{backend::DefaultBackend, AudioManager, AudioManagerSettings}; @@ -8,7 +10,7 @@ use warp::{http::Uri, reply::Response, Filter, Reply}; use std::sync::{Arc, Mutex}; use tauri::async_runtime::{self, spawn}; -use tauri::{AppHandle, Emitter, Manager}; +use tauri::Manager; use tokio::sync::mpsc; use crate::music::media_control_api::{config_mca, event_handler}; @@ -16,15 +18,28 @@ use crate::utils::general_utils::get_random_port; use super::setup_macos; -/// Initializes the BackendStateManager with required settings. -pub fn initialize_audio_manager() -> Arc> { +/// Initializes the kira audio manager with required settings. +pub fn initialise_kira_audio_manager() -> Arc> { let audio_manager = AudioManager::::new(AudioManagerSettings::default()) .expect("failed to initialize audio manager"); - - Arc::new(Mutex::new(BackendStateManager { + Arc::new(Mutex::new(KiraManager { manager: audio_manager, instance_handle: None, volume: 0.0, + crossfade: false, + duration: None, + })) +} + +/// Initializes the Rodio audio manager with required settings. +pub fn initialise_rodio_audio_manager() -> Arc> { + Arc::new(Mutex::new(RodioManager::new())) +} + +/// Initializes the AppAudioManager with required settings. +pub fn initialize_audio_manager() -> Arc> { + // Create a Rodio device and get the output stream handle + Arc::new(Mutex::new(AppAudioManager { controls: None, cover_url: String::new(), port: 0, @@ -38,9 +53,10 @@ pub fn setup_app(app: &mut tauri::App) -> Result<(), Box> mpsc::Receiver, ) = mpsc::channel(32); setup_macos::setup_macos(app)?; - let shared_audio_manager = Arc::clone(&app.state::>>()); + let shared_audio_manager = Arc::clone(&app.state::>>()); let shared_db_manager = Arc::clone(&app.state::>>()); - let window = app.handle().clone(); + + // setup audio manager // Set up the image route let cover_image_route = create_image_route_for_covers(shared_db_manager.clone()); @@ -83,14 +99,13 @@ pub fn setup_app(app: &mut tauri::App) -> Result<(), Box> .controls = Some(controls); // Handle media control events + let window = app.handle().clone(); spawn(async move { while let Some(event) = rx.recv().await { event_handler(&window, &event); } }); - // Collect args from the command line - collect_args(app.handle()); Ok(()) } @@ -229,14 +244,4 @@ pub fn setup_media_controls( controls, &format!("http://localhost:{}/covers/NULL_COVER_NULL", port).to_owned(), ); -} - -/// Collect args from the command line and return them as a vector of strings. -pub fn collect_args(app: &AppHandle) { - let args: Vec = std::env::args().collect(); - if args.len() > 1 { - let audio_file_path = &args[1]; - app.emit("loadSong", audio_file_path) - .expect("failed to emit loadSong"); - } } \ No newline at end of file diff --git a/muzik-offline/src-tauri/src/commands/general_commands.rs b/muzik-offline/src-tauri/src/commands/general_commands.rs index a7e80a2..a2c79eb 100644 --- a/muzik-offline/src-tauri/src/commands/general_commands.rs +++ b/muzik-offline/src-tauri/src/commands/general_commands.rs @@ -1,14 +1,15 @@ use crate::{ - components::audio_manager::BackendStateManager, - utils::general_utils::{ + components::audio_manager::AppAudioManager, database::db_manager::DbManager, utils::general_utils::{ decode_image_in_parallel, encode_image_in_parallel, resize_and_compress_image, - }, + } }; +use crate::database::db_api::{delete_song_from_tree, delete_album_from_tree, delete_artist_from_tree, delete_genre_from_tree}; use dirs::audio_dir; use std::{ process::Command, sync::{Arc, Mutex}, }; +use trash; use tauri::State; // Learn more about Tauri commands at https://tauri.app/v1/guides/features/command @@ -18,6 +19,21 @@ use tauri::State; // //this serves as an example template whenever new commands are to be created // //so don't delete this //} +#[tauri::command] +pub fn collect_env_args() -> String { + let args: Vec = std::env::args().collect(); + + // get first arg that ends with .ogg, .mp3, .flac, .wav + let mut audio_path = String::new(); + for arg in args { + if arg.ends_with(".ogg") || arg.ends_with(".mp3") || arg.ends_with(".flac") || arg.ends_with(".wav") { + audio_path = arg; + break; + } + } + + audio_path +} #[tauri::command] pub fn open_in_file_manager(file_path: &str) { @@ -25,7 +41,7 @@ pub fn open_in_file_manager(file_path: &str) { } #[tauri::command] -pub fn get_server_port(audio_manager: State<'_, Arc>>) -> u16 { +pub fn get_server_port(audio_manager: State<'_, Arc>>) -> u16 { match audio_manager.lock() { Ok(audio_manager) => { return audio_manager.port; @@ -71,6 +87,46 @@ pub fn get_audio_dir() -> String { } } +#[tauri::command] +pub async fn delete_song_metadata( + db_manager: State<'_, Arc>>, + path: String, + album: String, + album_appearance_count: i32, + artist: String, + artist_appearance_count: i32, + genre: String, + genre_appearance_count: i32, +) -> Result { + // attempt to move file path to trash + match trash::delete(&path) { + Ok(_) => {} + Err(e) => { + return Err(format!("Failed to move file to trash because {}", e)); + } + } + + // delete song from database + delete_song_from_tree(db_manager.clone(), &path); + + // delete album from database if it has no more songs + if album_appearance_count <= 1 { + delete_album_from_tree(db_manager.clone(), &album); + } + + // delete artist from database if it has no more songs + if artist_appearance_count <= 1 { + delete_artist_from_tree(db_manager.clone(), &artist); + } + + // delete genre from database if it has no more songs + if genre_appearance_count <= 1 { + delete_genre_from_tree(db_manager.clone(), &genre); + } + + Ok(String::from("SUCCESS")) +} + #[cfg(target_os = "macos")] fn open_file_at(file_path: &str) { match Command::new("open") diff --git a/muzik-offline/src-tauri/src/components/audio_manager.rs b/muzik-offline/src-tauri/src/components/audio_manager.rs index 19804f4..8a7597f 100644 --- a/muzik-offline/src-tauri/src/components/audio_manager.rs +++ b/muzik-offline/src-tauri/src/components/audio_manager.rs @@ -1,14 +1,7 @@ -use kira::{ - manager::{backend::DefaultBackend, AudioManager}, - sound::{streaming::StreamingSoundHandle, FromFileError}, -}; use souvlaki::MediaControls; -pub struct BackendStateManager { - pub manager: AudioManager, - pub instance_handle: Option>, +pub struct AppAudioManager { pub controls: Option, - pub volume: f64, pub cover_url: String, pub port: u16, } diff --git a/muzik-offline/src-tauri/src/components/kira_audio_manager.rs b/muzik-offline/src-tauri/src/components/kira_audio_manager.rs new file mode 100644 index 0000000..f927658 --- /dev/null +++ b/muzik-offline/src-tauri/src/components/kira_audio_manager.rs @@ -0,0 +1,12 @@ +use kira::{ + manager::{backend::DefaultBackend, AudioManager}, + sound::{streaming::StreamingSoundHandle, FromFileError}, +}; + +pub struct KiraManager { + pub manager: AudioManager, + pub instance_handle: Option>, + pub volume: f64, + pub crossfade: bool, + pub duration: Option, +} diff --git a/muzik-offline/src-tauri/src/components/mod.rs b/muzik-offline/src-tauri/src/components/mod.rs index aa02ae4..2716226 100644 --- a/muzik-offline/src-tauri/src/components/mod.rs +++ b/muzik-offline/src-tauri/src/components/mod.rs @@ -4,3 +4,5 @@ pub mod audio_manager; pub mod event_payload; pub mod genre; pub mod song; +pub mod kira_audio_manager; +pub mod rodio_audio_manager; diff --git a/muzik-offline/src-tauri/src/components/rodio_audio_manager.rs b/muzik-offline/src-tauri/src/components/rodio_audio_manager.rs new file mode 100644 index 0000000..a1fc9bb --- /dev/null +++ b/muzik-offline/src-tauri/src/components/rodio_audio_manager.rs @@ -0,0 +1,101 @@ +use rodio::{OutputStream, Device, Sink}; +use std::sync::{Arc, Mutex}; +use std::sync::mpsc::{channel, Sender}; +use std::time::Duration; + +enum AudioCommand { + SetDevice(Device, Duration) +} + +pub struct RodioManager { + sender: Sender, + pub sink: Arc>>, + pub crossfade: bool, + pub duration: Option, +} + +impl RodioManager { + pub fn new() -> Self { + let (sender, receiver) = channel(); + let sink: Arc>> = Arc::new(Mutex::new(None)); + let cloned_sink = sink.clone(); + + std::thread::spawn(move || { + // Initialize the default output stream + let (mut _current_stream, mut _current_handle) = OutputStream::try_default().expect("No default output stream available"); + cloned_sink.lock().expect("unable to lock").replace(Sink::try_new(&_current_handle).expect("Failed to create sink")); + + for command in receiver { + match command { + AudioCommand::SetDevice(device, pos) => { + if let Ok((new_stream, new_handle)) = OutputStream::try_from_device(&device) { + let was_paused = match cloned_sink.lock(){ + Ok(mut sink) => { + match sink.as_mut(){ + Some(sink) => { + sink.is_paused() + } + None => { + false + } + } + } + Err(_) => { + false + } + }; + + _current_stream = new_stream; + _current_handle = new_handle; + match cloned_sink.lock(){ + Ok(mut sink) => { + match sink.as_mut(){ + Some(sink) => { + match sink.try_seek(pos){ + Ok(_) => { + if !was_paused { + sink.play(); + } + } + Err(_) => { + eprintln!("Failed to seek sink"); + } + } + } + None => { + eprintln!("Failed to lock sink"); + } + } + } + Err(_) => { + eprintln!("Failed to lock sink"); + } + } + println!("Switched audio device successfully!"); + } else { + eprintln!("Failed to switch audio device"); + } + } + } + } + }); + + Self { + sender, + sink: sink.clone(), + crossfade: false, + duration: None, + } + } + + pub fn set_device(&self, device: Device, pos: Duration) { + match self.sender.send(AudioCommand::SetDevice(device, pos)){ + Ok(_) => { + + } + Err(_) => { + + } + } + } +} \ No newline at end of file diff --git a/muzik-offline/src-tauri/src/database/db_api.rs b/muzik-offline/src-tauri/src/database/db_api.rs index d39b62c..606eb52 100644 --- a/muzik-offline/src-tauri/src/database/db_api.rs +++ b/muzik-offline/src-tauri/src/database/db_api.rs @@ -1066,6 +1066,27 @@ pub fn insert_into_album_tree(db_manager: Arc>, song: &Song) { } } +pub fn delete_album_from_tree(db_manager: State<'_, Arc>>, album_name: &str) { + match db_manager.lock() { + Ok(dbm) => { + let album_tree = match dbm.album_tree.write() { + Ok(tree) => tree, + Err(_) => { + return; + } + }; + + match album_tree.remove( + uuid::Uuid::new_v5(&uuid::Uuid::NAMESPACE_URL, album_name.as_bytes()).to_string(), + ) { + Ok(_) => {} + Err(_) => {} + } + } + Err(_) => {} + } +} + pub fn insert_into_artist_tree(db_manager: Arc>, song: &Song) { match db_manager.lock() { Ok(dbm) => { @@ -1148,6 +1169,27 @@ pub fn insert_into_artist_tree(db_manager: Arc>, song: &Song) { } } +pub fn delete_artist_from_tree(db_manager: State<'_, Arc>>, artist_name: &str) { + match db_manager.lock() { + Ok(dbm) => { + let artist_tree = match dbm.artist_tree.write() { + Ok(tree) => tree, + Err(_) => { + return; + } + }; + + match artist_tree.remove( + uuid::Uuid::new_v5(&uuid::Uuid::NAMESPACE_URL, artist_name.as_bytes()).to_string(), + ) { + Ok(_) => {} + Err(_) => {} + } + } + Err(_) => {} + } +} + pub fn insert_into_genre_tree(db_manager: Arc>, song: &Song) { match db_manager.lock() { Ok(dbm) => { @@ -1227,6 +1269,27 @@ pub fn insert_into_genre_tree(db_manager: Arc>, song: &Song) { } } +pub fn delete_genre_from_tree(db_manager: State<'_, Arc>>, genre_name: &str) { + match db_manager.lock() { + Ok(dbm) => { + let genre_tree = match dbm.genre_tree.write() { + Ok(tree) => tree, + Err(_) => { + return; + } + }; + + match genre_tree.remove( + uuid::Uuid::new_v5(&uuid::Uuid::NAMESPACE_URL, genre_name.as_bytes()).to_string(), + ) { + Ok(_) => {} + Err(_) => {} + } + } + Err(_) => {} + } +} + pub fn insert_into_covers_tree( db_manager: Arc>, cover: Vec, diff --git a/muzik-offline/src-tauri/src/main.rs b/muzik-offline/src-tauri/src/main.rs index 813fb5e..4ac877b 100644 --- a/muzik-offline/src-tauri/src/main.rs +++ b/muzik-offline/src-tauri/src/main.rs @@ -13,7 +13,7 @@ mod windows; mod export; mod import; -use commands::general_commands::get_server_port; +use commands::general_commands::{collect_env_args, get_server_port}; use commands::refresh_paths_at_start::{detect_deleted_songs, refresh_paths}; use database::db_api::{ add_new_wallpaper_to_db, create_playlist_cover, delete_playlist_cover, @@ -23,6 +23,7 @@ use database::db_api::{ use database::db_manager::DbManager; use export::{export_csv::export_songs_as_csv, export_html::export_songs_as_html, export_json::export_songs_as_json,export_txt::export_songs_as_txt, export_xml::export_songs_as_xml}; +use music::player::set_playback_speed; //use export::export_pdf::export_songs_as_pdf; use socials::discord_rpc::{set_discord_rpc_activity_with_timestamps, DiscordRpc}; use utils::music_list_organizer::MLO; @@ -33,7 +34,7 @@ use crate::windows::controller::{drag_app_window, toggle_app_pin, toggle_minipla use crate::commands::{metadata_edit::edit_song_metadata, metadata_retriever::get_all_songs}; use crate::commands::general_commands::{ - get_audio_dir, open_in_file_manager, resize_frontend_image_to_fixed_height, + get_audio_dir, open_in_file_manager, resize_frontend_image_to_fixed_height, delete_song_metadata, }; use crate::database::db_api::{ get_all_albums, get_all_artists, get_all_genres, get_all_songs_in_db, @@ -43,6 +44,7 @@ use crate::music::player::{ get_song_position, load_a_song_from_path, load_and_play_song_from_path, pause_song, resume_playing, seek_by, seek_to, set_volume, stop_song, }; +use crate::music::rodio_player::{get_output_devices, set_output_device, get_default_output_device}; use crate::socials::discord_rpc::{ allow_connection_and_connect_to_discord_rpc, attempt_to_connect_if_possible, clear_discord_rpc_activity, disallow_connection_and_close_discord_rpc, @@ -55,6 +57,8 @@ use crate::utils::music_list_organizer::{ use app::setup::{ setup_app, initialize_audio_manager, + initialise_kira_audio_manager, + initialise_rodio_audio_manager }; fn main() { @@ -73,15 +77,18 @@ fn main() { DiscordRpc::new().expect("failed to initialize discord rpc"), )) .manage(initialize_audio_manager()) + .manage(initialise_kira_audio_manager()) + .manage(initialise_rodio_audio_manager()) .setup(setup_app) .invoke_handler(tauri::generate_handler![ // WINDOW CONTROL toggle_app_pin, toggle_miniplayer_view, drag_app_window, - update_metadata, set_player_state, // GENERAL COMMANDS + update_metadata, + delete_song_metadata, get_all_songs, open_in_file_manager, set_volume, @@ -90,6 +97,7 @@ fn main() { get_server_port, refresh_paths, detect_deleted_songs, + collect_env_args, // MUSIC PLAYER load_and_play_song_from_path, load_a_song_from_path, @@ -99,6 +107,10 @@ fn main() { seek_to, seek_by, get_song_position, + get_default_output_device, + get_output_devices, + set_output_device, + set_playback_speed, // UTILS resize_frontend_image_to_fixed_height, // MUSIC LIST ORGANIZER diff --git a/muzik-offline/src-tauri/src/music/kira_player.rs b/muzik-offline/src-tauri/src/music/kira_player.rs new file mode 100644 index 0000000..ee0c767 --- /dev/null +++ b/muzik-offline/src-tauri/src/music/kira_player.rs @@ -0,0 +1,487 @@ +use crate::{components::kira_audio_manager::KiraManager, utils::general_utils::calculate_volume}; +use kira::{ + sound::{ + streaming::{StreamingSoundData, StreamingSoundHandle, StreamingSoundSettings}, + FromFileError, + }, + tween::Tween, +}; +use std::{sync::{Arc, Mutex}, time::Duration}; +use tauri::State; + +pub fn load_and_play_song_from_path_kira( + audio_manager: State<'_, Arc>>, + sound_path: &str, + volume: f64, + duration: f64, + play_back_speed: f32, + fade_in_out: bool, +) { + match audio_manager.lock() { + Ok(mut manager) => { + //stop and clear all sounds that are playing + match &mut manager.instance_handle { + Some(handle) => { + match handle.stop(Tween::default()) { + Ok(_) => { + //stopped song + } + Err(_) => { + return; + } + } + } + None => { + //no song is currently playing + } + } + + if fade_in_out { + manager.crossfade = true; + } else { + manager.crossfade = false; + } + + manager.duration = Some(Duration::from_secs_f64(duration)); + + //try and load and play a new song + match StreamingSoundData::from_file(sound_path, StreamingSoundSettings::default()) { + Ok(sound_data) => { + if fade_in_out { + sound_data.settings.fade_in_tween(Tween{ + duration: Duration::from_secs(6), + start_time: kira::StartTime::Immediate, + easing: kira::tween::Easing::Linear, + }); + } + match manager.manager.play(sound_data) { + Ok(instance_handle) => { + //playback started + manager.instance_handle = Some(instance_handle); + + //set playback speed + match &mut manager.instance_handle { + Some(handle) => { + match handle.set_playback_rate(play_back_speed as f64, Tween::default()) { + Ok(_) => { + //set speed + } + Err(_) => { + //failed to set speed + } + } + } + None => { + //no song is currently playing + } + } + + if !fade_in_out { + //set volume + match &mut manager.instance_handle { + Some(handle) => { + match handle.set_volume(volume, Tween::default()) { + Ok(_) => { + //set volume + manager.volume = volume; + } + Err(_) => { + //failed to set volume + } + } + } + None => { + //no song is currently playing + } + } + } + } + Err(_) => { + //playback failed + } + } + } + Err(_) => { + //failed to load sound + } + } + } + Err(_) => { + //failed to lock audio manager + } + } +} + +pub fn load_a_song_from_path_kira( + audio_manager: State<'_, Arc>>, + sound_path: &str, + volume: f64, + duration: f64, + play_back_speed: f32, + fade_in_out: bool, +) { + match audio_manager.lock() { + Ok(mut manager) => { + //stop and clear all sounds that are playing + match &mut manager.instance_handle { + Some(handle) => { + match handle.stop(Tween::default()) { + Ok(_) => { + //stopped song + } + Err(_) => { + return; + } + } + } + None => { + //no song is currently playing + } + } + + if fade_in_out { + manager.crossfade = true; + } else { + manager.crossfade = false; + } + manager.duration = Some(Duration::from_secs_f64(duration)); + + //try and load and play then immediately pause a new song + match StreamingSoundData::from_file(sound_path, StreamingSoundSettings::default()) { + Ok(sound_data) => { + if fade_in_out { + sound_data.settings.fade_in_tween(Tween{ + duration: Duration::from_secs(6), + ..Default::default() + }); + } + match manager.manager.play(sound_data) { + Ok(instance_handle) => { + //playback started + manager.instance_handle = Some(instance_handle); + //pause the song + match &mut manager.instance_handle { + Some(handle) => { + match handle.pause(Tween::default()) { + Ok(_) => { + //paused song + } + Err(_) => { + //failed to pause song + } + } + } + None => { + //no song is currently playing + } + } + + //set playback speed + match &mut manager.instance_handle { + Some(handle) => { + match handle.set_playback_rate(play_back_speed as f64, Tween::default()) { + Ok(_) => { + //set speed + } + Err(_) => { + //failed to set speed + } + } + } + None => { + //no song is currently playing + } + } + + //set volume + match &mut manager.instance_handle { + Some(handle) => { + match handle.set_volume(volume, Tween::default()) { + Ok(_) => { + //set volume + manager.volume = volume; + } + Err(_) => { + //failed to set volume + } + } + } + None => { + //no song is currently playing + } + } + } + Err(_) => { + //playback failed + } + } + } + Err(_) => { + //failed to load sound + } + } + } + Err(_) => { + //failed to lock audio manager + } + } +} + +pub fn pause_song_kira(audio_manager: State<'_, Arc>>) { + match audio_manager.lock() { + Ok(mut manager) => { + match &mut manager.instance_handle { + Some(handle) => { + match handle.pause(Tween::default()) { + Ok(_) => { + //paused song + } + Err(_) => { + //failed to pause song + } + } + } + None => { + //no song is currently playing + } + } + } + Err(_) => { + //failed to lock audio manager + } + } +} + +pub fn stop_song_kira(audio_manager: State<'_, Arc>>) { + match audio_manager.lock() { + Ok(mut manager) => { + match &mut manager.instance_handle { + Some(handle) => { + match handle.stop(Tween::default()) { + Ok(_) => { + //stopped song + } + Err(_) => { + //failed to stop song + } + } + } + None => { + //no song is currently playing + } + } + } + Err(_) => { + //failed to lock audio manager + } + } +} + +pub fn resume_playing_kira(audio_manager: State<'_, Arc>>) { + match audio_manager.lock() { + Ok(mut manager) => { + match &mut manager.instance_handle { + Some(handle) => { + match handle.resume(Tween::default()) { + Ok(_) => { + //resumed song + } + Err(_) => { + //failed to resume song + } + } + } + None => { + //no song is currently paused or playing + } + } + } + Err(_) => { + //failed to lock audio manager + } + } +} + +pub fn seek_to_kira(audio_manager: State<'_, Arc>>, position: f64) { + match audio_manager.lock() { + Ok(mut manager) => { + //get volume + let volume = manager.volume.clone(); + match &mut manager.instance_handle { + Some(handle) => { + match handle.seek_to(position) { + Ok(_) => { + handle_true_seeking(handle, volume); + } + Err(_) => { + //failed to seek to position + } + } + } + None => { + //no song is currently paused or playing + } + } + } + Err(_) => { + //failed to lock audio manager + } + } +} + +pub fn seek_by_kira(audio_manager: State<'_, Arc>>, delta: f64) { + match audio_manager.lock() { + Ok(mut manager) => { + match &mut manager.instance_handle { + Some(handle) => { + match handle.seek_by(delta) { + Ok(_) => {} + Err(_) => { + //failed to seek by delta + } + } + } + None => { + //no song is currently paused or playing + } + } + } + Err(_) => { + //failed to lock audio manager + } + } +} + +pub fn get_song_position_kira(audio_manager: State<'_, Arc>>) -> f64 { + let (pos, duration, cross_fade) = match audio_manager.lock() { + Ok(mut manager) => { + match &mut manager.instance_handle { + Some(handle) => { + let song_position = handle.position(); + (std::time::Duration::from_secs_f64(song_position), manager.duration.unwrap_or(std::time::Duration::from_secs(0)), manager.crossfade) + } + None => { + //no song is currently paused or playing + (std::time::Duration::from_secs(0), std::time::Duration::from_secs(0), false) + } + } + } + Err(_) => { + //failed to lock audio manager + (std::time::Duration::from_secs(0), std::time::Duration::from_secs(0), false) + } + }; + + if cross_fade && pos > duration.saturating_sub(Duration::from_secs(6)) { + set_volume_kira(audio_manager, calculate_volume(duration.saturating_sub(pos))); + } + return pos.as_secs_f64().floor(); +} + +pub fn set_volume_kira(audio_manager: State<'_, Arc>>, volume: f64) { + match audio_manager.lock() { + Ok(mut manager) => { + match &mut manager.instance_handle { + Some(handle) => { + match handle.set_volume(volume, Tween::default()) { + Ok(_) => { + //set volume + manager.volume = volume; + } + Err(_) => { + //failed to set volume + } + } + } + None => { + //no song is currently paused or playing + } + } + } + Err(_) => { + //failed to lock audio manager + } + } +} + +fn handle_true_seeking(handle: &mut StreamingSoundHandle, volume: f64) { + //seeked to position + //there seems to be a weird issue(maybe it's intended to be that way in kira) where the song will seek correctly + //but the position will not update until the song is resumed + //Resuming and pausing the song immediately after seems to be unable to fix this + //but the seeker only updates if the song is resumed and allowed to play without being immediately paused + //so we resume the song and then pause it after a short delay + //this is a hacky solution but it works + + //the source of the problem is that when a song is playing, it loads the next chunk of audio data into memory + //and so when it is paused, that chunk of audio data is still in memory + //so even if you seek to a new spot, the audio data that was loaded into memory is still there + //so it will have to be played either way before the new seeked to position can be played + //the only way around this would be to figure out how to clear the audio data from memory + //or how long the audio data that we want to skip past is and just allow the song to play for that long + //before pausing it again whilst the volume is 0.0. Hopefully the user won't notice this because + //that delay may get as large as 300ms or so depending on the size of the audio data that was loaded into memory + + match handle.set_volume(0.0, Tween::default()) { + Ok(_) => { + //set volume + } + Err(_) => { + //failed to set volume + } + } + + match handle.resume(Tween::default()) { + Ok(_) => { + //resumed song + //pause the song after a short delay + std::thread::sleep(std::time::Duration::from_millis(270)); + match handle.pause(Tween::default()) { + Ok(_) => { + //paused song + } + Err(_) => { + //failed to pause song + } + } + } + Err(_) => { + //failed to resume song + } + } + + //reset volume back to previous value + match handle.set_volume(volume, Tween::default()) { + Ok(_) => { + //set volume + } + Err(_) => { + //failed to set volume + } + } +} + +pub fn set_playback_speed_kira(audio_manager: State<'_, Arc>>, speed: f64) { + match audio_manager.lock() { + Ok(mut manager) => { + match &mut manager.instance_handle { + Some(handle) => { + match handle.set_playback_rate(speed, Tween::default()) { + Ok(_) => { + //set speed + } + Err(_) => { + //failed to set speed + } + } + } + None => { + //no song is currently paused or playing + } + } + } + Err(_) => { + //failed to lock audio manager + } + } +} \ No newline at end of file diff --git a/muzik-offline/src-tauri/src/music/media_control_api.rs b/muzik-offline/src-tauri/src/music/media_control_api.rs index 992ce76..a344c73 100644 --- a/muzik-offline/src-tauri/src/music/media_control_api.rs +++ b/muzik-offline/src-tauri/src/music/media_control_api.rs @@ -4,7 +4,7 @@ use std::{ }; use crate::{ - components::{audio_manager::BackendStateManager, event_payload::Payload}, + components::{audio_manager::AppAudioManager, event_payload::Payload}, database::{db_api::get_song_from_tree, db_manager::DbManager} }; @@ -212,7 +212,7 @@ pub fn event_handler(app: &AppHandle, event: &MediaControlEvent) { #[tauri::command] pub fn update_metadata( - audio_manager: State<'_, Arc>>, + audio_manager: State<'_, Arc>>, db_manager: State<'_, Arc>>, uuid: String, ) { @@ -245,7 +245,7 @@ pub fn update_metadata( } #[tauri::command] -pub fn set_player_state(audio_manager: State<'_, Arc>>, state: &str) { +pub fn set_player_state(audio_manager: State<'_, Arc>>, state: &str) { //,position: f64 match audio_manager.lock() { Ok(mut manager) => { diff --git a/muzik-offline/src-tauri/src/music/mod.rs b/muzik-offline/src-tauri/src/music/mod.rs index ba1a636..5d67d75 100644 --- a/muzik-offline/src-tauri/src/music/mod.rs +++ b/muzik-offline/src-tauri/src/music/mod.rs @@ -1,2 +1,4 @@ pub mod media_control_api; +pub mod kira_player; pub mod player; +pub mod rodio_player; diff --git a/muzik-offline/src-tauri/src/music/player.rs b/muzik-offline/src-tauri/src/music/player.rs index 1ef097a..9b74223 100644 --- a/muzik-offline/src-tauri/src/music/player.rs +++ b/muzik-offline/src-tauri/src/music/player.rs @@ -1,394 +1,211 @@ -use crate::components::audio_manager::BackendStateManager; -use kira::{ - sound::{ - streaming::{StreamingSoundData, StreamingSoundHandle, StreamingSoundSettings}, - FromFileError, - }, - tween::Tween, -}; +use crate::components::{rodio_audio_manager::RodioManager, kira_audio_manager::KiraManager}; use std::sync::{Arc, Mutex}; use tauri::State; +use super::{ + kira_player::{ + get_song_position_kira, load_a_song_from_path_kira, load_and_play_song_from_path_kira, + pause_song_kira, resume_playing_kira, seek_by_kira, seek_to_kira, set_playback_speed_kira, + set_volume_kira, stop_song_kira + }, + rodio_player::{ + get_song_position_rodio, load_a_song_from_path_rodio, load_and_play_song_from_path_rodio, + pause_song_rodio, resume_playing_rodio, seek_by_rodio, seek_to_rodio, set_volume_rodio, + stop_song_rodio, set_playback_speed_rodio + }}; + #[tauri::command] pub fn load_and_play_song_from_path( - audio_manager: State<'_, Arc>>, + rodio_audio_manager: State<'_, Arc>>, + kira_audio_manager: State<'_, Arc>>, sound_path: &str, + player: &str, volume: f64, + duration: f64, + play_back_speed: f32, + fade_in_out: bool, ) { - match audio_manager.lock() { - Ok(mut manager) => { - //stop and clear all sounds that are playing - match &mut manager.instance_handle { - Some(handle) => { - match handle.stop(Tween::default()) { - Ok(_) => { - //stopped song - } - Err(_) => { - return; - } - } - } - None => { - //no song is currently playing - } - } - - //try and load and play a new song - match StreamingSoundData::from_file(sound_path, StreamingSoundSettings::default()) { - Ok(sound_data) => { - match manager.manager.play(sound_data) { - Ok(instance_handle) => { - //playback started - manager.instance_handle = Some(instance_handle); - //set volume - match &mut manager.instance_handle { - Some(handle) => { - match handle.set_volume(volume, Tween::default()) { - Ok(_) => { - //set volume - manager.volume = volume; - } - Err(_) => { - //failed to set volume - } - } - } - None => { - //no song is currently playing - } - } - } - Err(_) => { - //playback failed - } - } - } - Err(_) => { - //failed to load sound - } - } + match player{ + "rodio" => { + load_and_play_song_from_path_rodio(rodio_audio_manager, sound_path, volume, duration, play_back_speed, fade_in_out); } - Err(_) => { - //failed to lock audio manager + "kira" => { + load_and_play_song_from_path_kira(kira_audio_manager, sound_path, volume, duration, play_back_speed, fade_in_out); + } + _ => { + // Handle the case where the player is not recognized } } } #[tauri::command] pub fn load_a_song_from_path( - audio_manager: State<'_, Arc>>, + rodio_audio_manager: State<'_, Arc>>, + kira_audio_manager: State<'_, Arc>>, sound_path: &str, + player: &str, volume: f64, + duration: f64, + play_back_speed: f32, + fade_in_out: bool, ) { - match audio_manager.lock() { - Ok(mut manager) => { - //stop and clear all sounds that are playing - match &mut manager.instance_handle { - Some(handle) => { - match handle.stop(Tween::default()) { - Ok(_) => { - //stopped song - } - Err(_) => { - return; - } - } - } - None => { - //no song is currently playing - } - } - - //try and load and play then immediately pause a new song - match StreamingSoundData::from_file(sound_path, StreamingSoundSettings::default()) { - Ok(sound_data) => { - match manager.manager.play(sound_data) { - Ok(instance_handle) => { - //playback started - manager.instance_handle = Some(instance_handle); - //pause the song - match &mut manager.instance_handle { - Some(handle) => { - match handle.pause(Tween::default()) { - Ok(_) => { - //paused song - } - Err(_) => { - //failed to pause song - } - } - } - None => { - //no song is currently playing - } - } - //set volume - match &mut manager.instance_handle { - Some(handle) => { - match handle.set_volume(volume, Tween::default()) { - Ok(_) => { - //set volume - manager.volume = volume; - } - Err(_) => { - //failed to set volume - } - } - } - None => { - //no song is currently playing - } - } - } - Err(_) => { - //playback failed - } - } - } - Err(_) => { - //failed to load sound - } - } + match player{ + "rodio" => { + load_a_song_from_path_rodio(rodio_audio_manager, sound_path, volume, duration, play_back_speed, fade_in_out); } - Err(_) => { - //failed to lock audio manager + "kira" => { + load_a_song_from_path_kira(kira_audio_manager, sound_path, volume, duration, play_back_speed, fade_in_out); + } + _ => { + // Handle the case where the player is not recognized } } } #[tauri::command] -pub fn pause_song(audio_manager: State<'_, Arc>>) { - match audio_manager.lock() { - Ok(mut manager) => { - match &mut manager.instance_handle { - Some(handle) => { - match handle.pause(Tween::default()) { - Ok(_) => { - //paused song - } - Err(_) => { - //failed to pause song - } - } - } - None => { - //no song is currently playing - } - } +pub fn pause_song(rodio_audio_manager: State<'_, Arc>>, kira_audio_manager: State<'_, Arc>>, player: &str) { + match player{ + "rodio" => { + pause_song_rodio(rodio_audio_manager); + } + "kira" => { + pause_song_kira(kira_audio_manager); } - Err(_) => { - //failed to lock audio manager + _ => { + // Handle the case where the player is not recognized } } + } #[tauri::command] -pub fn stop_song(audio_manager: State<'_, Arc>>) { - match audio_manager.lock() { - Ok(mut manager) => { - match &mut manager.instance_handle { - Some(handle) => { - match handle.stop(Tween::default()) { - Ok(_) => { - //stopped song - } - Err(_) => { - //failed to stop song - } - } - } - None => { - //no song is currently playing - } - } +pub fn stop_song(rodio_audio_manager: State<'_, Arc>>, kira_audio_manager: State<'_, Arc>>, player: &str) { + match player{ + "rodio" => { + stop_song_rodio(rodio_audio_manager); + } + "kira" => { + stop_song_kira(kira_audio_manager); } - Err(_) => { - //failed to lock audio manager + _ => { + // Handle the case where the player is not recognized } } + } #[tauri::command] -pub fn resume_playing(audio_manager: State<'_, Arc>>) { - match audio_manager.lock() { - Ok(mut manager) => { - match &mut manager.instance_handle { - Some(handle) => { - match handle.resume(Tween::default()) { - Ok(_) => { - //resumed song - } - Err(_) => { - //failed to resume song - } - } - } - None => { - //no song is currently paused or playing - } - } +pub fn resume_playing(rodio_audio_manager: State<'_, Arc>>, kira_audio_manager: State<'_, Arc>>, player: &str) { + match player{ + "rodio" => { + resume_playing_rodio(rodio_audio_manager); } - Err(_) => { - //failed to lock audio manager + "kira" => { + resume_playing_kira(kira_audio_manager); + } + _ => { + // Handle the case where the player is not recognized } } + } #[tauri::command] -pub fn seek_to(audio_manager: State<'_, Arc>>, position: f64) { - match audio_manager.lock() { - Ok(mut manager) => { - //get volume - let volume = manager.volume.clone(); - match &mut manager.instance_handle { - Some(handle) => { - match handle.seek_to(position) { - Ok(_) => { - handle_true_seeking(handle, volume); - } - Err(_) => { - //failed to seek to position - } - } - } - None => { - //no song is currently paused or playing - } - } +pub fn seek_to( + rodio_audio_manager: State<'_, Arc>>, + kira_audio_manager: State<'_, Arc>>, + player: &str, + position: f64 +) { + match player{ + "rodio" => { + seek_to_rodio(rodio_audio_manager, position); } - Err(_) => { - //failed to lock audio manager + "kira" => { + seek_to_kira(kira_audio_manager, position); + } + _ => { + // Handle the case where the player is not recognized } } } #[tauri::command] -pub fn seek_by(audio_manager: State<'_, Arc>>, delta: f64) { - match audio_manager.lock() { - Ok(mut manager) => { - match &mut manager.instance_handle { - Some(handle) => { - match handle.seek_by(delta) { - Ok(_) => {} - Err(_) => { - //failed to seek by delta - } - } - } - None => { - //no song is currently paused or playing - } - } +pub fn seek_by( + rodio_audio_manager: State<'_, Arc>>, + kira_audio_manager: State<'_, Arc>>, + player: &str, + delta: f64 +) { + match player{ + "rodio" => { + seek_by_rodio(rodio_audio_manager, delta); } - Err(_) => { - //failed to lock audio manager + "kira" => { + seek_by_kira(kira_audio_manager, delta); + } + _ => { + // Handle the case where the player is not recognized } } } #[tauri::command] -pub fn get_song_position(audio_manager: State<'_, Arc>>) -> f64 { - match audio_manager.lock() { - Ok(mut manager) => { - match &mut manager.instance_handle { - Some(handle) => { - let song_position = handle.position(); - song_position.floor() - } - None => { - //no song is currently paused or playing - 0.0 - } - } - } - Err(_) => { - //failed to lock audio manager +pub fn get_song_position( + rodio_audio_manager: State<'_, Arc>>, + kira_audio_manager: State<'_, Arc>>, + player: &str +) -> f64 { + match player{ + "rodio" => { + get_song_position_rodio(rodio_audio_manager) + } + "kira" => { + get_song_position_kira(kira_audio_manager) + } + _ => { + // Handle the case where the player is not recognized 0.0 } } } #[tauri::command] -pub fn set_volume(audio_manager: State<'_, Arc>>, volume: f64) { - match audio_manager.lock() { - Ok(mut manager) => { - match &mut manager.instance_handle { - Some(handle) => { - match handle.set_volume(volume, Tween::default()) { - Ok(_) => { - //set volume - manager.volume = volume; - } - Err(_) => { - //failed to set volume - } - } - } - None => { - //no song is currently paused or playing - } - } - } - Err(_) => { - //failed to lock audio manager +pub fn set_volume( + rodio_audio_manager: State<'_, Arc>>, + kira_audio_manager: State<'_, Arc>>, + player: &str, + volume: f64 +) { + match player{ + "rodio" => { + set_volume_rodio(rodio_audio_manager, volume); } - } -} - -fn handle_true_seeking(handle: &mut StreamingSoundHandle, volume: f64) { - //seeked to position - //there seems to be a weird issue(maybe it's intended to be that way in kira) where the song will seek correctly - //but the position will not update until the song is resumed - //Resuming and pausing the song immediately after seems to be unable to fix this - //but the seeker only updates if the song is resumed and allowed to play without being immediately paused - //so we resume the song and then pause it after a short delay - //this is a hacky solution but it works - - //the source of the problem is that when a song is playing, it loads the next chunk of audio data into memory - //and so when it is paused, that chunk of audio data is still in memory - //so even if you seek to a new spot, the audio data that was loaded into memory is still there - //so it will have to be played either way before the new seeked to position can be played - //the only way around this would be to figure out how to clear the audio data from memory - //or how long the audio data that we want to skip past is and just allow the song to play for that long - //before pausing it again whilst the volume is 0.0. Hopefully the user won't notice this because - //that delay may get as large as 300ms or so depending on the size of the audio data that was loaded into memory - - match handle.set_volume(0.0, Tween::default()) { - Ok(_) => { - //set volume + "kira" => { + set_volume_kira(kira_audio_manager, volume); } - Err(_) => { - //failed to set volume + _ => { + // Handle the case where the player is not recognized } } +} - match handle.resume(Tween::default()) { - Ok(_) => { - //resumed song - //pause the song after a short delay - std::thread::sleep(std::time::Duration::from_millis(270)); - match handle.pause(Tween::default()) { - Ok(_) => { - //paused song - } - Err(_) => { - //failed to pause song - } - } - } - Err(_) => { - //failed to resume song +#[tauri::command] +pub fn set_playback_speed( + rodio_audio_manager: State<'_, Arc>>, + kira_audio_manager: State<'_, Arc>>, + player: &str, + speed: f32 +) { + match player{ + "rodio" => { + set_playback_speed_rodio(rodio_audio_manager, speed); } - } - - //reset volume back to previous value - match handle.set_volume(volume, Tween::default()) { - Ok(_) => { - //set volume + "kira" => { + set_playback_speed_kira(kira_audio_manager, speed as f64); } - Err(_) => { - //failed to set volume + _ => { + // Handle the case where the player is not recognized } } -} +} \ No newline at end of file diff --git a/muzik-offline/src-tauri/src/music/rodio_player.rs b/muzik-offline/src-tauri/src/music/rodio_player.rs new file mode 100644 index 0000000..542af13 --- /dev/null +++ b/muzik-offline/src-tauri/src/music/rodio_player.rs @@ -0,0 +1,398 @@ +use crate::{components::rodio_audio_manager::RodioManager, utils::general_utils::calculate_volume}; +use std::{sync::{Arc, Mutex}, time::Duration}; +use tauri::State; +use std::fs::File; +use std::io::BufReader; +use rodio::{Decoder, DeviceTrait, Source}; + +pub fn load_and_play_song_from_path_rodio( + audio_manager: State<'_, Arc>>, + sound_path: &str, + volume: f64, + duration: f64, + play_back_speed: f32, + fade_in_out: bool, +) { + match audio_manager.lock() { + Ok(mut manager) => { + let file = File::open(sound_path).expect("Failed to open audio file"); + let source = Decoder::new(BufReader::new(file)).expect("Failed to create decoder"); + if fade_in_out { + manager.crossfade = true; + } else { + manager.crossfade = false; + } + manager.duration = Some(Duration::from_secs_f64(duration)); + + match manager.sink.lock(){ + Ok(sink_guard) => { + if let Some(ref sink) = *sink_guard { + // Clear the sink and append the new audio file + sink.stop(); + sink.clear(); + if fade_in_out { + sink.append(source + .convert_samples::() + .speed(play_back_speed) + .fade_in(Duration::from_secs(6))); + } else { + sink.append(source.convert_samples::().speed(play_back_speed)); + } + sink.set_volume(volume as f32); // the volume will always be between 0.0 and 1.0 so hopefully this is safe + sink.play(); + } else { + // Handle the case where the sink is None + } + } + Err(_) => { + + } + } + } + Err(_) => { + //failed to lock audio manager + } + } +} + +pub fn load_a_song_from_path_rodio( + audio_manager: State<'_, Arc>>, + sound_path: &str, + volume: f64, + duration: f64, + play_back_speed: f32, + fade_in_out: bool, +) { + match audio_manager.lock() { + Ok(mut manager) => { + let file = File::open(sound_path).expect("Failed to open audio file"); + let source = Decoder::new(BufReader::new(file)).expect("Failed to create decoder"); + if fade_in_out { + manager.crossfade = true; + } else { + manager.crossfade = false; + } + manager.duration = Some(Duration::from_secs_f64(duration)); + + match manager.sink.lock(){ + Ok(sink_guard) => { + if let Some(ref sink) = *sink_guard { + // Clear the sink and append the new audio file + sink.stop(); + sink.clear(); + if fade_in_out { + sink.append(source.convert_samples::().speed(play_back_speed).fade_in(Duration::from_secs(6)).fade_out(Duration::from_secs(6))); + } else { + sink.append(source.convert_samples::().speed(play_back_speed)); + } + sink.set_volume(volume as f32); // the volume will always be between 0.0 and 1.0 so hopefully this is safe + } else { + // Handle the case where the sink is None + } + } + Err(_) => { + + } + } + } + Err(_) => { + //failed to lock audio manager + } + } +} + +pub fn pause_song_rodio(audio_manager: State<'_, Arc>>) { + match audio_manager.lock() { + Ok(manager) => { + match manager.sink.lock(){ + Ok(sink_guard) => { + if let Some(ref sink) = *sink_guard { + sink.pause(); + } else { + // Handle the case where the sink is None + } + } + Err(_) => { + + } + } + } + Err(_) => { + //failed to lock audio manager + } + } +} + +pub fn stop_song_rodio(audio_manager: State<'_, Arc>>) { + match audio_manager.lock() { + Ok(manager) => { + match manager.sink.lock(){ + Ok(sink_guard) => { + if let Some(ref sink) = *sink_guard { + sink.stop(); + sink.clear(); + } else { + // Handle the case where the sink is None + } + } + Err(_) => { + + } + } + } + Err(_) => { + //failed to lock audio manager + } + } +} + +pub fn resume_playing_rodio(audio_manager: State<'_, Arc>>) { + match audio_manager.lock() { + Ok(manager) => { + match manager.sink.lock(){ + Ok(sink_guard) => { + if let Some(ref sink) = *sink_guard { + sink.play(); + } else { + // Handle the case where the sink is None + } + } + Err(_) => { + + } + } + } + Err(_) => { + //failed to lock audio manager + } + } +} + +pub fn seek_to_rodio(audio_manager: State<'_, Arc>>, position: f64) { + let current_position = get_song_position_rodio(audio_manager.clone()); + + if current_position > position { + seek_by_rodio(audio_manager.clone(), -1.0 * (current_position - position)); + } else { + seek_by_rodio(audio_manager.clone(), position - current_position); + } + +} + +pub fn seek_by_rodio(audio_manager: State<'_, Arc>>, delta: f64) { + match audio_manager.lock() { + Ok(manager) => { + match manager.sink.lock(){ + Ok(sink_guard) => { + if let Some(ref sink) = *sink_guard { + match sink.try_seek(std::time::Duration::from_secs_f64(delta)){ + Ok(_) => { + + } + Err(_) => { + //failed to seek + } + } + } else { + // Handle the case where the sink is None + } + } + Err(_) => { + + } + } + } + Err(_) => { + //failed to lock audio manager + } + } +} + +pub fn get_song_position_rodio(audio_manager: State<'_, Arc>>) -> f64 { + let (pos, duration, cross_fade) = match audio_manager.lock() { + Ok(manager) => { + match manager.sink.lock(){ + Ok(sink_guard) => { + if let Some(ref sink) = *sink_guard { + (sink.get_pos(), manager.duration.unwrap_or(std::time::Duration::from_secs(0)), manager.crossfade) + } else { + // Handle the case where the sink is None + (std::time::Duration::from_secs(0), std::time::Duration::from_secs(0), false) + } + } + Err(_) => { + (std::time::Duration::from_secs(0), std::time::Duration::from_secs(0), false) + } + } + } + Err(_) => { + //failed to lock audio manager + (std::time::Duration::from_secs(0), std::time::Duration::from_secs(0), false) + } + }; + + if cross_fade && pos > duration.saturating_sub(Duration::from_secs(6)) { + set_volume_rodio(audio_manager, calculate_volume(duration.saturating_sub(pos))); + } + return pos.as_secs_f64().floor(); +} + +pub fn set_volume_rodio(audio_manager: State<'_, Arc>>, volume: f64) { + match audio_manager.lock() { + Ok(manager) => { + match manager.sink.lock(){ + Ok(sink_guard) => { + if let Some(ref sink) = *sink_guard { + sink.set_volume(volume as f32); // the volume will always be between 0.0 and 1.0 so hopefully this is safe + } else { + // Handle the case where the sink is None + } + } + Err(_) => { + + } + } + } + Err(_) => { + //failed to lock audio manager + } + } +} + +#[tauri::command] +pub fn get_default_output_device() -> String { + use rodio::cpal::traits::HostTrait; + let host = rodio::cpal::default_host(); + match host.default_output_device(){ + Some(device) => { + match device.name(){ + Ok(name) => { + name + } + Err(_) => { + String::new() + } + } + } + None => { + String::new() + } + } +} + +#[tauri::command] +pub fn get_output_devices() -> Vec { + use rodio::cpal::traits::HostTrait; + let host = rodio::cpal::default_host(); + let devices: Vec = match host.output_devices(){ + Ok(devices) => { + devices.collect() + } + Err(_) => { + return Vec::new(); + } + }; + + let mut usable_device_names = Vec::new(); + + for device in &devices{ + match device.name(){ + Ok(name) => { + usable_device_names.push(name); + } + Err(_) => { + + } + } + } + + usable_device_names +} + +#[tauri::command] +pub fn set_output_device( + audio_manager: State<'_, Arc>>, + device_name: &str +) { + use rodio::cpal::traits::HostTrait; + let host = rodio::cpal::default_host(); + let devices: Vec = match host.output_devices(){ + Ok(devices) => { + devices.collect() + } + Err(_) => { + return; + } + }; + + let device = devices.iter().find(|device| { + match device.name(){ + Ok(name) => { + name == device_name + } + Err(_) => { + false + } + } + }); + + let pos = match audio_manager.lock() { + Ok(manager) => { + match manager.sink.lock(){ + Ok(sink_guard) => { + if let Some(ref sink) = *sink_guard { + sink.get_pos() + } else { + // Handle the case where the sink is None + std::time::Duration::from_secs(0) + } + } + Err(_) => { + std::time::Duration::from_secs(0) + } + } + } + Err(_) => { + //failed to lock audio manager + std::time::Duration::from_secs(0) + } + }; + + match audio_manager.lock() { + Ok(manager) => { + match device{ + Some(device) => { + manager.set_device(device.clone(), pos); + } + None => { + // Handle the case where the device was not found + } + } + } + Err(_) => { + //failed to lock audio manager + } + } +} + +pub fn set_playback_speed_rodio(audio_manager: State<'_, Arc>>, speed: f32) { + match audio_manager.lock() { + Ok(manager) => { + match manager.sink.lock(){ + Ok(sink_guard) => { + if let Some(ref sink) = *sink_guard { + sink.set_speed(speed); + } else { + // Handle the case where the sink is None + } + } + Err(_) => { + + } + } + } + Err(_) => { + //failed to lock audio manager + } + } +} \ No newline at end of file diff --git a/muzik-offline/src-tauri/src/socials/discord_rpc.rs b/muzik-offline/src-tauri/src/socials/discord_rpc.rs index d4b3aac..da4bfb0 100644 --- a/muzik-offline/src-tauri/src/socials/discord_rpc.rs +++ b/muzik-offline/src-tauri/src/socials/discord_rpc.rs @@ -5,13 +5,13 @@ use tauri::State; use crate::utils::general_utils::get_cover_url_for_discord; -#[cfg(debug_assertions)] // Use dotenv in development mode +#[cfg(dev)] // Use dotenv in development mode use dotenv::dotenv; -#[cfg(debug_assertions)] +#[cfg(dev)] use std::env; -#[cfg(not(debug_assertions))] // Use embedded variables in release mode +#[cfg(not(dev))] // Use embedded variables in release mode include!(concat!(env!("OUT_DIR"), "/env_vars.rs")); pub struct DiscordRpc { @@ -22,7 +22,7 @@ pub struct DiscordRpc { impl DiscordRpc { pub fn new() -> Result> { - #[cfg(debug_assertions)] + #[cfg(dev)] { dotenv().ok(); let client_id = env::var("DISCORD_CLIENT_ID").expect("DISCORD_CLIENT_ID env variable not set"); @@ -34,7 +34,7 @@ impl DiscordRpc { }); } - #[cfg(not(debug_assertions))] + #[cfg(not(dev))] { let client_id = DISCORD_CLIENT_ID; // Use embedded variable let client: DiscordIpcClient = DiscordIpcClient::new(client_id)?; diff --git a/muzik-offline/src-tauri/src/utils/general_utils.rs b/muzik-offline/src-tauri/src/utils/general_utils.rs index a8c40dd..79f3e33 100644 --- a/muzik-offline/src-tauri/src/utils/general_utils.rs +++ b/muzik-offline/src-tauri/src/utils/general_utils.rs @@ -2,6 +2,7 @@ use base64::{engine::general_purpose, Engine as _}; use image::imageops::FilterType; use rayon::prelude::*; use std::net::TcpListener; +use std::time::Duration; use std::{io::Cursor, path::Path}; pub fn duration_to_string(duration: &u64) -> String { @@ -202,4 +203,8 @@ pub fn convert_single_to_double_backward_slash_on_path(path: &String) -> String #[cfg(not(target_os = "windows"))] return path.to_string(); +} + +pub fn calculate_volume(duration: Duration) -> f64{// duration will be between 0 and 6 seconds + 1.0 - (1.0 * std::f64::consts::E.powf(-1.0 * duration.as_secs_f64() as f64)) } \ No newline at end of file diff --git a/muzik-offline/src-tauri/tauri.conf.json b/muzik-offline/src-tauri/tauri.conf.json index 97e5ef3..2c344b9 100644 --- a/muzik-offline/src-tauri/tauri.conf.json +++ b/muzik-offline/src-tauri/tauri.conf.json @@ -36,7 +36,7 @@ }, "productName": "muzik-offline", "mainBinaryName": "muzik-offline", - "version": "0.6.2", + "version": "0.7.0", "identifier": "com.muzik-offline.dev", "plugins": {}, "app": { diff --git a/muzik-offline/src-tauri/tauri.linux.conf.json b/muzik-offline/src-tauri/tauri.linux.conf.json index 4fba83b..a43abc2 100644 --- a/muzik-offline/src-tauri/tauri.linux.conf.json +++ b/muzik-offline/src-tauri/tauri.linux.conf.json @@ -43,7 +43,7 @@ }, "productName": "muzik-offline", "mainBinaryName": "muzik-offline", - "version": "0.6.2", + "version": "0.7.0", "identifier": "com.muzik-offline.dev", "plugins": {}, "app": { diff --git a/muzik-offline/src-tauri/tauri.macos.conf.json b/muzik-offline/src-tauri/tauri.macos.conf.json index 99d75ed..56b2259 100644 --- a/muzik-offline/src-tauri/tauri.macos.conf.json +++ b/muzik-offline/src-tauri/tauri.macos.conf.json @@ -36,7 +36,7 @@ }, "productName": "muzik-offline", "mainBinaryName": "muzik-offline", - "version": "0.6.2", + "version": "0.7.0", "identifier": "com.muzik-offline.dev", "plugins": {}, "app": { diff --git a/muzik-offline/src-tauri/tauri.windows.conf.json b/muzik-offline/src-tauri/tauri.windows.conf.json index 13a70b1..c0ba654 100644 --- a/muzik-offline/src-tauri/tauri.windows.conf.json +++ b/muzik-offline/src-tauri/tauri.windows.conf.json @@ -36,7 +36,7 @@ }, "productName": "muzik-offline", "mainBinaryName": "muzik-offline", - "version": "0.6.2", + "version": "0.7.0", "identifier": "com.muzik-offline.dev", "plugins": {}, "app": { diff --git a/muzik-offline/src/assets/icons/Media/WaveForm.tsx b/muzik-offline/src/assets/icons/Media/WaveForm.tsx new file mode 100644 index 0000000..48de9da --- /dev/null +++ b/muzik-offline/src/assets/icons/Media/WaveForm.tsx @@ -0,0 +1,9 @@ +const WaveForm = () => { + return ( + + + + ) +} + +export default WaveForm \ No newline at end of file diff --git a/muzik-offline/src/assets/icons/index.ts b/muzik-offline/src/assets/icons/index.ts index a2f661a..1017dfd 100644 --- a/muzik-offline/src/assets/icons/index.ts +++ b/muzik-offline/src/assets/icons/index.ts @@ -50,6 +50,7 @@ import Minimize from './Layout/Minimize'; import ListIcon from './General/ListIcon'; import AlertTriangle from './General/AlertTriangle'; import Trash from './General/Trash'; +import WaveForm from './Media/WaveForm'; import FolderSearch from './General/FolderSearch'; import File from './General/File'; import Check from './General/Check'; @@ -76,7 +77,7 @@ export { NullArtistCoverOne, NullArtistCoverTwo, NullArtistCoverThree, NullArtistCoverFour, CheckGreen, CrossRed, InformationCircleContainedOrange, InformationCircleContainedBlue, EditImage, Edit, Overlap, Minimize, - ListIcon, AlertTriangle, Trash, FolderSearch, File , Check, + ListIcon, AlertTriangle, Trash, FolderSearch, File , Check, WaveForm, WindowsCloseIcon, WindowsMaximizeIcon, WindowsMinimizeIcon, WindowsRestoreIcon, LinuxClose, LinuxMaximize, LinuxMinimize, FolderPlus } diff --git a/muzik-offline/src/content/index.ts b/muzik-offline/src/content/index.ts index ac8f118..d85a91a 100644 --- a/muzik-offline/src/content/index.ts +++ b/muzik-offline/src/content/index.ts @@ -1,6 +1,33 @@ +import { AudioLabPreset } from '@muziktypes/index'; export const variants_list = {smaller: { height: "calc(100vh - 395px)" },bigger: { height: "calc(100vh - 195px)" }} export const modal_variants = { open: { opacity: 1, scale: 1}, closed: { opacity: 0, scale: 0.6}, -} \ No newline at end of file +} + +const premade_audio_labs: Map = new Map(); +premade_audio_labs.set("flat", { + SixtyTwoHz: 50, + OneTwentyFiveHz: 50, + TwoFiftyHz: 50, + FiveHundredHz: 50, + OnekHz: 50, + TwokHz: 50, + FourkHz: 50, + EightkHz: 50, + SixteenkHz: 50, +}); +premade_audio_labs.set("hip-hop", { + SixtyTwoHz: 75, + OneTwentyFiveHz: 100, + TwoFiftyHz: 50, + FiveHundredHz: 50, + OnekHz: 50, + TwokHz: 50, + FourkHz: 50, + EightkHz: 50, + SixteenkHz: 50, +}); + +export { premade_audio_labs }; \ No newline at end of file diff --git a/muzik-offline/src/database/player.ts b/muzik-offline/src/database/player.ts index 492d106..291ef24 100644 --- a/muzik-offline/src/database/player.ts +++ b/muzik-offline/src/database/player.ts @@ -1,12 +1,18 @@ import { Song } from "@muziktypes/index"; +export enum RepeatingLevel{ + NO_REPEAT, + REPEAT_ONE, + REPEAT_ALL +} + export interface Player{ playingSongMetadata: Song | null; isPlaying: boolean; wasPlayingBeforePause: boolean; lengthOfSongInSeconds: number; isShuffling: boolean; - repeatingLevel: 0 | 1 | 2; + repeatingLevel: RepeatingLevel; } export const emptyPlayer: Player = { @@ -15,5 +21,5 @@ export const emptyPlayer: Player = { wasPlayingBeforePause: false, lengthOfSongInSeconds: 0, isShuffling: false, - repeatingLevel: 0, + repeatingLevel: RepeatingLevel.NO_REPEAT, } \ No newline at end of file diff --git a/muzik-offline/src/database/saved_object.ts b/muzik-offline/src/database/saved_object.ts index d6462d1..5050a15 100644 --- a/muzik-offline/src/database/saved_object.ts +++ b/muzik-offline/src/database/saved_object.ts @@ -17,9 +17,16 @@ export interface SavedObject{ UpcomingHistoryLimit: string, SeekStepAmount: string, SongLengthORremaining: string, + AudioLabPreset: string, + SavedPresets: string[], AlwaysRoundedCornersWindows: string, AutoStartApp: string, - DirectoryScanningDepth: number + DirectoryScanningDepth: number, + player: "rodio" | "kira", + AudioQuality: string, + PlayBackSpeed: string, + AudioTransition: string, + OutputDevice: string } export const emptySavedObject: SavedObject = { @@ -39,7 +46,14 @@ export const emptySavedObject: SavedObject = { UpcomingHistoryLimit: "10", SeekStepAmount: "10", SongLengthORremaining: "song length", + AudioLabPreset: "flat", + SavedPresets: ["flat", "hip-hop"], AlwaysRoundedCornersWindows: "No", AutoStartApp: "No", - DirectoryScanningDepth: 1 + DirectoryScanningDepth: 1, + player: "rodio", + AudioQuality: "High(320kbps)", + PlayBackSpeed: "1", + AudioTransition: "No", + OutputDevice: "" } \ No newline at end of file diff --git a/muzik-offline/src/interface/App/App.tsx b/muzik-offline/src/interface/App/App.tsx index d209eea..f9db474 100644 --- a/muzik-offline/src/interface/App/App.tsx +++ b/muzik-offline/src/interface/App/App.tsx @@ -107,7 +107,7 @@ const App = () => { setFirstRun(false); }*/ invoke("refresh_paths", { - athsAsJsonArray: JSON.stringify(paths), + pathsAsJsonArray: JSON.stringify(paths), compressImageOption: local_store.CompressImage === "Yes" ? true : false, maxDepth: local_store.DirectoryScanningDepth }) @@ -134,8 +134,9 @@ const App = () => { } function request_song(){ - listen("loadSong", async(path) => { - const song = await local_songs_db.songs.where("path").equals(path.payload.toString()).first(); + invoke("collect_env_args").then(async(path) => { + if(path === "")return; + const song = await local_songs_db.songs.where("path").equals(path.toString()).first(); if(song)await startPlayingNewSong(song); }); } diff --git a/muzik-offline/src/interface/App/MiniPlayer.tsx b/muzik-offline/src/interface/App/MiniPlayer.tsx index 4c25cab..0c61b0f 100644 --- a/muzik-offline/src/interface/App/MiniPlayer.tsx +++ b/muzik-offline/src/interface/App/MiniPlayer.tsx @@ -35,7 +35,7 @@ const MiniPlayer: FunctionComponent = (props: MiniPlayerProps) } async function upDateSeeker(){ - const value: any = await invoke("get_song_position"); + const value: any = await invoke("get_song_position", { player: useSavedObjectStore.getState().local_store.player}); if(value === Player.lengthOfSongInSeconds && Player.playingSongMetadata){ reconfigurePlayer_AtEndOfSong(); } diff --git a/muzik-offline/src/interface/components/context_menu/GeneralContextMenu.tsx b/muzik-offline/src/interface/components/context_menu/GeneralContextMenu.tsx index 1783d60..f4222dc 100644 --- a/muzik-offline/src/interface/components/context_menu/GeneralContextMenu.tsx +++ b/muzik-offline/src/interface/components/context_menu/GeneralContextMenu.tsx @@ -35,12 +35,13 @@ const GeneralContextMenu: FunctionComponent = (props: G //else if(){//5 items // scmHeight = 250; //} - else if(props.CMtype === contextMenuEnum.ArtistCM || props.CMtype === contextMenuEnum.AlbumCM - || props.CMtype === contextMenuEnum.SongCM){//6 items + else if(props.CMtype === contextMenuEnum.ArtistCM || props.CMtype === contextMenuEnum.AlbumCM){//6 items scmHeight = 280; } - else if(props.CMtype === contextMenuEnum.PlaylistCM || props.CMtype === contextMenuEnum.PlaylistSongsCM){//7 items - scmHeight = 310; + else if(props.CMtype === contextMenuEnum.PlaylistCM || props.CMtype === contextMenuEnum.PlaylistSongsCM + || props.CMtype === contextMenuEnum.SongCM + ){//7 items + scmHeight = 317; } if(props.overRideY)return yPos; else return yPos > winHeight - scmHeight ? (winHeight - scmHeight) : yPos; @@ -66,8 +67,8 @@ const GeneralContextMenu: FunctionComponent = (props: G && } {(props.CMtype === contextMenuEnum.PlaylistCM || props.CMtype === contextMenuEnum.SongCM || props.CMtype === contextMenuEnum.PlaylistSongsCM) && } - {(props.CMtype === contextMenuEnum.PlaylistCM || props.CMtype === contextMenuEnum.PlaylistSongsCM) - && } + {(props.CMtype === contextMenuEnum.PlaylistCM || props.CMtype === contextMenuEnum.PlaylistSongsCM + || props.CMtype === contextMenuEnum.SongCM) && } ) } diff --git a/muzik-offline/src/interface/components/index.ts b/muzik-offline/src/interface/components/index.ts index c393361..44cd6be 100644 --- a/muzik-offline/src/interface/components/index.ts +++ b/muzik-offline/src/interface/components/index.ts @@ -36,6 +36,7 @@ import SongCardResizableDraggable from './lists/SongCardResizableDraggable'; import RectangleSongBoxDraggable from './lists/RectangleSongBoxDraggable'; import DeletePlaylistModal from './modals/DeletePlaylistModal'; import DeleteSongFromPlaylistModal from './modals/DeleteSongFromPlaylistModal'; +import EqualizerSlider from './sliders/EqualizerSlider'; import EditPropertiesModal from './modals/EditPropertiesModal'; import { EditSongButton } from './context_menu/ContextMenuButtons'; import DateInput from './input/DateInput'; @@ -44,6 +45,8 @@ import WallpapersSelectionModal from './modals/WallpapersSelectionModal'; import RectangleSongBoxView from './cards/RectangleSongBoxView'; import CheckboxComponent from './input/CheckboxComponent'; import ExportModal from './modals/ExportModal'; +import DeleteSongModal from './modals/DeleteSongModal'; +import EqualizerModal from './modals/EqualizerModal'; export { HeaderWindows, HeaderMacOS, HeaderLinuxOS, AppNavigator, LeftSidebar, AppMusicPlayer, FSMusicPlayer, @@ -57,8 +60,9 @@ export { CreatePlaylistModal, EditPlaylistModal, AddSongToPlaylistModal, LoaderAnimated, AirplayCastModal, AddSongsToPlaylistModal, MusicPopOver, SongCardResizableDraggable, RectangleSongBoxDraggable, DeletePlaylistModal, - DeleteButton, DeleteSongFromPlaylistModal, + DeleteButton, DeleteSongFromPlaylistModal, EqualizerSlider, EditPropertiesModal, EditSongButton, DateInput, DeleteDiretoryModal, WallpapersSelectionModal, - RectangleSongBoxView, CheckboxComponent, ExportModal + RectangleSongBoxView, CheckboxComponent, ExportModal, + DeleteSongModal, EqualizerModal } \ No newline at end of file diff --git a/muzik-offline/src/interface/components/modals/DeleteSongModal.tsx b/muzik-offline/src/interface/components/modals/DeleteSongModal.tsx new file mode 100644 index 0000000..f7c2352 --- /dev/null +++ b/muzik-offline/src/interface/components/modals/DeleteSongModal.tsx @@ -0,0 +1,39 @@ +import { AlertTriangle } from "@assets/icons"; +import { modal_variants } from "@content/index"; +import { motion } from "framer-motion"; +import { FunctionComponent } from "react"; +import "@styles/components/modals/DeleteSongFromPlaylistModal.scss"; + +type DeleteSongModalProps = { + title: string; + isOpen: boolean; + closeModal: (deleteSong: boolean) => void; +} + +const DeleteSongModal: FunctionComponent = (props: DeleteSongModalProps) => { + return ( +
) => {if(e.target === e.currentTarget)props.closeModal(false)}}> + +
+
+
+ +
+
+

Are you sure you want to delete {props.title}?

+ props.closeModal(true)}> +

delete

+
+ props.closeModal(false)}> +

cancel

+
+ +
+ ) +} + +export default DeleteSongModal \ No newline at end of file diff --git a/muzik-offline/src/interface/components/modals/EqualizerModal.tsx b/muzik-offline/src/interface/components/modals/EqualizerModal.tsx new file mode 100644 index 0000000..2b44cbb --- /dev/null +++ b/muzik-offline/src/interface/components/modals/EqualizerModal.tsx @@ -0,0 +1,145 @@ +import EqualizerSlider from "@components/sliders/EqualizerSlider"; +import { modal_variants } from "@content/index"; +import { selectedGeneralSettingEnum, toastType, AudioLabPreset } from "@muziktypes/index"; +import { useToastStore, useSavedObjectStore, useSavedPresetsValues, FlatAudioLab } from "@store/index"; +import { motion } from "framer-motion"; +import { FunctionComponent, useState } from "react"; +import "@styles/components/modals/EqualizerModal.scss"; +import { ChevronDown } from "@assets/icons"; +import DropDownMenuLarge from "@components/input/DropDownMenuLarge"; + +type EqualizerModalProps = { + isOpen: boolean; + closeModal: () => void; +} + +const EqualizerModal: FunctionComponent = (props: EqualizerModalProps) => { + const { setToast } = useToastStore((state) => { return { setToast: state.setToast }; }); + const {local_store, setStore} = useSavedObjectStore((state) => { return { local_store: state.local_store, setStore: state.setStore}; }); + const {map, addValues} = useSavedPresetsValues((state) => {return { map: state.map, addValues: state.addValue}; }); + const [AudioLabPreset, setAudioLabPreset] = useState(local_store.AudioLabPreset); + const [AudioLab, setAudioLab] = useState(map.get(local_store.AudioLabPreset) ?? FlatAudioLab); + const [selectedGeneralSetting, setselectedGeneralSetting] = useState(selectedGeneralSettingEnum.Nothing); + + function toggleDropDown(){ + if(selectedGeneralSetting === selectedGeneralSettingEnum.AudioLabPreset) + setselectedGeneralSetting(selectedGeneralSettingEnum.Nothing); + else setselectedGeneralSetting(selectedGeneralSettingEnum.AudioLabPreset); + } + + + function setStoreValue(arg: string){ + let temp = local_store; + temp.AudioLabPreset = arg; + setAudioLab(map.get(temp.AudioLabPreset) ?? FlatAudioLab); + setAudioLabPreset(temp.AudioLabPreset); + setStore(temp); + setselectedGeneralSetting(selectedGeneralSettingEnum.Nothing); + } + + function createNewPreset(){ + if(map.has(AudioLabPreset)){ + setToast({title: "Preset creation", message: "A preset with that name already exists", type: toastType.error, timeout: 3000}); + } + else if(AudioLabPreset === "") { + setToast({title: "Preset creation", message: "Preset name cannot be empty", type: toastType.error, timeout: 3000}); + } + else { + addValues(AudioLabPreset, AudioLab); + let temp = local_store; + temp.AudioLabPreset = AudioLabPreset; + temp.SavedPresets.push(AudioLabPreset); + setStore(temp); + } + } + + function deletePreset(){ + if(map.has(AudioLabPreset)){ + // don't allow flat to be deleted + if(AudioLabPreset === "flat"){ + setToast({title: "Preset deletion", message: "The flat preset cannot be deleted", type: toastType.error, timeout: 3000}); + return; + } + map.delete(AudioLabPreset); + let temp = local_store; + temp.SavedPresets = temp.SavedPresets.filter((preset) => preset !== AudioLabPreset); + setStore(temp); + resetPresetName(); + } + else { + setToast({title: "Preset deletion", message: "No preset with that name exists", type: toastType.error, timeout: 3000}); + } + } + + function resetPresetName(){setAudioLabPreset("");} + return ( +
) => {if(e.target === e.currentTarget)props.closeModal();}}> + +

Equalizer(Ineffective with rodio)

+
+
+ +

{local_store.AudioLabPreset}

+ + + +
+
+ +
+
+ setAudioLabPreset(e.target.value)}/> + { + map.has(AudioLabPreset) ? + +

delete preset

+
+ : + +

create a new preset

+
+ } +
+
+
+ { ["12dB", "6dB", "0dB", "-6dB", "-12dB"].map((value) =>
{value}
) } +
+
+
+ {resetPresetName(); setAudioLab({ ... AudioLab, SixtyTwoHz: value })}}/> + {resetPresetName(); setAudioLab({ ... AudioLab, OneTwentyFiveHz: value })}}/> + {resetPresetName(); setAudioLab({ ... AudioLab, TwoFiftyHz: value })}}/> + {resetPresetName(); setAudioLab({ ... AudioLab, FiveHundredHz: value })}}/> + {resetPresetName(); setAudioLab({ ... AudioLab, OnekHz: value })}}/> + {resetPresetName(); setAudioLab({ ... AudioLab, TwokHz: value })}}/> + {resetPresetName(); setAudioLab({ ... AudioLab, FourkHz: value })}}/> + {resetPresetName(); setAudioLab({ ... AudioLab, EightkHz: value })}}/> + {resetPresetName(); setAudioLab({ ... AudioLab, SixteenkHz: value })}}/> +
+
+
+
+
+ ) +} + +export default EqualizerModal \ No newline at end of file diff --git a/muzik-offline/src/interface/components/music/AppMusicPlayer.tsx b/muzik-offline/src/interface/components/music/AppMusicPlayer.tsx index 71b627b..1ed8ae8 100644 --- a/muzik-offline/src/interface/components/music/AppMusicPlayer.tsx +++ b/muzik-offline/src/interface/components/music/AppMusicPlayer.tsx @@ -8,6 +8,7 @@ import { invoke } from "@tauri-apps/api/core"; import { changeVolumeLevel, changeSeekerPosition, changeVolumeLevelBtnPress, dragSeeker, pauseSong, playSong, repeatToggle, shuffleToggle, setVolumeLevel, reconfigurePlayer_AtEndOfSong, playPreviousSong, playNextSong, changeSeekerPositionBtnPress } from "@utils/playerControl"; import { AirplayCastModal, MusicPopOver } from "@components/index"; import { OSTYPEenum } from "@muziktypes/index"; +import { RepeatingLevel } from "@database/player"; type AppMusicPlayerProps = { openPlayer: () => void; @@ -40,7 +41,7 @@ const AppMusicPlayer : FunctionComponent = (props: AppMusic } async function upDateSeeker(){ - const value: any = await invoke("get_song_position"); + const value: any = await invoke("get_song_position", { player: useSavedObjectStore.getState().local_store.player}); if(value === Player.lengthOfSongInSeconds && Player.playingSongMetadata){ reconfigurePlayer_AtEndOfSong(); } @@ -107,9 +108,9 @@ const AppMusicPlayer : FunctionComponent = (props: AppMusic
- 0 ? " coloured" : "")} + - {Player.repeatingLevel === 2 ? + {Player.repeatingLevel === RepeatingLevel.REPEAT_ONE ? : diff --git a/muzik-offline/src/interface/components/music/FSMusicPlayer.tsx b/muzik-offline/src/interface/components/music/FSMusicPlayer.tsx index 5178a26..7cb8f32 100644 --- a/muzik-offline/src/interface/components/music/FSMusicPlayer.tsx +++ b/muzik-offline/src/interface/components/music/FSMusicPlayer.tsx @@ -5,7 +5,7 @@ import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'; import { HistoryUpcoming, MainMusicPlayer } from "@components/index"; import { OSTYPEenum } from "@muziktypes/index"; import { Minimize, Overlap } from "@icons/index"; -import { useSavedObjectStore, usePlayerStore, useIsFSStore } from "store"; +import { useSavedObjectStore, usePlayerStore, useIsFSStore, useIsMaximisedStore } from "store"; import { getCoverURL, getNullRandomCover } from "utils"; const appWindow = getCurrentWebviewWindow() @@ -15,8 +15,8 @@ type FSMusicPlayerProps = { } const variants={ - open: {bottom: "-10vh", scale: 1, opacity: 1, borderRadius: "0px"}, - closed: {bottom: "-110vh", scale: 0.7, opacity: 0.5, borderRadius: "100px"}, + open: {marginTop: "0vh", scale: 1, opacity: 1, borderRadius: "0px"}, + closed: {marginTop: "110vh", scale: 0.7, opacity: 0, borderRadius: "100px"}, } const variants_list_appearance = { @@ -28,6 +28,8 @@ const FSMusicPlayer: FunctionComponent = (props: FSMusicPlay const [wasMaximized, setWasMaximized] = useState(false); const [isDoneOpening, setIsDoneOpening] = useState(false); + const [isDoneClosing, setIsDoneClosing] = useState(false); + const { isMaximised } = useIsMaximisedStore((state) => { return { isMaximised: state.isMaximised}; }); const {local_store} = useSavedObjectStore((state) => { return { local_store: state.local_store}; }); const {Player} = usePlayerStore((state) => { return { Player: state.Player}; }); const { setappFS, appFS } = useIsFSStore((state) => { return { setappFS: state.setFS, appFS: state.isFS}; }); @@ -56,79 +58,88 @@ const FSMusicPlayer: FunctionComponent = (props: FSMusicPlay useEffect(() => { if(props.openPlayer === true){ + setIsDoneClosing(false); const delay = setTimeout(() => { setIsDoneOpening(true); }, local_store.Animations ? 1000 : 290); return () => clearTimeout(delay); + } else if(props.openPlayer === false){ + setIsDoneOpening(false); + const delay = setTimeout(() => { setIsDoneClosing(true); }, local_store.Animations ? 1000 : 290); + return () => clearTimeout(delay); } - else setIsDoneOpening(false); }, [props.openPlayer]) return ( - -
-
- {props.openPlayer && isDoneOpening && - - {!Player.playingSongMetadata && song-art} - {/**no song is loaded onto the player */} - {Player.playingSongMetadata && Player.playingSongMetadata.cover_uuid && (song-art)} - {/**there is cover art */} - {Player.playingSongMetadata && !Player.playingSongMetadata.cover_uuid && song-cover} - {/**the cover art is null */} - } -
-
- {local_store.OStype === OSTYPEenum.Windows && appFS === false ? -
+
+ +
+
+ {props.openPlayer && isDoneOpening && + + {!Player.playingSongMetadata && song-art} + {/**no song is loaded onto the player */} + {Player.playingSongMetadata && Player.playingSongMetadata.cover_uuid && (song-art)} + {/**there is cover art */} + {Player.playingSongMetadata && !Player.playingSongMetadata.cover_uuid && song-cover} + {/**the cover art is null */} + } +
+
+ {local_store.OStype === OSTYPEenum.Windows && appFS === false ? +
+
+ { appFS === false && + ( +

close

+
) + } + + <>

fullscreen

+
+
+
+ :
{ appFS === false && - ( + (

close

) } - - <>

fullscreen

+ + { appFS === false ? (<>

fullscreen

) : (<>

minimize

) }
-
- : -
- { appFS === false && - ( -

close

-
) - } - - { appFS === false ? (<>

fullscreen

) : (<>

minimize

) } -
-
- } - {props.openPlayer && isDoneOpening && - -
- Loading...
}> - - -
-
- Loading...
}> - { - if(appFS === true)switchtoNONFS(); - props.closePlayer(); - }}/> - -
- - } + } + {props.openPlayer && isDoneOpening && + +
+ Loading...
}> + + +
+
+ Loading...
}> + { + if(appFS === true)switchtoNONFS(); + props.closePlayer(); + }}/> + +
+ + } +
-
- + + ) } diff --git a/muzik-offline/src/interface/components/music/MainMusicPlayer.tsx b/muzik-offline/src/interface/components/music/MainMusicPlayer.tsx index 9e75b0f..6bb6cef 100644 --- a/muzik-offline/src/interface/components/music/MainMusicPlayer.tsx +++ b/muzik-offline/src/interface/components/music/MainMusicPlayer.tsx @@ -5,6 +5,7 @@ import { usePlayerStore, usePlayingPosition, usePlayingPositionSec, useSavedObje import { getCoverURL, getNullRandomCover, secondsToTimeFormat } from "utils"; import { changeVolumeLevel, changeSeekerPosition, dragSeeker, changeVolumeLevelBtnPress, repeatToggle, pauseSong, playSong, shuffleToggle, setVolumeLevel, playPreviousSong, playNextSong } from "utils/playerControl"; import { OSTYPEenum } from "@muziktypes/index"; +import { RepeatingLevel } from "@database/player"; const MainMusicPlayer = () => { const {local_store} = useSavedObjectStore((state) => { return { local_store: state.local_store, setStore: state.setStore}; }); @@ -50,9 +51,9 @@ const MainMusicPlayer = () => {

{Player.playingSongMetadata ? Player.playingSongMetadata.artist : "No song is playing"}

- 0 ? " coloured" : "")} + RepeatingLevel.NO_REPEAT ? " coloured" : "")} whileTap={{scale: 0.98}} onClick={repeatToggle}> - {Player.repeatingLevel === 2 ? + {Player.repeatingLevel === RepeatingLevel.REPEAT_ONE ? : diff --git a/muzik-offline/src/interface/components/sliders/EqualizerSlider.tsx b/muzik-offline/src/interface/components/sliders/EqualizerSlider.tsx new file mode 100644 index 0000000..3fe0a14 --- /dev/null +++ b/muzik-offline/src/interface/components/sliders/EqualizerSlider.tsx @@ -0,0 +1,32 @@ +import { FunctionComponent, useState, useEffect } from "react"; +import "@styles/components/sliders/EqualizerSlider.scss"; + + +type EqualizerSliderProps = { + frequency: string; + value: number; + updateValue: (value: number) => void; +} + +const EqualizerSlider: FunctionComponent = (props: EqualizerSliderProps) => { + const [current_val, setValue] = useState(props.value); + + useEffect(() => {setValue(props.value)}, [props.value]) + + return ( +
+
+ setValue(Number.parseInt(e.target.value))} + onMouseUp={() => props.updateValue(current_val)} + style={{backgroundSize: current_val.toString() + "% 100%"}}/> +
+

{props.frequency}

+
+ ) +} + +export default EqualizerSlider \ No newline at end of file diff --git a/muzik-offline/src/interface/layouts/AboutSettings.tsx b/muzik-offline/src/interface/layouts/AboutSettings.tsx index 31bd0d9..aa44b74 100644 --- a/muzik-offline/src/interface/layouts/AboutSettings.tsx +++ b/muzik-offline/src/interface/layouts/AboutSettings.tsx @@ -11,7 +11,7 @@ const AboutSettings = () => {

Copyright 2024 muzik-apps. All rights reserved.

-

Version "0.6.2"

+

Version "0.7.0"

open("https://github.com/muzik-apps/muzik-offline")}> muzik-offline diff --git a/muzik-offline/src/interface/layouts/AudioLabSettings.tsx b/muzik-offline/src/interface/layouts/AudioLabSettings.tsx new file mode 100644 index 0000000..f1bd418 --- /dev/null +++ b/muzik-offline/src/interface/layouts/AudioLabSettings.tsx @@ -0,0 +1,211 @@ +import { ChevronDown } from "@assets/icons"; +import { DropDownMenuLarge } from "@components/index"; +import { SavedObject } from "@database/saved_object"; +import { selectedGeneralSettingEnum } from "@muziktypes/index"; +import { useSavedObjectStore } from "@store/index"; +import "@styles/layouts/AudioLabSettings.scss"; +import { invoke } from "@tauri-apps/api/core"; +import { stopSong } from "@utils/playerControl"; +import { motion } from "framer-motion"; +import { FunctionComponent, useEffect, useState } from "react";4 + +const settings_data: { + key: number; + title: string; + dropDownName: selectedGeneralSettingEnum; + options: string[] +}[] = [ + /*{ + key: 1, + title: "Audio quality of music", + dropDownName: selectedGeneralSettingEnum.AudioQuality, + options: ["Lossless(24b/192kHz)", "Lossless(24b/48kHz)", "High(320kbps)", "Medium(192kbps)", "Low(128kbps)"] + },*/ + { + key: 2, + title: "Playback speed of music", + dropDownName: selectedGeneralSettingEnum.PlayBackSpeed, + options: ["0.25", "0.5", "0.75", "1", "1.25", "1.5", "1.75", "2"] + }, + { + key: 3, + title: "Seamless, fade-in/out audio transitions(will only apply on next song)", + dropDownName: selectedGeneralSettingEnum.AudioTransition, + options: ["Yes", "No"] + } +] + +type AudioLabSettingsProps = { + openEqualiser: () => void; +} + +const AudioLabSettings: FunctionComponent = (_props: AudioLabSettingsProps) => { + const [selectedGeneralSetting, setselectedGeneralSetting] = useState(selectedGeneralSettingEnum.Nothing); + const {local_store, setStore} = useSavedObjectStore((state) => { return { local_store: state.local_store, setStore: state.setStore}; }); + const [currentOutputDevice, setOutputDevice] = useState("default"); + const [outputDevices, setOutputDevices] = useState([]); + const [audioBackend, setAudioBackend] = useState<"rodio" | "kira">(local_store.player); + + function toggleDropDown(arg: selectedGeneralSettingEnum){ + if(arg === selectedGeneralSetting)setselectedGeneralSetting(selectedGeneralSettingEnum.Nothing); + else setselectedGeneralSetting(arg); + } + + async function setStoreValue(arg: string, type: string){ + let temp: SavedObject = local_store; + temp[type as keyof SavedObject] = arg as never; + setStore(temp); + setselectedGeneralSetting(selectedGeneralSettingEnum.Nothing); + if (type === selectedGeneralSettingEnum.PlayBackSpeed){ + await invoke("set_playback_speed", {player: local_store.player, speed: parseFloat(arg)}); + } + if (type === selectedGeneralSettingEnum.OutputDevice){ + await invoke("set_output_device", {deviceName: arg}); + setOutputDevice(arg); + } + } + + function changeAudioBackend(arg: "rodio" | "kira"){ + if(arg === local_store.player)return; + setAudioBackend(arg); + stopSong().then(() => { + let temp: SavedObject = local_store; + temp.player = arg; + setStore(temp); + }).catch((err) => { + console.error(err); + setAudioBackend(local_store.player); + }); + } + + useEffect(() => { + const setup = () => { + if (local_store.OutputDevice === "") { + invoke("get_default_output_device").then((res) => { + setOutputDevice(res); + }); + } else { + setOutputDevice(local_store.OutputDevice); + } + + invoke("get_output_devices").then((res) => { + setOutputDevices(res); + }); + } + + setup(); + }, []); + + return ( +
+

Audio Lab Settings

+
+
Select your audio backend
+ changeAudioBackend("rodio")}> +
+ Rodio logo +
+

Rodio

+
+
+
    +
  • More stable and faster
  • +
  • More developed specifically for audio playback
  • +
  • Supports output device switching
  • +
+
+
+
+
- No equaliser support
+
- No audio effects
+
+
+
+
+ changeAudioBackend("kira")}> +
+ Kira logo +
+

Kira

+
+
+
    +
  • More developed specifically for game audio playback
  • +
  • Supports 3D spatial audio(coming soon)
  • +
  • Supports sound effects and equaliser(coming soon)
  • +
+
+
+
+
- Can be slow and unstable for large files
+
- No support for switching output device in app
+
+
+
+
+ { + settings_data.map((value) => +
+

{value.title}

+
+ toggleDropDown(value.dropDownName)}> +

{local_store[(value.dropDownName.toString() as keyof SavedObject)]}

+ + + +
+
+ +
+
+
) + } +
+

Output device

+
+ { local_store.player === "rodio" ? + toggleDropDown(selectedGeneralSettingEnum.OutputDevice)}> +

{currentOutputDevice}

+ + + +
+ : +
+

default

+
+ +
+
+ } +
+ +
+
+
+ {/*
+

Equaliser

+
+ +

Open equaliser

+
+
+
*/} +
+
+ ) +} + +export default AudioLabSettings \ No newline at end of file diff --git a/muzik-offline/src/interface/layouts/GeneralSettings.tsx b/muzik-offline/src/interface/layouts/GeneralSettings.tsx index bd37ada..e7465e7 100644 --- a/muzik-offline/src/interface/layouts/GeneralSettings.tsx +++ b/muzik-offline/src/interface/layouts/GeneralSettings.tsx @@ -40,7 +40,6 @@ const settings_data: { } ] - const GeneralSettings = () => { const [selectedGeneralSetting, setselectedGeneralSetting] = useState(selectedGeneralSettingEnum.Nothing); const {local_store, setStore} = useSavedObjectStore((state) => { return { local_store: state.local_store, setStore: state.setStore}; }); @@ -101,7 +100,7 @@ const GeneralSettings = () => { { settings_data.map((value) => value.dropDownName !== selectedGeneralSettingEnum.AlwaysRoundedCornersWindows || - (value.dropDownName === selectedGeneralSettingEnum.AlwaysRoundedCornersWindows && local_store.OStype === OSTYPEenum.Windows) ? + (value.dropDownName === selectedGeneralSettingEnum.AlwaysRoundedCornersWindows && (local_store.OStype === OSTYPEenum.Windows || local_store.OStype === OSTYPEenum.Linux)) ?

{value.title}

@@ -143,4 +142,4 @@ const GeneralSettings = () => { ) } -export default GeneralSettings \ No newline at end of file +export default GeneralSettings diff --git a/muzik-offline/src/interface/layouts/SearchSongs.tsx b/muzik-offline/src/interface/layouts/SearchSongs.tsx index 7a4ed04..f272e20 100644 --- a/muzik-offline/src/interface/layouts/SearchSongs.tsx +++ b/muzik-offline/src/interface/layouts/SearchSongs.tsx @@ -1,4 +1,4 @@ -import { RectangleSongBox, GeneralContextMenu, AddSongToPlaylistModal, PropertiesModal, EditPropertiesModal } from "@components/index"; +import { RectangleSongBox, GeneralContextMenu, AddSongToPlaylistModal, PropertiesModal, EditPropertiesModal, DeleteSongModal } from "@components/index"; import { contextMenuEnum, contextMenuButtons } from "@muziktypes/index"; import { useRef, useEffect, useReducer } from "react"; import "@styles/layouts/SearchSongs.scss"; @@ -8,7 +8,7 @@ import { reducerType, useSearchStore } from "@store/index"; import { useNavigate } from "react-router-dom"; import { SearchSongsState, searchSongsReducer } from "@store/reducerStore"; import { addThisSongToPlayLater, addThisSongToPlayNext, playThisListNow, startPlayingNewSong } from "@utils/playerControl"; -import { closeContextMenu, closeEditPropertiesModal, closePlaylistModal, closePropertiesModal, processArrowKeysInput, selectThisSong, setSongList } from "@utils/reducerUtils"; +import { closeContextMenu, closeDeleteSongModal, closeEditPropertiesModal, closePlaylistModal, closePropertiesModal, processArrowKeysInput, selectThisSong, setSongList } from "@utils/reducerUtils"; import Skeleton, { SkeletonTheme } from "react-loading-skeleton"; import 'react-loading-skeleton/dist/skeleton.css'; @@ -29,6 +29,7 @@ const SearchSongs = () => { if(arg === contextMenuButtons.ShowInfo){ dispatch({ type: reducerType.SET_PROPERTIES_MODAL, payload: true}); } else if(arg === contextMenuButtons.AddToPlaylist){ dispatch({ type: reducerType.SET_PLAYLIST_MODAL, payload: true}); } else if(arg === contextMenuButtons.EditSong){ dispatch({ type: reducerType.SET_EDIT_SONG_MODAL, payload: true}); } + else if(arg === contextMenuButtons.Delete){ dispatch({ type: reducerType.SET_DELETE_MODAL, payload: true}); } else if(arg === contextMenuButtons.PlayNext && state.songMenuToOpen){ addThisSongToPlayNext([state.songMenuToOpen.id]); closeContextMenu(dispatch); @@ -75,7 +76,7 @@ const SearchSongs = () => { else if(ev.target.id !== "gsearch" && state.selected >= 1 && state.selected <= state.SongList.length){ dispatch({type: reducerType.SET_SONG_MENU, payload: state.SongList[state.selected - 1]}); if(((ev.ctrlKey || ev.metaKey) && (ev.key === "p" || ev.key === "P" )) || ev.key === "Enter")chooseOption(contextMenuButtons.Play); - else if((ev.ctrlKey || ev.metaKey) && (ev.key === "i" || ev.key === "I"))chooseOption(contextMenuButtons.ShowInfo); + else if((ev.ctrlKey || ev.metaKey) && !ev.shiftKey && (ev.key === "i" || ev.key === "I"))chooseOption(contextMenuButtons.ShowInfo); else if((ev.ctrlKey || ev.metaKey) && ev.shiftKey && (ev.key === "a" || ev.key === "A"))chooseOption(contextMenuButtons.AddToPlaylist); else if((ev.ctrlKey || ev.metaKey) && ev.shiftKey && (ev.key === "n" || ev.key === "N"))chooseOption(contextMenuButtons.PlayNext); else if((ev.ctrlKey || ev.metaKey) && ev.shiftKey && (ev.key === "l" || ev.key === "L"))chooseOption(contextMenuButtons.PlayLater); @@ -155,6 +156,7 @@ const SearchSongs = () => { closePropertiesModal(dispatch)} /> closeEditPropertiesModal(dispatch)} /> closePlaylistModal(dispatch)} /> + closeDeleteSongModal(dispatch, state.songMenuToOpen, deleteSong)} />
) } diff --git a/muzik-offline/src/interface/layouts/index.ts b/muzik-offline/src/interface/layouts/index.ts index c6aa351..ecdfb83 100644 --- a/muzik-offline/src/interface/layouts/index.ts +++ b/muzik-offline/src/interface/layouts/index.ts @@ -9,6 +9,7 @@ import SearchPlaylists from './SearchPlaylists'; import AdvancedSettings from './AdvancedSettings'; import SecuritySettings from './SecuritySettings'; import AboutSettings from './AboutSettings'; +import AudioLabSettings from './AudioLabSettings'; import MusicFoldersSettings from './MusicFoldersSettings'; import ExportSettings from './ExportSettings'; @@ -16,5 +17,5 @@ export { GeneralSettings, AppearanceSettings, HistoryNextFloating, SearchSongs, SearchArtists, SearchAlbums, SearchGenres, SearchPlaylists, AdvancedSettings, SecuritySettings, AboutSettings, - MusicFoldersSettings, ExportSettings + AudioLabSettings, MusicFoldersSettings, ExportSettings } \ No newline at end of file diff --git a/muzik-offline/src/interface/pages/AlbumDetails.tsx b/muzik-offline/src/interface/pages/AlbumDetails.tsx index 75a1dfe..c7bc07f 100644 --- a/muzik-offline/src/interface/pages/AlbumDetails.tsx +++ b/muzik-offline/src/interface/pages/AlbumDetails.tsx @@ -1,5 +1,5 @@ import { useEffect, useReducer, useRef } from "react"; -import { AddSongToPlaylistModal, EditPropertiesModal, GeneralContextMenu, LargeResizableCover, LoaderAnimated, PropertiesModal, RectangleSongBox } from "@components/index"; +import { AddSongToPlaylistModal, DeleteSongModal, EditPropertiesModal, GeneralContextMenu, LargeResizableCover, LoaderAnimated, PropertiesModal, RectangleSongBox } from "@components/index"; import "@styles/pages/AlbumDetails.scss"; import { motion } from "framer-motion"; import { Play, Shuffle } from "@assets/icons"; @@ -10,7 +10,7 @@ import { getAlbumSongs, getCoverURL, getRandomCover, secondsToTimeFormat } from import { ViewportList } from "react-viewport-list"; import { albumDetailsReducer, AlbumDetailsState } from "@store/reducerStore"; import { startPlayingNewSong, playThisListNow, addThisSongToPlayLater, addThisSongToPlayNext } from "@utils/playerControl"; -import { closeContextMenu, closeEditPropertiesModal, closePlaylistModal, closePropertiesModal, processArrowKeysInput, selectThisSong, setSongList } from "@utils/reducerUtils"; +import { closeContextMenu, closeDeleteSongModal, closeEditPropertiesModal, closePlaylistModal, closePropertiesModal, processArrowKeysInput, selectThisSong, setSongList } from "@utils/reducerUtils"; import { variants_list } from "@content/index"; import { reducerType } from "@store/index"; @@ -31,6 +31,7 @@ const AlbumDetails = () => { if(arg === contextMenuButtons.ShowInfo){ dispatch({ type: reducerType.SET_PROPERTIES_MODAL, payload: true}); } else if(arg === contextMenuButtons.AddToPlaylist){ dispatch({ type: reducerType.SET_PLAYLIST_MODAL, payload: true}); } else if(arg === contextMenuButtons.EditSong){ dispatch({ type: reducerType.SET_EDIT_SONG_MODAL, payload: true}); } + else if(arg === contextMenuButtons.Delete){ dispatch({ type: reducerType.SET_DELETE_MODAL, payload: true}); } else if(arg === contextMenuButtons.PlayNext && state.songMenuToOpen){ addThisSongToPlayNext([state.songMenuToOpen.id]); closeContextMenu(dispatch); @@ -96,7 +97,7 @@ const AlbumDetails = () => { else if(ev.target.id !== "gsearch" && state.selected >= 1 && state.selected <= state.SongList.length){ dispatch({type: reducerType.SET_SONG_MENU, payload: state.SongList[state.selected - 1]}); if(((ev.ctrlKey || ev.metaKey) && (ev.key === "p" || ev.key === "P" )) || ev.key === "Enter")chooseOption(contextMenuButtons.Play); - else if((ev.ctrlKey || ev.metaKey) && (ev.key === "i" || ev.key === "I"))chooseOption(contextMenuButtons.ShowInfo); + else if((ev.ctrlKey || ev.metaKey) && !ev.shiftKey && (ev.key === "i" || ev.key === "I"))chooseOption(contextMenuButtons.ShowInfo); else if((ev.ctrlKey || ev.metaKey) && ev.shiftKey && (ev.key === "a" || ev.key === "A"))chooseOption(contextMenuButtons.AddToPlaylist); else if((ev.ctrlKey || ev.metaKey) && ev.shiftKey && (ev.key === "n" || ev.key === "N"))chooseOption(contextMenuButtons.PlayNext); else if((ev.ctrlKey || ev.metaKey) && ev.shiftKey && (ev.key === "l" || ev.key === "L"))chooseOption(contextMenuButtons.PlayLater); @@ -194,6 +195,7 @@ const AlbumDetails = () => { closePropertiesModal(dispatch)} /> closeEditPropertiesModal(dispatch)} /> closePlaylistModal(dispatch)} /> + closeDeleteSongModal(dispatch, state.songMenuToOpen, deleteSong)} /> ) } diff --git a/muzik-offline/src/interface/pages/AllTracks.tsx b/muzik-offline/src/interface/pages/AllTracks.tsx index 664c5b6..818f2d5 100644 --- a/muzik-offline/src/interface/pages/AllTracks.tsx +++ b/muzik-offline/src/interface/pages/AllTracks.tsx @@ -2,14 +2,14 @@ import { contextMenuButtons, contextMenuEnum } from "@muziktypes/index"; import { motion } from "framer-motion"; import { useRef, useEffect, useReducer } from "react"; import { ChevronDown, FolderPlus, Shuffle } from "@assets/icons"; -import { AddSongToPlaylistModal, DropDownMenuSmall, EditPropertiesModal, GeneralContextMenu, PropertiesModal, RectangleSongBox } from "@components/index"; +import { AddSongToPlaylistModal, DeleteSongModal, DropDownMenuSmall, EditPropertiesModal, GeneralContextMenu, PropertiesModal, RectangleSongBox } from "@components/index"; import { ViewportList } from 'react-viewport-list'; import { local_albums_db, local_songs_db } from "@database/database"; import { useNavigate } from "react-router-dom"; import { AllTracksState, alltracksReducer, reducerType } from "store"; import { addThisSongToPlayLater, addThisSongToPlayNext, playThisListNow, startPlayingNewSong } from "utils/playerControl"; import "@styles/pages/AllTracks.scss"; -import { closeContextMenu, closeEditPropertiesModal, closePlaylistModal, closePropertiesModal, openFileDialogDND, processArrowKeysInput, processDragEvents, selectSortOption, selectThisSong, setOpenedDDM, setSongList } from "utils/reducerUtils"; +import { closeContextMenu, closeDeleteSongModal, closeEditPropertiesModal, closePlaylistModal, closePropertiesModal, openFileDialogDND, processArrowKeysInput, processDragEvents, selectSortOption, selectThisSong, setOpenedDDM, setSongList } from "utils/reducerUtils"; import Skeleton, { SkeletonTheme } from "react-loading-skeleton"; import 'react-loading-skeleton/dist/skeleton.css'; @@ -29,6 +29,7 @@ const AllTracks = () => { if(arg === contextMenuButtons.ShowInfo){ dispatch({ type: reducerType.SET_PROPERTIES_MODAL, payload: true}); } else if(arg === contextMenuButtons.AddToPlaylist){ dispatch({ type: reducerType.SET_PLAYLIST_MODAL, payload: true}); } else if(arg === contextMenuButtons.EditSong){ dispatch({ type: reducerType.SET_EDIT_SONG_MODAL, payload: true}); } + else if(arg === contextMenuButtons.Delete){ dispatch({ type: reducerType.SET_DELETE_MODAL, payload: true}); } else if(arg === contextMenuButtons.PlayNext && state.songMenuToOpen){ addThisSongToPlayNext([state.songMenuToOpen.id]); closeContextMenu(dispatch); @@ -82,7 +83,7 @@ const AllTracks = () => { else if(ev.target.id !== "gsearch" && state.selected >= 1 && state.selected <= state.SongList.length){ dispatch({type: reducerType.SET_SONG_MENU, payload: state.SongList[state.selected - 1]}); if(((ev.ctrlKey || ev.metaKey) && (ev.key === "p" || ev.key === "P" )) || ev.key === "Enter")chooseOption(contextMenuButtons.Play); - else if((ev.ctrlKey || ev.metaKey) && (ev.key === "i" || ev.key === "I"))chooseOption(contextMenuButtons.ShowInfo); + else if((ev.ctrlKey || ev.metaKey) && !ev.shiftKey && (ev.key === "i" || ev.key === "I"))chooseOption(contextMenuButtons.ShowInfo); else if((ev.ctrlKey || ev.metaKey) && ev.shiftKey && (ev.key === "a" || ev.key === "A"))chooseOption(contextMenuButtons.AddToPlaylist); else if((ev.ctrlKey || ev.metaKey) && ev.shiftKey && (ev.key === "n" || ev.key === "N"))chooseOption(contextMenuButtons.PlayNext); else if((ev.ctrlKey || ev.metaKey) && ev.shiftKey && (ev.key === "l" || ev.key === "L"))chooseOption(contextMenuButtons.PlayLater); @@ -204,6 +205,7 @@ const AllTracks = () => { closePropertiesModal(dispatch)} /> closeEditPropertiesModal(dispatch)} /> closePlaylistModal(dispatch)} /> + closeDeleteSongModal(dispatch, state.songMenuToOpen, deleteSong)} /> ) } diff --git a/muzik-offline/src/interface/pages/GenreView.tsx b/muzik-offline/src/interface/pages/GenreView.tsx index 037e93b..272122b 100644 --- a/muzik-offline/src/interface/pages/GenreView.tsx +++ b/muzik-offline/src/interface/pages/GenreView.tsx @@ -3,7 +3,7 @@ import { useEffect, useReducer, useRef } from "react"; import { useNavigate, useParams } from "react-router-dom"; import { getGenreSongs, secondsToTimeFormat } from "utils"; import { motion } from "framer-motion"; -import { AddSongToPlaylistModal, EditPropertiesModal, GeneralContextMenu, LargeResizableCover, PropertiesModal, RectangleSongBox } from "@components/index"; +import { AddSongToPlaylistModal, DeleteSongModal, EditPropertiesModal, GeneralContextMenu, LargeResizableCover, PropertiesModal, RectangleSongBox } from "@components/index"; import { Play, Shuffle } from "@assets/icons"; import { local_albums_db, local_genres_db } from "@database/database"; import "@styles/pages/GenreView.scss"; @@ -11,7 +11,7 @@ import { ViewportList } from "react-viewport-list"; import { GenreViewState, genreViewReducer } from "store/reducerStore"; import { variants_list } from "@content/index"; import { reducerType } from "store"; -import { closeContextMenu, closeEditPropertiesModal, closePlaylistModal, closePropertiesModal, processArrowKeysInput, selectThisSong, setSongList } from "utils/reducerUtils"; +import { closeContextMenu, closeDeleteSongModal, closeEditPropertiesModal, closePlaylistModal, closePropertiesModal, processArrowKeysInput, selectThisSong, setSongList } from "utils/reducerUtils"; import { addThisSongToPlayLater, addThisSongToPlayNext, playThisListNow, startPlayingNewSong } from "utils/playerControl"; const GenreView = () => { @@ -31,6 +31,7 @@ const GenreView = () => { if(arg === contextMenuButtons.ShowInfo){ dispatch({ type: reducerType.SET_PROPERTIES_MODAL, payload: true}); } else if(arg === contextMenuButtons.AddToPlaylist){ dispatch({ type: reducerType.SET_PLAYLIST_MODAL, payload: true}); } else if(arg === contextMenuButtons.EditSong){ dispatch({ type: reducerType.SET_EDIT_SONG_MODAL, payload: true}); } + else if(arg === contextMenuButtons.Delete){ dispatch({ type: reducerType.SET_DELETE_MODAL, payload: true}); } else if(arg === contextMenuButtons.PlayNext && state.songMenuToOpen){ addThisSongToPlayNext([state.songMenuToOpen.id]); closeContextMenu(dispatch); @@ -101,7 +102,7 @@ const GenreView = () => { else if(ev.target.id !== "gsearch" && state.selected >= 1 && state.selected <= state.SongList.length){ dispatch({type: reducerType.SET_SONG_MENU, payload: state.SongList[state.selected - 1]}); if(((ev.ctrlKey || ev.metaKey) && (ev.key === "p" || ev.key === "P" )) || ev.key === "Enter")chooseOption(contextMenuButtons.Play); - else if((ev.ctrlKey || ev.metaKey) && (ev.key === "i" || ev.key === "I"))chooseOption(contextMenuButtons.ShowInfo); + else if((ev.ctrlKey || ev.metaKey) && !ev.shiftKey && (ev.key === "i" || ev.key === "I"))chooseOption(contextMenuButtons.ShowInfo); else if((ev.ctrlKey || ev.metaKey) && ev.shiftKey && (ev.key === "a" || ev.key === "A"))chooseOption(contextMenuButtons.AddToPlaylist); else if((ev.ctrlKey || ev.metaKey) && ev.shiftKey && (ev.key === "n" || ev.key === "N"))chooseOption(contextMenuButtons.PlayNext); else if((ev.ctrlKey || ev.metaKey) && ev.shiftKey && (ev.key === "l" || ev.key === "L"))chooseOption(contextMenuButtons.PlayLater); @@ -190,6 +191,7 @@ const GenreView = () => { closePropertiesModal(dispatch)} /> closeEditPropertiesModal(dispatch)} /> closePlaylistModal(dispatch)} /> + closeDeleteSongModal(dispatch, state.songMenuToOpen, deleteSong)} /> ) } diff --git a/muzik-offline/src/interface/pages/PlaylistView.tsx b/muzik-offline/src/interface/pages/PlaylistView.tsx index b5734cb..a131544 100644 --- a/muzik-offline/src/interface/pages/PlaylistView.tsx +++ b/muzik-offline/src/interface/pages/PlaylistView.tsx @@ -114,7 +114,7 @@ const PlaylistView = () => { else if(ev.target.id !== "gsearch" && state.selected >= 1 && state.selected <= state.SongList.length){ dispatch({type: reducerType.SET_SONG_MENU, payload: state.SongList[state.selected - 1]}); if(((ev.ctrlKey || ev.metaKey) && (ev.key === "p" || ev.key === "P" )) || ev.key === "Enter")chooseOption(contextMenuButtons.Play); - else if((ev.ctrlKey || ev.metaKey) && (ev.key === "i" || ev.key === "I"))chooseOption(contextMenuButtons.ShowInfo); + else if((ev.ctrlKey || ev.metaKey) && !ev.shiftKey && (ev.key === "i" || ev.key === "I"))chooseOption(contextMenuButtons.ShowInfo); else if((ev.ctrlKey || ev.metaKey) && ev.shiftKey && (ev.key === "a" || ev.key === "A"))chooseOption(contextMenuButtons.AddToPlaylist); else if((ev.ctrlKey || ev.metaKey) && ev.shiftKey && (ev.key === "n" || ev.key === "N"))chooseOption(contextMenuButtons.PlayNext); else if((ev.ctrlKey || ev.metaKey) && ev.shiftKey && (ev.key === "l" || ev.key === "L"))chooseOption(contextMenuButtons.PlayLater); diff --git a/muzik-offline/src/interface/pages/Settings.tsx b/muzik-offline/src/interface/pages/Settings.tsx index 89605b5..aa2d0cb 100644 --- a/muzik-offline/src/interface/pages/Settings.tsx +++ b/muzik-offline/src/interface/pages/Settings.tsx @@ -1,10 +1,10 @@ import { motion } from 'framer-motion'; import { FunctionComponent, useState, useEffect } from 'react'; import "@styles/pages/Settings.scss"; -import { ChevronDown, ComponentIcon, InformationCircleContained, Layout, SettingsIcon, FolderSearch, File } from "@icons/index"; -import { DeleteDiretoryModal, ExportModal, SettingsNavigator, WallpapersSelectionModal } from '@components/index'; +import { ChevronDown, ComponentIcon, InformationCircleContained, Layout, SettingsIcon, WaveForm, FolderSearch, File } from "@icons/index"; +import { DeleteDiretoryModal, EqualizerModal, ExportModal, SettingsNavigator, WallpapersSelectionModal } from '@components/index'; import { selectedSettingENUM } from 'types'; -import { AppearanceSettings, GeneralSettings, AdvancedSettings, AboutSettings, MusicFoldersSettings, ExportSettings } from '@layouts/index'; +import { AppearanceSettings, GeneralSettings, AdvancedSettings, AboutSettings, AudioLabSettings, MusicFoldersSettings, ExportSettings } from '@layouts/index'; import { useSavedObjectStore } from 'store'; type SettingsProps = { @@ -23,11 +23,13 @@ const Settings: FunctionComponent = (props: SettingsProps) => { const {local_store,} = useSavedObjectStore((state) => { return { local_store: state.local_store}; }); const [currentPath, setCurrentPath] = useState(null); const [wallpapersModal, setWallpapersModal] = useState(false); + const [equaliserModal, setEqualiserModal] = useState(false); const [uuids, setUuids] = useState(null); function convertToEnum(arg: string){ if(arg === selectedSettingENUM.General)return selectedSettingENUM.General; else if(arg === selectedSettingENUM.Appearance)return selectedSettingENUM.Appearance; + else if(arg === selectedSettingENUM.AudioLab)return selectedSettingENUM.AudioLab; else if(arg === selectedSettingENUM.MusicFolders)return selectedSettingENUM.MusicFolders; else if(arg === selectedSettingENUM.Security)return selectedSettingENUM.Security; else if(arg === selectedSettingENUM.ExportSongs)return selectedSettingENUM.ExportSongs; @@ -58,6 +60,7 @@ const Settings: FunctionComponent = (props: SettingsProps) => {
+ {/**/} @@ -72,6 +75,8 @@ const Settings: FunctionComponent = (props: SettingsProps) => { return case selectedSettingENUM.Appearance: return setWallpapersModal(true)}/> + case selectedSettingENUM.AudioLab: + return setEqualiserModal(true)}/> case selectedSettingENUM.MusicFolders: return //case selectedSettingENUM.Security: @@ -92,6 +97,7 @@ const Settings: FunctionComponent = (props: SettingsProps) => { setCurrentPath(null)}/> setWallpapersModal(false)}/> setUuids(null)}/> + setEqualiserModal(false)}/> ) } diff --git a/muzik-offline/src/interface/styles/App/App.scss b/muzik-offline/src/interface/styles/App/App.scss index 8dbf635..3c4363a 100644 --- a/muzik-offline/src/interface/styles/App/App.scss +++ b/muzik-offline/src/interface/styles/App/App.scss @@ -92,6 +92,6 @@ body { .windows-app-config{ border: 1.5px solid $songs-container-border-col; border-radius: 8px; - border-right: 2px solid $songs-container-border-col; - border-bottom: 2px solid $songs-container-border-col; + border-right: 1.8px solid $songs-container-border-col; + border-bottom: 1.8px solid $songs-container-border-col; } \ No newline at end of file diff --git a/muzik-offline/src/interface/styles/components/input/DropDownMenuLarge.scss b/muzik-offline/src/interface/styles/components/input/DropDownMenuLarge.scss index dbfa744..a86de09 100644 --- a/muzik-offline/src/interface/styles/components/input/DropDownMenuLarge.scss +++ b/muzik-offline/src/interface/styles/components/input/DropDownMenuLarge.scss @@ -5,17 +5,19 @@ border-radius: 10px; background: $app-theme-col; padding: 5px; - z-index: $z-index-8000; + padding-top: 0px; + padding-bottom: 0px; height: 0px; opacity: 0; + border: 2px solid $songs-container-border-col; + box-shadow: $box-shadow-10-10-25; div{ + margin-top: -5px; + margin-bottom: -5px; h4{ font-size: 13px; - height: 13px; - margin-top: 9px; - margin-bottom: 9px; - width: 100%; + width: calc(100% - 5px); color: $root-app-navigator-hover-col; } } diff --git a/muzik-offline/src/interface/styles/components/input/DropDownMenuSmall.scss b/muzik-offline/src/interface/styles/components/input/DropDownMenuSmall.scss index 5da0587..53124b3 100644 --- a/muzik-offline/src/interface/styles/components/input/DropDownMenuSmall.scss +++ b/muzik-offline/src/interface/styles/components/input/DropDownMenuSmall.scss @@ -8,6 +8,8 @@ z-index: $z-index-8000; height: 0px; opacity: 0; + border: 1px solid $songs-container-border-col; + box-shadow: $box-shadow-10-10-25; div{ h4{ diff --git a/muzik-offline/src/interface/styles/components/modals/EqualizerModal.scss b/muzik-offline/src/interface/styles/components/modals/EqualizerModal.scss new file mode 100644 index 0000000..40abe7b --- /dev/null +++ b/muzik-offline/src/interface/styles/components/modals/EqualizerModal.scss @@ -0,0 +1,151 @@ +@import "../../constants/constants"; + +.EqualizerModal{ + position: absolute; + width: 100vw; + height: 100vh; + top: 0; + left: 0; + background: $blur-element; + flex-direction: column; + display: none; + justify-content: center; + align-items: center; + z-index: $z-index-9999; + + /* Track */ + ::-webkit-scrollbar-track { margin-top: 0px; margin-bottom: 20px; } + + .modal{ + width: 70%; + height: 500px; + padding: 20px; + border: 1px solid $songs-container-border-col; + background: var(--var-songs-container-bg-col); + backdrop-filter: var(--var-bg-blur-150); + -webkit-backdrop-filter: var(--var-bg-blur-150); + border-radius: 20px; + box-shadow: $box-shadow-10-10-25; + + h2{ + font-size: 20px; + margin-top: 0px; + margin-bottom: 20px; + } + + .sub-heading{ + display: flex; + margin-bottom: 20px; + .setting_dropdown{ + display: flex; + position: relative; + .setting_dropdown{ + width: 156px; + height: 40px; + border-radius: 10px; + background: $app-theme-col; + display: flex; + align-items: center; + justify-content: space-between; + + h4{ + font-size: 13px; + height: 13px; + margin-left: 4px; + color: $root-app-navigator-hover-col; + } + + .chevron_icon{ + width: 32px; + height: 32px; + display: flex; + justify-content: center; + align-items: center; + margin-right: 4px; + svg{ + path{ + stroke: $root-app-navigator-hover-col; + } + } + } + } + + .DropDownMenu_container{ + position: absolute; + top: 45px; + z-index: $z-index-9250; + } + } + + input[type="text"]{ + flex-grow: 1; + height: 40px; + border-radius: 10px; + background: $gray-stroke-20; + border: none; + outline: none; + padding: 0px 0px 0px 0px; + padding-left: 4px; + padding-right: 4px; + font-size: 13px; + color: $white-900; + font-family: 'inter-italic-regular', sans-serif; + font-style: italic; + margin-left: 20px; + } + + .button-delete, .button-create{ + width: 156px; + height: 40px; + border-radius: 10px; + background: $app-theme-col; + display: flex; + align-items: center; + justify-content: center; + margin-left: 20px; + color: $root-app-navigator-hover-col; + + } + } + + .equalizer_container{ + height: calc(100% - 105px); + display: flex; + + .decibel_labels{ + height: calc(100% - 33px); + display: flex; + flex-direction: column; + justify-content: space-between; + .decibel_level{ + width: 156px; + height: 40px; + border-radius: 10px; + background: $on-hover-song; + display: flex; + align-items: center; + justify-content: center; + } + } + + .equalizer_border{ + height: 100%; + width: calc(100% - 176px); + border-radius: 10px; + background: $on-hover-song; + margin-left: 20px; + + .equalizer{ + display: flex; + justify-content: space-evenly; + height: 100%; + } + } + } + + } +} + +.EqualizerModal-visible{ + display: flex; +} \ No newline at end of file diff --git a/muzik-offline/src/interface/styles/components/music/FSMusicPlayer.scss b/muzik-offline/src/interface/styles/components/music/FSMusicPlayer.scss index a5e664d..178e865 100644 --- a/muzik-offline/src/interface/styles/components/music/FSMusicPlayer.scss +++ b/muzik-offline/src/interface/styles/components/music/FSMusicPlayer.scss @@ -1,35 +1,32 @@ @import "../../constants/constants"; -.FSMusicPlayer{ - min-width: 980px; +.FSMusicPlayer-hold{ width: 100%; - height: 110vh; + height: 100vh; overflow: hidden; - display: flex; - align-items: center; - z-index: $z-index-9000; - user-select: none; - justify-content: center; position: absolute; - background: $gray-1000; + display: none; + top: 0; + z-index: $z-index-9000; - .FSMusicPlayer-container{ - position: relative; + .FSMusicPlayer{ min-width: 980px; width: 100%; height: 110vh; - - .background-img{ + overflow: hidden; + display: flex; + align-items: center; + user-select: none; + justify-content: center; + background: $gray-1000; + + .FSMusicPlayer-container{ + position: relative; min-width: 980px; width: 100%; height: 110vh; - display: flex; - overflow: hidden; - justify-content: center; - align-items: center; - transform: scale(1.8); - - .image-container{ + + .background-img{ min-width: 980px; width: 100%; height: 110vh; @@ -37,84 +34,103 @@ overflow: hidden; justify-content: center; align-items: center; - filter: blur(30px); - -webkit-filter: blur(30px); - - img{ - display: inline; - min-width: 100%; - margin: 0 auto; - height: 100%; - object-fit: cover; - opacity: 0.7; - } - - svg{ + transform: scale(1.8); + + .image-container{ + min-width: 980px; width: 100%; - height: 100%; - opacity: 0.5; + height: 110vh; + display: flex; + overflow: hidden; + justify-content: center; + align-items: center; + filter: blur(30px); + -webkit-filter: blur(30px); + + img{ + display: inline; + min-width: 100%; + margin: 0 auto; + height: 100%; + object-fit: cover; + opacity: 0.7; + } + + svg{ + width: 100%; + height: 100%; + opacity: 0.5; + } } + + .rotate { animation: rotate 40s linear infinite normal; } + + @keyframes rotate { to { transform: rotate(360deg); } } } - - .rotate { animation: rotate 40s linear infinite normal; } - - @keyframes rotate { to { transform: rotate(360deg); } } - } - - .frontward_facing_player{ - min-width: 980px; - width: 100%; - height: 110vh; - margin-top: -110vh; - background: $blur-element; - position: absolute; - - .navbar_container{ + + .frontward_facing_player{ + min-width: 980px; width: 100%; - } - - .navbar_buttons{ - margin-top: 10px; - display: flex; - justify-content: space-between; - background: $on-hover-song; - width: fit-content; - margin-left: auto; - margin-right: auto; - height: fit-content; - padding: 9px; - border-radius: 24px; - - .close_full_screen_player_btn, .toggle_full_screen_player_btn{ - width: 34px; - height: 34px; + height: 110vh; + margin-top: -110vh; + background: $blur-element; + position: absolute; + + .navbar_container{ + width: 100%; + } + + .navbar_buttons{ + margin-top: 10px; display: flex; - justify-content: center; - align-items: center; - border-radius: 15px; - background: $white-900; - width: 157px; - transition: all 0.1s ease-in-out; - - h3{ - font-size: 13px; - margin-left: 9px; - color: $gray-1000; + justify-content: space-between; + background: $on-hover-song; + width: fit-content; + margin-left: auto; + margin-right: auto; + height: fit-content; + padding: 9px; + border-radius: 24px; + + .close_full_screen_player_btn, .toggle_full_screen_player_btn{ + width: 34px; + height: 34px; + display: flex; + justify-content: center; + align-items: center; + border-radius: 15px; + background: $white-900; + width: 157px; transition: all 0.1s ease-in-out; + + h3{ + font-size: 13px; + margin-left: 9px; + color: $gray-1000; + transition: all 0.1s ease-in-out; + } + } + + .give-margin{ + margin-right: 9px; } } - - .give-margin{ - margin-right: 9px; + + .main_visible_content{ + display: flex; + justify-content: space-around; + height: calc(100vh - 74px); + align-items: center; } } - - .main_visible_content{ - display: flex; - justify-content: space-around; - height: calc(100vh - 74px); - align-items: center; - } } } +} + +.FSMusicPlayer-hold-visible{ + display: block; +} + +.FSMusicPlayer-border{ + border-radius: 8px; } \ No newline at end of file diff --git a/muzik-offline/src/interface/styles/components/navbar/Header.scss b/muzik-offline/src/interface/styles/components/navbar/Header.scss index 49562b4..94c1e66 100644 --- a/muzik-offline/src/interface/styles/components/navbar/Header.scss +++ b/muzik-offline/src/interface/styles/components/navbar/Header.scss @@ -69,6 +69,7 @@ .user_controls{ display: flex; + align-items: center; .user_account{ display: flex; @@ -154,27 +155,36 @@ } .linux{ - width: 120px; + width: 110px; + height: 28px; + .button_area, #maximize{ border-radius: 50%; - width: 32px; + width: 28px; + height: 28px; background: $flags-blur; } .inter_changeable_btn{ - width: 32px; + width: 28px; + height: 28px; margin-left: 12px; margin-right: 11px; #restore{ margin-right: 45px; + top: 2px; .restore_sub_area{ border-radius: 50%; - width: 32px; + width: 28px; } } } + + #close:hover{background: #92929226;} + + #close:active{background: #F1707A;} } .window_controls_section-hidden{ diff --git a/muzik-offline/src/interface/styles/components/sliders/EqualizerSlider.scss b/muzik-offline/src/interface/styles/components/sliders/EqualizerSlider.scss new file mode 100644 index 0000000..fab9e10 --- /dev/null +++ b/muzik-offline/src/interface/styles/components/sliders/EqualizerSlider.scss @@ -0,0 +1,144 @@ +@import "../../constants/constants"; + +.EqualizerSlider{ + margin-top: 20px; + margin-bottom: 20px; + height: calc(100% - 40px); + margin-left: 10px; + margin-right: 10px; + width: 45px; + display: flex; + flex-direction: column; + align-items: center; + overflow: hidden; + + .input-container{ + height: 100%; + display: flex; + align-items: center; + -webkit-transform:rotate(90deg); + -moz-transform:rotate(90deg); + -o-transform:rotate(90deg); + transform:rotate(270deg); + + input[type="range"] { + appearance: none; + -webkit-appearance: none; + width: calc(100vh - 371px); + height: 10px; + background: $white-900; + border-radius: 50px; + background-image: linear-gradient($app-theme-col, $app-theme-col); + background-size: 50% 100%; + background-repeat: no-repeat; + } + + input[type="range"]::-webkit-slider-thumb { + -webkit-appearance: none; + height: 14px; + width: 14px; + border-radius: 50%; + cursor: grab; + box-shadow: 0 0 2px 0 #555; + transition: transform 0.2s ease-in-out; + background: $app-theme-col; + } + + input[type="range"]::-moz-range-thumb { + -webkit-appearance: none; + appearance: none; + height: 14px; + width: 14px; + border-radius: 50%; + cursor: grab; + box-shadow: 0 0 2px 0 #555; + transition: transform 0.2s ease-in-out; + background: $app-theme-col; + } + + input[type="range"]:hover::-webkit-slider-thumb { + background: $app-theme-col; + transform: scale(1.2); + } + + input[type="range"]:hover::-moz-range-thumb { + background: $app-theme-col; + transform: scale(1.2); + } + + input[type="range"]::-webkit-slider-runnable-track { + -webkit-appearance: none; + appearance: none; + box-shadow: none; + border: none; + background: transparent; + } + + input[type="range"]::-moz-range-track { + -webkit-appearance: none; + appearance: none; + box-shadow: none; + border: none; + background: transparent; + } + } + + h4{ + margin-top: 10px; + margin-bottom: 0px; + height: 24px; + font-size: 12px; + width: 45px; + text-align: center; + } +} + +@media only screen and (min-height: 650px){ + .EqualizerSlider{ + .input-container{ + input[type="range"] { + width: calc(90vh - 371px); + } + } + } +} + +@media only screen and (min-height: 750px){ + .EqualizerSlider{ + .input-container{ + input[type="range"] { + width: calc(80vh - 371px); + } + } + } +} + +@media only screen and (min-height: 850px){ + .EqualizerSlider{ + .input-container{ + input[type="range"] { + width: calc(70vh - 371px); + } + } + } +} + +@media only screen and (min-height: 950px){ + .EqualizerSlider{ + .input-container{ + input[type="range"] { + width: calc(60vh - 371px); + } + } + } +} + +@media only screen and (min-height: 1050px){ + .EqualizerSlider{ + .input-container{ + input[type="range"] { + width: calc(50vh - 371px); + } + } + } +} \ No newline at end of file diff --git a/muzik-offline/src/interface/styles/constants/_constants.scss b/muzik-offline/src/interface/styles/constants/_constants.scss index f4e4d1b..1f0184d 100644 --- a/muzik-offline/src/interface/styles/constants/_constants.scss +++ b/muzik-offline/src/interface/styles/constants/_constants.scss @@ -1,4 +1,3 @@ - :root{ color: #ececec;/*white-900*/ font-family: 'inter-regular', sans-serif; @@ -81,7 +80,6 @@ //add more themes... - //wallpaper opacity values [wallpaper-opacity='10']{ --wallpaper-foreground-top-value-opacity: 0.625; diff --git a/muzik-offline/src/interface/styles/layouts/AdvancedSettings.scss b/muzik-offline/src/interface/styles/layouts/AdvancedSettings.scss index b0b70c5..faa2df1 100644 --- a/muzik-offline/src/interface/styles/layouts/AdvancedSettings.scss +++ b/muzik-offline/src/interface/styles/layouts/AdvancedSettings.scss @@ -48,7 +48,7 @@ h4{ font-size: 13px; - height: 13px; + height: min-content; margin-left: 4px; color: $root-app-navigator-hover-col; } diff --git a/muzik-offline/src/interface/styles/layouts/AppearanceSettings.scss b/muzik-offline/src/interface/styles/layouts/AppearanceSettings.scss index 748c92e..f4fd6e7 100644 --- a/muzik-offline/src/interface/styles/layouts/AppearanceSettings.scss +++ b/muzik-offline/src/interface/styles/layouts/AppearanceSettings.scss @@ -3,7 +3,7 @@ .AppearanceSettings{ height: calc(100% - 20px); padding-left: 15px; - padding-right: 8px; + padding-right: 9px; padding-top: 10px; padding-bottom: 10px; diff --git a/muzik-offline/src/interface/styles/layouts/AudioLabSettings.scss b/muzik-offline/src/interface/styles/layouts/AudioLabSettings.scss new file mode 100644 index 0000000..7bc3d36 --- /dev/null +++ b/muzik-offline/src/interface/styles/layouts/AudioLabSettings.scss @@ -0,0 +1,138 @@ +@import "../constants/constants"; + +.AudioLabSettings{ + height: calc(100% - 20px); + padding-left: 15px; + padding-right: 4px; + padding-top: 10px; + padding-bottom: 10px; + + h2{ + font-size: 24px; + height: 24px; + margin-top: 0px; + margin-bottom: 23px; + } + + + .AudioLabSettings_container{ + height: calc(100vh - 127px); + width: 100%; + overflow-y: auto; + + h5{ + font-size: 13px; + height: 13px; + margin-top: 19px; + margin-bottom: 15px; + } + + .audio-backend{ + display: flex; + justify-content: space-between; + border-radius: 16px; + background: $on-hover-song; + padding: 10px; + width: calc(100% - 24px); + align-items: center; + margin-bottom: 20px; + + .logo{ + width: 125px; + height: 125px; + flex-shrink: 0; + display: flex; + overflow: hidden; + justify-content: center; + align-items: center; + margin-left: 25px; + border-radius: 50%; + + img{ + display: inline; + min-width: 100%; + margin: 0 auto; + height: 100%; + object-fit: cover; + } + } + } + + .selected{ + width: calc(100% - 30px); + border: 3px solid $app-theme-col; + } + + .setting{ + display: flex; + align-items: center; + width: calc(100% - 24px); + height: 50px; + border-radius: 16px; + background: $on-hover-song; + justify-content: space-between; + padding-left: 15px; + padding-right: 5px; + margin-bottom: 20px; + position: relative; + overflow: visible; + + h3{ + font-size: 13px; + } + + .setting_dropdown{ + display: flex; + + .setting_dropdown{ + width: 156px; + height: 40px; + border-radius: 10px; + background: $app-theme-col; + display: flex; + align-items: center; + justify-content: space-between; + + h4{ + font-size: 13px; + height: min-content; + margin-left: 4px; + color: $root-app-navigator-hover-col; + } + + .chevron_icon{ + width: 32px; + height: 32px; + display: flex; + justify-content: center; + align-items: center; + margin-right: 4px; + svg{ + path{ + stroke: $root-app-navigator-hover-col; + } + } + } + } + + .greyed_out{ + background: $flags-blur; + } + + .DropDownMenu_container{ + position: absolute; + right: 5px; + top: 55px; + z-index: $z-index-9250; + } + + .DropDownMenu_container_last{ + position: absolute; + right: 5px; + bottom: 55px; + z-index: $z-index-9250; + } + } + } + } +} \ No newline at end of file diff --git a/muzik-offline/src/interface/styles/layouts/GeneralSettings.scss b/muzik-offline/src/interface/styles/layouts/GeneralSettings.scss index 1533c77..187ed8e 100644 --- a/muzik-offline/src/interface/styles/layouts/GeneralSettings.scss +++ b/muzik-offline/src/interface/styles/layouts/GeneralSettings.scss @@ -48,7 +48,7 @@ h4{ font-size: 13px; - height: 13px; + height: min-content; margin-left: 4px; color: $root-app-navigator-hover-col; } diff --git a/muzik-offline/src/store/index.ts b/muzik-offline/src/store/index.ts index c731770..9a07c38 100644 --- a/muzik-offline/src/store/index.ts +++ b/muzik-offline/src/store/index.ts @@ -1,12 +1,14 @@ import { create } from 'zustand'; import { devtools, persist } from 'zustand/middleware'; -import { firstRunState, FSState, MaximisedState, PlayerInterface, PlayingPositionInterface, portState, QueueInterface, SavedDirectoriesInterface, SavedObjectInterface, searchInterface, toastInterface, viewableSideElInterface, wallpaperInterface } from './storeTypes'; +import { firstRunState, FSState, MaximisedState, PlayerInterface, PlayingPositionInterface, portState, QueueInterface, SavedDirectoriesInterface, SavedObjectInterface, searchInterface, toastInterface, viewableSideElInterface, wallpaperInterface, SavedPresetsValues } from './storeTypes'; import { emptyDirectories } from '@database/directories'; import { emptyPlayer } from '@database/player'; import { emptySavedObject } from '@database/saved_object'; import { viewableSideElements } from '@database/side_elements'; import { alltracksReducer, AllTracksState } from './reducerStore'; import { reducerType, AllTracksStateInterface, Action } from './reducerTypes'; +import { AudioLabPreset } from '@muziktypes/index'; +import { premade_audio_labs } from '@content/index'; export type{ AllTracksStateInterface, Action @@ -17,6 +19,18 @@ export { alltracksReducer, AllTracksState, } +export const FlatAudioLab: AudioLabPreset = { + SixtyTwoHz: 50, + OneTwentyFiveHz: 50, + TwoFiftyHz: 50, + FiveHundredHz: 50, + OnekHz: 50, + TwokHz: 50, + FourkHz: 50, + EightkHz: 50, + SixteenkHz: 50, +} + export const useFisrstRunStore = create()( devtools( persist( @@ -186,4 +200,11 @@ export const useHistorySongs = create()( pop_back: () => set((state) => ({ queue: state.queue.slice(0, state.queue.length - 1) })), setQueue: (setTo) => set((_state) => ({ queue: setTo })), }), +) + +export const useSavedPresetsValues = create()( + (set) => ({ + map: premade_audio_labs, + addValue: (key: string, value: AudioLabPreset) => set((state) => ({ map: state.map.set(key, value) })), + }), ) \ No newline at end of file diff --git a/muzik-offline/src/store/reducerStore.ts b/muzik-offline/src/store/reducerStore.ts index 5ed70e0..843538f 100644 --- a/muzik-offline/src/store/reducerStore.ts +++ b/muzik-offline/src/store/reducerStore.ts @@ -18,6 +18,7 @@ export const AllTracksState: AllTracksStateInterface = { isPlaylistModalOpen: false, isPropertiesModalOpen: false, isEditingSongModalOpen: false, + isDeleteSongModalOpen: false, inDragDropRegion: false, }; @@ -34,6 +35,7 @@ export const alltracksReducer = (state: AllTracksStateInterface, action: Action) case reducerType.SET_PROPERTIES_MODAL: return { ...state, isPropertiesModalOpen: action.payload }; case reducerType.SET_EDIT_SONG_MODAL: return { ...state, isEditingSongModalOpen: action.payload }; case reducerType.SET_IN_DRAG_DROP_REGION: return { ...state, inDragDropRegion: action.payload }; + case reducerType.SET_DELETE_MODAL: return { ...state, isDeleteSongModalOpen: action.payload }; default: return state; } }; @@ -47,6 +49,7 @@ export const SearchSongsState: SearchSongInterface = { isPlaylistModalOpen: false, isPropertiesModalOpen: false, isEditingSongModalOpen: false, + isDeleteSongModalOpen: false, }; export const searchSongsReducer = (state: SearchSongInterface, action: Action) => { @@ -59,6 +62,7 @@ export const searchSongsReducer = (state: SearchSongInterface, action: Action) = case reducerType.SET_PLAYLIST_MODAL: return { ...state, isPlaylistModalOpen: action.payload }; case reducerType.SET_PROPERTIES_MODAL: return { ...state, isPropertiesModalOpen: action.payload }; case reducerType.SET_EDIT_SONG_MODAL: return { ...state, isEditingSongModalOpen: action.payload }; + case reducerType.SET_DELETE_MODAL: return { ...state, isDeleteSongModalOpen: action.payload }; default: return state; } }; @@ -73,6 +77,7 @@ export const AlbumDetailsState: AlbumDetailsInterface = { isPlaylistModalOpen: false, isPropertiesModalOpen: false, isEditingSongModalOpen: false, + isDeleteSongModalOpen: false, resizeHeader: false, }; @@ -87,6 +92,7 @@ export const albumDetailsReducer = (state: AlbumDetailsInterface, action: Action case reducerType.SET_PLAYLIST_MODAL: return { ...state, isPlaylistModalOpen: action.payload }; case reducerType.SET_PROPERTIES_MODAL: return { ...state, isPropertiesModalOpen: action.payload }; case reducerType.SET_EDIT_SONG_MODAL: return { ...state, isEditingSongModalOpen: action.payload }; + case reducerType.SET_DELETE_MODAL: return { ...state, isDeleteSongModalOpen: action.payload }; case reducerType.SET_RESIZE_HEADER: return { ...state, resizeHeader: action.payload }; default: return state; } @@ -179,6 +185,7 @@ export const GenreViewState: GenreViewInterface = { isPlaylistModalOpen: false, isPropertiesModalOpen: false, isEditingSongModalOpen: false, + isDeleteSongModalOpen: false, resizeHeader: false, }; @@ -193,6 +200,7 @@ export const genreViewReducer = (state: GenreViewInterface, action: Action) => { case reducerType.SET_PLAYLIST_MODAL: return { ...state, isPlaylistModalOpen: action.payload }; case reducerType.SET_PROPERTIES_MODAL: return { ...state, isPropertiesModalOpen: action.payload }; case reducerType.SET_EDIT_SONG_MODAL: return { ...state, isEditingSongModalOpen: action.payload }; + case reducerType.SET_DELETE_MODAL: return { ...state, isDeleteSongModalOpen: action.payload }; case reducerType.SET_RESIZE_HEADER: return { ...state, resizeHeader: action.payload }; default: return state; } diff --git a/muzik-offline/src/store/reducerTypes.ts b/muzik-offline/src/store/reducerTypes.ts index 78e2cb1..13e8a28 100644 --- a/muzik-offline/src/store/reducerTypes.ts +++ b/muzik-offline/src/store/reducerTypes.ts @@ -95,6 +95,7 @@ export interface AllTracksStateInterface{ isPlaylistModalOpen: boolean, isPropertiesModalOpen: boolean, isEditingSongModalOpen: boolean, + isDeleteSongModalOpen: boolean, inDragDropRegion: boolean, } @@ -107,6 +108,7 @@ export interface SearchSongInterface{ isPlaylistModalOpen: boolean, isPropertiesModalOpen: boolean, isEditingSongModalOpen: boolean, + isDeleteSongModalOpen: boolean, } export interface AlbumDetailsInterface{ @@ -119,6 +121,7 @@ export interface AlbumDetailsInterface{ isPlaylistModalOpen: boolean, isPropertiesModalOpen: boolean, isEditingSongModalOpen: boolean, + isDeleteSongModalOpen: boolean, resizeHeader: boolean; } @@ -178,6 +181,7 @@ export interface GenreViewInterface{ isPlaylistModalOpen: boolean, isPropertiesModalOpen: boolean, isEditingSongModalOpen: boolean, + isDeleteSongModalOpen: boolean, resizeHeader: boolean; } diff --git a/muzik-offline/src/store/storeTypes.ts b/muzik-offline/src/store/storeTypes.ts index 96abc35..da64da7 100644 --- a/muzik-offline/src/store/storeTypes.ts +++ b/muzik-offline/src/store/storeTypes.ts @@ -2,7 +2,7 @@ import { SavedDirectories } from "@database/directories"; import { Player } from "@database/player"; import { SavedObject } from "@database/saved_object"; import { viewableSideEl } from "@database/side_elements"; -import { toast } from "@muziktypes/index"; +import { AudioLabPreset, toast } from "@muziktypes/index"; export interface MaximisedState { isMaximised: boolean; @@ -79,4 +79,9 @@ export interface QueueInterface{ push_front: (song: number) => void; pop_back: () => void; setQueue: (setTo: number[]) => void; +} + +export interface SavedPresetsValues{ + map: Map; + addValue: (key: string, value: AudioLabPreset) => void; } \ No newline at end of file diff --git a/muzik-offline/src/types/index.ts b/muzik-offline/src/types/index.ts index 24ede02..0ef1e59 100644 --- a/muzik-offline/src/types/index.ts +++ b/muzik-offline/src/types/index.ts @@ -1,6 +1,7 @@ export enum selectedSettingENUM { General = "General", Appearance = "Appearance", + AudioLab = "AudioLab", MusicFolders = "Music Folders", Security = "Security", ExportSongs = "Export Songs", @@ -17,8 +18,13 @@ export enum selectedGeneralSettingEnum{ UpcomingHistoryLimit = "UpcomingHistoryLimit", SeekStepAmount = "SeekStepAmount", SongLengthORremaining = "SongLengthORremaining", + AudioLabPreset = "AudioLabPreset", AlwaysRoundedCornersWindows = "AlwaysRoundedCornersWindows", AutoStartApp = "AutoStartApp", + AudioQuality = "AudioQuality", + PlayBackSpeed = "PlayBackSpeed", + AudioTransition = "AudioTransition", + OutputDevice = "OutputDevice" } export enum OSTYPEenum{ @@ -162,6 +168,18 @@ export interface PlaylistMD { length: string; } +export interface AudioLabPreset{ + SixtyTwoHz: number; + OneTwentyFiveHz: number; + TwoFiftyHz: number; + FiveHundredHz: number; + OnekHz: number; + TwokHz: number; + FourkHz: number; + EightkHz: number; + SixteenkHz: number; +} + export interface Payload { event: string; seek_direction: string; diff --git a/muzik-offline/src/utils/playerControl.ts b/muzik-offline/src/utils/playerControl.ts index 0551059..fbf214d 100644 --- a/muzik-offline/src/utils/playerControl.ts +++ b/muzik-offline/src/utils/playerControl.ts @@ -5,6 +5,7 @@ import { invoke } from "@tauri-apps/api/core"; import { SavedObject } from "@database/index"; import { local_playlists_db, local_songs_db } from "@database/database"; import { getNullRandomCover } from "."; +import { RepeatingLevel } from "@database/player"; export const addThisSongToPlayNext = async(songids: number[]) => { //get the song queue @@ -91,7 +92,14 @@ export async function startPlayingNewSong(song: Song){ temp.lengthOfSongInSeconds = song.duration_seconds; temp.isPlaying = true; const volume = (useSavedObjectStore.getState().local_store.Volume / 100); - await invoke("load_and_play_song_from_path", { soundPath: song.path, volume: volume }); + await invoke("load_and_play_song_from_path", { + soundPath: song.path, + player: useSavedObjectStore.getState().local_store.player, + volume: volume, + duration: song.duration_seconds, + playBackSpeed: parseInt(useSavedObjectStore.getState().local_store.PlayBackSpeed), + fadeInOut: useSavedObjectStore.getState().local_store.AudioTransition === "Yes" ? true : false + }); await invoke("update_metadata", { uuid: (song.cover_uuid !== null ? song.uuid : getNullRandomCover(song.id)) }); await invoke("set_player_state", { state: playerState.Playing}); usePlayerStore.getState().setPlayer(temp); @@ -104,7 +112,14 @@ export async function loadNewSong(song: Song){ temp.lengthOfSongInSeconds = song.duration_seconds; temp.isPlaying = false; const volume = (useSavedObjectStore.getState().local_store.Volume / 100); - await invoke("load_a_song_from_path", { soundPath: song.path, volume: volume }); + await invoke("load_a_song_from_path", { + soundPath: song.path, + player: useSavedObjectStore.getState().local_store.player, + volume: volume, + duration: song.duration_seconds, + playBackSpeed: parseInt(useSavedObjectStore.getState().local_store.PlayBackSpeed), + fadeInOut: useSavedObjectStore.getState().local_store.AudioTransition === "Yes" ? true : false + }); await invoke("update_metadata", { uuid: (song.cover_uuid !== null ? song.uuid : getNullRandomCover(song.id)) }); usePlayerStore.getState().setPlayer(temp); setDiscordActivity(song); @@ -112,7 +127,7 @@ export async function loadNewSong(song: Song){ export async function playSong(){ if(usePlayerStore.getState().Player.playingSongMetadata){ - await invoke("resume_playing"); + await invoke("resume_playing", {player: useSavedObjectStore.getState().local_store.player}); await invoke("set_player_state", { state: playerState.Playing}); let temp = usePlayerStore.getState().Player; temp.isPlaying = true; @@ -124,7 +139,7 @@ export async function playSong(){ export async function pauseSong(){ if(usePlayerStore.getState().Player.playingSongMetadata){ - await invoke("pause_song"); + await invoke("pause_song", {player: useSavedObjectStore.getState().local_store.player}); await invoke("set_player_state", { state: playerState.Paused}); let temp = usePlayerStore.getState().Player; temp.isPlaying = false; @@ -136,7 +151,7 @@ export async function pauseSong(){ export async function stopSong(){ if(usePlayerStore.getState().Player.playingSongMetadata){ - await invoke("stop_song"); + await invoke("stop_song", {player: useSavedObjectStore.getState().local_store.player}); await invoke("set_player_state", { state: playerState.Stopped}); let temp = usePlayerStore.getState().Player; temp.playingSongMetadata = null; @@ -161,7 +176,8 @@ export async function dragSeeker(){ export function changeSeekerPosition(value: number){ if(usePlayerStore.getState().Player.playingSongMetadata === null)return; const position = (value / 100) * usePlayerStore.getState().Player.lengthOfSongInSeconds; - invoke("seek_to", {position: position}).then(() => {if(usePlayerStore.getState().Player.wasPlayingBeforePause === true)playSong()}); + invoke("seek_to", {player: useSavedObjectStore.getState().local_store.player, position: position}) + .then(() => {if(usePlayerStore.getState().Player.wasPlayingBeforePause === true)playSong()}); } export function changeSeekerPositionBtnPress(isDecreasing: boolean){ @@ -182,6 +198,7 @@ export function changeSeekerPositionBtnPress(isDecreasing: boolean){ usePlayingPositionSec.getState().setPosition(position + delta_amount); } invoke("seek_by", { + player: useSavedObjectStore.getState().local_store.player, delta: delta_amount ? delta_amount : 10.0 }).then(() => {if(usePlayerStore.getState().Player.wasPlayingBeforePause === true)playSong()}); } @@ -193,7 +210,7 @@ export function changeVolumeLevel(value: number){ useSavedObjectStore.getState().setStore(temp); } -export async function setVolumeLevel(value: number){await invoke("set_volume", {volume: value / 100});} +export async function setVolumeLevel(value: number){await invoke("set_volume", {player: useSavedObjectStore.getState().local_store.player, volume: value / 100});} export function changeVolumeLevelBtnPress(isDecreasing: boolean){ if(isDecreasing === true){ @@ -221,16 +238,16 @@ export async function shuffleToggle(){ export async function repeatToggle(){ let temp = usePlayerStore.getState().Player; - temp.repeatingLevel = usePlayerStore.getState().Player.repeatingLevel + 1 > 2 ? 0 : (usePlayerStore.getState().Player.repeatingLevel + 1) as 0 | 1 | 2; + temp.repeatingLevel = temp.repeatingLevel === RepeatingLevel.NO_REPEAT ? RepeatingLevel.REPEAT_ONE : temp.repeatingLevel === RepeatingLevel.REPEAT_ONE ? RepeatingLevel.REPEAT_ALL : RepeatingLevel.NO_REPEAT; usePlayerStore.getState().setPlayer(temp); - await invoke("mlo_set_repeat_list", {repeatList: temp.repeatingLevel > 0 ? true : false}); + await invoke("mlo_set_repeat_list", {repeatList: temp.repeatingLevel !== RepeatingLevel.NO_REPEAT ? true : false}); } export function reconfigurePlayer_AtEndOfSong(){ - if(usePlayerStore.getState().Player.repeatingLevel === 0 || usePlayerStore.getState().Player.repeatingLevel === 1){ + if(usePlayerStore.getState().Player.repeatingLevel === RepeatingLevel.NO_REPEAT || usePlayerStore.getState().Player.repeatingLevel === RepeatingLevel.REPEAT_ALL){ playNextSong(); } - else if(usePlayerStore.getState().Player.repeatingLevel === 2){ + else if(usePlayerStore.getState().Player.repeatingLevel === RepeatingLevel.REPEAT_ONE){ const song = usePlayerStore.getState().Player.playingSongMetadata; if(song === null)return; usePlayingPositionSec.getState().setPosition(0); @@ -246,8 +263,8 @@ export async function playNextSong(){ usePlayingPositionSec.getState().setPosition(0); usePlayingPosition.getState().setPosition(0); useUpcomingSongs.getState().dequeue(); - if(useUpcomingSongs.getState().queue.length === 0 && usePlayerStore.getState().Player.repeatingLevel === 0)await stopSong(); - else if(useUpcomingSongs.getState().queue.length === 0 && usePlayerStore.getState().Player.repeatingLevel === 1){ + if(useUpcomingSongs.getState().queue.length === 0 && usePlayerStore.getState().Player.repeatingLevel === RepeatingLevel.NO_REPEAT)await stopSong(); + else if(useUpcomingSongs.getState().queue.length === 0 && usePlayerStore.getState().Player.repeatingLevel === RepeatingLevel.REPEAT_ALL){ const limit = Number.parseInt(useSavedObjectStore.getState().local_store.UpcomingHistoryLimit); get_next_batch(limit).then(async() => { if(useUpcomingSongs.getState().queue.length >= 1){ diff --git a/muzik-offline/src/utils/reducerUtils.ts b/muzik-offline/src/utils/reducerUtils.ts index 29c6b89..80b94e9 100644 --- a/muzik-offline/src/utils/reducerUtils.ts +++ b/muzik-offline/src/utils/reducerUtils.ts @@ -1,9 +1,12 @@ -import { Song } from "@muziktypes/index"; +import { Song, toastType } from "@muziktypes/index"; import { Action, reducerType } from "@store/reducerTypes"; import { getCurrentWebview } from "@tauri-apps/api/webview"; import { open } from '@tauri-apps/plugin-dialog'; import { appConfigDir } from '@tauri-apps/api/path'; import { reloadLibrary } from "."; +import { invoke } from "@tauri-apps/api/core"; +import { useToastStore } from "@store/index"; +import { local_albums_db, local_artists_db, local_genres_db, local_songs_db } from "@database/database"; export function selectThisSong(index: number, dispatch: React.Dispatch){ dispatch({ type: reducerType.SET_SELECTED, payload: index }); @@ -57,6 +60,52 @@ export function closeEditPropertiesModal(dispatch: React.Dispatch){ closeContextMenu(dispatch); } +export async function closeDeleteSongModal(dispatch: React.Dispatch, song: Song | null, deleteSong: boolean){ + dispatch({ type: reducerType.SET_DELETE_MODAL, payload: false }); + closeContextMenu(dispatch); + if(!deleteSong)return; + if(!song){ + useToastStore.getState().setToast({ + message: "Song not found", + type: toastType.error, + title: "Delete Song Error", + timeout: 3000 + }); + return; + } + const album_appearance_count = await local_albums_db.albums.where('title').equals(song.album).count(); + const artist_appearance_count = await local_artists_db.artists.where('artist_name').equals(song.artist).count(); + const genre_appearance_count = await local_genres_db.genres.where('title').equals(song.genre).count(); + + invoke('delete_song_metadata', { + path: song.path, + album: song.album, + albumAppearanceCount: album_appearance_count, + artist: song.artist, + artistAppearanceCount: artist_appearance_count, + genre: song.genre, + genreAppearanceCount: genre_appearance_count + }).then(() => { + local_songs_db.songs.where('uuid').equals(song.uuid).delete(); + if (album_appearance_count <= 1) local_albums_db.albums.where('title').equals(song.album).delete(); + if (artist_appearance_count <= 1) local_artists_db.artists.where('artist_name').equals(song.artist).delete(); + if (genre_appearance_count <= 1) local_genres_db.genres.where('title').equals(song.genre).delete(); + useToastStore.getState().setToast({ + message: `${song.name} was sent to trash`, + type: toastType.success, + title: "Delete Song", + timeout: 3000 + }); + }).catch((err: any) => { + useToastStore.getState().setToast({ + message: err, + type: toastType.error, + title: "Delete Song Error", + timeout: 3000 + }); + }); +} + export function closeContextMenu( dispatch: React.Dispatch, e?: React.MouseEvent,){ if(e){ if(e.target !== e.currentTarget)return;