phase 3(android): unify connect/disconnect/toggle_*/get_status commands
Some checks failed
Mirror to GitHub / mirror (push) Failing after 37s
Build Release Binaries / build-amd64 (push) Failing after 3m49s

Step 3 of the Tauri Android rewrite was still returning "audio backend not
yet wired on Android (step 3)" because the cfg-gated Android stubs for
connect/disconnect/toggle_mic/toggle_speaker/get_status were shadowing the
real commands. Now that CallEngine::start() has a real Android body (phase
3, commit fdbe502), the gates are unnecessary.

- Drop the #[cfg(not(target_os = "android"))] gates from all five
  engine-backed Tauri commands.
- Delete the Android stub block (~50 LOC of "not connected" boilerplate).
- Ungate `use engine::CallEngine;` and the AppState.engine field so both
  targets share the same Mutex<Option<CallEngine>>.
- CallEngine::stop() now calls crate::wzp_native::audio_stop() on Android so
  the mic + speaker are released between calls, matching the desktop
  behaviour where dropping _audio_handle tears down CPAL.

Direct-call flow on Android: peer sends DirectCallOffer → user accepts via
answer_call → relay sends signal "setup" event → main.ts auto-invokes
connect(relay, room) → CallEngine::start() runs the Android branch →
wzp_native::audio_start() brings up Oboe → send/recv tasks stream PCM
through the dlopen boundary.
This commit is contained in:
Siavash Sameni
2026-04-09 18:53:54 +04:00
parent fdbe502524
commit 5beea7de40
2 changed files with 11 additions and 66 deletions

View File

@@ -17,9 +17,9 @@ mod engine;
#[cfg(target_os = "android")]
mod wzp_native;
// CallEngine is only referenced from the non-Android connect/disconnect/etc
// commands; the Android stubs return errors directly.
#[cfg(not(target_os = "android"))]
// CallEngine has a unified impl on both targets now — the Android branch of
// CallEngine::start() routes audio through the standalone wzp-native cdylib
// (loaded via the wzp_native module below), the desktop branch uses CPAL.
use engine::CallEngine;
use serde::Serialize;
@@ -91,7 +91,6 @@ struct CallStatus {
}
struct AppState {
#[cfg(not(target_os = "android"))]
engine: Mutex<Option<CallEngine>>,
signal: Arc<Mutex<SignalState>>,
}
@@ -229,7 +228,6 @@ fn get_app_info() -> Result<AppInfo, String> {
})
}
#[cfg(not(target_os = "android"))]
#[tauri::command]
async fn connect(
state: tauri::State<'_, Arc<AppState>>,
@@ -265,7 +263,6 @@ async fn connect(
}
}
#[cfg(not(target_os = "android"))]
#[tauri::command]
async fn disconnect(state: tauri::State<'_, Arc<AppState>>) -> Result<String, String> {
let mut engine_lock = state.engine.lock().await;
@@ -277,7 +274,6 @@ async fn disconnect(state: tauri::State<'_, Arc<AppState>>) -> Result<String, St
}
}
#[cfg(not(target_os = "android"))]
#[tauri::command]
async fn toggle_mic(state: tauri::State<'_, Arc<AppState>>) -> Result<bool, String> {
let engine_lock = state.engine.lock().await;
@@ -288,7 +284,6 @@ async fn toggle_mic(state: tauri::State<'_, Arc<AppState>>) -> Result<bool, Stri
}
}
#[cfg(not(target_os = "android"))]
#[tauri::command]
async fn toggle_speaker(state: tauri::State<'_, Arc<AppState>>) -> Result<bool, String> {
let engine_lock = state.engine.lock().await;
@@ -299,7 +294,6 @@ async fn toggle_speaker(state: tauri::State<'_, Arc<AppState>>) -> Result<bool,
}
}
#[cfg(not(target_os = "android"))]
#[tauri::command]
async fn get_status(state: tauri::State<'_, Arc<AppState>>) -> Result<CallStatus, String> {
let engine_lock = state.engine.lock().await;
@@ -343,62 +337,6 @@ async fn get_status(state: tauri::State<'_, Arc<AppState>>) -> Result<CallStatus
}
}
// ─── Android stubs for engine-backed commands ────────────────────────────────
//
// Step 1 of the Android rewrite: signal-only. Audio is wired up in Step 3.
// These keep the JS frontend happy (same `invoke` surface) without pulling
// in CPAL, which doesn't support Android.
#[cfg(target_os = "android")]
#[tauri::command]
async fn connect(
_state: tauri::State<'_, Arc<AppState>>,
_app: tauri::AppHandle,
_relay: String,
_room: String,
_alias: String,
_os_aec: bool,
_quality: String,
) -> Result<String, String> {
Err("audio backend not yet wired on Android (step 3)".into())
}
#[cfg(target_os = "android")]
#[tauri::command]
async fn disconnect(_state: tauri::State<'_, Arc<AppState>>) -> Result<String, String> {
Ok("not connected".into())
}
#[cfg(target_os = "android")]
#[tauri::command]
async fn toggle_mic(_state: tauri::State<'_, Arc<AppState>>) -> Result<bool, String> {
Err("not connected".into())
}
#[cfg(target_os = "android")]
#[tauri::command]
async fn toggle_speaker(_state: tauri::State<'_, Arc<AppState>>) -> Result<bool, String> {
Err("not connected".into())
}
#[cfg(target_os = "android")]
#[tauri::command]
async fn get_status(_state: tauri::State<'_, Arc<AppState>>) -> Result<CallStatus, String> {
Ok(CallStatus {
active: false,
mic_muted: false,
spk_muted: false,
participants: vec![],
encode_fps: 0,
recv_fps: 0,
audio_level: 0,
call_duration_secs: 0.0,
fingerprint: String::new(),
tx_codec: String::new(),
rx_codec: String::new(),
})
}
// ─── Signaling commands — platform independent ───────────────────────────────
struct SignalState {
@@ -516,7 +454,6 @@ pub fn run() {
tracing_subscriber::fmt().init();
let state = Arc::new(AppState {
#[cfg(not(target_os = "android"))]
engine: Mutex::new(None),
signal: Arc::new(Mutex::new(SignalState {
transport: None, fingerprint: String::new(), signal_status: "idle".into(),