fix: clean up rejected peer's PeerSession to prevent stale sessions blocking hangup
When a peer rejects a group call, CallManager updated its state but never notified CallController to dispose the peer's PeerSession. This caused two issues: 1. The rejected peer's PeerConnection lingered in HAVE_LOCAL_OFFER state until the entire call ended (resource leak visible as "disposing 2 peer sessions" when only 1 should exist). 2. In onPeerDisconnected, the allDisconnected check required ALL sessions to be CLOSED. The stale session blocked this check, preventing automatic hangup when ICE failed — meaning the remote peer never received a hangup event and kept ringing. Fix: invoke onPeerLeft when a peer rejects in Offering/Connecting/Connected states, and make onPeerDisconnected treat sessions without a remote description as inactive. https://claude.ai/code/session_01UXaFKmHVPDyTzfHL8fkSw1
This commit is contained in:
@@ -729,12 +729,17 @@ class CallController(
|
|||||||
|
|
||||||
private fun onPeerDisconnected(peerPubKey: HexKey) {
|
private fun onPeerDisconnected(peerPubKey: HexKey) {
|
||||||
Log.d(TAG) { "Peer ${peerPubKey.take(8)} disconnected (ICE FAILED)" }
|
Log.d(TAG) { "Peer ${peerPubKey.take(8)} disconnected (ICE FAILED)" }
|
||||||
// If all peers disconnected, hang up
|
// If all peers disconnected, hang up.
|
||||||
val sessionStates = peerSessions.map { (key, ps) -> "${key.take(8)}=${ps.session.getSignalingState()}" }
|
// A session counts as "not active" if it is the one that just failed,
|
||||||
|
// if it was already closed, or if it never received a remote description
|
||||||
|
// (e.g. the peer rejected before answering).
|
||||||
|
val sessionStates = peerSessions.map { (key, ps) -> "${key.take(8)}=${ps.session.getSignalingState()},remoteSet=${ps.remoteDescriptionSet.get()}" }
|
||||||
Log.d(TAG) { "onPeerDisconnected: checking remaining sessions: $sessionStates" }
|
Log.d(TAG) { "onPeerDisconnected: checking remaining sessions: $sessionStates" }
|
||||||
val allDisconnected =
|
val allDisconnected =
|
||||||
peerSessions.keys.all { key ->
|
peerSessions.keys.all { key ->
|
||||||
key == peerPubKey || peerSessions[key]?.session?.getSignalingState() == PeerConnection.SignalingState.CLOSED
|
key == peerPubKey ||
|
||||||
|
peerSessions[key]?.session?.getSignalingState() == PeerConnection.SignalingState.CLOSED ||
|
||||||
|
peerSessions[key]?.remoteDescriptionSet?.get() != true
|
||||||
}
|
}
|
||||||
if (allDisconnected) {
|
if (allDisconnected) {
|
||||||
Log.d(TAG) { "onPeerDisconnected: all peers disconnected, hanging up" }
|
Log.d(TAG) { "onPeerDisconnected: all peers disconnected, hanging up" }
|
||||||
|
|||||||
@@ -349,6 +349,7 @@ class CallManager(
|
|||||||
transitionToEnded(current.callId, current.peerPubKeys, EndReason.PEER_REJECTED)
|
transitionToEnded(current.callId, current.peerPubKeys, EndReason.PEER_REJECTED)
|
||||||
} else {
|
} else {
|
||||||
_state.value = current.copy(peerPubKeys = remaining)
|
_state.value = current.copy(peerPubKeys = remaining)
|
||||||
|
onPeerLeft?.invoke(rejectingPeer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -356,12 +357,14 @@ class CallManager(
|
|||||||
if (callId != current.callId) return
|
if (callId != current.callId) return
|
||||||
_state.value =
|
_state.value =
|
||||||
current.copy(pendingPeerPubKeys = current.pendingPeerPubKeys - rejectingPeer)
|
current.copy(pendingPeerPubKeys = current.pendingPeerPubKeys - rejectingPeer)
|
||||||
|
onPeerLeft?.invoke(rejectingPeer)
|
||||||
}
|
}
|
||||||
|
|
||||||
is CallState.Connected -> {
|
is CallState.Connected -> {
|
||||||
if (callId != current.callId) return
|
if (callId != current.callId) return
|
||||||
_state.value =
|
_state.value =
|
||||||
current.copy(pendingPeerPubKeys = current.pendingPeerPubKeys - rejectingPeer)
|
current.copy(pendingPeerPubKeys = current.pendingPeerPubKeys - rejectingPeer)
|
||||||
|
onPeerLeft?.invoke(rejectingPeer)
|
||||||
}
|
}
|
||||||
|
|
||||||
is CallState.IncomingCall -> {
|
is CallState.IncomingCall -> {
|
||||||
|
|||||||
Reference in New Issue
Block a user