mirror of
https://gitlab.linphone.org/BC/public/linphone-android.git
synced 2026-01-24 23:28:10 +00:00
Started audio routes
This commit is contained in:
parent
a02896333a
commit
c79f2896da
3 changed files with 218 additions and 17 deletions
|
|
@ -30,12 +30,14 @@ import androidx.lifecycle.ViewModel
|
|||
import java.util.Locale
|
||||
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||
import org.linphone.R
|
||||
import org.linphone.core.AudioDevice
|
||||
import org.linphone.core.Call
|
||||
import org.linphone.core.CallListenerStub
|
||||
import org.linphone.core.MediaDirection
|
||||
import org.linphone.core.MediaEncryption
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.ui.main.contacts.model.ContactAvatarModel
|
||||
import org.linphone.utils.AudioRouteUtils
|
||||
import org.linphone.utils.Event
|
||||
import org.linphone.utils.LinphoneUtils
|
||||
|
||||
|
|
@ -105,10 +107,18 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
|
|||
|
||||
@WorkerThread
|
||||
override fun onStateChanged(call: Call, state: Call.State?, message: String) {
|
||||
if (CurrentCallViewModel@call != call) {
|
||||
return
|
||||
}
|
||||
|
||||
if (LinphoneUtils.isCallOutgoing(call.state)) {
|
||||
isVideoEnabled.postValue(call.params.isVideoEnabled)
|
||||
} else {
|
||||
val videoEnabled = call.currentParams.isVideoEnabled
|
||||
if (videoEnabled && isVideoEnabled.value == false) {
|
||||
Log.i("$TAG Video enabled, routing audio to speaker")
|
||||
AudioRouteUtils.routeAudioToSpeaker(call)
|
||||
}
|
||||
isVideoEnabled.postValue(videoEnabled)
|
||||
|
||||
// Toggle full screen OFF when remote disables video
|
||||
|
|
@ -117,6 +127,12 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
override fun onAudioDeviceChanged(call: Call, audioDevice: AudioDevice) {
|
||||
Log.i("$TAG Audio device changed [$audioDevice]")
|
||||
isSpeakerEnabled.postValue(AudioRouteUtils.isSpeakerAudioRouteCurrentlyUsed(call))
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
|
|
@ -133,8 +149,9 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
|
|||
call = currentCall
|
||||
Log.i("$TAG Found call [$call]")
|
||||
configureCall(call)
|
||||
isSpeakerEnabled.postValue(AudioRouteUtils.isSpeakerAudioRouteCurrentlyUsed(call))
|
||||
} else {
|
||||
Log.e("$TAG Failed to find outgoing call!")
|
||||
Log.e("$TAG Failed to find call!")
|
||||
}
|
||||
|
||||
showSwitchCamera.postValue(coreContext.showSwitchCameraButton())
|
||||
|
|
@ -155,16 +172,20 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
|
|||
@UiThread
|
||||
fun answer() {
|
||||
coreContext.postOnCoreThread {
|
||||
Log.i("$TAG Answering call [$call]")
|
||||
call.accept()
|
||||
if (::call.isInitialized) {
|
||||
Log.i("$TAG Answering call [$call]")
|
||||
call.accept()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
fun hangUp() {
|
||||
coreContext.postOnCoreThread {
|
||||
Log.i("$TAG Terminating call [$call]")
|
||||
call.terminate()
|
||||
if (::call.isInitialized) {
|
||||
Log.i("$TAG Terminating call [$call]")
|
||||
call.terminate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -187,15 +208,30 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
|
|||
// TODO: request record audio permission
|
||||
return
|
||||
}
|
||||
|
||||
coreContext.postOnCoreThread {
|
||||
call.microphoneMuted = !call.microphoneMuted
|
||||
isMicrophoneMuted.postValue(call.microphoneMuted)
|
||||
if (::call.isInitialized) {
|
||||
call.microphoneMuted = !call.microphoneMuted
|
||||
isMicrophoneMuted.postValue(call.microphoneMuted)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
fun changeAudioOutputDevice() {
|
||||
// TODO: display list of all output devices
|
||||
// TODO: display list of all output devices if more then earpiece &
|
||||
|
||||
val routeAudioToSpeaker = isSpeakerEnabled.value != true
|
||||
|
||||
coreContext.postOnCoreThread {
|
||||
if (::call.isInitialized) {
|
||||
if (routeAudioToSpeaker) {
|
||||
AudioRouteUtils.routeAudioToSpeaker(call)
|
||||
} else {
|
||||
AudioRouteUtils.routeAudioToEarpiece(call)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
|
|
@ -258,14 +294,6 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
|
|||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
fun forceShowZrtpSasDialog() {
|
||||
val authToken = call.authenticationToken
|
||||
if (authToken.orEmpty().isNotEmpty()) {
|
||||
showZrtpSasDialog(authToken!!.uppercase(Locale.getDefault()))
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun showZrtpSasDialog(authToken: String) {
|
||||
val toRead: String
|
||||
|
|
|
|||
173
app/src/main/java/org/linphone/utils/AudioRouteUtils.kt
Normal file
173
app/src/main/java/org/linphone/utils/AudioRouteUtils.kt
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2023 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-android
|
||||
* (see https://www.linphone.org).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.linphone.utils
|
||||
|
||||
import androidx.annotation.WorkerThread
|
||||
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||
import org.linphone.core.AudioDevice
|
||||
import org.linphone.core.Call
|
||||
import org.linphone.core.tools.Log
|
||||
|
||||
class AudioRouteUtils {
|
||||
companion object {
|
||||
private const val TAG = "[Audio Route Utils]"
|
||||
|
||||
@WorkerThread
|
||||
fun isSpeakerAudioRouteCurrentlyUsed(call: Call? = null): Boolean {
|
||||
val currentCall = if (coreContext.core.callsNb > 0) {
|
||||
call ?: coreContext.core.currentCall ?: coreContext.core.calls[0]
|
||||
} else {
|
||||
Log.w("$TAG No call found, checking audio route on Core")
|
||||
null
|
||||
}
|
||||
val conference = coreContext.core.conference
|
||||
|
||||
val audioDevice = if (conference != null && conference.isIn) {
|
||||
conference.outputAudioDevice
|
||||
} else if (currentCall != null) {
|
||||
currentCall.outputAudioDevice
|
||||
} else {
|
||||
coreContext.core.outputAudioDevice
|
||||
}
|
||||
|
||||
if (audioDevice == null) return false
|
||||
Log.i(
|
||||
"$TAG Playback audio device currently in use is [${audioDevice.deviceName} (${audioDevice.driverName}) ${audioDevice.type}]"
|
||||
)
|
||||
return audioDevice.type == AudioDevice.Type.Speaker
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
fun routeAudioToEarpiece(call: Call? = null) {
|
||||
routeAudioTo(call, arrayListOf(AudioDevice.Type.Earpiece))
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
fun routeAudioToSpeaker(call: Call? = null) {
|
||||
routeAudioTo(call, arrayListOf(AudioDevice.Type.Speaker))
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun routeAudioTo(
|
||||
call: Call?,
|
||||
types: List<AudioDevice.Type>
|
||||
) {
|
||||
val currentCall = call ?: coreContext.core.currentCall ?: coreContext.core.calls.firstOrNull()
|
||||
if (currentCall != null) {
|
||||
applyAudioRouteChange(currentCall, types)
|
||||
changeCaptureDeviceToMatchAudioRoute(currentCall, types)
|
||||
} else {
|
||||
applyAudioRouteChange(call, types)
|
||||
changeCaptureDeviceToMatchAudioRoute(call, types)
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun applyAudioRouteChange(
|
||||
call: Call?,
|
||||
types: List<AudioDevice.Type>,
|
||||
output: Boolean = true
|
||||
) {
|
||||
val currentCall = if (coreContext.core.callsNb > 0) {
|
||||
call ?: coreContext.core.currentCall ?: coreContext.core.calls[0]
|
||||
} else {
|
||||
Log.w("$TAG No call found, setting audio route on Core")
|
||||
null
|
||||
}
|
||||
|
||||
val capability = if (output) {
|
||||
AudioDevice.Capabilities.CapabilityPlay
|
||||
} else {
|
||||
AudioDevice.Capabilities.CapabilityRecord
|
||||
}
|
||||
val preferredDriver = if (output) {
|
||||
coreContext.core.defaultOutputAudioDevice?.driverName
|
||||
} else {
|
||||
coreContext.core.defaultInputAudioDevice?.driverName
|
||||
}
|
||||
|
||||
val extendedAudioDevices = coreContext.core.extendedAudioDevices
|
||||
Log.i(
|
||||
"$TAG Looking for an ${if (output) "output" else "input"} audio device with capability [$capability], driver name [$preferredDriver] and type [$types] in extended audio devices list (size ${extendedAudioDevices.size})"
|
||||
)
|
||||
val foundAudioDevice = extendedAudioDevices.find {
|
||||
it.driverName == preferredDriver && types.contains(it.type) && it.hasCapability(
|
||||
capability
|
||||
)
|
||||
}
|
||||
val audioDevice = if (foundAudioDevice == null) {
|
||||
Log.w(
|
||||
"$TAG Failed to find an audio device with capability [$capability], driver name [$preferredDriver] and type [$types]"
|
||||
)
|
||||
extendedAudioDevices.find {
|
||||
types.contains(it.type) && it.hasCapability(capability)
|
||||
}
|
||||
} else {
|
||||
foundAudioDevice
|
||||
}
|
||||
|
||||
if (audioDevice == null) {
|
||||
Log.e(
|
||||
"$TAG Couldn't find audio device with capability [$capability] and type [$types]"
|
||||
)
|
||||
for (device in extendedAudioDevices) {
|
||||
Log.i(
|
||||
"$TAG Extended audio device: [${device.deviceName} (${device.driverName}) ${device.type} / ${device.capabilities}]"
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
if (currentCall != null) {
|
||||
Log.i(
|
||||
"$TAG Found [${audioDevice.type}] ${if (output) "playback" else "recorder"} audio device [${audioDevice.deviceName} (${audioDevice.driverName})], routing call audio to it"
|
||||
)
|
||||
if (output) {
|
||||
currentCall.outputAudioDevice = audioDevice
|
||||
} else {
|
||||
currentCall.inputAudioDevice = audioDevice
|
||||
}
|
||||
} else {
|
||||
Log.i(
|
||||
"$TAG Found [${audioDevice.type}] ${if (output) "playback" else "recorder"} audio device [${audioDevice.deviceName} (${audioDevice.driverName})], changing core default audio device"
|
||||
)
|
||||
if (output) {
|
||||
coreContext.core.outputAudioDevice = audioDevice
|
||||
} else {
|
||||
coreContext.core.inputAudioDevice = audioDevice
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun changeCaptureDeviceToMatchAudioRoute(call: Call?, types: List<AudioDevice.Type>) {
|
||||
when (types.first()) {
|
||||
AudioDevice.Type.Earpiece, AudioDevice.Type.Speaker -> {
|
||||
Log.i(
|
||||
"$TAG Audio route requested to Earpiece or Speaker, setting input to Microphone"
|
||||
)
|
||||
applyAudioRouteChange(call, (arrayListOf(AudioDevice.Type.Microphone)), false)
|
||||
}
|
||||
else -> {
|
||||
Log.w("$TAG Unexpected audio device type: ${types.first()}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -81,7 +81,7 @@
|
|||
android:layout_height="@dimen/voip_button_size"
|
||||
android:layout_marginEnd="30dp"
|
||||
android:padding="@dimen/voip_button_icon_padding"
|
||||
android:src="@{viewModel.isSpeakerEnabled ? @drawable/speaker_slash : @drawable/speaker_high, default=@drawable/speaker_slash}"
|
||||
android:src="@{viewModel.isSpeakerEnabled ? @drawable/speaker_high : @drawable/speaker_slash, default=@drawable/speaker_slash}"
|
||||
android:background="@drawable/in_call_button_background"
|
||||
app:tint="@color/white"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue