diff --git a/app/src/main/java/org/linphone/ui/main/settings/viewmodel/SettingsViewModel.kt b/app/src/main/java/org/linphone/ui/main/settings/viewmodel/SettingsViewModel.kt index 31ae13353..9887fbc0b 100644 --- a/app/src/main/java/org/linphone/ui/main/settings/viewmodel/SettingsViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/settings/viewmodel/SettingsViewModel.kt @@ -21,6 +21,7 @@ package org.linphone.ui.main.settings.viewmodel import android.os.Vibrator import androidx.annotation.UiThread +import androidx.annotation.WorkerThread import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import java.io.File @@ -28,8 +29,11 @@ import java.util.Locale import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.R +import org.linphone.core.Player +import org.linphone.core.PlayerListener import org.linphone.core.tools.Log import org.linphone.utils.AppUtils +import org.linphone.utils.AudioRouteUtils class SettingsViewModel @UiThread constructor() : ViewModel() { companion object { @@ -49,6 +53,7 @@ class SettingsViewModel @UiThread constructor() : ViewModel() { val availableRingtonesPaths = arrayListOf() val availableRingtonesNames = arrayListOf() val selectedRingtone = MutableLiveData() + val isRingtonePlaying = MutableLiveData() val vibrateDuringIncomingCall = MutableLiveData() val autoRecordCalls = MutableLiveData() @@ -59,6 +64,15 @@ class SettingsViewModel @UiThread constructor() : ViewModel() { val isVibrationAvailable = MutableLiveData() + // Other + + private lateinit var ringtonePlayer: Player + + private val playerListener = PlayerListener { + Log.i("[$TAG] End of ringtone reached") + stopRingtonePlayer() + } + init { expandCalls.value = false expandNetwork.value = false @@ -86,6 +100,18 @@ class SettingsViewModel @UiThread constructor() : ViewModel() { } } + @UiThread + override fun onCleared() { + super.onCleared() + + coreContext.postOnCoreThread { + if (::ringtonePlayer.isInitialized) { + stopRingtonePlayer() + ringtonePlayer.removeListener(playerListener) + } + } + } + @UiThread fun toggleEchoCanceller() { val newValue = echoCancellerEnabled.value == false @@ -140,6 +166,40 @@ class SettingsViewModel @UiThread constructor() : ViewModel() { fun setRingtone(ringtone: String) { coreContext.postOnCoreThread { core -> core.ring = ringtone + + if (::ringtonePlayer.isInitialized) { + if (ringtonePlayer.state == Player.State.Playing) { + stopRingtonePlayer() + } + } + } + } + + @UiThread + fun playPauseRingtone() { + coreContext.postOnCoreThread { core -> + if (!::ringtonePlayer.isInitialized) { + val playbackDevice = AudioRouteUtils.getAudioPlaybackDeviceIdForCallRecordingOrVoiceMessage() + val player = core.createLocalPlayer(playbackDevice, null, null) + ringtonePlayer = player ?: return@postOnCoreThread + ringtonePlayer.addListener(playerListener) + } + + if (ringtonePlayer.state == Player.State.Playing) { + stopRingtonePlayer() + } else { + val path = core.ring.orEmpty() + // TODO FIXME: play device default ringtone if selected + if (ringtonePlayer.open(path) == 0) { + if (ringtonePlayer.start() == 0) { + isRingtonePlaying.postValue(true) + } else { + Log.e("$TAG Failed to play ringtone [$path]") + } + } else { + Log.e("$TAG Failed to open ringtone [$path]") + } + } } } @@ -205,4 +265,15 @@ class SettingsViewModel @UiThread constructor() : ViewModel() { } } } + + @WorkerThread + private fun stopRingtonePlayer() { + if (::ringtonePlayer.isInitialized && ringtonePlayer.state != Player.State.Closed) { + Log.i("$TAG Stopping ringtone player") + ringtonePlayer.pause() + ringtonePlayer.seek(0) + ringtonePlayer.close() + isRingtonePlaying.postValue(false) + } + } } diff --git a/app/src/main/java/org/linphone/utils/AudioRouteUtils.kt b/app/src/main/java/org/linphone/utils/AudioRouteUtils.kt index 769104e64..4a6a5526e 100644 --- a/app/src/main/java/org/linphone/utils/AudioRouteUtils.kt +++ b/app/src/main/java/org/linphone/utils/AudioRouteUtils.kt @@ -160,5 +160,38 @@ class AudioRouteUtils { } } } + + @WorkerThread + fun getAudioPlaybackDeviceIdForCallRecordingOrVoiceMessage(): String? { + // In case no headphones/headset/hearing aid/bluetooth is connected, use speaker sound card to play recordings, otherwise use earpiece + // If none are available, default one will be used + var headphonesCard: String? = null + var bluetoothCard: String? = null + var speakerCard: String? = null + var earpieceCard: String? = null + for (device in coreContext.core.audioDevices) { + if (device.hasCapability(AudioDevice.Capabilities.CapabilityPlay)) { + when (device.type) { + AudioDevice.Type.Headphones, AudioDevice.Type.Headset -> { + headphonesCard = device.id + } + AudioDevice.Type.Bluetooth, AudioDevice.Type.HearingAid -> { + bluetoothCard = device.id + } + AudioDevice.Type.Speaker -> { + speakerCard = device.id + } + AudioDevice.Type.Earpiece -> { + earpieceCard = device.id + } + else -> {} + } + } + } + Log.i( + "$TAG Found headset/headphones/hearingAid sound card [$headphonesCard], bluetooth sound card [$bluetoothCard], speaker sound card [$speakerCard] and earpiece sound card [$earpieceCard]" + ) + return headphonesCard ?: bluetoothCard ?: speakerCard ?: earpieceCard + } } } diff --git a/app/src/main/res/drawable/pause.xml b/app/src/main/res/drawable/pause.xml new file mode 100644 index 000000000..cffb47b14 --- /dev/null +++ b/app/src/main/res/drawable/pause.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/play.xml b/app/src/main/res/drawable/play.xml new file mode 100644 index 000000000..7b4a2281d --- /dev/null +++ b/app/src/main/res/drawable/play.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/settings_fragment.xml b/app/src/main/res/layout/settings_fragment.xml index 24a77b0e3..b80eaaed8 100644 --- a/app/src/main/res/layout/settings_fragment.xml +++ b/app/src/main/res/layout/settings_fragment.xml @@ -296,7 +296,7 @@ android:ellipsize="end" app:layout_constraintTop_toBottomOf="@id/auto_accept_video_switch" app:layout_constraintStart_toStartOf="@id/calls_background" - app:layout_constraintEnd_toEndOf="@id/calls_background"/> + app:layout_constraintEnd_toStartOf="@id/device_ringtone_player"/> + app:layout_constraintEnd_toStartOf="@id/device_ringtone_player" /> + + Always accept video calls Choose your ringtone: Use this device\'s ringtone + Play + Pause Vibrate while incoming call is ringing Automatically start recording calls Network