fix: signal mode UI + place_call via stored signal transport
- Don't set callState for signal-only states (prevents auto-join room) - Store signal transport + fingerprint in EngineState after registration - place_call/answer_call send directly via signal transport (not command channel) - Spawn small threads for async signal sends (non-blocking) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -690,12 +690,12 @@ class CallViewModel : ViewModel(), WzpCallback {
|
|||||||
val s = CallStats.fromJson(json)
|
val s = CallStats.fromJson(json)
|
||||||
lastCallDuration = s.durationSecs
|
lastCallDuration = s.durationSecs
|
||||||
_stats.value = s
|
_stats.value = s
|
||||||
if (s.state != 0) {
|
|
||||||
_callState.value = s.state
|
|
||||||
}
|
|
||||||
// Track signal state changes for direct calling
|
// Track signal state changes for direct calling
|
||||||
if (s.state in 5..7) {
|
if (s.state in 5..7) {
|
||||||
_signalState.value = s.state
|
_signalState.value = s.state
|
||||||
|
// Don't update callState for signal-only states
|
||||||
|
} else if (s.state != 0) {
|
||||||
|
_callState.value = s.state
|
||||||
}
|
}
|
||||||
// Incoming call detection
|
// Incoming call detection
|
||||||
if (s.state == 7) { // IncomingCall
|
if (s.state == 7) { // IncomingCall
|
||||||
|
|||||||
@@ -97,6 +97,10 @@ pub(crate) struct EngineState {
|
|||||||
/// QUIC transport handle — stored so stop_call() can close it immediately,
|
/// QUIC transport handle — stored so stop_call() can close it immediately,
|
||||||
/// triggering relay-side leave + RoomUpdate broadcast.
|
/// triggering relay-side leave + RoomUpdate broadcast.
|
||||||
pub quic_transport: Mutex<Option<Arc<wzp_transport::QuinnTransport>>>,
|
pub quic_transport: Mutex<Option<Arc<wzp_transport::QuinnTransport>>>,
|
||||||
|
/// Signal transport for direct calling — stored so place_call/answer_call can send.
|
||||||
|
pub signal_transport: Mutex<Option<Arc<wzp_transport::QuinnTransport>>>,
|
||||||
|
/// Our fingerprint (set during signaling registration).
|
||||||
|
pub signal_fingerprint: Mutex<Option<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct WzpEngine {
|
pub struct WzpEngine {
|
||||||
@@ -118,6 +122,8 @@ impl WzpEngine {
|
|||||||
playout_ring: AudioRing::new(),
|
playout_ring: AudioRing::new(),
|
||||||
audio_level_rms: AtomicU32::new(0),
|
audio_level_rms: AtomicU32::new(0),
|
||||||
quic_transport: Mutex::new(None),
|
quic_transport: Mutex::new(None),
|
||||||
|
signal_transport: Mutex::new(None),
|
||||||
|
signal_fingerprint: Mutex::new(None),
|
||||||
});
|
});
|
||||||
Self {
|
Self {
|
||||||
state,
|
state,
|
||||||
@@ -314,6 +320,10 @@ impl WzpEngine {
|
|||||||
info!(fingerprint = %fp, "signal: registered");
|
info!(fingerprint = %fp, "signal: registered");
|
||||||
let mut stats = signal_state.stats.lock().unwrap();
|
let mut stats = signal_state.stats.lock().unwrap();
|
||||||
stats.state = crate::stats::CallState::Registered;
|
stats.state = crate::stats::CallState::Registered;
|
||||||
|
drop(stats);
|
||||||
|
// Store transport + fingerprint so place_call/answer_call can use them
|
||||||
|
*signal_state.signal_transport.lock().unwrap() = Some(transport.clone());
|
||||||
|
*signal_state.signal_fingerprint.lock().unwrap() = Some(fp.clone());
|
||||||
}
|
}
|
||||||
other => {
|
other => {
|
||||||
error!("signal registration failed: {other:?}");
|
error!("signal registration failed: {other:?}");
|
||||||
@@ -379,19 +389,63 @@ impl WzpEngine {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Place a direct call to a target fingerprint via the signal connection.
|
/// Place a direct call to a target fingerprint via the signal transport.
|
||||||
pub fn place_call(&self, target_fingerprint: &str) -> Result<(), anyhow::Error> {
|
pub fn place_call(&self, target_fingerprint: &str) -> Result<(), anyhow::Error> {
|
||||||
let _ = self.state.command_tx.send(EngineCommand::PlaceCall {
|
use wzp_proto::SignalMessage;
|
||||||
target_fingerprint: target_fingerprint.to_string(),
|
|
||||||
|
let transport = self.state.signal_transport.lock().unwrap().clone()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("not registered"))?;
|
||||||
|
let caller_fp = self.state.signal_fingerprint.lock().unwrap().clone()
|
||||||
|
.unwrap_or_default();
|
||||||
|
let target = target_fingerprint.to_string();
|
||||||
|
let call_id = format!("{:016x}", std::time::SystemTime::now()
|
||||||
|
.duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos());
|
||||||
|
|
||||||
|
// Send on a separate thread since we can't block the UI thread
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
let rt = tokio::runtime::Builder::new_current_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.expect("tokio runtime");
|
||||||
|
rt.block_on(async {
|
||||||
|
let _ = transport.send_signal(&SignalMessage::DirectCallOffer {
|
||||||
|
caller_fingerprint: caller_fp,
|
||||||
|
caller_alias: None,
|
||||||
|
target_fingerprint: target,
|
||||||
|
call_id,
|
||||||
|
identity_pub: [0u8; 32],
|
||||||
|
ephemeral_pub: [0u8; 32],
|
||||||
|
signature: vec![],
|
||||||
|
supported_profiles: vec![wzp_proto::QualityProfile::GOOD],
|
||||||
|
}).await;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Answer an incoming direct call.
|
/// Answer an incoming direct call via the signal transport.
|
||||||
pub fn answer_call(&self, call_id: &str, mode: wzp_proto::CallAcceptMode) -> Result<(), anyhow::Error> {
|
pub fn answer_call(&self, call_id: &str, mode: wzp_proto::CallAcceptMode) -> Result<(), anyhow::Error> {
|
||||||
let _ = self.state.command_tx.send(EngineCommand::AnswerCall {
|
use wzp_proto::SignalMessage;
|
||||||
call_id: call_id.to_string(),
|
|
||||||
|
let transport = self.state.signal_transport.lock().unwrap().clone()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("not registered"))?;
|
||||||
|
let call_id = call_id.to_string();
|
||||||
|
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
let rt = tokio::runtime::Builder::new_current_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.expect("tokio runtime");
|
||||||
|
rt.block_on(async {
|
||||||
|
let _ = transport.send_signal(&SignalMessage::DirectCallAnswer {
|
||||||
|
call_id,
|
||||||
accept_mode: mode,
|
accept_mode: mode,
|
||||||
|
identity_pub: None,
|
||||||
|
ephemeral_pub: None,
|
||||||
|
signature: None,
|
||||||
|
chosen_profile: Some(wzp_proto::QualityProfile::GOOD),
|
||||||
|
}).await;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user