WIP reproductor de audio

This commit is contained in:
Erik Everardo 2023-12-20 23:06:01 -06:00
parent d082d7c4d6
commit c6c32e54fb
4 changed files with 165 additions and 13 deletions

View File

@ -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<Listener> = 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)
}
}

View File

@ -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<UserProfile> { 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)

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10s10,-4.48 10,-10S17.52,2 12,2zM11,16H9V8h2V16zM15,16h-2V8h2V16z"/>
</vector>

View File

@ -71,18 +71,31 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.button.MaterialButton
android:id="@+id/play_button"
<RelativeLayout
android:id="@+id/play_button_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginStart="8dp"
app:icon="@drawable/baseline_play_circle_24"
android:visibility="gone"
tools:visibility="visible"
style="?attr/materialIconButtonStyle"/>
tools:visibility="visible">
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/audio_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<com.google.android.material.button.MaterialButton
android:id="@+id/play_button"
style="?attr/materialIconButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:icon="@drawable/baseline_play_circle_24" />
</RelativeLayout>
<TextView
android:id="@+id/text_view_description"
@ -95,7 +108,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/play_button"
app:layout_constraintStart_toEndOf="@id/play_button_container"
app:layout_constraintTop_toTopOf="parent"
tools:text="Hi there, I am software developer!" />
</androidx.constraintlayout.widget.ConstraintLayout>