Another bluetooth proto

This commit is contained in:
Sylvain Berfini 2025-09-22 14:03:23 +02:00
parent ae7a3c5bce
commit d4baa8e588
3 changed files with 79 additions and 13 deletions

View file

@ -26,13 +26,11 @@ import androidx.core.telecom.CallControlResult
import androidx.core.telecom.CallControlScope
import androidx.core.telecom.CallEndpointCompat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.LinphoneApplication.Companion.corePreferences
import org.linphone.core.AudioDevice
import org.linphone.core.Call
import org.linphone.core.CallListenerStub
import org.linphone.core.Reason
@ -143,14 +141,14 @@ class TelecomCallControlCallback(
}.launchIn(scope)
callControl.currentCallEndpoint.onEach { endpoint ->
var newEndpointToUse = endpoint
// var newEndpointToUse = endpoint
if (endpointUpdateRequestFromLinphone) {
Log.i("$TAG Linphone requests to use [${endpoint.name}] audio endpoint with type [${endpointTypeToString(endpoint.type)}]")
} else {
Log.i("$TAG Android requests us to use [${endpoint.name}] audio endpoint with type [${endpointTypeToString(endpoint.type)}]")
}
val requestedEndpoint = latestLinphoneRequestedEndpoint
/*val requestedEndpoint = latestLinphoneRequestedEndpoint
if (endpointUpdateRequestFromLinphone && requestedEndpoint != null && requestedEndpoint != endpoint) {
Log.w("$TAG WARNING: Linphone requested endpoint [${requestedEndpoint.name}] but Telecom Manager notified endpoint [${endpoint.name}], trying to use the one we requested anyway")
newEndpointToUse = requestedEndpoint
@ -163,7 +161,13 @@ class TelecomCallControlCallback(
Log.w("$TAG Device isn't connected to Android Auto, do not follow system request to change audio endpoint to [${newEndpointToUse.name}] with type [${endpointTypeToString(type)}]")
return@onEach
}
endpointUpdateRequestFromLinphone = false
endpointUpdateRequestFromLinphone = false*/
/*val type = endpoint.type
if (!endpointUpdateRequestFromLinphone && !coreContext.isConnectedToAndroidAuto && (type == CallEndpointCompat.Companion.TYPE_EARPIECE || type == CallEndpointCompat.Companion.TYPE_SPEAKER)) {
endpointUpdateRequestFromLinphone = false
Log.w("$TAG Device isn't connected to Android Auto, do not follow system request to change audio endpoint to [${endpoint.name}] with type [${endpointTypeToString(type)}]")
return@onEach
}
// Change audio route in SDK, this way the usual listener will trigger
// and we'll be able to update the UI accordingly
@ -186,6 +190,7 @@ class TelecomCallControlCallback(
}
if (route.isNotEmpty()) {
coreContext.postOnCoreThread {
Log.i("$TAG Requesting Linphone to change audio route to [$route]([${endpointTypeToString(endpoint.type)}])")
if (!AudioUtils.applyAudioRouteChangeInLinphone(call, route)) {
Log.w("$TAG Failed to apply audio route change, trying again in 200ms")
coreContext.postOnCoreThreadDelayed({
@ -193,7 +198,7 @@ class TelecomCallControlCallback(
}, 200)
}
}
}
}*/
}.launchIn(scope)
callControl.isMuted.onEach { muted ->
@ -224,7 +229,7 @@ class TelecomCallControlCallback(
}.launchIn(scope)
}
fun applyAudioRouteToCallWithId(routes: List<AudioDevice.Type>): Boolean {
/*fun applyAudioRouteToCallWithId(routes: List<AudioDevice.Type>): Boolean {
Log.i("$TAG Looking for audio endpoint with type [${routes.first()}]")
var wiredHeadsetFound = false
@ -260,13 +265,14 @@ class TelecomCallControlCallback(
continue
}
var endpointChangeSuccessful = false
scope.launch {
Log.i("$TAG Requesting audio endpoint change to [${endpoint.name}] with type [${endpointTypeToString(endpoint.type)}]")
endpointUpdateRequestFromLinphone = true
latestLinphoneRequestedEndpoint = endpoint
var result: CallControlResult = callControl.requestEndpointChange(endpoint)
var attempts = 1
while (result is CallControlResult.Error && attempts <= 10) {
while (result is CallControlResult.Error && attempts <= 2) {
delay(100)
Log.i(
"$TAG Previous attempt failed [$result], requesting again audio endpoint change to [${endpoint.name}] with type [${endpointTypeToString(endpoint.type)}]"
@ -282,10 +288,11 @@ class TelecomCallControlCallback(
"$TAG It took [$attempts] attempt(s) to change endpoint audio device..."
)
currentEndpoint = endpoint.type
endpointChangeSuccessful = true
}
}
return true
return endpointChangeSuccessful
}
}
@ -297,7 +304,7 @@ class TelecomCallControlCallback(
Log.e("$TAG No matching endpoint found")
}
return false
}
}*/
private fun answerCall() {
val isVideo = LinphoneUtils.isVideoEnabled(call)

View file

@ -20,6 +20,8 @@
package org.linphone.telecom
import android.content.Context
import android.media.AudioDeviceInfo
import android.media.AudioManager
import androidx.annotation.WorkerThread
import androidx.core.telecom.CallAttributesCompat
import androidx.core.telecom.CallException
@ -29,13 +31,14 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.core.AudioDevice
import org.linphone.core.Call
import org.linphone.core.Core
import org.linphone.core.CoreListenerStub
import org.linphone.core.tools.Log
import org.linphone.utils.LinphoneUtils
import androidx.core.net.toUri
import org.linphone.core.AudioDevice
import org.linphone.utils.AudioUtils
class TelecomManager
@WorkerThread
@ -66,6 +69,9 @@ class TelecomManager
@WorkerThread
override fun onLastCallEnded(core: Core) {
currentlyFollowedCalls = 0
val audioManager = coreContext.context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
Log.i("$TAG Clearing communication device")
audioManager.clearCommunicationDevice()
}
}
@ -87,6 +93,18 @@ class TelecomManager
} catch (e: Exception) {
Log.e("$TAG Can't init TelecomManager: $e")
}
val listener =
AudioManager.OnCommunicationDeviceChangedListener { device -> // Handle changes
Log.i("$TAG Communication device changed: $device")
if (device?.type != AudioDeviceInfo.TYPE_BLUETOOTH_SCO) {
AudioUtils.applyAudioRouteChangeInLinphone(null, listOf(AudioDevice.Type.Speaker), true)
} else {
AudioUtils.applyAudioRouteChangeInLinphone(null, listOf(AudioDevice.Type.Bluetooth), true)
}
}
val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
audioManager.addOnCommunicationDeviceChangedListener(context.mainExecutor, listener)
}
@WorkerThread
@ -214,12 +232,33 @@ class TelecomManager
Log.i(
"$TAG Looking for audio endpoint with type [${routes.first()}] for call with ID [$callId]"
)
val callControlCallback = map[callId]
/*val callControlCallback = map[callId]
if (callControlCallback == null) {
Log.w("$TAG Failed to find callbacks for call with ID [$callId]")
return false
}
return callControlCallback.applyAudioRouteToCallWithId(routes)
return callControlCallback.applyAudioRouteToCallWithId(routes)*/
val audioManager = coreContext.context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
if (AudioDevice.Type.Bluetooth in routes) {
val devices = audioManager.availableCommunicationDevices
for (device in devices) {
if (device.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO && device.isSink) {
Log.i("$TAG [Telecom] Setting communication device: $device")
audioManager.setCommunicationDevice(device)
return true
}
}
} else {
val devices = audioManager.availableCommunicationDevices
for (device in devices) {
if (device.type == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER && device.isSink) {
Log.i("$TAG [Telecom] Setting communication device: $device")
audioManager.setCommunicationDevice(device)
return true
}
}
}
return false
}
}

View file

@ -95,6 +95,26 @@ class AudioUtils {
null
}
/*val audioManager = coreContext.context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
if (AudioDevice.Type.Bluetooth in types) {
val devices = audioManager.availableCommunicationDevices
for (device in devices) {
if (device.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO && device.isSink) {
Log.i("$TAG [Telecom] Setting communication device: $device")
audioManager.setCommunicationDevice(device)
}
}
} else {
val devices = audioManager.availableCommunicationDevices
for (device in devices) {
if (device.type == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER && device.isSink) {
Log.i("$TAG [Telecom] Setting communication device: $device")
audioManager.setCommunicationDevice(device)
}
}
}*/
// applyAudioRouteChangeInLinphone(currentCall, types, output)
if (!skipTelecom) {
val callId = currentCall?.callLog?.callId.orEmpty()
Log.i("$TAG Trying to change audio endpoint using Telecom Manager APIs")