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.Manifest
import android.animation.ObjectAnimator import android.animation.ObjectAnimator
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.content.ClipData import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.res.AssetFileDescriptor import android.content.res.AssetFileDescriptor
import android.content.res.Resources import android.content.res.Resources
@ -42,7 +44,9 @@ import android.database.Cursor
import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.media.AudioFocusRequest
import android.media.AudioFormat import android.media.AudioFormat
import android.media.AudioManager
import android.media.AudioRecord import android.media.AudioRecord
import android.media.MediaPlayer import android.media.MediaPlayer
import android.media.MediaRecorder import android.media.MediaRecorder
@ -384,6 +388,20 @@ class ChatActivity :
private var lastRecordMediaPosition: Int = 0 private var lastRecordMediaPosition: Int = 0
private var lastRecordedSeeked: Boolean = false 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 lateinit var participantPermissions: ParticipantPermissions
private var videoURI: Uri? = null private var videoURI: Uri? = null
@ -1099,7 +1117,9 @@ class ChatActivity :
binding.messageInputView.micInputCloud.setOnClickListener { binding.messageInputView.micInputCloud.setOnClickListener {
if (mediaRecorderState == MediaRecorderState.RECORDING) { if (mediaRecorderState == MediaRecorderState.RECORDING) {
recorder?.stop() audioFocusRequest(false) {
recorder?.stop()
}
mediaRecorderState = MediaRecorderState.INITIAL mediaRecorderState = MediaRecorderState.INITIAL
stopMicInputRecordingAnimation() stopMicInputRecordingAnimation()
showPreviewVoiceRecording(true) showPreviewVoiceRecording(true)
@ -1328,8 +1348,11 @@ class ChatActivity :
duration = it.duration.toLong() duration = it.duration.toLong()
interpolator = LinearInterpolator() interpolator = LinearInterpolator()
} }
voicePreviewMediaPlayer!!.start() audioFocusRequest(true) {
voicePreviewObjectAnimator!!.start() voicePreviewMediaPlayer!!.start()
voicePreviewObjectAnimator!!.start()
handleBecomingNoisyBroadcast(register = true)
}
} }
setOnCompletionListener { setOnCompletionListener {
@ -1343,15 +1366,21 @@ class ChatActivity :
if (voicePreviewMediaPlayer == null) { if (voicePreviewMediaPlayer == null) {
initPreviewVoiceRecording() initPreviewVoiceRecording()
} else { } else {
voicePreviewMediaPlayer!!.start() audioFocusRequest(true) {
voicePreviewObjectAnimator!!.resume() voicePreviewMediaPlayer!!.start()
voicePreviewObjectAnimator!!.resume()
handleBecomingNoisyBroadcast(register = true)
}
} }
} }
private fun pausePreviewVoicePlaying() { private fun pausePreviewVoicePlaying() {
Log.d(TAG, "paused preview voice recording") Log.d(TAG, "paused preview voice recording")
voicePreviewMediaPlayer!!.pause() audioFocusRequest(false) {
voicePreviewObjectAnimator!!.pause() voicePreviewMediaPlayer!!.pause()
voicePreviewObjectAnimator!!.pause()
handleBecomingNoisyBroadcast(register = false)
}
} }
private fun stopPreviewVoicePlaying() { private fun stopPreviewVoicePlaying() {
@ -1361,9 +1390,12 @@ class ChatActivity :
voicePreviewObjectAnimator!!.end() voicePreviewObjectAnimator!!.end()
voicePreviewObjectAnimator = null voicePreviewObjectAnimator = null
binding.messageInputView.seekBar.clearAnimation() binding.messageInputView.seekBar.clearAnimation()
voicePreviewMediaPlayer!!.stop() audioFocusRequest(false) {
voicePreviewMediaPlayer!!.release() voicePreviewMediaPlayer!!.stop()
voicePreviewMediaPlayer = null 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) { private fun startPlayback(message: ChatMessage) {
if (!active) { if (!active) {
// don't begin to play voice message if screen is not visible anymore. // don't begin to play voice message if screen is not visible anymore.
@ -1830,8 +1929,10 @@ class ChatActivity :
mediaPlayer?.let { mediaPlayer?.let {
if (!it.isPlaying) { if (!it.isPlaying) {
it.start() audioFocusRequest(true) {
Log.d(TAG, "MediaPlayer has Started") it.start()
handleBecomingNoisyBroadcast(register = true)
}
} }
mediaPlayerHandler = Handler() mediaPlayerHandler = Handler()
@ -1866,8 +1967,10 @@ class ChatActivity :
private fun pausePlayback(message: ChatMessage) { private fun pausePlayback(message: ChatMessage) {
if (mediaPlayer!!.isPlaying) { if (mediaPlayer!!.isPlaying) {
mediaPlayer!!.pause() audioFocusRequest(false) {
Log.d(TAG, "MediaPlayer is paused") mediaPlayer!!.pause()
handleBecomingNoisyBroadcast(register = false)
}
} }
message.isPlayingVoiceMessage = false message.isPlayingVoiceMessage = false
@ -1925,7 +2028,10 @@ class ChatActivity :
mediaPlayer?.let { mediaPlayer?.let {
if (it.isPlaying) { if (it.isPlaying) {
Log.d(TAG, "media player is stopped") Log.d(TAG, "media player is stopped")
it.stop() audioFocusRequest(false) {
it.stop()
handleBecomingNoisyBroadcast(register = false)
}
} }
} }
} catch (e: IllegalStateException) { } catch (e: IllegalStateException) {
@ -2141,8 +2247,10 @@ class ChatActivity :
private fun stopMicInputRecordingAnimation() { private fun stopMicInputRecordingAnimation() {
if (micInputAudioRecordThread != null) { if (micInputAudioRecordThread != null) {
Log.d(TAG, "Mic Animation Ended") Log.d(TAG, "Mic Animation Ended")
micInputAudioRecorder.stop() audioFocusRequest(false) {
micInputAudioRecorder.release() micInputAudioRecorder.stop()
micInputAudioRecorder.release()
}
isMicInputAudioThreadRunning = false isMicInputAudioThreadRunning = false
micInputAudioRecordThread = null micInputAudioRecordThread = null
} }
@ -2193,7 +2301,9 @@ class ChatActivity :
} }
try { try {
start() audioFocusRequest(true) {
start()
}
mediaRecorderState = MediaRecorderState.RECORDING mediaRecorderState = MediaRecorderState.RECORDING
Log.d(TAG, "recording started") Log.d(TAG, "recording started")
} catch (e: IllegalStateException) { } catch (e: IllegalStateException) {
@ -2234,8 +2344,10 @@ class ChatActivity :
recorder?.apply { recorder?.apply {
try { try {
if (mediaRecorderState == MediaRecorderState.RECORDING) { if (mediaRecorderState == MediaRecorderState.RECORDING) {
stop() audioFocusRequest(false) {
reset() stop()
reset()
}
mediaRecorderState = MediaRecorderState.INITIAL mediaRecorderState = MediaRecorderState.INITIAL
Log.d(TAG, "stopped recorder") 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 open class GetReminderExistState(val reminder: Reminder) : ViewState
private val _getReminderExistState: MutableLiveData<ViewState> = MutableLiveData(GetReminderStartState) private val _getReminderExistState: MutableLiveData<ViewState> = MutableLiveData(GetReminderStartState)
var isPausedDueToBecomingNoisy = false
var receiverRegistered = false
var receiverUnregistered = false
val getReminderExistState: LiveData<ViewState> val getReminderExistState: LiveData<ViewState>
get() = _getReminderExistState get() = _getReminderExistState

View File

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