diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/call/CallController.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/call/CallController.kt index d0d6b08b7..0fea96b86 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/call/CallController.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/call/CallController.kt @@ -220,6 +220,7 @@ class CallController( callManager.beginOffering(callId, peerPubKeys, callType) + var successCount = 0 for (peerPubKey in peerPubKeys) { try { val webRtcSession = withContext(Dispatchers.IO) { createWebRtcSession(peerPubKey) } @@ -230,10 +231,16 @@ class CallController( callManager.publishOfferToPeer(peerPubKey, peerPubKeys, callType, callId, sdp.description) } } + successCount++ } catch (e: Exception) { Log.e(TAG, "Failed to create PeerConnection for ${peerPubKey.take(8)}", e) } } + if (successCount == 0) { + Log.e(TAG, "All PeerConnection creations failed, hanging up") + _errorMessage.value = "Failed to start call: could not create any connections" + callManager.hangup() + } } } @@ -576,6 +583,7 @@ class CallController( // ---- Per-peer cleanup ---- fun disposePeerSession(peerPubKey: HexKey) { + videoSenders.remove(peerPubKey) val entry = peerSessionMgr.removeSession(peerPubKey) if (entry != null) { try { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/call/CallActivity.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/call/CallActivity.kt index 152aa6c8e..a0836fa87 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/call/CallActivity.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/call/CallActivity.kt @@ -46,7 +46,10 @@ import com.vitorpamplona.amethyst.ui.StringResSetup import com.vitorpamplona.amethyst.ui.screen.ManageRelayServices import com.vitorpamplona.amethyst.ui.screen.ManageWebOkHttp import com.vitorpamplona.amethyst.ui.theme.AmethystTheme +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.NonCancellable +import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -213,13 +216,13 @@ class CallActivity : AppCompatActivity() { // Safety net: if the Activity is destroyed while a call is still // ringing/offering, ensure the call is hung up so audio stops. - // Use NonCancellable so the signaling event is published even - // though the lifecycle scope is being cancelled. + // Use a standalone CoroutineScope because lifecycleScope is cancelled + // during super.onDestroy() and may drop suspend work. val manager = CallSessionBridge.callManager when (manager?.state?.value) { is CallState.IncomingCall -> { - lifecycleScope.launch { - withContext(NonCancellable) { manager.rejectCall() } + CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate).launch { + manager.rejectCall() } } @@ -227,8 +230,8 @@ class CallActivity : AppCompatActivity() { is CallState.Connecting, is CallState.Connected, -> { - lifecycleScope.launch { - withContext(NonCancellable) { manager.hangup() } + CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate).launch { + manager.hangup() } }