From 32a78cfe556785d0ffe951d320df5267eb540b28 Mon Sep 17 00:00:00 2001 From: Julius Linus Date: Tue, 16 Apr 2024 12:02:09 -0500 Subject: [PATCH] WIP replicating functionality 13 - Seekbar is functional - still buggy though Signed-off-by: rapterjet2004 --- .../MessageInputVoiceRecordingFragment.kt | 23 +++++---- .../talk/chat/data/io/MediaPlayerManager.kt | 49 ++++++++++++++++--- .../chat/viewmodels/MessageInputViewModel.kt | 6 ++- .../talk/dagger/modules/ViewModelModule.kt | 5 +- app/src/main/res/layout/activity_chat.xml | 6 --- 5 files changed, 63 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/chat/MessageInputVoiceRecordingFragment.kt b/app/src/main/java/com/nextcloud/talk/chat/MessageInputVoiceRecordingFragment.kt index 65cb1e5c31..cdbd8c7566 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/MessageInputVoiceRecordingFragment.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/MessageInputVoiceRecordingFragment.kt @@ -1,6 +1,5 @@ package com.nextcloud.talk.chat -import android.animation.ObjectAnimator import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -31,7 +30,6 @@ class MessageInputVoiceRecordingFragment : Fragment() { lateinit var binding: FragmentMessageInputVoiceRecordingBinding private lateinit var chatActivity: ChatActivity - private var voicePreviewObjectAnimator: ObjectAnimator? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -62,13 +60,17 @@ class MessageInputVoiceRecordingFragment : Fragment() { chatActivity.messageInputViewModel.micInputAudioObserver.observe(viewLifecycleOwner) { binding.micInputCloud.setRotationSpeed(it.first, it.second) } + chatActivity.messageInputViewModel.mediaPlayerSeekbarObserver.observe(viewLifecycleOwner) { + binding.seekbar.progress = it + } chatActivity.messageInputViewModel.isPlayerFinished.observe(viewLifecycleOwner) { - if (it) { - binding.playPauseBtn.icon = ContextCompat.getDrawable( - requireContext(), - R.drawable.ic_baseline_play_arrow_voice_message_24 - ) - } + // TODO set up auto replay + // if (it) { + // binding.playPauseBtn.icon = ContextCompat.getDrawable( + // requireContext(), + // R.drawable.ic_baseline_play_arrow_voice_message_24 + // ) + // } } } @@ -101,7 +103,10 @@ class MessageInputVoiceRecordingFragment : Fragment() { binding.seekbar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { - chatActivity.messageInputViewModel.seekMediaPlayerTo(progress) + // FIXME this causes infinite loop for some weird reason + if (fromUser) { + chatActivity.messageInputViewModel.seekMediaPlayerTo(progress) + } } override fun onStartTrackingTouch(seekBar: SeekBar?) { diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/io/MediaPlayerManager.kt b/app/src/main/java/com/nextcloud/talk/chat/data/io/MediaPlayerManager.kt index 725fd503e7..8a1acbf734 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/io/MediaPlayerManager.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/io/MediaPlayerManager.kt @@ -12,6 +12,12 @@ import android.os.Build import android.util.Log import androidx.lifecycle.MutableLiveData import com.nextcloud.talk.chat.ChatActivity +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext /** * Abstraction over the [MediaPlayer](https://developer.android.com/reference/android/media/MediaPlayer) class used @@ -21,18 +27,25 @@ class MediaPlayerManager : LifecycleAwareManager { companion object { val TAG: String = MediaPlayerManager::class.java.simpleName private const val ONE_SECOND_IN_MILLIS = 1000L - private const val DIVIDER = 100f + private const val SEEKBAR_UPDATE_DELAY = 15L + const val DIVIDER = 100f } private var mediaPlayer: MediaPlayer? = null private var mediaPlayerPosition: Int = 0 + private var loop = false + private var scope = MainScope() var mediaPlayerDuration: Int = 0 /** * Starts playing audio from the given path, initializes or resumes if the player is already created. */ - fun start(path: String, callback: MutableLiveData) { - init(path, callback) + fun start( + path: String, + completionObserver: MutableLiveData, + seekbarObserver: MutableLiveData + ) { + init(path, completionObserver, seekbarObserver) } /** @@ -41,6 +54,7 @@ class MediaPlayerManager : LifecycleAwareManager { fun stop() { if (mediaPlayer != null) { Log.d("Julius", "media player destroyed") + loop = false mediaPlayer!!.stop() mediaPlayer!!.release() mediaPlayer = null @@ -54,12 +68,32 @@ class MediaPlayerManager : LifecycleAwareManager { if (mediaPlayer != null) { val pos = mediaPlayer!!.duration * (progress / DIVIDER) mediaPlayer!!.seekTo(pos.toInt()) - mediaPlayerPosition = progress + mediaPlayerPosition = pos.toInt() + } + } + + private suspend fun seekbarUpdateObserver(callback: MutableLiveData) = withContext(Dispatchers.IO) { + while (true) { + if (!loop) { + return@withContext + } + if (mediaPlayer != null && mediaPlayer!!.isPlaying) { // TODO include check for if user is seeking + val pos = mediaPlayer!!.currentPosition + val progress = (pos.toFloat() / mediaPlayerDuration) * DIVIDER + Log.d("SeekbarUpdate", "progress: $progress") + callback.postValue(progress.toInt()) + } + + delay(SEEKBAR_UPDATE_DELAY) } } @Suppress("Detekt.TooGenericExceptionCaught") - private fun init(path: String, callback: MutableLiveData) { + private fun init( + path: String, + completionObserver: MutableLiveData, + seekbarObserver: MutableLiveData + ) { try { mediaPlayer = MediaPlayer().apply { setDataSource(path) @@ -67,6 +101,8 @@ class MediaPlayerManager : LifecycleAwareManager { setOnPreparedListener { mediaPlayerDuration = it.duration start() + loop = true + scope.launch { seekbarUpdateObserver(seekbarObserver) } } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { setOnMediaTimeDiscontinuityListener { mp, _ -> @@ -78,7 +114,7 @@ class MediaPlayerManager : LifecycleAwareManager { this.seekTo(mediaPlayerPosition) } setOnCompletionListener { - callback.postValue(true) + completionObserver.postValue(true) } } } catch (e: Exception) { @@ -96,5 +132,6 @@ class MediaPlayerManager : LifecycleAwareManager { override fun handleOnStop() { stop() + scope.cancel() } } diff --git a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt index 849c3cbc14..d0b1578e44 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt @@ -78,6 +78,10 @@ class MessageInputViewModel @Inject constructor( val micInputAudioObserver: LiveData> get() = _micInputAudioObserver + private val _mediaPlayerSeekbarObserver: MutableLiveData = MutableLiveData() + val mediaPlayerSeekbarObserver: LiveData + get() = _mediaPlayerSeekbarObserver + private val _getEditChatMessage: MutableLiveData = MutableLiveData() val getEditChatMessage: LiveData get() = _getEditChatMessage @@ -184,7 +188,7 @@ class MessageInputViewModel @Inject constructor( fun startMediaPlayer(path: String) { _isPlayerFinished.value = false - mediaPlayerManager.start(path, _isPlayerFinished) + mediaPlayerManager.start(path, _isPlayerFinished, _mediaPlayerSeekbarObserver) _isVoicePreviewPlaying.postValue(true) } diff --git a/app/src/main/java/com/nextcloud/talk/dagger/modules/ViewModelModule.kt b/app/src/main/java/com/nextcloud/talk/dagger/modules/ViewModelModule.kt index 1e5018cca4..e800e90afb 100644 --- a/app/src/main/java/com/nextcloud/talk/dagger/modules/ViewModelModule.kt +++ b/app/src/main/java/com/nextcloud/talk/dagger/modules/ViewModelModule.kt @@ -124,10 +124,7 @@ abstract class ViewModelModule { @ViewModelKey(MessageInputViewModel::class) abstract fun messageInputViewModel(viewModel: MessageInputViewModel): ViewModel - @Binds - @IntoMap - @ViewModelKey(CallNotificationViewModel::class) - abstract fun callNotificationViewModel(viewModel: CallNotificationViewModel): ViewModel + // TODO I had a merge conflict here that went weird. choose their version @Binds @IntoMap diff --git a/app/src/main/res/layout/activity_chat.xml b/app/src/main/res/layout/activity_chat.xml index 8bff6a7201..e542c9b4df 100644 --- a/app/src/main/res/layout/activity_chat.xml +++ b/app/src/main/res/layout/activity_chat.xml @@ -239,12 +239,6 @@ - -