Merge pull request #2076 from vitorpamplona/claude/fix-multi-device-call-ringing-m7FYV

Add multi-device call state synchronization
This commit is contained in:
Vitor Pamplona
2026-04-02 14:11:34 -04:00
committed by GitHub
2 changed files with 34 additions and 3 deletions
@@ -108,6 +108,12 @@ class CallManager(
_state.value = CallState.Connecting(current.callId, current.callerPubKey, current.callType)
cancelTimeout()
publishEvent(result.wrap)
// Notify other devices of this user that the call was answered here.
// This gift-wraps an answer event to our own pubkey so other logged-in
// devices see it and stop ringing.
val selfNotify = factory.createCallAnswer(sdpAnswer, signer.pubKey, current.callId, signer)
publishEvent(selfNotify.wrap)
}
suspend fun rejectCall() {
@@ -117,6 +123,10 @@ class CallManager(
val result = factory.createReject(current.callerPubKey, current.callId, signer = signer)
transitionToEnded(current.callId, current.callerPubKey, EndReason.REJECTED)
publishEvent(result.wrap)
// Notify other devices of this user that the call was rejected here.
val selfNotify = factory.createReject(signer.pubKey, current.callId, signer = signer)
publishEvent(selfNotify.wrap)
}
fun onCallAnswered(event: CallAnswerEvent) {
@@ -131,6 +141,12 @@ class CallManager(
onAnswerReceived?.invoke(event)
}
is CallState.IncomingCall -> {
// Another device of this user answered the call — stop ringing.
if (callId != current.callId) return
transitionToEnded(current.callId, current.callerPubKey, EndReason.ANSWERED_ELSEWHERE)
}
is CallState.Connected -> {
// Renegotiation answer (e.g., peer accepted our video upgrade offer)
if (callId != current.callId) return
@@ -145,10 +161,24 @@ class CallManager(
fun onCallRejected(event: CallRejectEvent) {
val current = _state.value
if (current !is CallState.Offering) return
if (event.callId() != current.callId) return
val callId = event.callId()
transitionToEnded(current.callId, current.peerPubKey, EndReason.PEER_REJECTED)
when (current) {
is CallState.Offering -> {
if (callId != current.callId) return
transitionToEnded(current.callId, current.peerPubKey, EndReason.PEER_REJECTED)
}
is CallState.IncomingCall -> {
// Another device of this user rejected the call — stop ringing.
if (callId != current.callId) return
transitionToEnded(current.callId, current.callerPubKey, EndReason.REJECTED)
}
else -> {
return
}
}
}
fun onIceCandidate(event: CallIceCandidateEvent) {
@@ -68,4 +68,5 @@ enum class EndReason {
ERROR,
PEER_HANGUP,
PEER_REJECTED,
ANSWERED_ELSEWHERE,
}