Fixed audio focus requests for voice recording/playing + call record playback and switched to MKV/Opus encoding instead of WAV because it's lighter

This commit is contained in:
Sylvain Berfini 2021-08-25 10:53:33 +02:00
parent c699ddc1e9
commit 2e36f1aa86
5 changed files with 90 additions and 34 deletions

View file

@ -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<String>()
val voiceRecordPlayingPosition = MutableLiveData<Int>()
val isVoiceRecordPlaying = MutableLiveData<Boolean>()
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

View file

@ -90,7 +90,7 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel()
val voiceRecordPlayingPosition = MutableLiveData<Int>()
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

View file

@ -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<Boolean>()
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()
}

View file

@ -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

View file

@ -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")
}
}
}