WIP reproductor de audio
This commit is contained in:
parent
d082d7c4d6
commit
c6c32e54fb
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
|
||||
|
||||
|
||||
5
app/src/main/res/drawable/baseline_pause_circle_24.xml
Normal file
5
app/src/main/res/drawable/baseline_pause_circle_24.xml
Normal 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>
|
||||
@ -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>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user