mirror of
https://gitlab.linphone.org/BC/public/linphone-android.git
synced 2026-01-17 11:28:06 +00:00
Added audio route selection to conference waiting room fragment
This commit is contained in:
parent
4697af6c27
commit
ce1d3d4807
8 changed files with 206 additions and 9 deletions
|
|
@ -29,6 +29,7 @@ import androidx.annotation.UiThread
|
|||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import org.linphone.R
|
||||
import org.linphone.databinding.CallAudioDevicesMenuBinding
|
||||
import org.linphone.ui.call.model.AudioDeviceModel
|
||||
|
||||
|
|
@ -56,6 +57,9 @@ class AudioDevicesMenuDialogFragment(
|
|||
// Makes sure all menu entries are visible,
|
||||
// required for landscape mode (otherwise only first item is visible)
|
||||
dialog.behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
|
||||
// Force this navigation bar color
|
||||
dialog.window?.navigationBarColor = requireContext().getColor(R.color.gray_600)
|
||||
return dialog
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -32,10 +32,13 @@ import androidx.core.view.doOnPreDraw
|
|||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||
import org.linphone.R
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.databinding.MeetingWaitingRoomFragmentBinding
|
||||
import org.linphone.ui.call.fragment.AudioDevicesMenuDialogFragment
|
||||
import org.linphone.ui.call.model.AudioDeviceModel
|
||||
import org.linphone.ui.main.fragment.GenericFragment
|
||||
import org.linphone.ui.main.meetings.viewmodel.MeetingWaitingRoomViewModel
|
||||
|
||||
|
|
@ -63,6 +66,8 @@ class MeetingWaitingRoomFragment : GenericFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
private var bottomSheetDialog: BottomSheetDialogFragment? = null
|
||||
|
||||
private var navBarDefaultColor: Int = -1
|
||||
|
||||
override fun onCreateView(
|
||||
|
|
@ -99,6 +104,12 @@ class MeetingWaitingRoomFragment : GenericFragment() {
|
|||
goBack()
|
||||
}
|
||||
|
||||
viewModel.showAudioDevicesListEvent.observe(viewLifecycleOwner) {
|
||||
it.consume { devices ->
|
||||
showAudioRoutesMenu(devices)
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.conferenceInfoFoundEvent.observe(viewLifecycleOwner) {
|
||||
it.consume { found ->
|
||||
if (found) {
|
||||
|
|
@ -144,6 +155,9 @@ class MeetingWaitingRoomFragment : GenericFragment() {
|
|||
}
|
||||
|
||||
override fun onPause() {
|
||||
bottomSheetDialog?.dismiss()
|
||||
bottomSheetDialog = null
|
||||
|
||||
coreContext.postOnCoreThread { core ->
|
||||
core.nativePreviewWindowId = null
|
||||
core.isVideoPreviewEnabled = false
|
||||
|
|
@ -177,4 +191,10 @@ class MeetingWaitingRoomFragment : GenericFragment() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showAudioRoutesMenu(devicesList: List<AudioDeviceModel>) {
|
||||
val modalBottomSheet = AudioDevicesMenuDialogFragment(devicesList)
|
||||
modalBottomSheet.show(parentFragmentManager, AudioDevicesMenuDialogFragment.TAG)
|
||||
bottomSheetDialog = modalBottomSheet
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,9 @@ import androidx.core.app.ActivityCompat
|
|||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||
import org.linphone.LinphoneApplication.Companion.corePreferences
|
||||
import org.linphone.R
|
||||
import org.linphone.core.AudioDevice
|
||||
import org.linphone.core.Conference
|
||||
import org.linphone.core.ConferenceInfo
|
||||
import org.linphone.core.Core
|
||||
|
|
@ -34,7 +37,9 @@ import org.linphone.core.CoreListenerStub
|
|||
import org.linphone.core.Factory
|
||||
import org.linphone.core.MediaDirection
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.ui.call.model.AudioDeviceModel
|
||||
import org.linphone.ui.main.contacts.model.ContactAvatarModel
|
||||
import org.linphone.utils.AppUtils
|
||||
import org.linphone.utils.Event
|
||||
import org.linphone.utils.TimestampUtils
|
||||
|
||||
|
|
@ -51,6 +56,12 @@ class MeetingWaitingRoomViewModel @UiThread constructor() : ViewModel() {
|
|||
|
||||
val isMicrophoneMuted = MutableLiveData<Boolean>()
|
||||
|
||||
val isSpeakerEnabled = MutableLiveData<Boolean>()
|
||||
|
||||
val isHeadsetEnabled = MutableLiveData<Boolean>()
|
||||
|
||||
val isBluetoothEnabled = MutableLiveData<Boolean>()
|
||||
|
||||
val isVideoAvailable = MutableLiveData<Boolean>()
|
||||
|
||||
val isVideoEnabled = MutableLiveData<Boolean>()
|
||||
|
|
@ -61,12 +72,22 @@ class MeetingWaitingRoomViewModel @UiThread constructor() : ViewModel() {
|
|||
|
||||
val conferenceInfoFoundEvent = MutableLiveData<Event<Boolean>>()
|
||||
|
||||
val showAudioDevicesListEvent: MutableLiveData<Event<ArrayList<AudioDeviceModel>>> by lazy {
|
||||
MutableLiveData<Event<ArrayList<AudioDeviceModel>>>()
|
||||
}
|
||||
|
||||
val conferenceCreatedEvent: MutableLiveData<Event<Boolean>> by lazy {
|
||||
MutableLiveData<Event<Boolean>>()
|
||||
}
|
||||
|
||||
private lateinit var conferenceInfo: ConferenceInfo
|
||||
|
||||
private lateinit var selectedOutputAudioDevice: AudioDevice
|
||||
|
||||
private var earpieceAudioDevice: AudioDevice? = null
|
||||
private var speakerAudioDevice: AudioDevice? = null
|
||||
private var bluetoothAudioDevice: AudioDevice? = null
|
||||
|
||||
private val coreListener = object : CoreListenerStub() {
|
||||
override fun onConferenceStateChanged(
|
||||
core: Core,
|
||||
|
|
@ -84,6 +105,15 @@ class MeetingWaitingRoomViewModel @UiThread constructor() : ViewModel() {
|
|||
coreContext.postOnCoreThread { core ->
|
||||
core.addListener(coreListener)
|
||||
|
||||
val audioDevices = core.audioDevices
|
||||
earpieceAudioDevice = audioDevices.find { it.type == AudioDevice.Type.Earpiece }
|
||||
speakerAudioDevice = audioDevices.find { it.type == AudioDevice.Type.Speaker }
|
||||
bluetoothAudioDevice = audioDevices.find {
|
||||
it.hasCapability(
|
||||
AudioDevice.Capabilities.CapabilityPlay
|
||||
) && (it.type == AudioDevice.Type.Bluetooth || it.type == AudioDevice.Type.HearingAid)
|
||||
}
|
||||
|
||||
hideVideo.postValue(!core.isVideoEnabled)
|
||||
}
|
||||
}
|
||||
|
|
@ -159,6 +189,9 @@ class MeetingWaitingRoomViewModel @UiThread constructor() : ViewModel() {
|
|||
params.isVideoEnabled = true
|
||||
params.videoDirection = if (isVideoEnabled.value == true) MediaDirection.SendRecv else MediaDirection.RecvOnly
|
||||
params.isMicEnabled = isMicrophoneMuted.value == false
|
||||
if (::selectedOutputAudioDevice.isInitialized) {
|
||||
params.outputAudioDevice = selectedOutputAudioDevice
|
||||
}
|
||||
params.account = core.defaultAccount
|
||||
coreContext.startCall(conferenceUri, params)
|
||||
}
|
||||
|
|
@ -176,6 +209,20 @@ class MeetingWaitingRoomViewModel @UiThread constructor() : ViewModel() {
|
|||
@UiThread
|
||||
fun toggleVideo() {
|
||||
isVideoEnabled.value = isVideoEnabled.value == false
|
||||
if (isVideoEnabled.value == true) {
|
||||
coreContext.postOnCoreThread {
|
||||
if (corePreferences.routeAudioToSpeakerWhenVideoIsEnabled) {
|
||||
// If setting says to use speaker when video is enabled, use speaker instead of earpiece
|
||||
if (!::selectedOutputAudioDevice.isInitialized || selectedOutputAudioDevice.type == AudioDevice.Type.Earpiece) {
|
||||
val speaker = speakerAudioDevice
|
||||
if (speaker != null) {
|
||||
selectedOutputAudioDevice = speaker
|
||||
updateOutputAudioDevice(speaker)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
|
|
@ -184,8 +231,85 @@ class MeetingWaitingRoomViewModel @UiThread constructor() : ViewModel() {
|
|||
}
|
||||
|
||||
@UiThread
|
||||
fun toggleSpeaker() {
|
||||
// TODO
|
||||
fun changeAudioOutputDevice() {
|
||||
val routeAudioToSpeaker = isSpeakerEnabled.value != true
|
||||
|
||||
coreContext.postOnCoreThread { core ->
|
||||
val audioDevices = core.audioDevices
|
||||
val list = arrayListOf<AudioDeviceModel>()
|
||||
var earpieceAudioDevice: AudioDevice? = null
|
||||
var speakerAudioDevice: AudioDevice? = null
|
||||
|
||||
for (device in audioDevices) {
|
||||
// Only list output audio devices
|
||||
if (!device.hasCapability(AudioDevice.Capabilities.CapabilityPlay)) continue
|
||||
|
||||
val name = when (device.type) {
|
||||
AudioDevice.Type.Earpiece -> {
|
||||
earpieceAudioDevice = device
|
||||
AppUtils.getString(R.string.call_audio_device_type_earpiece)
|
||||
}
|
||||
AudioDevice.Type.Speaker -> {
|
||||
speakerAudioDevice = device
|
||||
AppUtils.getString(R.string.call_audio_device_type_speaker)
|
||||
}
|
||||
AudioDevice.Type.Headset -> {
|
||||
AppUtils.getString(R.string.call_audio_device_type_headset)
|
||||
}
|
||||
AudioDevice.Type.Headphones -> {
|
||||
AppUtils.getString(R.string.call_audio_device_type_headphones)
|
||||
}
|
||||
AudioDevice.Type.Bluetooth -> {
|
||||
AppUtils.getFormattedString(
|
||||
R.string.call_audio_device_type_bluetooth,
|
||||
device.deviceName
|
||||
)
|
||||
}
|
||||
AudioDevice.Type.HearingAid -> {
|
||||
AppUtils.getFormattedString(
|
||||
R.string.call_audio_device_type_hearing_aid,
|
||||
device.deviceName
|
||||
)
|
||||
}
|
||||
else -> device.deviceName
|
||||
}
|
||||
|
||||
val currentDevice = if (::selectedOutputAudioDevice.isInitialized) {
|
||||
selectedOutputAudioDevice
|
||||
} else {
|
||||
core.outputAudioDevice ?: core.defaultOutputAudioDevice
|
||||
}
|
||||
val isCurrentlyInUse = device.type == currentDevice?.type && device.deviceName == currentDevice?.deviceName
|
||||
val model = AudioDeviceModel(device, name, device.type, isCurrentlyInUse) {
|
||||
// onSelected
|
||||
coreContext.postOnCoreThread {
|
||||
Log.i("$TAG Selected audio device with ID [${device.id}]")
|
||||
selectedOutputAudioDevice = device
|
||||
updateOutputAudioDevice(device)
|
||||
}
|
||||
}
|
||||
list.add(model)
|
||||
Log.i("$TAG Found audio device [$device]")
|
||||
}
|
||||
|
||||
if (list.size > 2) {
|
||||
Log.i("$TAG Found more than two devices, showing list to let user choose")
|
||||
showAudioDevicesListEvent.postValue(Event(list))
|
||||
} else {
|
||||
Log.i(
|
||||
"$TAG Found less than two devices, simply switching between earpiece & speaker"
|
||||
)
|
||||
val newAudioDevice = if (routeAudioToSpeaker) {
|
||||
speakerAudioDevice
|
||||
} else {
|
||||
earpieceAudioDevice ?: speakerAudioDevice
|
||||
}
|
||||
if (newAudioDevice != null) {
|
||||
selectedOutputAudioDevice = newAudioDevice
|
||||
updateOutputAudioDevice(newAudioDevice)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
|
|
@ -222,12 +346,61 @@ class MeetingWaitingRoomViewModel @UiThread constructor() : ViewModel() {
|
|||
coreContext.context,
|
||||
Manifest.permission.CAMERA
|
||||
) == PackageManager.PERMISSION_GRANTED
|
||||
isVideoEnabled.postValue(core.isVideoEnabled && cameraPermissionGranted)
|
||||
val videoEnabled = core.isVideoEnabled && cameraPermissionGranted
|
||||
isVideoEnabled.postValue(videoEnabled)
|
||||
|
||||
isSwitchCameraAvailable.postValue(coreContext.showSwitchCameraButton())
|
||||
|
||||
isMicrophoneMuted.postValue(!core.isMicEnabled)
|
||||
|
||||
// TODO: audio routes
|
||||
initOutputAudioDevice(videoEnabled)
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun initOutputAudioDevice(videoEnabled: Boolean) {
|
||||
val core = coreContext.core
|
||||
|
||||
val audioDevice = if (corePreferences.routeAudioToBluetoothIfAvailable) {
|
||||
// Prefer bluetooth audio device if setting says so
|
||||
if (bluetoothAudioDevice != null) {
|
||||
bluetoothAudioDevice
|
||||
} else {
|
||||
if (corePreferences.routeAudioToSpeakerWhenVideoIsEnabled && videoEnabled) {
|
||||
// If setting says to use speaker when video is enabled, use speaker instead of earpiece
|
||||
val defaultDevice = core.outputAudioDevice ?: core.defaultOutputAudioDevice
|
||||
if (defaultDevice?.type == AudioDevice.Type.Earpiece) {
|
||||
speakerAudioDevice
|
||||
} else {
|
||||
defaultDevice
|
||||
}
|
||||
} else {
|
||||
core.outputAudioDevice ?: core.defaultOutputAudioDevice
|
||||
}
|
||||
}
|
||||
} else if (corePreferences.routeAudioToSpeakerWhenVideoIsEnabled && videoEnabled) {
|
||||
// If setting says to use speaker when video is enabled, use speaker instead of earpiece
|
||||
val defaultDevice = core.outputAudioDevice ?: core.defaultOutputAudioDevice
|
||||
if (defaultDevice?.type == AudioDevice.Type.Earpiece) {
|
||||
speakerAudioDevice
|
||||
} else {
|
||||
defaultDevice
|
||||
}
|
||||
} else {
|
||||
core.outputAudioDevice ?: core.defaultOutputAudioDevice
|
||||
}
|
||||
if (audioDevice != null) {
|
||||
selectedOutputAudioDevice = audioDevice
|
||||
updateOutputAudioDevice(audioDevice)
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun updateOutputAudioDevice(audioDevice: AudioDevice) {
|
||||
Log.i("$TAG Selected output audio device is [${audioDevice.id}]")
|
||||
isSpeakerEnabled.postValue(audioDevice.type == AudioDevice.Type.Speaker)
|
||||
isHeadsetEnabled.postValue(
|
||||
audioDevice.type == AudioDevice.Type.Headphones || audioDevice.type == AudioDevice.Type.Headset
|
||||
)
|
||||
isBluetoothEnabled.postValue(audioDevice.type == AudioDevice.Type.Bluetooth)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@
|
|||
|
||||
<include
|
||||
android:id="@+id/main_actions"
|
||||
layout="@layout/call_common_actions"
|
||||
layout="@layout/call_actions_generic"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/call_main_actions_menu_height"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@
|
|||
|
||||
<include
|
||||
android:id="@+id/main_actions"
|
||||
layout="@layout/call_common_actions"
|
||||
layout="@layout/call_actions_generic"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/call_main_actions_menu_height"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<include
|
||||
android:id="@+id/main_actions"
|
||||
layout="@layout/call_common_actions"
|
||||
layout="@layout/call_actions_generic"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/call_main_actions_menu_height"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
|
|
|||
|
|
@ -157,12 +157,12 @@
|
|||
|
||||
<ImageView
|
||||
android:id="@+id/change_audio_output"
|
||||
android:onClick="@{() -> viewModel.toggleSpeaker()}"
|
||||
android:onClick="@{() -> viewModel.changeAudioOutputDevice()}"
|
||||
android:layout_width="@dimen/call_button_size"
|
||||
android:layout_height="@dimen/call_button_size"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:padding="@dimen/call_button_icon_padding"
|
||||
android:src="@drawable/speaker_slash"
|
||||
android:src="@{viewModel.isHeadsetEnabled ? @drawable/headset : viewModel.isBluetoothEnabled ? @drawable/bluetooth : viewModel.isSpeakerEnabled ? @drawable/speaker_high : @drawable/speaker_slash, default=@drawable/speaker_slash}"
|
||||
android:background="@drawable/in_call_button_background_red"
|
||||
app:tint="@color/in_call_button_tint_color"
|
||||
app:layout_constraintTop_toTopOf="@id/toggle_mute_mic"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue