From b44641c0c7f758a36b7ca71e3aec6bd67e78398f Mon Sep 17 00:00:00 2001 From: Giacomo Pacini Date: Sun, 10 Mar 2024 11:23:52 +0100 Subject: [PATCH 1/3] removed stopMediaPlayer method call in onStop(), so that audio continues playing when activity in background. if backpressed, stops mediaplayer Signed-off-by: Giacomo Pacini --- .../main/java/com/nextcloud/talk/chat/ChatActivity.kt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) 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 77ec8f886..bde34e21a 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -405,6 +405,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) @@ -558,9 +561,10 @@ class ChatActivity : if (mediaRecorderState == MediaRecorderState.RECORDING) { stopAudioRecording() } - if (currentlyPlayedVoiceMessage != null) { - stopMediaPlayer(currentlyPlayedVoiceMessage!!) - } + //if (currentlyPlayedVoiceMessage != null) { + // stopMediaPlayer(currentlyPlayedVoiceMessage!!) + //} this is done also in onDestroy, + // it is better to continue audio playback when the activity is not visible but still open val text = binding.messageInputView.messageInput.text.toString() val cursor = binding.messageInputView.messageInput.selectionStart val previous = context.getSharedPreferences(localClassName, MODE_PRIVATE).getString(roomToken, "null") From ca5a3798837fa6579511af98e810f90d10abdf89 Mon Sep 17 00:00:00 2001 From: Giacomo Pacini Date: Sun, 10 Mar 2024 11:50:28 +0100 Subject: [PATCH 2/3] save audio message id and position when activity is destroyed, also restores view position to that message and then resumes audio playback if was playing. it allows to continue playing audio on screen rotation. Signed-off-by: Giacomo Pacini --- .../com/nextcloud/talk/chat/ChatActivity.kt | 117 +++++++++++++++++- 1 file changed, 111 insertions(+), 6 deletions(-) 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 bde34e21a..4a6bf80df 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -11,6 +11,7 @@ * Copyright (C) 2021-2022 Marcel Hibbe * Copyright (C) 2017-2019 Mario Danic * Copyright (C) 2023 Ezhil Shanmugham + * Copyright (C) 2024 Giacomo Pacini * * 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 @@ -420,6 +421,14 @@ class ChatActivity : var callStarted = false + private val CURRENT_AUDIO_MESSAGE_KEY = "CURRENT_AUDIO_MESSAGE" + private val CURRENT_AUDIO_POSITION_KEY = "CURRENT_AUDIO_POSITION" + private val CURRENT_AUDIO_WAS_PLAYING_KEY = "CURRENT_AUDIO_PLAYING" + private var RESUME_AUDIO_TAG = "RESUME_AUDIO_TAG" + private var voiceMessageToRestoreId = "" + private var voiceMessageToRestoreAudioPosition = 0 + private var voiceMessageToRestoreWasPlaying = false + private val localParticipantMessageListener = object : SignalingMessageReceiver.LocalParticipantMessageListener { override fun onSwitchTo(token: String?) { if (token != null) { @@ -490,6 +499,31 @@ 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) { @@ -551,6 +585,21 @@ class ChatActivity : this.lifecycle.addObserver(ChatViewModel.LifeCycleObserver) } + override fun onSaveInstanceState(outState: Bundle) { + if(currentlyPlayedVoiceMessage != null) { + // stores audio message ID and audio position + // so that can be restored in resumeAudioPlaybackIfNeeded method + 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) + // stores also audio currently playing status + } + super.onSaveInstanceState(outState) + } + override fun onStop() { super.onStop() active = false @@ -1350,7 +1399,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) { @@ -1361,11 +1410,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) } } @@ -2319,7 +2368,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. @@ -2331,7 +2380,7 @@ class ChatActivity : initMediaPlayer(message) mediaPlayer?.let { - if (!it.isPlaying) { + if (!it.isPlaying && doPlay) { audioFocusRequest(true) { it.start() handleBecomingNoisyBroadcast(register = true) @@ -2363,7 +2412,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) } } @@ -2406,6 +2459,8 @@ class ChatActivity : lastRecordedSeeked = true } } + // this ensures that audio can be resumed at a given position + this.seekTo(lastRecordMediaPosition) } setOnCompletionListener { stopMediaPlayer(message) @@ -2424,6 +2479,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) @@ -3753,6 +3809,9 @@ class ChatActivity : adapter?.addToEnd(chatMessageList, false) } scrollToRequestedMessageIfNeeded() + //FENOM: add here audio resume policy + resumeAudioPlaybackIfNeeded() + } private fun scrollToFirstUnreadMessage() { @@ -3857,6 +3916,52 @@ 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) From 064c3809b03d154881c8e81a45536045428ace61 Mon Sep 17 00:00:00 2001 From: Giacomo Pacini Date: Tue, 12 Mar 2024 21:13:16 +0100 Subject: [PATCH 3/3] removed some comments, modified indentation, moved some strings to companion object Signed-off-by: Giacomo Pacini --- .../com/nextcloud/talk/chat/ChatActivity.kt | 56 +++++++++---------- 1 file changed, 25 insertions(+), 31 deletions(-) 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 4a6bf80df..0e061efc2 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -420,11 +420,6 @@ class ChatActivity : val typingParticipants = HashMap() var callStarted = false - - private val CURRENT_AUDIO_MESSAGE_KEY = "CURRENT_AUDIO_MESSAGE" - private val CURRENT_AUDIO_POSITION_KEY = "CURRENT_AUDIO_POSITION" - private val CURRENT_AUDIO_WAS_PLAYING_KEY = "CURRENT_AUDIO_PLAYING" - private var RESUME_AUDIO_TAG = "RESUME_AUDIO_TAG" private var voiceMessageToRestoreId = "" private var voiceMessageToRestoreAudioPosition = 0 private var voiceMessageToRestoreWasPlaying = false @@ -505,21 +500,20 @@ class ChatActivity : 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("")) { + 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{ + } else { Log.d(RESUME_AUDIO_TAG, "stored voice message id is empty, not resuming audio playing") voiceMessageToRestoreId = "" voiceMessageToRestoreAudioPosition = 0 voiceMessageToRestoreWasPlaying = false } - - }else{ + } else { voiceMessageToRestoreId = "" voiceMessageToRestoreAudioPosition = 0 voiceMessageToRestoreWasPlaying = false @@ -586,16 +580,15 @@ class ChatActivity : } override fun onSaveInstanceState(outState: Bundle) { - if(currentlyPlayedVoiceMessage != null) { - // stores audio message ID and audio position - // so that can be restored in resumeAudioPlaybackIfNeeded method - outState.putString( CURRENT_AUDIO_MESSAGE_KEY, currentlyPlayedVoiceMessage!!.getId()) + 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) - // stores also audio currently playing status + Log.d( + RESUME_AUDIO_TAG, "Audio Position: " + currentlyPlayedVoiceMessage!!.voiceMessagePlayedSeconds + .toString() + " | isPLaying: " + currentlyPlayedVoiceMessage!!.isPlayingVoiceMessage + ) } super.onSaveInstanceState(outState) } @@ -610,10 +603,6 @@ class ChatActivity : if (mediaRecorderState == MediaRecorderState.RECORDING) { stopAudioRecording() } - //if (currentlyPlayedVoiceMessage != null) { - // stopMediaPlayer(currentlyPlayedVoiceMessage!!) - //} this is done also in onDestroy, - // it is better to continue audio playback when the activity is not visible but still open val text = binding.messageInputView.messageInput.text.toString() val cursor = binding.messageInputView.messageInput.selectionStart val previous = context.getSharedPreferences(localClassName, MODE_PRIVATE).getString(roomToken, "null") @@ -1399,7 +1388,7 @@ class ChatActivity : } } - private fun setUpWaveform(message: ChatMessage, thenPlay : Boolean = true) { + 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) { @@ -2368,7 +2357,7 @@ class ChatActivity : } } - private fun startPlayback(message: ChatMessage, doPlay : Boolean = true) { + 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. @@ -3811,7 +3800,6 @@ class ChatActivity : scrollToRequestedMessageIfNeeded() //FENOM: add here audio resume policy resumeAudioPlaybackIfNeeded() - } private fun scrollToFirstUnreadMessage() { @@ -3923,8 +3911,8 @@ class ChatActivity : * and eventually resumes audio playback * @author Giacomo Pacini */ - private fun resumeAudioPlaybackIfNeeded(){ - if( ! voiceMessageToRestoreId.equals("")) { + private fun resumeAudioPlaybackIfNeeded() { + if (!voiceMessageToRestoreId.equals("")) { Log.d(RESUME_AUDIO_TAG, "begin method to resume audio playback") if (adapter != null) { @@ -3932,7 +3920,7 @@ class ChatActivity : val voiceMessagePosition = adapter!!.items!!.indexOfFirst { it.item is ChatMessage && (it.item as ChatMessage).id == voiceMessageToRestoreId } - if(voiceMessagePosition >= 0) { + if (voiceMessagePosition >= 0) { val currentItem = adapter?.items?.get(voiceMessagePosition)?.item if (currentItem is ChatMessage && currentItem.id == voiceMessageToRestoreId) { currentlyPlayedVoiceMessage = currentItem @@ -3947,19 +3935,21 @@ class ChatActivity : } 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, + "voiceMessagePosition is -1, adapter # of items: " + adapter!!.getItemCount() + ) } } else { Log.d(RESUME_AUDIO_TAG, "TalkMessagesListAdapater is null") } - }else{ + } else { Log.d(RESUME_AUDIO_TAG, "No voice message to restore") } voiceMessageToRestoreId = "" voiceMessageToRestoreAudioPosition = 0 voiceMessageToRestoreWasPlaying = false - } private fun scrollToRequestedMessageIfNeeded() { @@ -4494,7 +4484,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) { @@ -4975,5 +4965,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" } }