From 98ccf5a17c6a1f20aa64149c72e83f950141b7a4 Mon Sep 17 00:00:00 2001 From: Avently <7953703+avently@users.noreply.github.com> Date: Fri, 11 Oct 2024 16:27:49 +0700 Subject: [PATCH] android: prevent WebView from changing audio device when toggling mic --- .../views/call/CallAudioDeviceManager.kt | 31 +++++++++++++++++++ .../common/views/call/CallView.android.kt | 6 +++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallAudioDeviceManager.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallAudioDeviceManager.kt index ec0fd9fea8..26032bdfc3 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallAudioDeviceManager.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallAudioDeviceManager.kt @@ -7,9 +7,12 @@ import android.os.Build import androidx.annotation.RequiresApi import androidx.compose.runtime.* import chat.simplex.common.platform.* +import chat.simplex.common.views.helpers.withBGApi import dev.icerock.moko.resources.ImageResource import chat.simplex.res.MR import dev.icerock.moko.resources.StringResource +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay import java.util.concurrent.Executors interface CallAudioDeviceManagerInterface { @@ -19,6 +22,7 @@ interface CallAudioDeviceManagerInterface { fun stop() // AudioDeviceInfo.AudioDeviceType fun selectLastExternalDeviceOrDefault(speaker: Boolean, keepAnyExternal: Boolean) + fun selectSameDeviceOnWebViewChange() // AudioDeviceInfo.AudioDeviceType fun selectDevice(id: Int) @@ -35,12 +39,15 @@ interface CallAudioDeviceManagerInterface { @RequiresApi(Build.VERSION_CODES.S) class PostSCallAudioDeviceManager: CallAudioDeviceManagerInterface { private val am = androidAppContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager + private var resetDeviceToDevice: AudioDeviceInfo? = null + private var resetDeviceToDeviceJob: Job = Job() override val devices: MutableState> = mutableStateOf(emptyList()) override val currentDevice: MutableState = mutableStateOf(null) private val audioCallback = object: AudioDeviceCallback() { override fun onAudioDevicesAdded(addedDevices: Array) { Log.d(TAG, "Added audio devices: ${addedDevices.map { it.type }}") + resetDeviceToDevice = null super.onAudioDevicesAdded(addedDevices) val oldDevices = devices.value devices.value = am.availableCommunicationDevices @@ -53,13 +60,21 @@ class PostSCallAudioDeviceManager: CallAudioDeviceManagerInterface { override fun onAudioDevicesRemoved(removedDevices: Array) { Log.d(TAG, "Removed audio devices: ${removedDevices.map { it.type }}") + resetDeviceToDevice = null super.onAudioDevicesRemoved(removedDevices) devices.value = am.availableCommunicationDevices } } private val listener: OnCommunicationDeviceChangedListener = OnCommunicationDeviceChangedListener { device -> + val resetTo = resetDeviceToDevice + if (resetTo != null && device != null && resetTo.id != device.id) { + Log.w(TAG, "Resetting device that was set by WebView ${device.name?.localized()} to previously set device ${resetTo.name?.localized()}") + selectDevice(resetTo.id) + return@OnCommunicationDeviceChangedListener + } devices.value = am.availableCommunicationDevices + //Log.d(TAG, "Devices changed ${device?.details()} | ${devices.value.map { it.details() }}") currentDevice.value = device } @@ -98,7 +113,18 @@ class PostSCallAudioDeviceManager: CallAudioDeviceManagerInterface { } } + // WebView modifies speaker when muting/unmuting microphone. It's not needed at all, returning back current device if it will be changed + override fun selectSameDeviceOnWebViewChange() { + resetDeviceToDevice = currentDevice.value + resetDeviceToDeviceJob.cancel() + resetDeviceToDeviceJob = withBGApi { + delay(5000) + resetDeviceToDevice = null + } + } + override fun selectDevice(id: Int) { + resetDeviceToDevice = null val device = devices.value.lastOrNull { it.id == id } if (device != null && am.communicationDevice?.id != id ) { am.setCommunicationDevice(device) @@ -153,6 +179,9 @@ class PreSCallAudioDeviceManager: CallAudioDeviceManagerInterface { } } + // Works without it + override fun selectSameDeviceOnWebViewChange() {} + override fun selectDevice(id: Int) { val device = devices.value.lastOrNull { it.id == id } val isExternalDevice = device != null && device.type != AudioDeviceInfo.TYPE_BUILTIN_EARPIECE && device.type != AudioDeviceInfo.TYPE_BUILTIN_SPEAKER @@ -216,6 +245,8 @@ val AudioDeviceInfo.icon: ImageResource else -> MR.images.ic_brand_awareness_filled } +private fun AudioDeviceInfo.details(): String = "$productName id:$id name:${name?.localized()} type:$type sink:$isSink source:$isSource" + val AudioDeviceInfo.name: StringResource? get() = when (this.type) { AudioDeviceInfo.TYPE_BUILTIN_EARPIECE -> MR.strings.audio_device_earpiece diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt index 37bf8d1330..8af1a973f1 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt @@ -202,6 +202,7 @@ actual fun ActiveCallView() { is WCallCommand.Camera -> { updateActiveCall(call) { it.copy(localCamera = cmd.camera) } if (!call.localMediaSources.mic) { + callAudioDeviceManager.selectSameDeviceOnWebViewChange() chatModel.callCommand.add(WCallCommand.Media(CallMediaSource.Mic, enable = false)) } } @@ -261,7 +262,10 @@ private fun ActiveCallOverlay(call: Call, chatModel: ChatModel, callAudioDeviceM devices = remember { callAudioDeviceManager.devices }.value, currentDevice = remember { callAudioDeviceManager.currentDevice }, dismiss = { withBGApi { chatModel.callManager.endCall(call) } }, - toggleAudio = { chatModel.callCommand.add(WCallCommand.Media(CallMediaSource.Mic, enable = !call.localMediaSources.mic)) }, + toggleAudio = { + callAudioDeviceManager.selectSameDeviceOnWebViewChange() + chatModel.callCommand.add(WCallCommand.Media(CallMediaSource.Mic, enable = !call.localMediaSources.mic)) + }, selectDevice = { callAudioDeviceManager.selectDevice(it.id) }, toggleVideo = { if (ContextCompat.checkSelfPermission(androidAppContext, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {