Skip to content

Commit

Permalink
playback speed control button for voice messages
Browse files Browse the repository at this point in the history
Signed-off-by: Christian Reiner <[email protected]>
  • Loading branch information
arkascha committed Nov 27, 2024
1 parent 1065f13 commit 6410dae
Show file tree
Hide file tree
Showing 13 changed files with 211 additions and 15 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 All @@ -27,9 +28,6 @@ import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedA
import com.nextcloud.talk.chat.ChatActivity
import com.nextcloud.talk.chat.data.model.ChatMessage
import com.nextcloud.talk.databinding.ItemCustomIncomingVoiceMessageBinding
import com.nextcloud.talk.extensions.loadBotsAvatar
import com.nextcloud.talk.extensions.loadChangelogBotAvatar
import com.nextcloud.talk.extensions.loadFederatedUserAvatar
import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.ChatMessageUtils
Expand Down Expand Up @@ -162,6 +160,10 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) :
}
})

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

Reaction().showReactions(
message,
::clickOnReaction,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,10 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) :

binding.checkMark.contentDescription = readStatusContentDescriptionString

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

Reaction().showReactions(
message,
::clickOnReaction,
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 _voiceMessagePlaybackSpeeds: MutableLiveData<Map<String, PlaybackSpeed>> = MutableLiveData()
val voiceMessagePlaybackSpeedPreferences: LiveData<Map<String, PlaybackSpeed>>
get() = _voiceMessagePlaybackSpeeds

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>) {
_voiceMessagePlaybackSpeeds.postValue(speeds)
}

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

// inner class GetRoomObserver : Observer<ConversationModel> {
// override fun onSubscribe(d: Disposable) {
// // unused atm
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class MessageInputViewModel @Inject constructor(
private val audioRecorderManager: AudioRecorderManager,
private val mediaPlayerManager: MediaPlayerManager,
private val audioFocusRequestManager: AudioFocusRequestManager,
private val dataStore: AppPreferences
private val appPreferences: AppPreferences
) : ViewModel(), DefaultLifecycleObserver {
enum class LifeCycleFlag {
PAUSED,
Expand Down Expand Up @@ -147,9 +147,9 @@ class MessageInputViewModel @Inject constructor(
if (isQueueing) {
val tempID = System.currentTimeMillis().toInt()
val qMsg = QueuedMessage(tempID, message, displayName, replyTo, sendWithoutNotification)
messageQueue = dataStore.getMessageQueue(internalId)
messageQueue = appPreferences.getMessageQueue(internalId)
messageQueue.add(qMsg)
dataStore.saveMessageQueue(internalId, messageQueue)
appPreferences.saveMessageQueue(internalId, messageQueue)
_messageQueueSizeFlow.update { messageQueue.size }
_messageQueueFlow.postValue(listOf(qMsg))
return
Expand Down Expand Up @@ -260,8 +260,8 @@ class MessageInputViewModel @Inject constructor(
if (isQueueing) return
messageQueue.clear()

val queue = dataStore.getMessageQueue(internalId)
dataStore.saveMessageQueue(internalId, null) // empties the queue
val queue = appPreferences.getMessageQueue(internalId)
appPreferences.saveMessageQueue(internalId, null) // empties the queue
while (queue.size > 0) {
val msg = queue.removeAt(0)
sendChatMessage(
Expand All @@ -279,7 +279,7 @@ class MessageInputViewModel @Inject constructor(
}

fun getTempMessagesFromMessageQueue(internalId: String) {
val queue = dataStore.getMessageQueue(internalId)
val queue = appPreferences.getMessageQueue(internalId)
val list = mutableListOf<QueuedMessage>()
for (msg in queue) {
list.add(msg)
Expand All @@ -292,31 +292,31 @@ class MessageInputViewModel @Inject constructor(
}

fun restoreMessageQueue(internalId: String) {
messageQueue = dataStore.getMessageQueue(internalId)
messageQueue = appPreferences.getMessageQueue(internalId)
_messageQueueSizeFlow.tryEmit(messageQueue.size)
}

fun removeFromQueue(internalId: String, id: Int) {
val queue = dataStore.getMessageQueue(internalId)
val queue = appPreferences.getMessageQueue(internalId)
for (qMsg in queue) {
if (qMsg.id == id) {
queue.remove(qMsg)
break
}
}
dataStore.saveMessageQueue(internalId, queue)
appPreferences.saveMessageQueue(internalId, queue)
_messageQueueSizeFlow.tryEmit(queue.size)
}

fun editQueuedMessage(internalId: String, id: Int, newMessage: String) {
val queue = dataStore.getMessageQueue(internalId)
val queue = appPreferences.getMessageQueue(internalId)
for (qMsg in queue) {
if (qMsg.id == id) {
qMsg.message = newMessage
break
}
}
dataStore.saveMessageQueue(internalId, queue)
appPreferences.saveMessageQueue(internalId, queue)
}

fun showCallStartedIndicator(recent: ChatMessage, show: Boolean) {
Expand Down
65 changes: 65 additions & 0 deletions app/src/main/java/com/nextcloud/talk/ui/PlaybackSpeedControl.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2024 Christian Reiner <[email protected]>
* SPDX-License-Identifier: GPL-3.0-or-later
*/

package com.nextcloud.talk.ui

import android.content.Context
import android.util.AttributeSet
import com.google.android.material.button.MaterialButton
import java.util.Locale

internal const val SPEED_FACTOR_SLOW = 0.8f
internal const val SPEED_FACTOR_NORMAL = 1.0f
internal const val SPEED_FACTOR_FASTER = 1.5f
internal const val SPEED_FACTOR_FASTEST = 2.0f

class PlaybackSpeedControl @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : MaterialButton(context, attrs, defStyleAttr) {

private var currentSpeed = PlaybackSpeed.NORMAL

init {
text = currentSpeed.label
}

fun setSpeed(newSpeed: PlaybackSpeed) {
currentSpeed = newSpeed
text = currentSpeed.label
}

fun getSpeed(): PlaybackSpeed {
return currentSpeed
}
}

enum class PlaybackSpeed(val value: Float) {
SLOW(SPEED_FACTOR_SLOW),
NORMAL(SPEED_FACTOR_NORMAL),
FASTER(SPEED_FACTOR_FASTER),
FASTEST(SPEED_FACTOR_FASTEST);

// no fixed, literal labels, since we want to obey numeric interpunctuation for different locales
val label: String = String.format(Locale.getDefault(), "%01.1fx", value)

fun next(): PlaybackSpeed {
return entries[(ordinal + 1) % entries.size]
}

companion object {
fun byName(name: String): PlaybackSpeed {
for (speed in entries) {
if (speed.name.equals(name, ignoreCase = true)) {
return speed
}
}
return NORMAL
}
}
}
Loading

0 comments on commit 6410dae

Please sign in to comment.