Skip to content

Commit

Permalink
Merge pull request #4482 from arkascha/issues-334-allow-voice-message…
Browse files Browse the repository at this point in the history
…s-to-be-played-at-different-speeds

Add playback speed control to voice messages
  • Loading branch information
AndyScherzinger authored Dec 2, 2024
2 parents 187f98a + 5011649 commit c506f2e
Show file tree
Hide file tree
Showing 14 changed files with 288 additions and 58 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2024 Christian Reiner <[email protected]>
* SPDX-FileCopyrightText: 2021 Andy Scherzinger <[email protected]>
* SPDX-FileCopyrightText: 2021 Tim Krüger <[email protected]>
* SPDX-FileCopyrightText: 2021 Marcel Hibbe <[email protected]>
Expand Down Expand Up @@ -68,10 +69,16 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) :

lateinit var voiceMessageInterface: VoiceMessageInterface
lateinit var commonMessageInterface: CommonMessageInterface
private var isBound = false

@SuppressLint("SetTextI18n")
override fun onBind(message: ChatMessage) {
super.onBind(message)
if (isBound) {
handleIsPlayingVoiceMessageState(message)
return
}

this.message = message
sharedApplication!!.componentApplication.inject(this)

Expand Down Expand Up @@ -100,25 +107,6 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) :
viewThemeUtils.talk.themeWaveFormSeekBar(binding.seekbar)
viewThemeUtils.platform.colorCircularProgressBar(binding.progressBar, ColorRole.ON_SURFACE_VARIANT)

if (message.isPlayingVoiceMessage) {
showPlayButton()
binding.playPauseBtn.icon = ContextCompat.getDrawable(
context!!,
R.drawable.ic_baseline_pause_voice_message_24
)
val d = message.voiceMessageDuration.toLong()
val t = message.voiceMessagePlayedSeconds.toLong()
binding.voiceMessageDuration.text = android.text.format.DateUtils.formatElapsedTime(d - t)
binding.voiceMessageDuration.visibility = View.VISIBLE
binding.seekbar.progress = message.voiceMessageSeekbarProgress
} else {
binding.playPauseBtn.visibility = View.VISIBLE
binding.playPauseBtn.icon = ContextCompat.getDrawable(
context!!,
R.drawable.ic_baseline_play_arrow_voice_message_24
)
}

if (message.isDownloadingVoiceMessage) {
showVoiceMessageLoading()
} else {
Expand Down Expand Up @@ -158,6 +146,10 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) :
}
})

voiceMessageInterface.registerMessageToObservePlaybackSpeedPreferences(message.user.id) { speed ->
binding.playbackSpeedControlBtn.setSpeed(speed)
}

Reaction().showReactions(
message,
::clickOnReaction,
Expand All @@ -167,6 +159,8 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) :
false,
viewThemeUtils
)

isBound = true
}

private fun longClickOnReaction(chatMessage: ChatMessage) {
Expand All @@ -177,6 +171,29 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) :
commonMessageInterface.onClickReaction(chatMessage, emoji)
}

private fun handleIsPlayingVoiceMessageState(message: ChatMessage) {
if (message.isPlayingVoiceMessage) {
showPlayButton()
binding.playPauseBtn.icon = ContextCompat.getDrawable(
context!!,
R.drawable.ic_baseline_pause_voice_message_24
)

val d = message.voiceMessageDuration.toLong()
val t = message.voiceMessagePlayedSeconds.toLong()
binding.voiceMessageDuration.text = android.text.format.DateUtils.formatElapsedTime(d - t)
binding.voiceMessageDuration.visibility = View.VISIBLE
binding.seekbar.max = message.voiceMessageDuration * ONE_SEC
binding.seekbar.progress = message.voiceMessageSeekbarProgress
} else {
binding.playPauseBtn.visibility = View.VISIBLE
binding.playPauseBtn.icon = ContextCompat.getDrawable(
context!!,
R.drawable.ic_baseline_play_arrow_voice_message_24
)
}
}

private fun updateDownloadState(message: ChatMessage) {
// check if download worker is already running
val fileId = message.selectedIndividualHashMap!!["id"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,16 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) :

lateinit var voiceMessageInterface: VoiceMessageInterface
lateinit var commonMessageInterface: CommonMessageInterface
private var isBound = false

@SuppressLint("SetTextI18n")
override fun onBind(message: ChatMessage) {
super.onBind(message)
if (isBound) {
handleIsPlayingVoiceMessageState(message)
return
}

this.message = message
sharedApplication!!.componentApplication.inject(this)
viewThemeUtils.platform.colorTextView(binding.messageTime, ColorRole.ON_SURFACE_VARIANT)
Expand All @@ -102,12 +108,9 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) :
setParentMessageDataOnMessageItem(message)

updateDownloadState(message)
binding.seekbar.max = message.voiceMessageDuration * ONE_SEC
viewThemeUtils.talk.themeWaveFormSeekBar(binding.seekbar)
viewThemeUtils.platform.colorCircularProgressBar(binding.progressBar, ColorRole.ON_SURFACE_VARIANT)

handleIsPlayingVoiceMessageState(message)

handleIsDownloadingVoiceMessageState(message)

handleResetVoiceMessageState(message)
Expand Down Expand Up @@ -149,6 +152,10 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) :

binding.checkMark.contentDescription = readStatusContentDescriptionString

voiceMessageInterface.registerMessageToObservePlaybackSpeedPreferences(message.user.id) { speed ->
binding.playbackSpeedControlBtn.setSpeed(speed)
}

Reaction().showReactions(
message,
::clickOnReaction,
Expand All @@ -158,6 +165,7 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) :
true,
viewThemeUtils
)
isBound = true
}

private fun longClickOnReaction(chatMessage: ChatMessage) {
Expand Down Expand Up @@ -207,6 +215,7 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) :
val t = message.voiceMessagePlayedSeconds.toLong()
binding.voiceMessageDuration.text = android.text.format.DateUtils.formatElapsedTime(d - t)
binding.voiceMessageDuration.visibility = View.VISIBLE
binding.seekbar.max = message.voiceMessageDuration * ONE_SEC
binding.seekbar.progress = message.voiceMessageSeekbarProgress
} else {
binding.playPauseBtn.visibility = View.VISIBLE
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2024 Christian Reiner <[email protected]>
* SPDX-FileCopyrightText: 2021 Marcel Hibbe <[email protected]>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.adapters.messages

import com.nextcloud.talk.chat.data.model.ChatMessage
import com.nextcloud.talk.ui.PlaybackSpeed

interface VoiceMessageInterface {
fun updateMediaPlayerProgressBySlider(message: ChatMessage, progress: Int)
fun registerMessageToObservePlaybackSpeedPreferences(userId: String, listener: (speed: PlaybackSpeed) -> Unit)
}
52 changes: 52 additions & 0 deletions app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2024 Christian Reiner <[email protected]>
* SPDX-FileCopyrightText: 2024 Parneet Singh <[email protected]>
* SPDX-FileCopyrightText: 2024 Giacomo Pacini <[email protected]>
* SPDX-FileCopyrightText: 2023 Ezhil Shanmugham <[email protected]>
Expand Down Expand Up @@ -136,6 +137,8 @@ import com.nextcloud.talk.shareditems.activities.SharedItemsActivity
import com.nextcloud.talk.signaling.SignalingMessageReceiver
import com.nextcloud.talk.signaling.SignalingMessageSender
import com.nextcloud.talk.translate.ui.TranslateActivity
import com.nextcloud.talk.ui.PlaybackSpeed
import com.nextcloud.talk.ui.PlaybackSpeedControl
import com.nextcloud.talk.ui.StatusDrawable
import com.nextcloud.talk.ui.bottom.sheet.ProfileBottomSheet
import com.nextcloud.talk.ui.dialog.DateTimePickerFragment
Expand Down Expand Up @@ -205,6 +208,7 @@ import java.util.Date
import java.util.Locale
import java.util.concurrent.ExecutionException
import javax.inject.Inject
import kotlin.String
import kotlin.collections.set
import kotlin.math.roundToInt

Expand Down Expand Up @@ -357,6 +361,19 @@ class ChatActivity :
private var voiceMessageToRestoreAudioPosition = 0
private var voiceMessageToRestoreWasPlaying = false

private val playbackSpeedPreferencesObserver: (Map<String, PlaybackSpeed>) -> Unit = { speedPreferenceLiveData ->
mediaPlayer?.let { mediaPlayer ->
(mediaPlayer.isPlaying == true).also {
currentlyPlayedVoiceMessage?.let { message ->
mediaPlayer.playbackParams.let { params ->
params.setSpeed(chatViewModel.getPlaybackSpeedPreference(message).value)
mediaPlayer.playbackParams = params
}
}
}
}
}

private val localParticipantMessageListener = object : SignalingMessageReceiver.LocalParticipantMessageListener {
override fun onSwitchTo(token: String?) {
if (token != null) {
Expand Down Expand Up @@ -434,6 +451,10 @@ class ChatActivity :

onBackPressedDispatcher.addCallback(this, onBackPressedCallback)

appPreferences.readVoiceMessagePlaybackSpeedPreferences().let { playbackSpeedPreferences ->
chatViewModel.applyPlaybackSpeedPreferences(playbackSpeedPreferences)
}

initObservers()

if (savedInstanceState != null) {
Expand Down Expand Up @@ -1045,6 +1066,8 @@ class ChatActivity :

setupSwipeToReply()

chatViewModel.voiceMessagePlaybackSpeedPreferences.observe(this, playbackSpeedPreferencesObserver)

binding.unreadMessagesPopup.setOnClickListener {
binding.messagesListView.smoothScrollToPosition(0)
binding.unreadMessagesPopup.visibility = View.GONE
Expand Down Expand Up @@ -1131,6 +1154,7 @@ class ChatActivity :
adapter?.setLoadMoreListener(this)
adapter?.setDateHeadersFormatter { format(it) }
adapter?.setOnMessageViewLongClickListener { view, message -> onMessageViewLongClick(view, message) }

adapter?.registerViewClickListener(
R.id.playPauseBtn
) { _, message ->
Expand All @@ -1154,6 +1178,15 @@ class ChatActivity :
}
}
}

adapter?.registerViewClickListener(R.id.playbackSpeedControlBtn) { button, message ->
val nextSpeed = (button as PlaybackSpeedControl).getSpeed().next()
HashMap(appPreferences.readVoiceMessagePlaybackSpeedPreferences()).let { playbackSpeedPreferences ->
playbackSpeedPreferences[message.user.id] = nextSpeed
chatViewModel.applyPlaybackSpeedPreferences(playbackSpeedPreferences)
appPreferences.saveVoiceMessagePlaybackSpeedPreferences(playbackSpeedPreferences)
}
}
}

private fun setUpWaveform(message: ChatMessage, thenPlay: Boolean = true) {
Expand Down Expand Up @@ -1579,6 +1612,9 @@ class ChatActivity :
mediaPlayer?.let {
if (!it.isPlaying && doPlay) {
chatViewModel.audioRequest(true) {
it.playbackParams = it.playbackParams.apply {
setSpeed(chatViewModel.getPlaybackSpeedPreference(message).value)
}
it.start()
}
}
Expand Down Expand Up @@ -1703,6 +1739,20 @@ class ChatActivity :
}
}

override fun registerMessageToObservePlaybackSpeedPreferences(
userId: String,
listener: (speed: PlaybackSpeed) -> Unit
) {
chatViewModel.voiceMessagePlaybackSpeedPreferences.let { liveData ->
liveData.observe(this) { playbackSpeedPreferences ->
listener(playbackSpeedPreferences[userId] ?: PlaybackSpeed.NORMAL)
}
liveData.value?.let { playbackSpeedPreferences ->
listener(playbackSpeedPreferences[userId] ?: PlaybackSpeed.NORMAL)
}
}
}

@SuppressLint("NotifyDataSetChanged")
override fun collapseSystemMessages() {
adapter?.items?.forEach {
Expand Down Expand Up @@ -2372,6 +2422,8 @@ class ChatActivity :
if (mentionAutocomplete != null && mentionAutocomplete!!.isPopupShowing) {
mentionAutocomplete?.dismissPopup()
}

chatViewModel.voiceMessagePlaybackSpeedPreferences.removeObserver(playbackSpeedPreferencesObserver)
}

private fun isActivityNotChangingConfigurations(): Boolean = !isChangingConfigurations
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2024 Christian Reiner <[email protected]>
* SPDX-FileCopyrightText: 2023 Marcel Hibbe <[email protected]>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
Expand Down Expand Up @@ -33,6 +34,7 @@ import com.nextcloud.talk.models.json.conversations.RoomsOverall
import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.models.json.reminder.Reminder
import com.nextcloud.talk.repositories.reactions.ReactionsRepository
import com.nextcloud.talk.ui.PlaybackSpeed
import com.nextcloud.talk.utils.ConversationUtils
import com.nextcloud.talk.utils.bundle.BundleKeys
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
Expand Down Expand Up @@ -107,6 +109,10 @@ class ChatViewModel @Inject constructor(
val getVoiceRecordingLocked: LiveData<Boolean>
get() = _getVoiceRecordingLocked

private val _voiceMessagePlaybackSpeedPreferences: MutableLiveData<Map<String, PlaybackSpeed>> = MutableLiveData()
val voiceMessagePlaybackSpeedPreferences: LiveData<Map<String, PlaybackSpeed>>
get() = _voiceMessagePlaybackSpeedPreferences

val getMessageFlow = chatRepository.messageFlow
.onEach {
_chatMessageViewState.value = if (_chatMessageViewState.value == ChatMessageInitialState) {
Expand Down Expand Up @@ -644,6 +650,13 @@ class ChatViewModel @Inject constructor(
emit(message.first())
}

fun applyPlaybackSpeedPreferences(speeds: Map<String, PlaybackSpeed>) {
_voiceMessagePlaybackSpeedPreferences.postValue(speeds)
}

fun getPlaybackSpeedPreference(message: ChatMessage) =
_voiceMessagePlaybackSpeedPreferences.value?.get(message.user.id) ?: PlaybackSpeed.NORMAL

// inner class GetRoomObserver : Observer<ConversationModel> {
// override fun onSubscribe(d: Disposable) {
// // unused atm
Expand Down
Loading

0 comments on commit c506f2e

Please sign in to comment.