android(audio): Speaker button toggles earpiece↔speaker via JNI (WIP, untested)
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:
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user