From 6a5b014789d15ea4d72e4d5d8632949fd6a82569 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 9 Jan 2023 13:59:22 +0100 Subject: [PATCH] Fixed ANRs in voice message recording/playback + Allow voice recording Bluetooth playback + allow voice message recording using headset/headphones/hearing aids/bluetooth device --- .../main/chat/data/ChatMessageContentData.kt | 18 +++-- .../viewmodels/ChatMessageSendingViewModel.kt | 65 ++++++++++++++----- 2 files changed, 62 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageContentData.kt b/app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageContentData.kt index 4761ebd85..20ec64c49 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageContentData.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageContentData.kt @@ -424,30 +424,36 @@ class ChatMessageContentData( private fun initVoiceRecordPlayer() { Log.i("[Voice Recording] Creating player for voice record") - // In case no headphones/headset is connected, use speaker sound card to play recordings, otherwise use earpiece + // 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, AudioDevice.Type.HearingAid -> { + headphonesCard = device.id + } + AudioDevice.Type.Bluetooth -> { + bluetoothCard = device.id + } AudioDevice.Type.Speaker -> { speakerCard = device.id } AudioDevice.Type.Earpiece -> { earpieceCard = device.id } - AudioDevice.Type.Headphones, AudioDevice.Type.Headset -> { - headphonesCard = device.id - } else -> {} } } } - Log.i("[Voice Recording] Found headset/headphones sound card [$headphonesCard], speaker sound card [$speakerCard] and earpiece sound card [$earpieceCard]") + Log.i("[Voice Recording] Found headset/headphones/hearingAid sound card [$headphonesCard], bluetooth sound card [$bluetoothCard], speaker sound card [$speakerCard] and earpiece sound card [$earpieceCard]") + val playbackSoundCard = headphonesCard ?: bluetoothCard ?: speakerCard ?: earpieceCard + Log.i("[Voice Recording] Using device $playbackSoundCard to make the voice message playback") - val localPlayer = coreContext.core.createLocalPlayer(headphonesCard ?: speakerCard ?: earpieceCard, null, null) + val localPlayer = coreContext.core.createLocalPlayer(playbackSoundCard, null, null) if (localPlayer != null) { voiceRecordingPlayer = localPlayer } else { diff --git a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatMessageSendingViewModel.kt b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatMessageSendingViewModel.kt index 3b0abf8a5..55cbda6f8 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatMessageSendingViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatMessageSendingViewModel.kt @@ -142,6 +142,32 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel() } else { recorderParams.fileFormat = RecorderFileFormat.Wav } + + // In case no headphones/headset/hearing aid/bluetooth is connected, use microphone + // If none are available, default one will be used + var bluetoothAudioDevice: AudioDevice? = null + var headsetAudioDevice: AudioDevice? = null + var builtinMicrophone: AudioDevice? = null + for (device in coreContext.core.audioDevices) { + if (device.hasCapability(AudioDevice.Capabilities.CapabilityRecord)) { + when (device.type) { + AudioDevice.Type.Bluetooth -> { + bluetoothAudioDevice = device + } + AudioDevice.Type.Headset, AudioDevice.Type.HearingAid, AudioDevice.Type.Headphones -> { + headsetAudioDevice = device + } + AudioDevice.Type.Microphone -> { + builtinMicrophone = device + } + else -> {} + } + } + } + Log.i("[Chat Message Sending] Found headset/headphones/hearingAid [${headsetAudioDevice?.id}], bluetooth [${bluetoothAudioDevice?.id}] and builtin microphone [${builtinMicrophone?.id}]") + recorderParams.audioDevice = headsetAudioDevice ?: bluetoothAudioDevice ?: builtinMicrophone + Log.i("[Chat Message Sending] Using device ${recorderParams.audioDevice?.id} to make the voice message recording") + recorder = coreContext.core.createRecorder(recorderParams) } @@ -287,14 +313,14 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel() } private fun tickerFlowRecording() = flow { - while (recorder.state == RecorderState.Running) { + while (isVoiceRecording.value == true) { emit(Unit) delay(100) } } private fun tickerFlowPlaying() = flow { - while (voiceRecordingPlayer.state == Player.State.Playing) { + while (isPlayingVoiceRecording.value == true) { emit(Unit) delay(100) } @@ -353,14 +379,15 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel() isVoiceRecording.value = true sendMessageEnabled.value = true + val maxVoiceRecordDuration = corePreferences.voiceRecordingMaxDuration tickerFlowRecording().onEach { - val duration = recorder.duration - voiceRecordingDuration.postValue(recorder.duration % voiceRecordingProgressBarMax) - formattedDuration.postValue(SimpleDateFormat("mm:ss", Locale.getDefault()).format(duration)) // duration is in ms + withContext(Dispatchers.Main) { + val duration = recorder.duration + voiceRecordingDuration.value = recorder.duration % voiceRecordingProgressBarMax + formattedDuration.value = SimpleDateFormat("mm:ss", Locale.getDefault()).format(duration) // duration is in ms - if (duration >= corePreferences.voiceRecordingMaxDuration) { - withContext(Dispatchers.Main) { - Log.w("[Chat Message Sending] Max duration for voice recording exceeded (${corePreferences.voiceRecordingMaxDuration}ms), stopping.") + if (duration >= maxVoiceRecordDuration) { + Log.w("[Chat Message Sending] Max duration for voice recording exceeded (${maxVoiceRecordDuration}ms), stopping.") stopVoiceRecording() } } @@ -435,7 +462,9 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel() isPlayingVoiceRecording.value = true tickerFlowPlaying().onEach { - voiceRecordPlayingPosition.postValue(voiceRecordingPlayer.currentPosition) + withContext(Dispatchers.Main) { + voiceRecordPlayingPosition.value = voiceRecordingPlayer.currentPosition + } }.launchIn(scope) } @@ -456,30 +485,36 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel() private fun initVoiceRecordPlayer() { Log.i("[Chat Message Sending] Creating player for voice record") - // In case no headphones/headset is connected, use speaker sound card to play recordings, otherwise use earpiece + // 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, AudioDevice.Type.HearingAid -> { + headphonesCard = device.id + } + AudioDevice.Type.Bluetooth -> { + bluetoothCard = device.id + } AudioDevice.Type.Speaker -> { speakerCard = device.id } AudioDevice.Type.Earpiece -> { earpieceCard = device.id } - AudioDevice.Type.Headphones, AudioDevice.Type.Headset -> { - headphonesCard = device.id - } else -> {} } } } - Log.i("[Chat Message Sending] Found headset/headphones sound card [$headphonesCard], speaker sound card [$speakerCard] and earpiece sound card [$earpieceCard]") + Log.i("[Chat Message Sending] Found headset/headphones/hearingAid sound card [$headphonesCard], bluetooth sound card [$bluetoothCard], speaker sound card [$speakerCard] and earpiece sound card [$earpieceCard]") + val playbackSoundCard = headphonesCard ?: bluetoothCard ?: speakerCard ?: earpieceCard + Log.i("[Chat Message Sending] Using device $playbackSoundCard to make the voice message playback") - val localPlayer = coreContext.core.createLocalPlayer(headphonesCard ?: speakerCard ?: earpieceCard, null, null) + val localPlayer = coreContext.core.createLocalPlayer(playbackSoundCard, null, null) if (localPlayer != null) { voiceRecordingPlayer = localPlayer } else {