diff --git a/crates/wzp-android/src/engine.rs b/crates/wzp-android/src/engine.rs index 08ec63e..b459bc7 100644 --- a/crates/wzp-android/src/engine.rs +++ b/crates/wzp-android/src/engine.rs @@ -67,6 +67,9 @@ pub(crate) struct EngineState { pub playout_ring: AudioRing, /// Current audio level (RMS) for UI display, updated by capture path. pub audio_level_rms: AtomicU32, + /// QUIC transport handle — stored so stop_call() can close it immediately, + /// triggering relay-side leave + RoomUpdate broadcast. + pub quic_transport: Mutex>>, } pub struct WzpEngine { @@ -87,6 +90,7 @@ impl WzpEngine { capture_ring: AudioRing::new(), playout_ring: AudioRing::new(), audio_level_rms: AtomicU32::new(0), + quic_transport: Mutex::new(None), }); Self { state, @@ -145,6 +149,11 @@ impl WzpEngine { pub fn stop_call(&mut self) { self.state.running.store(false, Ordering::Release); + // Close QUIC connection immediately so the relay detects disconnect + // and removes us from the room (broadcasts RoomUpdate to others). + if let Some(transport) = self.state.quic_transport.lock().unwrap().take() { + transport.close_now(); + } let _ = self.state.command_tx.send(EngineCommand::Stop); if let Some(rt) = self.tokio_runtime.take() { rt.shutdown_background(); @@ -223,6 +232,9 @@ async fn run_call( let transport = Arc::new(wzp_transport::QuinnTransport::new(conn)); + // Store transport handle so stop_call() can close the connection immediately + *state.quic_transport.lock().unwrap() = Some(transport.clone()); + // Crypto handshake let mut kx = WarzoneKeyExchange::from_identity_seed(identity_seed); let ephemeral_pub = kx.generate_ephemeral(); diff --git a/crates/wzp-transport/src/quic.rs b/crates/wzp-transport/src/quic.rs index 68fddb2..40c0cea 100644 --- a/crates/wzp-transport/src/quic.rs +++ b/crates/wzp-transport/src/quic.rs @@ -33,6 +33,12 @@ impl QuinnTransport { &self.connection } + /// Close the QUIC connection immediately (synchronous, no async needed). + /// The relay will detect the close and remove this participant from the room. + pub fn close_now(&self) { + self.connection.close(quinn::VarInt::from_u32(0), b"hangup"); + } + /// Feed an external RTT observation (e.g. from QUIC path stats) into the path monitor. pub fn feed_rtt(&self, rtt_ms: u32) { self.path_monitor.lock().unwrap().observe_rtt(rtt_ms);