fix(android): spawn_blocking for audio_start + 15s JS connect timeout

wzp_oboe_start is a sync FFI call that can block the OS thread
indefinitely waiting on the Android audio HAL. Calling it directly
from an async context freezes all tokio tasks including Rust-side
timeouts. Fix: run it via spawn_blocking so tokio stays responsive.

Also add a 15s Promise.race timeout in JS so a frozen audio_start
surfaces as "connect timed out — check audio permissions" instead of
the join button staying stuck in "Connecting…" forever.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Siavash Sameni
2026-05-25 07:13:26 +04:00
parent b0a3b1f18e
commit 5a13f12334
2 changed files with 21 additions and 8 deletions

View File

@@ -601,8 +601,15 @@ impl CallEngine {
}
}
// Run audio_start on a blocking thread — wzp_oboe_start is a
// sync FFI call that can stall waiting for the Android audio
// HAL. Calling it directly blocks the tokio worker thread,
// which freezes all async tasks including our own timeouts.
let t_pre_audio = call_t0.elapsed().as_millis();
if let Err(code) = crate::wzp_native::audio_start() {
let audio_start_result = tokio::task::spawn_blocking(crate::wzp_native::audio_start)
.await
.map_err(|e| anyhow::anyhow!("audio_start task panic: {e}"))?;
if let Err(code) = audio_start_result {
return Err(anyhow::anyhow!(
"wzp_native_audio_start failed: code {code}"
));

View File

@@ -335,13 +335,19 @@ joinVoiceBtn.addEventListener("click", async () => {
joinVoiceBtn.textContent = "Connecting…";
(joinVoiceBtn as HTMLButtonElement).disabled = true;
try {
await invoke("connect", {
relay: relay.address,
room: s.room || "general",
alias: s.alias || "",
osAec: s.osAec,
quality: s.quality || "auto",
});
const connectRace = Promise.race([
invoke("connect", {
relay: relay.address,
room: s.room || "general",
alias: s.alias || "",
osAec: s.osAec,
quality: s.quality || "auto",
}),
new Promise<never>((_, reject) =>
setTimeout(() => reject("connect timed out (15s) — check audio permissions"), 15000)
),
]);
await connectRace;
enterVoice(false);
} catch (e: any) {
console.error("connect failed:", e);