diff --git a/app/src/main/java/com/isolaatti/audio/player/AudioPlayerConnector.kt b/app/src/main/java/com/isolaatti/audio/player/AudioPlayerConnector.kt index 317a7c6..6a9d06f 100644 --- a/app/src/main/java/com/isolaatti/audio/player/AudioPlayerConnector.kt +++ b/app/src/main/java/com/isolaatti/audio/player/AudioPlayerConnector.kt @@ -2,6 +2,8 @@ package com.isolaatti.audio.player import android.content.Context import android.net.Uri +import android.os.Looper +import android.util.Log import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleOwner @@ -9,17 +11,98 @@ import androidx.media3.common.MediaItem import androidx.media3.common.Player import androidx.media3.exoplayer.ExoPlayer import com.isolaatti.audio.common.domain.Audio +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import java.util.Timer +import java.util.TimerTask +import java.util.concurrent.Executors +import java.util.concurrent.ScheduledFuture +import java.util.concurrent.TimeUnit class AudioPlayerConnector( private val context: Context ): LifecycleEventObserver { + companion object { + const val TAG = "AudioPlayerConnector" + } private var player: Player? = null - + private var audio: Audio? = null private var mediaItem: MediaItem? = null + private var ended = false + + private val listeners: MutableList = mutableListOf() + + private val scheduleExecutorService = Executors.newScheduledThreadPool(1) + private var timerFuture: ScheduledFuture<*>? = null + + private val timerRunnable = Runnable { + CoroutineScope(Dispatchers.Main).launch { + audio?.let { + val progress = (player?.currentPosition) + if(progress != null) { + listeners.forEach { listener -> listener.progressChanged((progress / 1000).toInt(), it) } + } + } + } + + } + + private fun startTimer() { + Log.d(TAG, "startTimer()") + if(timerFuture == null) { + timerFuture = scheduleExecutorService.scheduleAtFixedRate(timerRunnable, 0, 100, TimeUnit.MILLISECONDS) + } + } + + private fun stopTimer() { + timerFuture?.cancel(false) + timerFuture = null + } private val playerListener: Player.Listener = object: Player.Listener { + override fun onIsPlayingChanged(isPlaying: Boolean) { + if(audio != null) { + listeners.forEach { listener -> listener.onPlaying(isPlaying, audio!!)} + } + if(isPlaying) { + ended = false + } + } + override fun onIsLoadingChanged(isLoading: Boolean) { + audio?.let { + listeners.forEach { listener -> listener.isLoading(isLoading, it) } + } + } + + + override fun onPlaybackStateChanged(playbackState: Int) { + when(playbackState) { + Player.STATE_ENDED -> { + Log.d(TAG, "STATE_ENDED") + audio?.let { + listeners.forEach { listener -> listener.onPlaying(false, it)} + } + stopTimer() + ended = true + } + Player.STATE_BUFFERING -> {} + Player.STATE_IDLE -> {} + Player.STATE_READY -> { + Log.d(TAG, "STATE_READY") + player?.totalBufferedDuration?.let { + val seconds = (it / 1000).toInt() + Log.d(TAG, "Duration $it") + audio?.let { + listeners.forEach { listener -> listener.durationChanged(seconds, it) } + } + } + startTimer() + } + } + } } private fun initializePlayer() { @@ -29,11 +112,16 @@ class AudioPlayerConnector( player?.prepare() } + fun addListener(listener: Listener) { + listeners.add(listener) + } + private fun releasePlayer() { player?.run { release() } player = null + stopTimer() } override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { @@ -53,9 +141,31 @@ class AudioPlayerConnector( } } - fun playAudio(audio: Audio) { + fun playPauseAudio(audio: Audio) { + + // intention is to pause current audio + if(audio == this.audio && player?.isPlaying == true) { + player?.pause() + return + } else if(audio == this.audio) { + if(ended) { + player?.seekTo(0) + ended = false + return + } + player?.play() + return + } + this.audio = audio mediaItem = MediaItem.fromUri(Uri.parse(audio.downloadUrl)) player?.setMediaItem(mediaItem!!) } + + interface Listener { + fun onPlaying(isPlaying: Boolean, audio: Audio) + fun isLoading(isLoading: Boolean, audio: Audio) + fun progressChanged(second: Int, audio: Audio) + fun durationChanged(duration: Int, audio: Audio) + } } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/profile/ui/ProfileMainFragment.kt b/app/src/main/java/com/isolaatti/profile/ui/ProfileMainFragment.kt index 54ea195..2d9148c 100644 --- a/app/src/main/java/com/isolaatti/profile/ui/ProfileMainFragment.kt +++ b/app/src/main/java/com/isolaatti/profile/ui/ProfileMainFragment.kt @@ -7,6 +7,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Toast +import androidx.appcompat.content.res.AppCompatResources import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels @@ -86,6 +87,27 @@ class ProfileMainFragment : Fragment() { } } + private val audioPlayerConnectorListener = object: AudioPlayerConnector.Listener { + override fun onPlaying(isPlaying: Boolean, audio: Audio) { + viewBinding.playButton.icon = AppCompatResources.getDrawable(requireContext(), if(isPlaying) R.drawable.baseline_pause_circle_24 else R.drawable.baseline_play_circle_24) + + } + + override fun isLoading(isLoading: Boolean, audio: Audio) { + viewBinding.playButton.isEnabled = !isLoading + viewBinding.audioProgress.isIndeterminate = isLoading + } + + override fun progressChanged(second: Int, audio: Audio) { + viewBinding.audioProgress.setProgress(second, true) + } + + override fun durationChanged(duration: Int, audio: Audio) { + viewBinding.audioProgress.max = duration + } + + } + private val profileObserver = Observer { profile -> viewBinding.profileImageView.load(UrlGen.userProfileImage(profile.userId), imageLoader) @@ -110,7 +132,7 @@ class ProfileMainFragment : Fragment() { } audioDescriptionAudio = profile.descriptionAudio - viewBinding.playButton.visibility = if(profile.descriptionAudio != null) View.VISIBLE else View.GONE + viewBinding.playButtonContainer.visibility = if(profile.descriptionAudio != null) View.VISIBLE else View.GONE setupUiForUserType(profile.isUserItself) } @@ -258,7 +280,7 @@ class ProfileMainFragment : Fragment() { } viewBinding.playButton.setOnClickListener { audioDescriptionAudio?.let { audio -> - audioPlayerConnector.playAudio(audio) + audioPlayerConnector.playPauseAudio(audio) } } } @@ -370,6 +392,8 @@ class ProfileMainFragment : Fragment() { audioPlayerConnector = AudioPlayerConnector(requireContext()) + audioPlayerConnector.addListener(audioPlayerConnectorListener) + viewLifecycleOwner.lifecycle.addObserver(audioPlayerConnector) diff --git a/app/src/main/res/drawable/baseline_pause_circle_24.xml b/app/src/main/res/drawable/baseline_pause_circle_24.xml new file mode 100644 index 0000000..0ec1ffa --- /dev/null +++ b/app/src/main/res/drawable/baseline_pause_circle_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/fragment_discussions.xml b/app/src/main/res/layout/fragment_discussions.xml index 7fc0b7a..7667a5d 100644 --- a/app/src/main/res/layout/fragment_discussions.xml +++ b/app/src/main/res/layout/fragment_discussions.xml @@ -71,18 +71,31 @@ - + tools:visibility="visible"> + + + + +