diff --git a/src-tauri/Info.plist b/src-tauri/Info.plist
index fe253ec7b..61e8c7229 100644
--- a/src-tauri/Info.plist
+++ b/src-tauri/Info.plist
@@ -6,5 +6,18 @@
Request camera access for WebRTC
NSMicrophoneUsageDescription
Request microphone access for WebRTC
+ CFBundleURLTypes
+
+
+ CFBundleURLName
+
+ Museeks
+ CFBundleURLSchemes
+
+
+ museeks
+
+
+
diff --git a/src-tauri/src/libs/events.rs b/src-tauri/src/libs/events.rs
index 0ebeb0118..e92dedfa9 100644
--- a/src-tauri/src/libs/events.rs
+++ b/src-tauri/src/libs/events.rs
@@ -12,6 +12,7 @@ pub enum IPCEvent<'a> {
PlaybackPlayPause,
PlaybackPrevious,
PlaybackNext,
+ PlaybackStart,
// Scan-related events
LibraryScanProgress,
// Menu-related events
diff --git a/src-tauri/src/libs/file_associations.rs b/src-tauri/src/libs/file_associations.rs
new file mode 100644
index 000000000..171f1027a
--- /dev/null
+++ b/src-tauri/src/libs/file_associations.rs
@@ -0,0 +1,94 @@
+use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
+use std::path::PathBuf;
+use tauri::{AppHandle, Emitter, Manager};
+use tauri_plugin_dialog::{DialogExt, MessageDialogKind};
+
+use crate::libs::track::get_track_from_file;
+use crate::libs::utils::is_file_valid;
+use crate::plugins::database::SUPPORTED_TRACKS_EXTENSIONS;
+
+use super::events::IPCEvent;
+
+/**
+ * Linux + Windows
+ */
+#[cfg(any(windows, target_os = "linux"))]
+pub fn setup_file_associations(app: tauri::App) {
+ let mut files = Vec::new();
+
+ // NOTICE: `args` may include URL protocol (`your-app-protocol://`)
+ // or arguments (`--`) if your app supports them.
+ // files may aslo be passed as `file://path/to/file`
+ for maybe_file in std::env::args().skip(1) {
+ // skip flags like -f or --flag
+ if maybe_file.starts_with("-") {
+ continue;
+ }
+
+ // handle `file://` path urls and skip other urls
+ if let Ok(url) = url::Url::parse(&maybe_file) {
+ if let Ok(path) = url.to_file_path() {
+ files.push(path);
+ }
+ } else {
+ files.push(PathBuf::from(maybe_file))
+ }
+ }
+
+ handle_file_associations(app.handle().clone(), files);
+}
+
+/**
+ * macOS
+ */
+#[cfg(target_os = "macos")]
+pub fn setup_file_associations(app: &AppHandle, event: tauri::RunEvent) {
+ if let tauri::RunEvent::Opened { urls } = event {
+ let files = urls
+ .into_iter()
+ .filter_map(|url| url.to_file_path().ok())
+ .collect::>();
+
+ handle_file_associations(app.clone(), files);
+ }
+}
+
+/**
+ * Handle the app file association.
+ * For audio files, it will scan the files, create a queue and play it, *without* adding the tracks to the library.
+ * For playlists files, not implemented.
+ */
+fn handle_file_associations(app_handle: AppHandle, mut files: Vec) {
+ files = files
+ .into_iter()
+ .filter(|path| is_file_valid(path, &SUPPORTED_TRACKS_EXTENSIONS))
+ .collect();
+
+ // This is for the `asset:` protocol to work, ensuring access to the files
+ let asset_protocol_scope = app_handle.asset_protocol_scope();
+
+ for file in &files {
+ let _ = asset_protocol_scope.allow_file(file);
+ }
+
+ // Build a list of tracks, without importing them to the library
+ let queue = files
+ .par_iter()
+ .map(|path| get_track_from_file(&path))
+ .flatten()
+ .collect::>();
+
+ let window = app_handle.get_webview_window("main").unwrap();
+
+ match window.emit(IPCEvent::PlaybackStart.as_ref(), queue) {
+ Ok(_) => (),
+ Err(err) => app_handle
+ .dialog()
+ .message(format!(
+ "Something went wrong when attempting to play this file: {}",
+ err
+ ))
+ .kind(MessageDialogKind::Error)
+ .show(|_| {}),
+ };
+}
diff --git a/src-tauri/src/libs/mod.rs b/src-tauri/src/libs/mod.rs
index 7947cb112..0e5a3d9b9 100644
--- a/src-tauri/src/libs/mod.rs
+++ b/src-tauri/src/libs/mod.rs
@@ -1,3 +1,5 @@
pub mod error;
pub mod events;
+pub mod file_associations;
+pub mod track;
pub mod utils;
diff --git a/src-tauri/src/libs/track.rs b/src-tauri/src/libs/track.rs
new file mode 100644
index 000000000..1fafb913c
--- /dev/null
+++ b/src-tauri/src/libs/track.rs
@@ -0,0 +1,126 @@
+use std::path::PathBuf;
+
+use bonsaidb::core::schema::Collection;
+use lofty::file::{AudioFile, TaggedFileExt};
+use lofty::tag::{Accessor, ItemKey};
+use log::{error, warn};
+use serde::{Deserialize, Serialize};
+use ts_rs::TS;
+use uuid::Uuid;
+
+/**
+ * Track
+ * represent a single track, id and path should be unique
+ */
+#[derive(Debug, Clone, Serialize, Deserialize, Collection, TS)]
+#[collection(name="tracks", primary_key = String)]
+#[ts(export, export_to = "../../src/generated/typings/index.ts")]
+pub struct Track {
+ #[natural_id]
+ pub _id: String,
+ pub title: String,
+ pub album: String,
+ pub artists: Vec,
+ pub genres: Vec,
+ pub year: Option,
+ pub duration: u32,
+ pub track: NumberOf,
+ pub disk: NumberOf,
+ pub path: PathBuf,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, TS)]
+#[ts(export, export_to = "../../src/generated/typings/index.ts")]
+pub struct NumberOf {
+ pub no: Option,
+ pub of: Option,
+}
+
+/**
+ * Generate a Track struct from a Path, or nothing if it is not a valid audio
+ * file
+ */
+pub fn get_track_from_file(path: &PathBuf) -> Option