Skip to content

Commit

Permalink
Merge pull request #3535 from nextcloud/handle-audio-focus
Browse files Browse the repository at this point in the history
Handle audio focus and becoming noisy
  • Loading branch information
mahibi authored Feb 7, 2024
2 parents 26f59c9 + 86cd617 commit ffdb932
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 21 deletions.
152 changes: 132 additions & 20 deletions app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,22 @@ 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
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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand All @@ -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() {
Expand All @@ -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)
}
}
}

Expand Down Expand Up @@ -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.
Expand All @@ -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()
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -2193,7 +2301,9 @@ class ChatActivity :
}

try {
start()
audioFocusRequest(true) {
start()
}
mediaRecorderState = MediaRecorderState.RECORDING
Log.d(TAG, "recording started")
} catch (e: IllegalStateException) {
Expand Down Expand Up @@ -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")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit ffdb932

Please sign in to comment.