fix: auto-reset CallManager state after call ends
CallManager now auto-resets from Ended to Idle after 2 seconds via transitionToEnded(). Previously reset() was called from a LaunchedEffect in CallScreen which could be cancelled when the composable was disposed via popBack(), leaving the state stuck at Ended and silently dropping subsequent incoming calls. Also: CallController now observes CallManager state and auto-cleans up the WebRTC session when a call ends (handles peer hangup case). https://claude.ai/code/session_017hZm7yu7CzmcQgZGSaqSXS
This commit is contained in:
@@ -48,6 +48,16 @@ class CallController(
|
|||||||
private var currentCallId: String? = null
|
private var currentCallId: String? = null
|
||||||
private var currentPeerPubKey: HexKey? = null
|
private var currentPeerPubKey: HexKey? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
scope.launch {
|
||||||
|
callManager.state.collect { state ->
|
||||||
|
if (state is CallState.Ended && webRtcSession != null) {
|
||||||
|
cleanup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun initiateCall(
|
fun initiateCall(
|
||||||
peerPubKey: HexKey,
|
peerPubKey: HexKey,
|
||||||
callType: CallType,
|
callType: CallType,
|
||||||
|
|||||||
@@ -119,11 +119,6 @@ fun CallScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
is CallState.Ended -> {
|
is CallState.Ended -> {
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
delay(2000)
|
|
||||||
callManager.reset()
|
|
||||||
onCallEnded()
|
|
||||||
}
|
|
||||||
CallInProgressUI(
|
CallInProgressUI(
|
||||||
peerPubKey = state.peerPubKey,
|
peerPubKey = state.peerPubKey,
|
||||||
statusText = "Call ended",
|
statusText = "Call ended",
|
||||||
|
|||||||
+26
-9
@@ -55,9 +55,11 @@ class CallManager(
|
|||||||
var onIceCandidateReceived: ((CallIceCandidateEvent) -> Unit)? = null
|
var onIceCandidateReceived: ((CallIceCandidateEvent) -> Unit)? = null
|
||||||
|
|
||||||
private var timeoutJob: Job? = null
|
private var timeoutJob: Job? = null
|
||||||
|
private var resetJob: Job? = null
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val CALL_TIMEOUT_MS = 60_000L // 60 seconds ringing timeout
|
const val CALL_TIMEOUT_MS = 60_000L // 60 seconds ringing timeout
|
||||||
|
const val ENDED_DISPLAY_MS = 2_000L // show "call ended" briefly before resetting
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun initiateCall(
|
suspend fun initiateCall(
|
||||||
@@ -106,8 +108,7 @@ class CallManager(
|
|||||||
if (current !is CallState.IncomingCall) return
|
if (current !is CallState.IncomingCall) return
|
||||||
|
|
||||||
val result = factory.createReject(current.callerPubKey, current.callId, signer = signer)
|
val result = factory.createReject(current.callerPubKey, current.callId, signer = signer)
|
||||||
_state.value = CallState.Ended(current.callId, current.callerPubKey, EndReason.REJECTED)
|
transitionToEnded(current.callId, current.callerPubKey, EndReason.REJECTED)
|
||||||
cancelTimeout()
|
|
||||||
publishEvent(result.wrap)
|
publishEvent(result.wrap)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,8 +127,7 @@ class CallManager(
|
|||||||
if (current !is CallState.Offering) return
|
if (current !is CallState.Offering) return
|
||||||
if (event.callId() != current.callId) return
|
if (event.callId() != current.callId) return
|
||||||
|
|
||||||
_state.value = CallState.Ended(current.callId, current.peerPubKey, EndReason.PEER_REJECTED)
|
transitionToEnded(current.callId, current.peerPubKey, EndReason.PEER_REJECTED)
|
||||||
cancelTimeout()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onIceCandidate(event: CallIceCandidateEvent) {
|
fun onIceCandidate(event: CallIceCandidateEvent) {
|
||||||
@@ -172,8 +172,7 @@ class CallManager(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val result = factory.createHangup(peerPubKey, callId, signer = signer)
|
val result = factory.createHangup(peerPubKey, callId, signer = signer)
|
||||||
_state.value = CallState.Ended(callId, peerPubKey, EndReason.HANGUP)
|
transitionToEnded(callId, peerPubKey, EndReason.HANGUP)
|
||||||
cancelTimeout()
|
|
||||||
publishEvent(result.wrap)
|
publishEvent(result.wrap)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,8 +190,7 @@ class CallManager(
|
|||||||
if (callId != currentCallId) return
|
if (callId != currentCallId) return
|
||||||
|
|
||||||
val peerPubKey = event.pubKey
|
val peerPubKey = event.pubKey
|
||||||
_state.value = CallState.Ended(callId, peerPubKey, EndReason.PEER_HANGUP)
|
transitionToEnded(callId, peerPubKey, EndReason.PEER_HANGUP)
|
||||||
cancelTimeout()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onSignalingEvent(event: Event) {
|
fun onSignalingEvent(event: Event) {
|
||||||
@@ -229,6 +227,25 @@ class CallManager(
|
|||||||
fun reset() {
|
fun reset() {
|
||||||
_state.value = CallState.Idle
|
_state.value = CallState.Idle
|
||||||
cancelTimeout()
|
cancelTimeout()
|
||||||
|
resetJob?.cancel()
|
||||||
|
resetJob = null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun transitionToEnded(
|
||||||
|
callId: String,
|
||||||
|
peerPubKey: HexKey,
|
||||||
|
reason: EndReason,
|
||||||
|
) {
|
||||||
|
_state.value = CallState.Ended(callId, peerPubKey, reason)
|
||||||
|
cancelTimeout()
|
||||||
|
resetJob?.cancel()
|
||||||
|
resetJob =
|
||||||
|
scope.launch {
|
||||||
|
delay(ENDED_DISPLAY_MS)
|
||||||
|
if (_state.value is CallState.Ended) {
|
||||||
|
_state.value = CallState.Idle
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startTimeout(callId: String) {
|
private fun startTimeout(callId: String) {
|
||||||
@@ -250,7 +267,7 @@ class CallManager(
|
|||||||
is CallState.IncomingCall -> current.callerPubKey
|
is CallState.IncomingCall -> current.callerPubKey
|
||||||
else -> return@launch
|
else -> return@launch
|
||||||
}
|
}
|
||||||
_state.value = CallState.Ended(callId, peerPubKey, EndReason.TIMEOUT)
|
transitionToEnded(callId, peerPubKey, EndReason.TIMEOUT)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user