Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resumes playback of VoiceMessages on screen rotation and continue playing a voice message if chatActivity in background but still open #3704

Merged
merged 6 commits into from
Mar 15, 2024
123 changes: 113 additions & 10 deletions app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
* Copyright (C) 2021-2022 Marcel Hibbe <[email protected]>
* Copyright (C) 2017-2019 Mario Danic <[email protected]>
* Copyright (C) 2023 Ezhil Shanmugham <[email protected]>
* Copyright (C) 2024 Giacomo Pacini <[email protected]>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -405,6 +406,9 @@ class ChatActivity :

private val onBackPressedCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (currentlyPlayedVoiceMessage != null) {
stopMediaPlayer(currentlyPlayedVoiceMessage!!)
}
val intent = Intent(this@ChatActivity, ConversationsListActivity::class.java)
intent.putExtras(Bundle())
startActivity(intent)
Expand All @@ -416,6 +420,9 @@ class ChatActivity :
val typingParticipants = HashMap<String, TypingParticipant>()

var callStarted = false
private var voiceMessageToRestoreId = ""
private var voiceMessageToRestoreAudioPosition = 0
private var voiceMessageToRestoreWasPlaying = false

private val localParticipantMessageListener = object : SignalingMessageReceiver.LocalParticipantMessageListener {
override fun onSwitchTo(token: String?) {
Expand Down Expand Up @@ -487,6 +494,30 @@ class ChatActivity :
onBackPressedDispatcher.addCallback(this, onBackPressedCallback)

initObservers()

if (savedInstanceState != null) {
// Restore value of members from saved state
var voiceMessageId = savedInstanceState.getString(CURRENT_AUDIO_MESSAGE_KEY, "")
var voiceMessagePosition = savedInstanceState.getInt(CURRENT_AUDIO_POSITION_KEY, 0)
var wasAudioPLaying = savedInstanceState.getBoolean(CURRENT_AUDIO_WAS_PLAYING_KEY, false)
if (!voiceMessageId.equals("")) {
Log.d(RESUME_AUDIO_TAG, "restored voice messageID: " + voiceMessageId)
Log.d(RESUME_AUDIO_TAG, "audio position: " + voiceMessagePosition)
Log.d(RESUME_AUDIO_TAG, "audio was playing: " + wasAudioPLaying.toString())
voiceMessageToRestoreId = voiceMessageId
voiceMessageToRestoreAudioPosition = voiceMessagePosition
voiceMessageToRestoreWasPlaying = wasAudioPLaying
} else {
Log.d(RESUME_AUDIO_TAG, "stored voice message id is empty, not resuming audio playing")
voiceMessageToRestoreId = ""
voiceMessageToRestoreAudioPosition = 0
voiceMessageToRestoreWasPlaying = false
}
} else {
voiceMessageToRestoreId = ""
voiceMessageToRestoreAudioPosition = 0
voiceMessageToRestoreWasPlaying = false
}
}

override fun onNewIntent(intent: Intent) {
Expand Down Expand Up @@ -548,6 +579,20 @@ class ChatActivity :
this.lifecycle.addObserver(ChatViewModel.LifeCycleObserver)
}

override fun onSaveInstanceState(outState: Bundle) {
if (currentlyPlayedVoiceMessage != null) {
outState.putString(CURRENT_AUDIO_MESSAGE_KEY, currentlyPlayedVoiceMessage!!.getId())
outState.putInt(CURRENT_AUDIO_POSITION_KEY, currentlyPlayedVoiceMessage!!.voiceMessagePlayedSeconds)
outState.putBoolean(CURRENT_AUDIO_WAS_PLAYING_KEY, currentlyPlayedVoiceMessage!!.isPlayingVoiceMessage)
Log.d(RESUME_AUDIO_TAG, "Stored current audio message ID: " + currentlyPlayedVoiceMessage!!.getId())
Log.d(
RESUME_AUDIO_TAG, "Audio Position: " + currentlyPlayedVoiceMessage!!.voiceMessagePlayedSeconds
.toString() + " | isPLaying: " + currentlyPlayedVoiceMessage!!.isPlayingVoiceMessage
)
}
super.onSaveInstanceState(outState)
}

override fun onStop() {
super.onStop()
active = false
Expand All @@ -558,9 +603,6 @@ class ChatActivity :
if (mediaRecorderState == MediaRecorderState.RECORDING) {
stopAudioRecording()
}
if (currentlyPlayedVoiceMessage != null) {
stopMediaPlayer(currentlyPlayedVoiceMessage!!)
}
val text = binding.messageInputView.messageInput.text.toString()
val cursor = binding.messageInputView.messageInput.selectionStart
val previous = context.getSharedPreferences(localClassName, MODE_PRIVATE).getString(roomToken, "null")
Expand Down Expand Up @@ -1352,7 +1394,7 @@ class ChatActivity :
}
}

private fun setUpWaveform(message: ChatMessage) {
private fun setUpWaveform(message: ChatMessage, thenPlay: Boolean = true) {
val filename = message.selectedIndividualHashMap!!["name"]
val file = File(context.cacheDir, filename!!)
if (file.exists() && message.voiceMessageFloatArray == null) {
Expand All @@ -1363,11 +1405,11 @@ class ChatActivity :
appPreferences.saveWaveFormForFile(filename, r.toTypedArray())
message.voiceMessageFloatArray = r
withContext(Dispatchers.Main) {
startPlayback(message)
startPlayback(message, thenPlay)
}
}
} else {
startPlayback(message)
startPlayback(message, thenPlay)
}
}

Expand Down Expand Up @@ -2321,7 +2363,7 @@ class ChatActivity :
}
}

private fun startPlayback(message: ChatMessage) {
private fun startPlayback(message: ChatMessage, doPlay: Boolean = true) {
if (!active) {
// don't begin to play voice message if screen is not visible anymore.
// this situation might happen if file is downloading but user already left the chatview.
Expand All @@ -2333,7 +2375,7 @@ class ChatActivity :
initMediaPlayer(message)

mediaPlayer?.let {
if (!it.isPlaying) {
if (!it.isPlaying && doPlay) {
audioFocusRequest(true) {
it.start()
handleBecomingNoisyBroadcast(register = true)
Expand Down Expand Up @@ -2365,7 +2407,11 @@ class ChatActivity :
})

message.isDownloadingVoiceMessage = false
message.isPlayingVoiceMessage = true
message.isPlayingVoiceMessage = doPlay
//message.voiceMessagePlayedSeconds = lastRecordMediaPosition / VOICE_MESSAGE_SEEKBAR_BASE
//message.voiceMessageSeekbarProgress = lastRecordMediaPosition
// the commented instructions objective was to update audio seekbarprogress
// in the case in which audio status is paused when the position is resumed
adapter?.update(message)
}
}
Expand Down Expand Up @@ -2408,6 +2454,8 @@ class ChatActivity :
lastRecordedSeeked = true
}
}
// this ensures that audio can be resumed at a given position
this.seekTo(lastRecordMediaPosition)
}
setOnCompletionListener {
stopMediaPlayer(message)
Expand All @@ -2426,6 +2474,7 @@ class ChatActivity :
adapter?.update(message)

currentlyPlayedVoiceMessage = null
lastRecordMediaPosition = 0 //this ensures that if audio track is changed, then it is played from the beginning

mediaPlayerHandler.removeCallbacksAndMessages(null)

Expand Down Expand Up @@ -3750,6 +3799,8 @@ class ChatActivity :
adapter?.addToEnd(chatMessageList, false)
}
scrollToRequestedMessageIfNeeded()
//FENOM: add here audio resume policy
resumeAudioPlaybackIfNeeded()
}

private fun scrollToFirstUnreadMessage() {
Expand Down Expand Up @@ -3854,6 +3905,54 @@ class ChatActivity :
}
}

/**
* this method must be called after that the adatper has finished loading ChatMessages items
* it searches by ID the message that was playing,
* then, if it finds it, it restores audio position
* and eventually resumes audio playback
* @author Giacomo Pacini
*/
private fun resumeAudioPlaybackIfNeeded() {
if (!voiceMessageToRestoreId.equals("")) {
Log.d(RESUME_AUDIO_TAG, "begin method to resume audio playback")

if (adapter != null) {
Log.d(RESUME_AUDIO_TAG, "adapter is not null, proceeding")
val voiceMessagePosition = adapter!!.items!!.indexOfFirst {
it.item is ChatMessage && (it.item as ChatMessage).id == voiceMessageToRestoreId
}
if (voiceMessagePosition >= 0) {
val currentItem = adapter?.items?.get(voiceMessagePosition)?.item
if (currentItem is ChatMessage && currentItem.id == voiceMessageToRestoreId) {
currentlyPlayedVoiceMessage = currentItem
lastRecordMediaPosition = voiceMessageToRestoreAudioPosition * 1000
Log.d(RESUME_AUDIO_TAG, "trying to resume audio")
binding.messagesListView.scrollToPosition(voiceMessagePosition)
// WORKAROUND TO FETCH FILE INFO:
currentlyPlayedVoiceMessage!!.getImageUrl()
// see getImageUrl() source code
setUpWaveform(currentlyPlayedVoiceMessage!!, voiceMessageToRestoreWasPlaying)
Log.d(RESUME_AUDIO_TAG, "resume audio procedure completed")
} else {
Log.d(RESUME_AUDIO_TAG, "currentItem retrieved was not chatmessage or its id was not correct")
}
} else {
Log.d(
RESUME_AUDIO_TAG,
"voiceMessagePosition is -1, adapter # of items: " + adapter!!.getItemCount()
)
}
} else {
Log.d(RESUME_AUDIO_TAG, "TalkMessagesListAdapater is null")
}
} else {
Log.d(RESUME_AUDIO_TAG, "No voice message to restore")
}
voiceMessageToRestoreId = ""
voiceMessageToRestoreAudioPosition = 0
voiceMessageToRestoreWasPlaying = false
}

private fun scrollToRequestedMessageIfNeeded() {
intent.getStringExtra(BundleKeys.KEY_MESSAGE_ID)?.let {
scrollToMessageWithId(it)
Expand Down Expand Up @@ -4380,7 +4479,7 @@ class ChatActivity :
val lon = data["longitude"]!!
metaData =
"{\"type\":\"geo-location\",\"id\":\"geo:$lat,$lon\",\"latitude\":\"$lat\"," +
"\"longitude\":\"$lon\",\"name\":\"$name\"}"
"\"longitude\":\"$lon\",\"name\":\"$name\"}"
}

when (type) {
Expand Down Expand Up @@ -4861,5 +4960,9 @@ class ChatActivity :
private const val MILISEC_15: Long = 15
private const val LINEBREAK = "\n"
private const val CURSOR_KEY = "_cursor"
private const val CURRENT_AUDIO_MESSAGE_KEY = "CURRENT_AUDIO_MESSAGE"
private const val CURRENT_AUDIO_POSITION_KEY = "CURRENT_AUDIO_POSITION"
private const val CURRENT_AUDIO_WAS_PLAYING_KEY = "CURRENT_AUDIO_PLAYING"
private const val RESUME_AUDIO_TAG = "RESUME_AUDIO_TAG"
}
}
Loading