Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
505 changes: 505 additions & 0 deletions desktop/src-tauri/Cargo.lock

Large diffs are not rendered by default.

8 changes: 6 additions & 2 deletions desktop/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
[package]
name = "harmonia-desktop"
version = "0.1.0"
edition = "2021"
edition = "2024"

[lib]
name = "harmonia_desktop_lib"
Expand All @@ -18,7 +18,11 @@ tauri-plugin-opener = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
reqwest = { version = "0.12", features = ["rustls-tls", "json"], default-features = false }
tokio = { version = "1", features = ["rt", "rt-multi-thread", "time", "sync"] }
tokio = { version = "1", features = ["rt", "rt-multi-thread", "sync", "time", "macros"] }
snafu = "0.8"
tracing = "0.1"
rand = "0.9"
akroasis-core = { path = "../../akroasis/shared/akroasis-core" }

[profile.release]
panic = "abort"
Expand Down
4 changes: 2 additions & 2 deletions desktop/src-tauri/src/dsp/commands.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use tauri::State;

use super::DspController;
use super::config::{
CompressorConfig, CrossfeedConfig, DspConfig, EqBand, ReplayGainConfig, VolumeConfig,
};
use super::presets::{built_in_presets, EqPreset};
use super::DspController;
use super::presets::{EqPreset, built_in_presets};

#[tauri::command]
pub fn get_dsp_config(controller: State<'_, DspController>) -> DspConfig {
Expand Down
23 changes: 23 additions & 0 deletions desktop/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@ mod config;
mod dsp;
mod playback;

use playback::PlaybackEngine;

pub fn run() {
let engine = PlaybackEngine::new().expect("audio engine failed to initialise");

tauri::Builder::default()
.plugin(tauri_plugin_opener::init())
.manage(dsp::DspController::new())
.manage(playback::podcast::PodcastController::new())
.manage(playback::audiobook::AudiobookController::new())
.manage(engine)
.invoke_handler(tauri::generate_handler![
commands::health_check,
commands::get_server_url,
Expand Down Expand Up @@ -54,6 +59,24 @@ pub fn run() {
playback::audiobook::sleep_timer_get,
playback::audiobook::audiobook_get_position,
playback::audiobook::audiobook_update_offset,
playback::commands::play_track,
playback::commands::pause,
playback::commands::resume,
playback::commands::stop,
playback::commands::seek,
playback::commands::next_track,
playback::commands::previous_track,
playback::commands::playback_set_volume,
playback::commands::playback_get_volume,
playback::commands::queue_add,
playback::commands::queue_remove,
playback::commands::queue_clear,
playback::commands::queue_move,
playback::commands::queue_get,
playback::commands::set_repeat_mode,
playback::commands::set_shuffle,
playback::commands::get_playback_state,
playback::commands::get_signal_path,
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
Expand Down
201 changes: 201 additions & 0 deletions desktop/src-tauri/src/playback/commands.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
//! Tauri IPC commands for playback control.

use tauri::State;

use super::{PlaybackEngine, PlaybackState, QueueEntry, QueueState, RepeatMode, SignalPathInfo};

// ---------------------------------------------------------------------------
// Transport
// ---------------------------------------------------------------------------

#[tauri::command]
pub(crate) async fn play_track(
entry: QueueEntry,
base_url: String,
token: Option<String>,
app: tauri::AppHandle,
engine: State<'_, PlaybackEngine>,
) -> Result<(), String> {
engine
.play_entry(entry, &base_url, token.as_deref(), app)
.await
.map_err(|e| e.to_string())
}

#[tauri::command]
pub(crate) async fn pause(
app: tauri::AppHandle,
engine: State<'_, PlaybackEngine>,
) -> Result<(), String> {
engine.pause(&app).await.map_err(|e| e.to_string())
}

#[tauri::command]
pub(crate) async fn resume(
app: tauri::AppHandle,
engine: State<'_, PlaybackEngine>,
) -> Result<(), String> {
engine.resume(&app).await.map_err(|e| e.to_string())
}

#[tauri::command]
pub(crate) async fn stop(
app: tauri::AppHandle,
engine: State<'_, PlaybackEngine>,
) -> Result<(), String> {
engine.stop(&app).await;
Ok(())
}

#[tauri::command]
pub(crate) async fn seek(
position_ms: u64,
engine: State<'_, PlaybackEngine>,
) -> Result<(), String> {
engine.seek(position_ms).await.map_err(|e| e.to_string())
}

#[tauri::command]
pub(crate) async fn next_track(
app: tauri::AppHandle,
engine: State<'_, PlaybackEngine>,
) -> Result<(), String> {
let queue = engine.queue_state().await;
let next = queue.entries.get(queue.current_index + 1).cloned();

if let Some(entry) = next {
engine
.play_entry(entry, "", None, app)
.await
.map_err(|e| e.to_string())
} else {
engine.stop(&app).await;
Ok(())
}
}

#[tauri::command]
pub(crate) async fn previous_track(
app: tauri::AppHandle,
engine: State<'_, PlaybackEngine>,
) -> Result<(), String> {
let state = engine.playback_state().await;
let prev = engine.go_previous(state.position_ms).await;

if let Some(entry) = prev {
engine
.play_entry(entry, "", None, app)
.await
.map_err(|e| e.to_string())
} else {
Ok(())
}
}

// ---------------------------------------------------------------------------
// Volume
// ---------------------------------------------------------------------------

#[tauri::command]
pub(crate) async fn playback_set_volume(
level: f64,
engine: State<'_, PlaybackEngine>,
) -> Result<(), String> {
engine.set_volume(level).await;
Ok(())
}

#[tauri::command]
pub(crate) async fn playback_get_volume(engine: State<'_, PlaybackEngine>) -> Result<f64, String> {
Ok(engine.volume().await)
}

// ---------------------------------------------------------------------------
// Queue management
// ---------------------------------------------------------------------------

#[tauri::command]
pub(crate) async fn queue_add(
entries: Vec<QueueEntry>,
app: tauri::AppHandle,
engine: State<'_, PlaybackEngine>,
) -> Result<(), String> {
engine.queue_add(entries, &app).await;
Ok(())
}

#[tauri::command]
pub(crate) async fn queue_remove(
index: usize,
app: tauri::AppHandle,
engine: State<'_, PlaybackEngine>,
) -> Result<(), String> {
engine
.queue_remove(index, &app)
.await
.map_err(|e| e.to_string())
}

#[tauri::command]
pub(crate) async fn queue_clear(
app: tauri::AppHandle,
engine: State<'_, PlaybackEngine>,
) -> Result<(), String> {
engine.queue_clear(&app).await;
Ok(())
}

#[tauri::command]
pub(crate) async fn queue_move(
from: usize,
to: usize,
app: tauri::AppHandle,
engine: State<'_, PlaybackEngine>,
) -> Result<(), String> {
engine
.queue_move(from, to, &app)
.await
.map_err(|e| e.to_string())
}

#[tauri::command]
pub(crate) async fn queue_get(engine: State<'_, PlaybackEngine>) -> Result<QueueState, String> {
Ok(engine.queue_state().await)
}

#[tauri::command]
pub(crate) async fn set_repeat_mode(
mode: RepeatMode,
engine: State<'_, PlaybackEngine>,
) -> Result<(), String> {
engine.set_repeat_mode(mode).await;
Ok(())
}

#[tauri::command]
pub(crate) async fn set_shuffle(
enabled: bool,
app: tauri::AppHandle,
engine: State<'_, PlaybackEngine>,
) -> Result<(), String> {
engine.set_shuffle(enabled, &app).await;
Ok(())
}

// ---------------------------------------------------------------------------
// State query
// ---------------------------------------------------------------------------

#[tauri::command]
pub(crate) async fn get_playback_state(
engine: State<'_, PlaybackEngine>,
) -> Result<PlaybackState, String> {
Ok(engine.playback_state().await)
}

#[tauri::command]
pub(crate) async fn get_signal_path(
engine: State<'_, PlaybackEngine>,
) -> Result<SignalPathInfo, String> {
Ok(engine.signal_path())
}
Loading
Loading