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:
parent
c699ddc1e9
commit
2e36f1aa86
5 changed files with 90 additions and 34 deletions
|
@ -30,7 +30,6 @@ import androidx.media.AudioFocusRequestCompat
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.channels.ticker
|
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
@ -41,7 +40,6 @@ import org.linphone.core.tools.Log
|
||||||
import org.linphone.utils.AppUtils
|
import org.linphone.utils.AppUtils
|
||||||
import org.linphone.utils.FileUtils
|
import org.linphone.utils.FileUtils
|
||||||
import org.linphone.utils.ImageUtils
|
import org.linphone.utils.ImageUtils
|
||||||
import java.lang.Exception
|
|
||||||
|
|
||||||
class ChatMessageContentData(
|
class ChatMessageContentData(
|
||||||
private val chatMessage: ChatMessage,
|
private val chatMessage: ChatMessage,
|
||||||
|
@ -74,7 +72,7 @@ class ChatMessageContentData(
|
||||||
val formattedDuration = MutableLiveData<String>()
|
val formattedDuration = MutableLiveData<String>()
|
||||||
val voiceRecordPlayingPosition = MutableLiveData<Int>()
|
val voiceRecordPlayingPosition = MutableLiveData<Int>()
|
||||||
val isVoiceRecordPlaying = MutableLiveData<Boolean>()
|
val isVoiceRecordPlaying = MutableLiveData<Boolean>()
|
||||||
var voiceRecordPlayingAudioFocusRequest: AudioFocusRequestCompat? = null
|
var voiceRecordAudioFocusRequest: AudioFocusRequestCompat? = null
|
||||||
|
|
||||||
val isAlone: Boolean
|
val isAlone: Boolean
|
||||||
get() {
|
get() {
|
||||||
|
@ -210,7 +208,7 @@ class ChatMessageContentData(
|
||||||
val isVoiceRecord = content.isVoiceRecording
|
val isVoiceRecord = content.isVoiceRecording
|
||||||
filePath.value = path
|
filePath.value = path
|
||||||
isImage.value = FileUtils.isExtensionImage(path)
|
isImage.value = FileUtils.isExtensionImage(path)
|
||||||
isVideo.value = FileUtils.isExtensionVideo(path)
|
isVideo.value = FileUtils.isExtensionVideo(path) && !isVoiceRecord
|
||||||
isAudio.value = FileUtils.isExtensionAudio(path) && !isVoiceRecord
|
isAudio.value = FileUtils.isExtensionAudio(path) && !isVoiceRecord
|
||||||
isPdf.value = FileUtils.isExtensionPdf(path)
|
isPdf.value = FileUtils.isExtensionPdf(path)
|
||||||
isVoiceRecording.value = isVoiceRecord
|
isVoiceRecording.value = isVoiceRecord
|
||||||
|
@ -259,8 +257,12 @@ class ChatMessageContentData(
|
||||||
initVoiceRecordPlayer()
|
initVoiceRecordPlayer()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (voiceRecordPlayingAudioFocusRequest == null) {
|
if (AppUtils.isMediaVolumeLow(coreContext.context)) {
|
||||||
voiceRecordPlayingAudioFocusRequest = AppUtils.acquireAudioFocusForVoiceRecording(
|
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
|
coreContext.context
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -277,10 +279,10 @@ class ChatMessageContentData(
|
||||||
voiceRecordingPlayer.pause()
|
voiceRecordingPlayer.pause()
|
||||||
}
|
}
|
||||||
|
|
||||||
val request = voiceRecordPlayingAudioFocusRequest
|
val request = voiceRecordAudioFocusRequest
|
||||||
if (request != null) {
|
if (request != null) {
|
||||||
AppUtils.releaseAudioFocusForVoiceRecording(coreContext.context, request)
|
AppUtils.releaseAudioFocusForVoiceRecordingOrPlayback(coreContext.context, request)
|
||||||
voiceRecordPlayingAudioFocusRequest = null
|
voiceRecordAudioFocusRequest = null
|
||||||
}
|
}
|
||||||
|
|
||||||
isVoiceRecordPlaying.value = false
|
isVoiceRecordPlaying.value = false
|
||||||
|
@ -310,10 +312,6 @@ class ChatMessageContentData(
|
||||||
}
|
}
|
||||||
Log.i("[Voice Recording] Found speaker sound card [$speakerCard] and earpiece sound card [$earpieceCard]")
|
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)
|
val localPlayer = coreContext.core.createLocalPlayer(speakerCard ?: earpieceCard, null, null)
|
||||||
if (localPlayer != null) {
|
if (localPlayer != null) {
|
||||||
voiceRecordingPlayer = localPlayer
|
voiceRecordingPlayer = localPlayer
|
||||||
|
|
|
@ -90,7 +90,7 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel()
|
||||||
|
|
||||||
val voiceRecordPlayingPosition = MutableLiveData<Int>()
|
val voiceRecordPlayingPosition = MutableLiveData<Int>()
|
||||||
|
|
||||||
var voiceRecordPlayingAudioFocusRequest: AudioFocusRequestCompat? = null
|
var voiceRecordAudioFocusRequest: AudioFocusRequestCompat? = null
|
||||||
|
|
||||||
private lateinit var voiceRecordingPlayer: Player
|
private lateinit var voiceRecordingPlayer: Player
|
||||||
private val playerListener = PlayerListener {
|
private val playerListener = PlayerListener {
|
||||||
|
@ -108,7 +108,11 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel()
|
||||||
isReadOnly.value = chatRoom.hasBeenLeft()
|
isReadOnly.value = chatRoom.hasBeenLeft()
|
||||||
|
|
||||||
val recorderParams = coreContext.core.createRecorderParams()
|
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)
|
recorder = coreContext.core.createRecorder(recorderParams)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -279,6 +283,12 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (voiceRecordAudioFocusRequest == null) {
|
||||||
|
voiceRecordAudioFocusRequest = AppUtils.acquireAudioFocusForVoiceRecordingOrPlayback(
|
||||||
|
coreContext.context
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
when (recorder.state) {
|
when (recorder.state) {
|
||||||
RecorderState.Running -> Log.w("[Chat Message Sending] Recorder is already recording")
|
RecorderState.Running -> Log.w("[Chat Message Sending] Recorder is already recording")
|
||||||
RecorderState.Paused -> {
|
RecorderState.Paused -> {
|
||||||
|
@ -286,7 +296,11 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel()
|
||||||
recorder.start()
|
recorder.start()
|
||||||
}
|
}
|
||||||
RecorderState.Closed -> {
|
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)
|
val file = FileUtils.getFileStoragePath(tempFileName)
|
||||||
Log.w("[Chat Message Sending] Recorder is closed, starting recording in ${file.absoluteFile}")
|
Log.w("[Chat Message Sending] Recorder is closed, starting recording in ${file.absoluteFile}")
|
||||||
recorder.open(file.absolutePath)
|
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
|
isPendingVoiceRecord.value = false
|
||||||
isVoiceRecording.value = false
|
isVoiceRecording.value = false
|
||||||
sendMessageEnabled.value = textToSend.value?.isNotEmpty() == true || attachments.value?.isNotEmpty() == true
|
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
|
voiceRecordingDuration.value = recorder.duration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val request = voiceRecordAudioFocusRequest
|
||||||
|
if (request != null) {
|
||||||
|
AppUtils.releaseAudioFocusForVoiceRecordingOrPlayback(coreContext.context, request)
|
||||||
|
voiceRecordAudioFocusRequest = null
|
||||||
|
}
|
||||||
|
|
||||||
isVoiceRecording.value = false
|
isVoiceRecording.value = false
|
||||||
if (corePreferences.sendVoiceRecordingRightAway) {
|
if (corePreferences.sendVoiceRecordingRightAway) {
|
||||||
Log.i("[Chat Message Sending] Sending voice recording right away")
|
Log.i("[Chat Message Sending] Sending voice recording right away")
|
||||||
|
@ -355,8 +381,12 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel()
|
||||||
initVoiceRecordPlayer()
|
initVoiceRecordPlayer()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (voiceRecordPlayingAudioFocusRequest == null) {
|
if (AppUtils.isMediaVolumeLow(coreContext.context)) {
|
||||||
voiceRecordPlayingAudioFocusRequest = AppUtils.acquireAudioFocusForVoiceRecording(
|
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
|
coreContext.context
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -375,10 +405,10 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel()
|
||||||
voiceRecordingPlayer.pause()
|
voiceRecordingPlayer.pause()
|
||||||
}
|
}
|
||||||
|
|
||||||
val request = voiceRecordPlayingAudioFocusRequest
|
val request = voiceRecordAudioFocusRequest
|
||||||
if (request != null) {
|
if (request != null) {
|
||||||
AppUtils.releaseAudioFocusForVoiceRecording(coreContext.context, request)
|
AppUtils.releaseAudioFocusForVoiceRecordingOrPlayback(coreContext.context, request)
|
||||||
voiceRecordPlayingAudioFocusRequest = null
|
voiceRecordAudioFocusRequest = null
|
||||||
}
|
}
|
||||||
|
|
||||||
isPlayingVoiceRecording.value = false
|
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]")
|
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)
|
val localPlayer = coreContext.core.createLocalPlayer(speakerCard ?: earpieceCard, null, null)
|
||||||
if (localPlayer != null) {
|
if (localPlayer != null) {
|
||||||
voiceRecordingPlayer = localPlayer
|
voiceRecordingPlayer = localPlayer
|
||||||
|
@ -431,10 +457,10 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel()
|
||||||
voiceRecordingPlayer.close()
|
voiceRecordingPlayer.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
val request = voiceRecordPlayingAudioFocusRequest
|
val request = voiceRecordAudioFocusRequest
|
||||||
if (request != null) {
|
if (request != null) {
|
||||||
AppUtils.releaseAudioFocusForVoiceRecording(coreContext.context, request)
|
AppUtils.releaseAudioFocusForVoiceRecordingOrPlayback(coreContext.context, request)
|
||||||
voiceRecordPlayingAudioFocusRequest = null
|
voiceRecordAudioFocusRequest = null
|
||||||
}
|
}
|
||||||
|
|
||||||
isPlayingVoiceRecording.value = false
|
isPlayingVoiceRecording.value = false
|
||||||
|
|
|
@ -21,9 +21,12 @@ package org.linphone.activities.main.recordings.viewmodels
|
||||||
|
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.media.AudioFocusRequestCompat
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
|
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||||
import org.linphone.activities.main.recordings.data.RecordingData
|
import org.linphone.activities.main.recordings.data.RecordingData
|
||||||
import org.linphone.core.tools.Log
|
import org.linphone.core.tools.Log
|
||||||
|
import org.linphone.utils.AppUtils
|
||||||
import org.linphone.utils.FileUtils
|
import org.linphone.utils.FileUtils
|
||||||
|
|
||||||
class RecordingsViewModel : ViewModel() {
|
class RecordingsViewModel : ViewModel() {
|
||||||
|
@ -31,12 +34,26 @@ class RecordingsViewModel : ViewModel() {
|
||||||
|
|
||||||
val isVideoVisible = MutableLiveData<Boolean>()
|
val isVideoVisible = MutableLiveData<Boolean>()
|
||||||
|
|
||||||
|
private var recordingPlayingAudioFocusRequest: AudioFocusRequestCompat? = null
|
||||||
|
|
||||||
private val recordingListener = object : RecordingData.RecordingListener {
|
private val recordingListener = object : RecordingData.RecordingListener {
|
||||||
override fun onPlayingStarted(videoAvailable: Boolean) {
|
override fun onPlayingStarted(videoAvailable: Boolean) {
|
||||||
|
if (recordingPlayingAudioFocusRequest == null) {
|
||||||
|
recordingPlayingAudioFocusRequest = AppUtils.acquireAudioFocusForVoiceRecordingOrPlayback(
|
||||||
|
coreContext.context
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
isVideoVisible.value = videoAvailable
|
isVideoVisible.value = videoAvailable
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPlayingEnded() {
|
override fun onPlayingEnded() {
|
||||||
|
val request = recordingPlayingAudioFocusRequest
|
||||||
|
if (request != null) {
|
||||||
|
AppUtils.releaseAudioFocusForVoiceRecordingOrPlayback(coreContext.context, request)
|
||||||
|
recordingPlayingAudioFocusRequest = null
|
||||||
|
}
|
||||||
|
|
||||||
isVideoVisible.value = false
|
isVideoVisible.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,6 +65,13 @@ class RecordingsViewModel : ViewModel() {
|
||||||
|
|
||||||
override fun onCleared() {
|
override fun onCleared() {
|
||||||
recordingsList.value.orEmpty().forEach(RecordingData::destroy)
|
recordingsList.value.orEmpty().forEach(RecordingData::destroy)
|
||||||
|
|
||||||
|
val request = recordingPlayingAudioFocusRequest
|
||||||
|
if (request != null) {
|
||||||
|
AppUtils.releaseAudioFocusForVoiceRecordingOrPlayback(coreContext.context, request)
|
||||||
|
recordingPlayingAudioFocusRequest = null
|
||||||
|
}
|
||||||
|
|
||||||
super.onCleared()
|
super.onCleared()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -412,6 +412,11 @@ class CorePreferences constructor(private val context: Context) {
|
||||||
val preventInterfaceFromShowingUp: Boolean
|
val preventInterfaceFromShowingUp: Boolean
|
||||||
get() = config.getBool("app", "keep_app_invisible", false)
|
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 */
|
/* Default values related */
|
||||||
|
|
||||||
val echoCancellerCalibration: Int
|
val echoCancellerCalibration: Int
|
||||||
|
|
|
@ -156,7 +156,7 @@ class AppUtils {
|
||||||
return currentVolume <= maxVolume * 0.5
|
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 audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||||
val audioAttrs = AudioAttributesCompat.Builder()
|
val audioAttrs = AudioAttributesCompat.Builder()
|
||||||
.setUsage(AudioAttributesCompat.USAGE_MEDIA)
|
.setUsage(AudioAttributesCompat.USAGE_MEDIA)
|
||||||
|
@ -170,22 +170,25 @@ class AppUtils {
|
||||||
.build()
|
.build()
|
||||||
when (AudioManagerCompat.requestAudioFocus(audioManager, request)) {
|
when (AudioManagerCompat.requestAudioFocus(audioManager, request)) {
|
||||||
AudioManager.AUDIOFOCUS_REQUEST_GRANTED -> {
|
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 -> {
|
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 -> {
|
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
|
return request
|
||||||
}
|
}
|
||||||
|
|
||||||
fun releaseAudioFocusForVoiceRecording(context: Context, request: AudioFocusRequestCompat) {
|
fun releaseAudioFocusForVoiceRecordingOrPlayback(
|
||||||
|
context: Context,
|
||||||
|
request: AudioFocusRequestCompat
|
||||||
|
) {
|
||||||
val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||||
AudioManagerCompat.abandonAudioFocusRequest(audioManager, request)
|
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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue