diff --git a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt index 3cd1013b5..c78b0eaf4 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -42,7 +42,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 +386,8 @@ class ChatActivity : private var lastRecordMediaPosition: Int = 0 private var lastRecordedSeeked: Boolean = false + private val audioFocusChangeListener = getAudioFocusChangeListener() + private lateinit var participantPermissions: ParticipantPermissions private var videoURI: Uri? = null @@ -1099,7 +1103,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 +1334,10 @@ class ChatActivity : duration = it.duration.toLong() interpolator = LinearInterpolator() } - voicePreviewMediaPlayer!!.start() - voicePreviewObjectAnimator!!.start() + audioFocusRequest(true) { + voicePreviewMediaPlayer!!.start() + voicePreviewObjectAnimator!!.start() + } } setOnCompletionListener { @@ -1343,15 +1351,19 @@ class ChatActivity : if (voicePreviewMediaPlayer == null) { initPreviewVoiceRecording() } else { - voicePreviewMediaPlayer!!.start() - voicePreviewObjectAnimator!!.resume() + audioFocusRequest(true) { + voicePreviewMediaPlayer!!.start() + voicePreviewObjectAnimator!!.resume() + } } } private fun pausePreviewVoicePlaying() { Log.d(TAG, "paused preview voice recording") - voicePreviewMediaPlayer!!.pause() - voicePreviewObjectAnimator!!.pause() + audioFocusRequest(false) { + voicePreviewMediaPlayer!!.pause() + voicePreviewObjectAnimator!!.pause() + } } private fun stopPreviewVoicePlaying() { @@ -1361,9 +1373,11 @@ class ChatActivity : voicePreviewObjectAnimator!!.end() voicePreviewObjectAnimator = null binding.messageInputView.seekBar.clearAnimation() - voicePreviewMediaPlayer!!.stop() - voicePreviewMediaPlayer!!.release() - voicePreviewMediaPlayer = null + audioFocusRequest(false) { + voicePreviewMediaPlayer!!.stop() + voicePreviewMediaPlayer!!.release() + voicePreviewMediaPlayer = null + } } } @@ -1817,6 +1831,56 @@ class ChatActivity : } } + private fun getAudioFocusChangeListener(): AudioManager.OnAudioFocusChangeListener { + return AudioManager.OnAudioFocusChangeListener { flag -> + when (flag) { + AudioManager.AUDIOFOCUS_LOSS -> { + if (isVoicePreviewPlaying) { + stopPreviewVoicePlaying() + } + if (currentlyPlayedVoiceMessage != null) { + stopMediaPlayer(currentlyPlayedVoiceMessage!!) + } + } + + AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> { + if (isVoicePreviewPlaying) { + pausePreviewVoicePlaying() + } + if (currentlyPlayedVoiceMessage != null) { + pausePlayback(currentlyPlayedVoiceMessage!!) + } + } + } + } + } + + private fun audioFocusRequest(shouldRequestFocus: Boolean, onGranted: () -> Unit) { + 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 startPlayback(message: ChatMessage) { if (!active) { // don't begin to play voice message if screen is not visible anymore. @@ -1830,8 +1894,9 @@ class ChatActivity : mediaPlayer?.let { if (!it.isPlaying) { - it.start() - Log.d(TAG, "MediaPlayer has Started") + audioFocusRequest(true) { + it.start() + } } mediaPlayerHandler = Handler() @@ -1866,8 +1931,9 @@ class ChatActivity : private fun pausePlayback(message: ChatMessage) { if (mediaPlayer!!.isPlaying) { - mediaPlayer!!.pause() - Log.d(TAG, "MediaPlayer is paused") + audioFocusRequest(false) { + mediaPlayer!!.pause() + } } message.isPlayingVoiceMessage = false @@ -1925,7 +1991,9 @@ class ChatActivity : mediaPlayer?.let { if (it.isPlaying) { Log.d(TAG, "media player is stopped") - it.stop() + audioFocusRequest(false) { + it.stop() + } } } } catch (e: IllegalStateException) { @@ -2141,8 +2209,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 +2263,9 @@ class ChatActivity : } try { - start() + audioFocusRequest(true) { + start() + } mediaRecorderState = MediaRecorderState.RECORDING Log.d(TAG, "recording started") } catch (e: IllegalStateException) { @@ -2234,8 +2306,10 @@ class ChatActivity : recorder?.apply { try { if (mediaRecorderState == MediaRecorderState.RECORDING) { - stop() - reset() + audioFocusRequest(false) { + stop() + reset() + } mediaRecorderState = MediaRecorderState.INITIAL Log.d(TAG, "stopped recorder") } diff --git a/app/src/main/java/com/nextcloud/talk/fullscreenfile/FullScreenMediaActivity.kt b/app/src/main/java/com/nextcloud/talk/fullscreenfile/FullScreenMediaActivity.kt index 1d6796b52..0dd0b9ba5 100644 --- a/app/src/main/java/com/nextcloud/talk/fullscreenfile/FullScreenMediaActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/fullscreenfile/FullScreenMediaActivity.kt @@ -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(R.id.exo_bottom_bar)