android(audio): Speaker button toggles earpiece↔speaker via JNI (WIP, untested)
Some checks failed
Mirror to GitHub / mirror (push) Failing after 39s
Build Release Binaries / build-amd64 (push) Has been cancelled

Build 9e37201 confirmed on-device that Usage::VoiceCommunication +
MODE_IN_COMMUNICATION + speakerphoneOn=false routes Oboe playout to the
handset earpiece and the callback drains the ring correctly. Next step:
let the user flip speakerphoneOn at runtime so the existing Speaker
button actually switches audio routing instead of just gating writes.

- Cargo.toml (android target): pull in `jni = 0.21` and
  `ndk-context = 0.1`. Both are already transitively in the lockfile
  via Tauri/Wry, so this just promotes them to direct deps.
- desktop/src-tauri/src/android_audio.rs: new module. Grabs the JavaVM +
  current Activity from `ndk_context::android_context()`, attaches a
  JNI thread, calls `activity.getSystemService("audio")` to get the
  AudioManager, and exposes `set_speakerphone(bool)` +
  `is_speakerphone_on()` helpers that call the AudioManager method of
  the same name. All gated behind `#[cfg(target_os = "android")]`.
- lib.rs: adds `mod android_audio;` (android only), two new Tauri
  commands `set_speakerphone(on)` and `is_speakerphone_on()` — desktop
  gets no-op stubs so the same frontend invoke() works everywhere.
  Both registered in the invoke_handler.
- desktop/src/main.ts: the Speaker button (previously toggled the
  playout-write gate via `toggle_speaker`) now calls `set_speakerphone`
  and reads back the new routing state. Labels switched from
  "Spk" / "Spk Off" to "Earpiece" / "Speaker" so users can't be
  confused into thinking clicking turns audio off. pollStatus no longer
  clobbers the spkBtn label based on engine spk_muted, since the two
  concepts are now decoupled.

WIP because this has NOT been built or tested yet — committing at night
to save the work. Tomorrow: build #50 with this change, smoke-test the
Handset↔Speaker toggle, then move on to call history + last-contacts UI
and the Speaker-button mute bug on the other phone.
This commit is contained in:
Siavash Sameni
2026-04-09 22:00:34 +04:00
parent 9e37201198
commit 0178cbd91d
4 changed files with 161 additions and 3 deletions

View File

@@ -17,6 +17,10 @@ mod engine;
#[cfg(target_os = "android")]
mod wzp_native;
// Android AudioManager bridge (routing earpiece / speaker / BT).
#[cfg(target_os = "android")]
mod android_audio;
// 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.
@@ -346,6 +350,37 @@ async fn get_status(state: tauri::State<'_, Arc<AppState>>) -> Result<CallStatus
}
}
// ─── Audio routing (Android-specific, no-op on desktop) ─────────────────────
/// Switch the call audio between earpiece (`on=false`) and loudspeaker
/// (`on=true`). On Android this calls AudioManager.setSpeakerphoneOn via
/// JNI; on desktop it's a no-op that always succeeds.
#[tauri::command]
#[allow(unused_variables)]
async fn set_speakerphone(on: bool) -> Result<(), String> {
#[cfg(target_os = "android")]
{
android_audio::set_speakerphone(on)
}
#[cfg(not(target_os = "android"))]
{
Ok(())
}
}
/// Query whether the call is currently routed to the loudspeaker.
#[tauri::command]
async fn is_speakerphone_on() -> Result<bool, String> {
#[cfg(target_os = "android")]
{
android_audio::is_speakerphone_on()
}
#[cfg(not(target_os = "android"))]
{
Ok(false)
}
}
// ─── Signaling commands — platform independent ───────────────────────────────
struct SignalState {
@@ -548,6 +583,7 @@ pub fn run() {
ping_relay, get_identity, get_app_info,
connect, disconnect, toggle_mic, toggle_speaker, get_status,
register_signal, place_call, answer_call, get_signal_status,
set_speakerphone, is_speakerphone_on,
])
.run(tauri::generate_context!())
.expect("error while running WarzonePhone");