From 2e36f1aa862241e88339d4f3dac90cbe288a1d8a Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Wed, 25 Aug 2021 10:53:33 +0200 Subject: [PATCH] Fixed audio focus requests for voice recording/playing + call record playback and switched to MKV/Opus encoding instead of WAV because it's lighter --- .../main/chat/data/ChatMessageContentData.kt | 24 ++++---- .../viewmodels/ChatMessageSendingViewModel.kt | 56 ++++++++++++++----- .../viewmodels/RecordingsViewModel.kt | 24 ++++++++ .../java/org/linphone/core/CorePreferences.kt | 5 ++ .../main/java/org/linphone/utils/AppUtils.kt | 15 +++-- 5 files changed, 90 insertions(+), 34 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 8c2be03cc..37c44ea20 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 @@ -30,7 +30,6 @@ import androidx.media.AudioFocusRequestCompat import java.text.SimpleDateFormat import java.util.* import kotlinx.coroutines.* -import kotlinx.coroutines.channels.ticker import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -41,7 +40,6 @@ import org.linphone.core.tools.Log import org.linphone.utils.AppUtils import org.linphone.utils.FileUtils import org.linphone.utils.ImageUtils -import java.lang.Exception class ChatMessageContentData( private val chatMessage: ChatMessage, @@ -74,7 +72,7 @@ class ChatMessageContentData( val formattedDuration = MutableLiveData() val voiceRecordPlayingPosition = MutableLiveData() val isVoiceRecordPlaying = MutableLiveData() - var voiceRecordPlayingAudioFocusRequest: AudioFocusRequestCompat? = null + var voiceRecordAudioFocusRequest: AudioFocusRequestCompat? = null val isAlone: Boolean get() { @@ -210,7 +208,7 @@ class ChatMessageContentData( val isVoiceRecord = content.isVoiceRecording filePath.value = path isImage.value = FileUtils.isExtensionImage(path) - isVideo.value = FileUtils.isExtensionVideo(path) + isVideo.value = FileUtils.isExtensionVideo(path) && !isVoiceRecord isAudio.value = FileUtils.isExtensionAudio(path) && !isVoiceRecord isPdf.value = FileUtils.isExtensionPdf(path) isVoiceRecording.value = isVoiceRecord @@ -259,8 +257,12 @@ class ChatMessageContentData( initVoiceRecordPlayer() } - if (voiceRecordPlayingAudioFocusRequest == null) { - voiceRecordPlayingAudioFocusRequest = AppUtils.acquireAudioFocusForVoiceRecording( + if (AppUtils.isMediaVolumeLow(coreContext.context)) { + Toast.makeText(coreContext.context, R.string.chat_message_voice_recording_playback_low_volume, Toast.LENGTH_LONG).show() + } + + if (voiceRecordAudioFocusRequest == null) { + voiceRecordAudioFocusRequest = AppUtils.acquireAudioFocusForVoiceRecordingOrPlayback( coreContext.context ) } @@ -277,10 +279,10 @@ class ChatMessageContentData( voiceRecordingPlayer.pause() } - val request = voiceRecordPlayingAudioFocusRequest + val request = voiceRecordAudioFocusRequest if (request != null) { - AppUtils.releaseAudioFocusForVoiceRecording(coreContext.context, request) - voiceRecordPlayingAudioFocusRequest = null + AppUtils.releaseAudioFocusForVoiceRecordingOrPlayback(coreContext.context, request) + voiceRecordAudioFocusRequest = null } isVoiceRecordPlaying.value = false @@ -310,10 +312,6 @@ class ChatMessageContentData( } Log.i("[Voice Recording] Found speaker sound card [$speakerCard] and earpiece sound card [$earpieceCard]") - if (AppUtils.isMediaVolumeLow(coreContext.context)) { - Toast.makeText(coreContext.context, R.string.chat_message_voice_recording_playback_low_volume, Toast.LENGTH_LONG).show() - } - val localPlayer = coreContext.core.createLocalPlayer(speakerCard ?: earpieceCard, null, null) if (localPlayer != null) { voiceRecordingPlayer = localPlayer 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 ec192b441..724ac6dce 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 @@ -90,7 +90,7 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel() val voiceRecordPlayingPosition = MutableLiveData() - var voiceRecordPlayingAudioFocusRequest: AudioFocusRequestCompat? = null + var voiceRecordAudioFocusRequest: AudioFocusRequestCompat? = null private lateinit var voiceRecordingPlayer: Player private val playerListener = PlayerListener { @@ -108,7 +108,11 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel() isReadOnly.value = chatRoom.hasBeenLeft() val recorderParams = coreContext.core.createRecorderParams() - recorderParams.fileFormat = RecorderFileFormat.Wav + if (corePreferences.voiceMessagesFormatMkv) { + recorderParams.fileFormat = RecorderFileFormat.Mkv + } else { + recorderParams.fileFormat = RecorderFileFormat.Wav + } recorder = coreContext.core.createRecorder(recorderParams) } @@ -279,6 +283,12 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel() return } + if (voiceRecordAudioFocusRequest == null) { + voiceRecordAudioFocusRequest = AppUtils.acquireAudioFocusForVoiceRecordingOrPlayback( + coreContext.context + ) + } + when (recorder.state) { RecorderState.Running -> Log.w("[Chat Message Sending] Recorder is already recording") RecorderState.Paused -> { @@ -286,7 +296,11 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel() recorder.start() } RecorderState.Closed -> { - val tempFileName = "voice-recording-${System.currentTimeMillis()}.wav" + val extension = when (recorder.params.fileFormat) { + RecorderFileFormat.Mkv -> "mkv" + else -> "wav" + } + val tempFileName = "voice-recording-${System.currentTimeMillis()}.$extension" val file = FileUtils.getFileStoragePath(tempFileName) Log.w("[Chat Message Sending] Recorder is closed, starting recording in ${file.absoluteFile}") recorder.open(file.absolutePath) @@ -329,6 +343,12 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel() } } + val request = voiceRecordAudioFocusRequest + if (request != null) { + AppUtils.releaseAudioFocusForVoiceRecordingOrPlayback(coreContext.context, request) + voiceRecordAudioFocusRequest = null + } + isPendingVoiceRecord.value = false isVoiceRecording.value = false sendMessageEnabled.value = textToSend.value?.isNotEmpty() == true || attachments.value?.isNotEmpty() == true @@ -342,6 +362,12 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel() voiceRecordingDuration.value = recorder.duration } + val request = voiceRecordAudioFocusRequest + if (request != null) { + AppUtils.releaseAudioFocusForVoiceRecordingOrPlayback(coreContext.context, request) + voiceRecordAudioFocusRequest = null + } + isVoiceRecording.value = false if (corePreferences.sendVoiceRecordingRightAway) { Log.i("[Chat Message Sending] Sending voice recording right away") @@ -355,8 +381,12 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel() initVoiceRecordPlayer() } - if (voiceRecordPlayingAudioFocusRequest == null) { - voiceRecordPlayingAudioFocusRequest = AppUtils.acquireAudioFocusForVoiceRecording( + if (AppUtils.isMediaVolumeLow(coreContext.context)) { + Toast.makeText(coreContext.context, R.string.chat_message_voice_recording_playback_low_volume, Toast.LENGTH_LONG).show() + } + + if (voiceRecordAudioFocusRequest == null) { + voiceRecordAudioFocusRequest = AppUtils.acquireAudioFocusForVoiceRecordingOrPlayback( coreContext.context ) } @@ -375,10 +405,10 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel() voiceRecordingPlayer.pause() } - val request = voiceRecordPlayingAudioFocusRequest + val request = voiceRecordAudioFocusRequest if (request != null) { - AppUtils.releaseAudioFocusForVoiceRecording(coreContext.context, request) - voiceRecordPlayingAudioFocusRequest = null + AppUtils.releaseAudioFocusForVoiceRecordingOrPlayback(coreContext.context, request) + voiceRecordAudioFocusRequest = null } isPlayingVoiceRecording.value = false @@ -401,10 +431,6 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel() } Log.i("[Chat Message Sending] Found speaker sound card [$speakerCard] and earpiece sound card [$earpieceCard]") - if (AppUtils.isMediaVolumeLow(coreContext.context)) { - Toast.makeText(coreContext.context, R.string.chat_message_voice_recording_playback_low_volume, Toast.LENGTH_LONG).show() - } - val localPlayer = coreContext.core.createLocalPlayer(speakerCard ?: earpieceCard, null, null) if (localPlayer != null) { voiceRecordingPlayer = localPlayer @@ -431,10 +457,10 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel() voiceRecordingPlayer.close() } - val request = voiceRecordPlayingAudioFocusRequest + val request = voiceRecordAudioFocusRequest if (request != null) { - AppUtils.releaseAudioFocusForVoiceRecording(coreContext.context, request) - voiceRecordPlayingAudioFocusRequest = null + AppUtils.releaseAudioFocusForVoiceRecordingOrPlayback(coreContext.context, request) + voiceRecordAudioFocusRequest = null } isPlayingVoiceRecording.value = false diff --git a/app/src/main/java/org/linphone/activities/main/recordings/viewmodels/RecordingsViewModel.kt b/app/src/main/java/org/linphone/activities/main/recordings/viewmodels/RecordingsViewModel.kt index b3a0e4848..3ed58c02c 100644 --- a/app/src/main/java/org/linphone/activities/main/recordings/viewmodels/RecordingsViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/recordings/viewmodels/RecordingsViewModel.kt @@ -21,9 +21,12 @@ package org.linphone.activities.main.recordings.viewmodels import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.media.AudioFocusRequestCompat import kotlin.collections.ArrayList +import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.activities.main.recordings.data.RecordingData import org.linphone.core.tools.Log +import org.linphone.utils.AppUtils import org.linphone.utils.FileUtils class RecordingsViewModel : ViewModel() { @@ -31,12 +34,26 @@ class RecordingsViewModel : ViewModel() { val isVideoVisible = MutableLiveData() + private var recordingPlayingAudioFocusRequest: AudioFocusRequestCompat? = null + private val recordingListener = object : RecordingData.RecordingListener { override fun onPlayingStarted(videoAvailable: Boolean) { + if (recordingPlayingAudioFocusRequest == null) { + recordingPlayingAudioFocusRequest = AppUtils.acquireAudioFocusForVoiceRecordingOrPlayback( + coreContext.context + ) + } + isVideoVisible.value = videoAvailable } override fun onPlayingEnded() { + val request = recordingPlayingAudioFocusRequest + if (request != null) { + AppUtils.releaseAudioFocusForVoiceRecordingOrPlayback(coreContext.context, request) + recordingPlayingAudioFocusRequest = null + } + isVideoVisible.value = false } } @@ -48,6 +65,13 @@ class RecordingsViewModel : ViewModel() { override fun onCleared() { recordingsList.value.orEmpty().forEach(RecordingData::destroy) + + val request = recordingPlayingAudioFocusRequest + if (request != null) { + AppUtils.releaseAudioFocusForVoiceRecordingOrPlayback(coreContext.context, request) + recordingPlayingAudioFocusRequest = null + } + super.onCleared() } diff --git a/app/src/main/java/org/linphone/core/CorePreferences.kt b/app/src/main/java/org/linphone/core/CorePreferences.kt index 62be01a4f..bbdcb9a11 100644 --- a/app/src/main/java/org/linphone/core/CorePreferences.kt +++ b/app/src/main/java/org/linphone/core/CorePreferences.kt @@ -412,6 +412,11 @@ class CorePreferences constructor(private val context: Context) { val preventInterfaceFromShowingUp: Boolean get() = config.getBool("app", "keep_app_invisible", false) + // By default we will record voice messages using MKV format and Opus audio encoding + // If disabled, WAV format will be used instead. Warning: files will be heavier! + val voiceMessagesFormatMkv: Boolean + get() = config.getBool("app", "record_voice_messages_in_mkv_format", true) + /* Default values related */ val echoCancellerCalibration: Int diff --git a/app/src/main/java/org/linphone/utils/AppUtils.kt b/app/src/main/java/org/linphone/utils/AppUtils.kt index 377542169..f3b83fa42 100644 --- a/app/src/main/java/org/linphone/utils/AppUtils.kt +++ b/app/src/main/java/org/linphone/utils/AppUtils.kt @@ -156,7 +156,7 @@ class AppUtils { return currentVolume <= maxVolume * 0.5 } - fun acquireAudioFocusForVoiceRecording(context: Context): AudioFocusRequestCompat { + fun acquireAudioFocusForVoiceRecordingOrPlayback(context: Context): AudioFocusRequestCompat { val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager val audioAttrs = AudioAttributesCompat.Builder() .setUsage(AudioAttributesCompat.USAGE_MEDIA) @@ -170,22 +170,25 @@ class AppUtils { .build() when (AudioManagerCompat.requestAudioFocus(audioManager, request)) { AudioManager.AUDIOFOCUS_REQUEST_GRANTED -> { - Log.i("[Audio Focus] Voice recording audio focus request granted") + Log.i("[Audio Focus] Voice recording/playback audio focus request granted") } AudioManager.AUDIOFOCUS_REQUEST_FAILED -> { - Log.w("[Audio Focus] Voice recording audio focus request failed") + Log.w("[Audio Focus] Voice recording/playback audio focus request failed") } AudioManager.AUDIOFOCUS_REQUEST_DELAYED -> { - Log.w("[Audio Focus] Voice recording audio focus request delayed") + Log.w("[Audio Focus] Voice recording/playback audio focus request delayed") } } return request } - fun releaseAudioFocusForVoiceRecording(context: Context, request: AudioFocusRequestCompat) { + fun releaseAudioFocusForVoiceRecordingOrPlayback( + context: Context, + request: AudioFocusRequestCompat + ) { val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager AudioManagerCompat.abandonAudioFocusRequest(audioManager, request) - Log.i("[Audio Focus] Voice recording audio focus request abandoned") + Log.i("[Audio Focus] Voice recording/playback audio focus request abandoned") } } }