mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2024-12-17 17:20:21 +01:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e2d00fb68a | |||
| 98ccf5a17c |
+46
@@ -7,9 +7,12 @@ import android.os.Build
|
|||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import chat.simplex.common.platform.*
|
import chat.simplex.common.platform.*
|
||||||
|
import chat.simplex.common.views.helpers.withBGApi
|
||||||
import dev.icerock.moko.resources.ImageResource
|
import dev.icerock.moko.resources.ImageResource
|
||||||
import chat.simplex.res.MR
|
import chat.simplex.res.MR
|
||||||
import dev.icerock.moko.resources.StringResource
|
import dev.icerock.moko.resources.StringResource
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
interface CallAudioDeviceManagerInterface {
|
interface CallAudioDeviceManagerInterface {
|
||||||
@@ -19,6 +22,7 @@ interface CallAudioDeviceManagerInterface {
|
|||||||
fun stop()
|
fun stop()
|
||||||
// AudioDeviceInfo.AudioDeviceType
|
// AudioDeviceInfo.AudioDeviceType
|
||||||
fun selectLastExternalDeviceOrDefault(speaker: Boolean, keepAnyExternal: Boolean)
|
fun selectLastExternalDeviceOrDefault(speaker: Boolean, keepAnyExternal: Boolean)
|
||||||
|
fun selectSameDeviceOnWebViewChange()
|
||||||
// AudioDeviceInfo.AudioDeviceType
|
// AudioDeviceInfo.AudioDeviceType
|
||||||
fun selectDevice(id: Int)
|
fun selectDevice(id: Int)
|
||||||
|
|
||||||
@@ -35,12 +39,15 @@ interface CallAudioDeviceManagerInterface {
|
|||||||
@RequiresApi(Build.VERSION_CODES.S)
|
@RequiresApi(Build.VERSION_CODES.S)
|
||||||
class PostSCallAudioDeviceManager: CallAudioDeviceManagerInterface {
|
class PostSCallAudioDeviceManager: CallAudioDeviceManagerInterface {
|
||||||
private val am = androidAppContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
private val am = androidAppContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||||
|
private var resetCurrentToDevice: AudioDeviceInfo? = null
|
||||||
|
private var resetCurrentToDeviceJob: Job = Job()
|
||||||
override val devices: MutableState<List<AudioDeviceInfo>> = mutableStateOf(emptyList())
|
override val devices: MutableState<List<AudioDeviceInfo>> = mutableStateOf(emptyList())
|
||||||
override val currentDevice: MutableState<AudioDeviceInfo?> = mutableStateOf(null)
|
override val currentDevice: MutableState<AudioDeviceInfo?> = mutableStateOf(null)
|
||||||
|
|
||||||
private val audioCallback = object: AudioDeviceCallback() {
|
private val audioCallback = object: AudioDeviceCallback() {
|
||||||
override fun onAudioDevicesAdded(addedDevices: Array<out AudioDeviceInfo>) {
|
override fun onAudioDevicesAdded(addedDevices: Array<out AudioDeviceInfo>) {
|
||||||
Log.d(TAG, "Added audio devices: ${addedDevices.map { it.type }}")
|
Log.d(TAG, "Added audio devices: ${addedDevices.map { it.type }}")
|
||||||
|
resetCurrentToDevice = null
|
||||||
super.onAudioDevicesAdded(addedDevices)
|
super.onAudioDevicesAdded(addedDevices)
|
||||||
val oldDevices = devices.value
|
val oldDevices = devices.value
|
||||||
devices.value = am.availableCommunicationDevices
|
devices.value = am.availableCommunicationDevices
|
||||||
@@ -53,13 +60,21 @@ class PostSCallAudioDeviceManager: CallAudioDeviceManagerInterface {
|
|||||||
|
|
||||||
override fun onAudioDevicesRemoved(removedDevices: Array<out AudioDeviceInfo>) {
|
override fun onAudioDevicesRemoved(removedDevices: Array<out AudioDeviceInfo>) {
|
||||||
Log.d(TAG, "Removed audio devices: ${removedDevices.map { it.type }}")
|
Log.d(TAG, "Removed audio devices: ${removedDevices.map { it.type }}")
|
||||||
|
resetCurrentToDevice = null
|
||||||
super.onAudioDevicesRemoved(removedDevices)
|
super.onAudioDevicesRemoved(removedDevices)
|
||||||
devices.value = am.availableCommunicationDevices
|
devices.value = am.availableCommunicationDevices
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val listener: OnCommunicationDeviceChangedListener = OnCommunicationDeviceChangedListener { device ->
|
private val listener: OnCommunicationDeviceChangedListener = OnCommunicationDeviceChangedListener { device ->
|
||||||
|
val resetTo = resetCurrentToDevice
|
||||||
|
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
|
devices.value = am.availableCommunicationDevices
|
||||||
|
//Log.d(TAG, "Devices changed ${device?.details()} | ${devices.value.map { it.details() }}")
|
||||||
currentDevice.value = device
|
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() {
|
||||||
|
resetCurrentToDevice = currentDevice.value
|
||||||
|
resetCurrentToDeviceJob.cancel()
|
||||||
|
resetCurrentToDeviceJob = withBGApi {
|
||||||
|
delay(5000)
|
||||||
|
resetCurrentToDevice = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun selectDevice(id: Int) {
|
override fun selectDevice(id: Int) {
|
||||||
|
resetCurrentToDevice = null
|
||||||
val device = devices.value.lastOrNull { it.id == id }
|
val device = devices.value.lastOrNull { it.id == id }
|
||||||
if (device != null && am.communicationDevice?.id != id ) {
|
if (device != null && am.communicationDevice?.id != id ) {
|
||||||
am.setCommunicationDevice(device)
|
am.setCommunicationDevice(device)
|
||||||
@@ -108,12 +134,14 @@ class PostSCallAudioDeviceManager: CallAudioDeviceManagerInterface {
|
|||||||
|
|
||||||
class PreSCallAudioDeviceManager: CallAudioDeviceManagerInterface {
|
class PreSCallAudioDeviceManager: CallAudioDeviceManagerInterface {
|
||||||
private val am = androidAppContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
private val am = androidAppContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||||
|
private var resetCurrentToDeviceJob: Job = Job()
|
||||||
override val devices: MutableState<List<AudioDeviceInfo>> = mutableStateOf(emptyList())
|
override val devices: MutableState<List<AudioDeviceInfo>> = mutableStateOf(emptyList())
|
||||||
override val currentDevice: MutableState<AudioDeviceInfo?> = mutableStateOf(null)
|
override val currentDevice: MutableState<AudioDeviceInfo?> = mutableStateOf(null)
|
||||||
|
|
||||||
private val audioCallback = object: AudioDeviceCallback() {
|
private val audioCallback = object: AudioDeviceCallback() {
|
||||||
override fun onAudioDevicesAdded(addedDevices: Array<out AudioDeviceInfo>) {
|
override fun onAudioDevicesAdded(addedDevices: Array<out AudioDeviceInfo>) {
|
||||||
Log.d(TAG, "Added audio devices: ${addedDevices.map { it.type }}")
|
Log.d(TAG, "Added audio devices: ${addedDevices.map { it.type }}")
|
||||||
|
resetCurrentToDeviceJob.cancel()
|
||||||
super.onAudioDevicesAdded(addedDevices)
|
super.onAudioDevicesAdded(addedDevices)
|
||||||
devices.value = am.getDevices(AudioManager.GET_DEVICES_OUTPUTS).filter { it.hasSupportedType() }.excludeSameType().excludeEarpieceIfWired()
|
devices.value = am.getDevices(AudioManager.GET_DEVICES_OUTPUTS).filter { it.hasSupportedType() }.excludeSameType().excludeEarpieceIfWired()
|
||||||
selectLastExternalDeviceOrDefault(chatModel.activeCall.value?.hasVideo == true, false)
|
selectLastExternalDeviceOrDefault(chatModel.activeCall.value?.hasVideo == true, false)
|
||||||
@@ -121,6 +149,7 @@ class PreSCallAudioDeviceManager: CallAudioDeviceManagerInterface {
|
|||||||
|
|
||||||
override fun onAudioDevicesRemoved(removedDevices: Array<out AudioDeviceInfo>) {
|
override fun onAudioDevicesRemoved(removedDevices: Array<out AudioDeviceInfo>) {
|
||||||
Log.d(TAG, "Removed audio devices: ${removedDevices.map { it.type }}")
|
Log.d(TAG, "Removed audio devices: ${removedDevices.map { it.type }}")
|
||||||
|
resetCurrentToDeviceJob.cancel()
|
||||||
super.onAudioDevicesRemoved(removedDevices)
|
super.onAudioDevicesRemoved(removedDevices)
|
||||||
devices.value = am.getDevices(AudioManager.GET_DEVICES_OUTPUTS).filter { it.hasSupportedType() }.excludeSameType().excludeEarpieceIfWired()
|
devices.value = am.getDevices(AudioManager.GET_DEVICES_OUTPUTS).filter { it.hasSupportedType() }.excludeSameType().excludeEarpieceIfWired()
|
||||||
selectLastExternalDeviceOrDefault(chatModel.activeCall.value?.hasVideo == true, true)
|
selectLastExternalDeviceOrDefault(chatModel.activeCall.value?.hasVideo == true, true)
|
||||||
@@ -153,7 +182,22 @@ class PreSCallAudioDeviceManager: CallAudioDeviceManagerInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun selectSameDeviceOnWebViewChange() {
|
||||||
|
val current = currentDevice.value ?: return
|
||||||
|
resetCurrentToDeviceJob.cancel()
|
||||||
|
resetCurrentToDeviceJob = withBGApi {
|
||||||
|
// select old device manually because WebView will change it for sure.
|
||||||
|
// smaller delay first if it's possible to make the switch less noticeable
|
||||||
|
delay(300)
|
||||||
|
selectDevice(current.id)
|
||||||
|
// and in order to be sure that it will be selected even if WebView will be slow in changing its device
|
||||||
|
delay(700)
|
||||||
|
selectDevice(current.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun selectDevice(id: Int) {
|
override fun selectDevice(id: Int) {
|
||||||
|
resetCurrentToDeviceJob.cancel()
|
||||||
val device = devices.value.lastOrNull { it.id == id }
|
val device = devices.value.lastOrNull { it.id == id }
|
||||||
val isExternalDevice = device != null && device.type != AudioDeviceInfo.TYPE_BUILTIN_EARPIECE && device.type != AudioDeviceInfo.TYPE_BUILTIN_SPEAKER
|
val isExternalDevice = device != null && device.type != AudioDeviceInfo.TYPE_BUILTIN_EARPIECE && device.type != AudioDeviceInfo.TYPE_BUILTIN_SPEAKER
|
||||||
if (isExternalDevice) {
|
if (isExternalDevice) {
|
||||||
@@ -216,6 +260,8 @@ val AudioDeviceInfo.icon: ImageResource
|
|||||||
else -> MR.images.ic_brand_awareness_filled
|
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?
|
val AudioDeviceInfo.name: StringResource?
|
||||||
get() = when (this.type) {
|
get() = when (this.type) {
|
||||||
AudioDeviceInfo.TYPE_BUILTIN_EARPIECE -> MR.strings.audio_device_earpiece
|
AudioDeviceInfo.TYPE_BUILTIN_EARPIECE -> MR.strings.audio_device_earpiece
|
||||||
|
|||||||
+5
-1
@@ -202,6 +202,7 @@ actual fun ActiveCallView() {
|
|||||||
is WCallCommand.Camera -> {
|
is WCallCommand.Camera -> {
|
||||||
updateActiveCall(call) { it.copy(localCamera = cmd.camera) }
|
updateActiveCall(call) { it.copy(localCamera = cmd.camera) }
|
||||||
if (!call.localMediaSources.mic) {
|
if (!call.localMediaSources.mic) {
|
||||||
|
callAudioDeviceManager.selectSameDeviceOnWebViewChange()
|
||||||
chatModel.callCommand.add(WCallCommand.Media(CallMediaSource.Mic, enable = false))
|
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,
|
devices = remember { callAudioDeviceManager.devices }.value,
|
||||||
currentDevice = remember { callAudioDeviceManager.currentDevice },
|
currentDevice = remember { callAudioDeviceManager.currentDevice },
|
||||||
dismiss = { withBGApi { chatModel.callManager.endCall(call) } },
|
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) },
|
selectDevice = { callAudioDeviceManager.selectDevice(it.id) },
|
||||||
toggleVideo = {
|
toggleVideo = {
|
||||||
if (ContextCompat.checkSelfPermission(androidAppContext, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
|
if (ContextCompat.checkSelfPermission(androidAppContext, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
|||||||
Reference in New Issue
Block a user