feat: RoomUpdate protocol — broadcast participant list on join/leave

- Add RoomUpdate signal message to wzp-proto with participant count + list
- Add RoomParticipant struct (fingerprint + optional alias)
- Store fingerprint/alias in relay Participant struct
- Broadcast RoomUpdate to all room members on join and leave
- Add signal recv task in Android engine to handle RoomUpdate
- Surface room_participant_count + room_participants in CallStats JSON
- Show "X in room" with participant names in Android in-call UI

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude
2026-04-05 18:12:24 +00:00
parent a23d9f5e41
commit 2d4b8eebd5
9 changed files with 187 additions and 16 deletions

View File

@@ -529,10 +529,45 @@ async fn run_call(
}
};
// Signal recv task — listens for RoomUpdate and other signaling messages
let transport_signal = transport.clone();
let state_signal = state.clone();
let signal_task = async {
loop {
match transport_signal.recv_signal().await {
Ok(Some(SignalMessage::RoomUpdate { count, participants })) => {
info!(count, "RoomUpdate received");
let members: Vec<crate::stats::RoomMember> = participants
.iter()
.map(|p| crate::stats::RoomMember {
fingerprint: p.fingerprint.clone(),
alias: p.alias.clone(),
})
.collect();
let mut stats = state_signal.stats.lock().unwrap();
stats.room_participant_count = count;
stats.room_participants = members;
}
Ok(Some(msg)) => {
info!("signal received: {:?}", std::mem::discriminant(&msg));
}
Ok(None) => {
info!("signal stream closed");
break;
}
Err(e) => {
warn!("signal recv error: {e}");
break;
}
}
}
};
tokio::select! {
_ = send_task => {}
_ = recv_task => {}
_ = stats_task => {}
_ = signal_task => {}
}
transport.close().await.ok();

View File

@@ -53,4 +53,15 @@ pub struct CallStats {
pub fec_recovered: u64,
/// Current mic audio level (RMS of i16 samples, 0-32767).
pub audio_level: u32,
/// Number of participants in the room (from last RoomUpdate).
pub room_participant_count: u32,
/// Participant list (fingerprint + optional alias) serialized as JSON array.
pub room_participants: Vec<RoomMember>,
}
/// A room member entry, serialized into the stats JSON.
#[derive(Clone, Debug, Default, serde::Serialize)]
pub struct RoomMember {
pub fingerprint: String,
pub alias: Option<String>,
}