Merge pull request #3535 from nextcloud/handle-audio-focus

Handle audio focus and becoming noisy
This commit is contained in:
Marcel Hibbe 2024-02-07 13:02:43 +01:00 committed by GitHub
commit ffdb93204d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 143 additions and 21 deletions

View File

@ -31,10 +31,12 @@ package com.nextcloud.talk.chat
import android.Manifest
import android.animation.ObjectAnimator
import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.content.res.AssetFileDescriptor
import android.content.res.Resources
@ -42,7 +44,9 @@ import android.database.Cursor
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.media.AudioFocusRequest
import android.media.AudioFormat
import android.media.AudioManager
import android.media.AudioRecord
import android.media.MediaPlayer
import android.media.MediaRecorder
@ -384,6 +388,20 @@ class ChatActivity :
private var lastRecordMediaPosition: Int = 0
private var lastRecordedSeeked: Boolean = false
private val audioFocusChangeListener = getAudioFocusChangeListener()
private val noisyAudioStreamReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
chatViewModel.isPausedDueToBecomingNoisy = true
if (isVoicePreviewPlaying) {
pausePreviewVoicePlaying()
}
if (currentlyPlayedVoiceMessage != null) {
pausePlayback(currentlyPlayedVoiceMessage!!)
}
}
}
private lateinit var participantPermissions: ParticipantPermissions
private var videoURI: Uri? = null
@ -1099,7 +1117,9 @@ class ChatActivity :
binding.messageInputView.micInputCloud.setOnClickListener {
if (mediaRecorderState == MediaRecorderState.RECORDING) {
recorder?.stop()
audioFocusRequest(false) {
recorder?.stop()
}
mediaRecorderState = MediaRecorderState.INITIAL
stopMicInputRecordingAnimation()
showPreviewVoiceRecording(true)
@ -1328,8 +1348,11 @@ class ChatActivity :
duration = it.duration.toLong()
interpolator = LinearInterpolator()
}
voicePreviewMediaPlayer!!.start()
voicePreviewObjectAnimator!!.start()
audioFocusRequest(true) {
voicePreviewMediaPlayer!!.start()
voicePreviewObjectAnimator!!.start()
handleBecomingNoisyBroadcast(register = true)
}
}
setOnCompletionListener {
@ -1343,15 +1366,21 @@ class ChatActivity :
if (voicePreviewMediaPlayer == null) {
initPreviewVoiceRecording()
} else {
voicePreviewMediaPlayer!!.start()
voicePreviewObjectAnimator!!.resume()
audioFocusRequest(true) {
voicePreviewMediaPlayer!!.start()
voicePreviewObjectAnimator!!.resume()
handleBecomingNoisyBroadcast(register = true)
}
}
}
private fun pausePreviewVoicePlaying() {
Log.d(TAG, "paused preview voice recording")
voicePreviewMediaPlayer!!.pause()
voicePreviewObjectAnimator!!.pause()
audioFocusRequest(false) {
voicePreviewMediaPlayer!!.pause()
voicePreviewObjectAnimator!!.pause()
handleBecomingNoisyBroadcast(register = false)
}
}
private fun stopPreviewVoicePlaying() {
@ -1361,9 +1390,12 @@ class ChatActivity :
voicePreviewObjectAnimator!!.end()
voicePreviewObjectAnimator = null
binding.messageInputView.seekBar.clearAnimation()
voicePreviewMediaPlayer!!.stop()
voicePreviewMediaPlayer!!.release()
voicePreviewMediaPlayer = null
audioFocusRequest(false) {
voicePreviewMediaPlayer!!.stop()
voicePreviewMediaPlayer!!.release()
voicePreviewMediaPlayer = null
handleBecomingNoisyBroadcast(register = false)
}
}
}
@ -1817,6 +1849,73 @@ class ChatActivity :
}
}
private fun getAudioFocusChangeListener(): AudioManager.OnAudioFocusChangeListener {
return AudioManager.OnAudioFocusChangeListener { flag ->
when (flag) {
AudioManager.AUDIOFOCUS_LOSS -> {
chatViewModel.isPausedDueToBecomingNoisy = false
if (isVoicePreviewPlaying) {
stopPreviewVoicePlaying()
}
if (currentlyPlayedVoiceMessage != null) {
stopMediaPlayer(currentlyPlayedVoiceMessage!!)
}
}
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
chatViewModel.isPausedDueToBecomingNoisy = false
if (isVoicePreviewPlaying) {
pausePreviewVoicePlaying()
}
if (currentlyPlayedVoiceMessage != null) {
pausePlayback(currentlyPlayedVoiceMessage!!)
}
}
}
}
}
private fun audioFocusRequest(shouldRequestFocus: Boolean, onGranted: () -> Unit) {
if (chatViewModel.isPausedDueToBecomingNoisy) {
onGranted()
return
}
val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
val duration = AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE
val isGranted: Int = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val focusRequest = AudioFocusRequest.Builder(duration)
.setOnAudioFocusChangeListener(audioFocusChangeListener)
.build()
if (shouldRequestFocus) {
audioManager.requestAudioFocus(focusRequest)
} else {
audioManager.abandonAudioFocusRequest(focusRequest)
}
} else {
@Deprecated("This method was deprecated in API level 26.")
if (shouldRequestFocus) {
audioManager.requestAudioFocus(audioFocusChangeListener, AudioManager.STREAM_MUSIC, duration)
} else {
audioManager.abandonAudioFocus(audioFocusChangeListener)
}
}
if (isGranted == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
onGranted()
}
}
private fun handleBecomingNoisyBroadcast(register: Boolean) {
if (register && !chatViewModel.receiverRegistered) {
registerReceiver(noisyAudioStreamReceiver, IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY))
chatViewModel.receiverRegistered = true
} else if (!chatViewModel.receiverUnregistered) {
unregisterReceiver(noisyAudioStreamReceiver)
chatViewModel.receiverUnregistered = true
chatViewModel.receiverRegistered = false
}
}
private fun startPlayback(message: ChatMessage) {
if (!active) {
// don't begin to play voice message if screen is not visible anymore.
@ -1830,8 +1929,10 @@ class ChatActivity :
mediaPlayer?.let {
if (!it.isPlaying) {
it.start()
Log.d(TAG, "MediaPlayer has Started")
audioFocusRequest(true) {
it.start()
handleBecomingNoisyBroadcast(register = true)
}
}
mediaPlayerHandler = Handler()
@ -1866,8 +1967,10 @@ class ChatActivity :
private fun pausePlayback(message: ChatMessage) {
if (mediaPlayer!!.isPlaying) {
mediaPlayer!!.pause()
Log.d(TAG, "MediaPlayer is paused")
audioFocusRequest(false) {
mediaPlayer!!.pause()
handleBecomingNoisyBroadcast(register = false)
}
}
message.isPlayingVoiceMessage = false
@ -1925,7 +2028,10 @@ class ChatActivity :
mediaPlayer?.let {
if (it.isPlaying) {
Log.d(TAG, "media player is stopped")
it.stop()
audioFocusRequest(false) {
it.stop()
handleBecomingNoisyBroadcast(register = false)
}
}
}
} catch (e: IllegalStateException) {
@ -2141,8 +2247,10 @@ class ChatActivity :
private fun stopMicInputRecordingAnimation() {
if (micInputAudioRecordThread != null) {
Log.d(TAG, "Mic Animation Ended")
micInputAudioRecorder.stop()
micInputAudioRecorder.release()
audioFocusRequest(false) {
micInputAudioRecorder.stop()
micInputAudioRecorder.release()
}
isMicInputAudioThreadRunning = false
micInputAudioRecordThread = null
}
@ -2193,7 +2301,9 @@ class ChatActivity :
}
try {
start()
audioFocusRequest(true) {
start()
}
mediaRecorderState = MediaRecorderState.RECORDING
Log.d(TAG, "recording started")
} catch (e: IllegalStateException) {
@ -2234,8 +2344,10 @@ class ChatActivity :
recorder?.apply {
try {
if (mediaRecorderState == MediaRecorderState.RECORDING) {
stop()
reset()
audioFocusRequest(false) {
stop()
reset()
}
mediaRecorderState = MediaRecorderState.INITIAL
Log.d(TAG, "stopped recorder")
}

View File

@ -48,6 +48,11 @@ class ChatViewModel @Inject constructor(private val repository: ChatRepository)
open class GetReminderExistState(val reminder: Reminder) : ViewState
private val _getReminderExistState: MutableLiveData<ViewState> = MutableLiveData(GetReminderStartState)
var isPausedDueToBecomingNoisy = false
var receiverRegistered = false
var receiverUnregistered = false
val getReminderExistState: LiveData<ViewState>
get() = _getReminderExistState

View File

@ -44,6 +44,7 @@ import androidx.core.view.marginBottom
import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding
import androidx.fragment.app.DialogFragment
import androidx.media3.common.AudioAttributes
import androidx.media3.common.MediaItem
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer
@ -165,7 +166,10 @@ class FullScreenMediaActivity : AppCompatActivity() {
}
private fun initializePlayer() {
player = ExoPlayer.Builder(applicationContext).build()
player = ExoPlayer.Builder(applicationContext)
.setAudioAttributes(AudioAttributes.DEFAULT, true)
.setHandleAudioBecomingNoisy(true)
.build()
binding.playerView.player = player
}
@ -202,6 +206,7 @@ class FullScreenMediaActivity : AppCompatActivity() {
windowInsetsController.show(WindowInsetsCompat.Type.systemBars())
supportActionBar?.show()
}
private fun applyWindowInsets() {
val playerView = binding.playerView
val exoControls = playerView.findViewById<FrameLayout>(R.id.exo_bottom_bar)