mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-12 01:07:46 +00:00
Merge pull request #2107 from vitorpamplona/claude/fix-call-ringing-issue-OGVEG
Fix call audio state transitions and notification handling
This commit is contained in:
@@ -107,6 +107,7 @@ class CallAudioManager(
|
||||
}
|
||||
|
||||
fun startRingbackTone() {
|
||||
stopRingbackTone()
|
||||
try {
|
||||
ringbackTone =
|
||||
ToneGenerator(AudioManager.STREAM_VOICE_CALL, 80).also {
|
||||
@@ -123,6 +124,7 @@ class CallAudioManager(
|
||||
}
|
||||
|
||||
fun switchToCallAudioMode() {
|
||||
if (audioManager.mode == AudioManager.MODE_IN_COMMUNICATION) return
|
||||
previousAudioMode = audioManager.mode
|
||||
audioManager.mode = AudioManager.MODE_IN_COMMUNICATION
|
||||
|
||||
|
||||
@@ -177,7 +177,15 @@ class CallController(
|
||||
}
|
||||
|
||||
is CallState.Connected -> {
|
||||
// Stop ringing/ringback in case the Connecting state
|
||||
// was skipped due to StateFlow conflation (the value
|
||||
// can change Offering → Connecting → Connected before
|
||||
// the collector processes Connecting).
|
||||
audioManager.stopRinging()
|
||||
audioManager.stopRingbackTone()
|
||||
withContext(Dispatchers.IO) { audioManager.switchToCallAudioMode() }
|
||||
audioManager.acquireProximityWakeLock()
|
||||
NotificationUtils.cancelCallNotification(context)
|
||||
}
|
||||
|
||||
is CallState.Ended -> {
|
||||
@@ -185,12 +193,11 @@ class CallController(
|
||||
}
|
||||
|
||||
is CallState.Idle -> {
|
||||
// Safety net: ensure ringing and notifications are
|
||||
// stopped even if the Ended state was missed due to
|
||||
// StateFlow conflation.
|
||||
audioManager.stopRinging()
|
||||
audioManager.stopRingbackTone()
|
||||
NotificationUtils.cancelCallNotification(context)
|
||||
// Safety net: full cleanup in case the Ended state
|
||||
// was missed due to StateFlow conflation. cleanup()
|
||||
// is idempotent — calling it twice is harmless because
|
||||
// each resource is null-checked and nulled out.
|
||||
cleanup()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -729,24 +736,71 @@ class CallController(
|
||||
// ---- Cleanup ----
|
||||
|
||||
fun cleanup() {
|
||||
audioManager.release()
|
||||
stopForegroundService()
|
||||
// Each block is wrapped individually so that a failure in one
|
||||
// (e.g. a WebRTC native crash) does not prevent the rest from
|
||||
// running. Without this, a single exception could leave the
|
||||
// camera open, audio mode stuck, or the foreground service alive.
|
||||
try {
|
||||
audioManager.release()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "cleanup: audioManager.release() failed", e)
|
||||
}
|
||||
try {
|
||||
stopForegroundService()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "cleanup: stopForegroundService() failed", e)
|
||||
}
|
||||
foregroundServiceStarted = false
|
||||
NotificationUtils.cancelCallNotification(context)
|
||||
stopRemoteVideoMonitor()
|
||||
|
||||
// Dispose all peer sessions
|
||||
peerSessions.values.forEach { it.session.dispose() }
|
||||
for (ps in peerSessions.values) {
|
||||
try {
|
||||
ps.session.dispose()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "cleanup: PeerSession.dispose() failed", e)
|
||||
}
|
||||
}
|
||||
peerSessions.clear()
|
||||
|
||||
// Dispose shared resources
|
||||
stopCamera()
|
||||
localAudioTrackInternal?.dispose()
|
||||
localVideoTrackInternal?.dispose()
|
||||
localAudioSource?.dispose()
|
||||
localVideoSource?.dispose()
|
||||
peerConnectionFactory?.dispose()
|
||||
sharedEglBase?.release()
|
||||
// Dispose shared resources — each in its own try-catch so one
|
||||
// failure does not prevent the others from being released.
|
||||
try {
|
||||
stopCamera()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "cleanup: stopCamera() failed", e)
|
||||
}
|
||||
try {
|
||||
localAudioTrackInternal?.dispose()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "cleanup: localAudioTrack.dispose() failed", e)
|
||||
}
|
||||
try {
|
||||
localVideoTrackInternal?.dispose()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "cleanup: localVideoTrack.dispose() failed", e)
|
||||
}
|
||||
try {
|
||||
localAudioSource?.dispose()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "cleanup: localAudioSource.dispose() failed", e)
|
||||
}
|
||||
try {
|
||||
localVideoSource?.dispose()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "cleanup: localVideoSource.dispose() failed", e)
|
||||
}
|
||||
try {
|
||||
peerConnectionFactory?.dispose()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "cleanup: peerConnectionFactory.dispose() failed", e)
|
||||
}
|
||||
try {
|
||||
sharedEglBase?.release()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "cleanup: sharedEglBase.release() failed", e)
|
||||
}
|
||||
|
||||
localAudioTrackInternal = null
|
||||
localVideoTrackInternal = null
|
||||
|
||||
+1
@@ -1429,6 +1429,7 @@ class AccountViewModel(
|
||||
|
||||
override fun onCleared() {
|
||||
Log.d("AccountViewModel", "onCleared")
|
||||
callController?.cleanup()
|
||||
feedStates.destroy()
|
||||
super.onCleared()
|
||||
}
|
||||
|
||||
@@ -606,6 +606,7 @@ class CallManager(
|
||||
delay(ENDED_DISPLAY_MS)
|
||||
if (_state.value is CallState.Ended) {
|
||||
_state.value = CallState.Idle
|
||||
processedEventIds.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user