From bb08b20e5893ff0aae28f00eb432c1e3b04766b6 Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 29 Oct 2026 10:13:55 +0100 Subject: [PATCH] Wait for voice recording and playback release before call signalling --- .../app/src/main/java/com/utun/chat/data/CallController.kt | 3 ++- .../src/main/java/com/utun/chat/viewmodel/ChatViewModel.kt | 2 ++ .../src/main/kotlin/com/utun/chat/data/CallServiceHost.kt | 4 ++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallController.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallController.kt index 5902ef84..0ee738ed 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallController.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallController.kt @@ -216,9 +216,10 @@ object CallController : TelecomCallManager.Listener { if (_state.value?.callId != id || _state.value?.ongoing != true) return@launch LogManager.addLog("INFO", "Call", "media release begins id=$id restoreRadio=$radioToRestore") val radioStopped = RadioAudioEngine.instance().stopForCall() + val voiceStopped = AudioRecorderManager.stopAll() StreamController.stopForCall() StreamPlayerEngine.stopForCall() - withContext(Dispatchers.IO) { radioStopped.get(); MediaAudioController.awaitIdle() } + withContext(Dispatchers.IO) { radioStopped.get(); voiceStopped.forEach { it.get() }; MediaAudioController.awaitIdle() } val current = _state.value if (current?.callId != id || !current.ongoing) { LogManager.addLog("INFO", "Call", "media released after call cancellation id=$id") diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/viewmodel/ChatViewModel.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/viewmodel/ChatViewModel.kt index e738d361..1128c01a 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/viewmodel/ChatViewModel.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/viewmodel/ChatViewModel.kt @@ -1747,6 +1747,7 @@ class ChatViewModel : ViewModel() { } fun playVoiceMessage(filePath: String, durMs: Int) { + if (CallController.blocksMedia()) { LogManager.addLog("WARN", "Voice", "Playback rejected: call owns audio"); return } val cur = _playbackState.value if (cur.filePath == filePath) { if (cur.paused) { @@ -1774,6 +1775,7 @@ class ChatViewModel : ViewModel() { } fun seekVoice(filePath: String, seekMs: Int, durMs: Int) { + if (CallController.blocksMedia()) { LogManager.addLog("WARN", "Voice", "Seek rejected: call owns audio"); return } audioRecorder.stopPlayback() realPlaybackElapsedMs = seekMs _playbackState.value = PlaybackState(filePath, playing = true, durMs = durMs, elapsedMs = seekMs, diff --git a/tools/chatgui-android/call-service-tests/src/main/kotlin/com/utun/chat/data/CallServiceHost.kt b/tools/chatgui-android/call-service-tests/src/main/kotlin/com/utun/chat/data/CallServiceHost.kt index 413533eb..cacfdaf8 100644 --- a/tools/chatgui-android/call-service-tests/src/main/kotlin/com/utun/chat/data/CallServiceHost.kt +++ b/tools/chatgui-android/call-service-tests/src/main/kotlin/com/utun/chat/data/CallServiceHost.kt @@ -112,4 +112,8 @@ class RadioAudioEngine { } object StreamPlayerEngine { val viewing = MutableStateFlow(false); fun stopForCall() { viewing.value = false } } object MediaAudioController { var beforeIdle: () -> Unit = {}; fun awaitIdle() { beforeIdle() } } +object AudioRecorderManager { + var stopped: CompletableFuture = CompletableFuture.completedFuture(Unit) + fun stopAll() = listOf(stopped) +} enum class MediaAudioProfile { AUTO, PHONE, WIRED, BLUETOOTH }