fix: send ICE candidates to all peers and exclude self from peer set

Two bugs caused WebRTC group calls to get stuck in "Connecting":

1. CallController.onLocalIceCandidate() used currentPeerPubKey() which
   returns only the first peer via firstOrNull(). ICE candidates were
   gift-wrapped to only one peer; the others never received them.
   Fixed by iterating over all currentPeerPubKeys().

2. CallManager.acceptCall() set Connecting.peerPubKeys to groupMembers
   which includes the local user's own pubkey. This caused
   currentPeerPubKey() to potentially return self, sending ICE
   candidates to oneself instead of the caller.
   Fixed by filtering out signer.pubKey from the peer set.

https://claude.ai/code/session_01J5fJx9YbSBx1BsBMctiAm8
This commit is contained in:
Claude
2026-04-03 15:36:50 +00:00
parent 7eaa01f197
commit 0488245200
2 changed files with 6 additions and 4 deletions
@@ -491,13 +491,15 @@ class CallController(
private fun onLocalIceCandidate(candidate: IceCandidate) {
Log.d(TAG) { "Local ICE candidate: ${candidate.sdp.take(50)}" }
val callId = callManager.currentCallId() ?: return
val peerPubKey = callManager.currentPeerPubKey() ?: return
val peerPubKeys = callManager.currentPeerPubKeys()?.takeIf { it.isNotEmpty() } ?: return
val candidateJson = CallIceCandidateEvent.serializeCandidate(candidate.sdp, candidate.sdpMid, candidate.sdpMLineIndex)
scope.launch {
val signer = signerProvider()
val result = callFactory.createIceCandidate(candidateJson, peerPubKey, callId, signer)
publishWrap(result.wrap)
for (peerPubKey in peerPubKeys) {
val result = callFactory.createIceCandidate(candidateJson, peerPubKey, callId, signer)
publishWrap(result.wrap)
}
}
}
@@ -148,7 +148,7 @@ class CallManager(
}
Log.d("CallManager") { "acceptCall: callId=${current.callId}, transitioning to Connecting, sdpAnswerLength=${sdpAnswer.length}" }
_state.value = CallState.Connecting(current.callId, current.peerPubKeys(), current.callType)
_state.value = CallState.Connecting(current.callId, current.peerPubKeys() - signer.pubKey, current.callType)
cancelTimeout()
// Include all group members + self so other devices get notified too.