Skip to content

Commit

Permalink
Add PlayerListener & Ui changes [Not tested]
Browse files Browse the repository at this point in the history
  • Loading branch information
brahmkshatriya committed Feb 5, 2024
1 parent 6982ec8 commit 474b259
Show file tree
Hide file tree
Showing 6 changed files with 244 additions and 64 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package dev.brahmkshatriya.echo.ui.player

import android.annotation.SuppressLint
import android.graphics.Bitmap
import androidx.core.net.toUri
import androidx.media3.common.MediaMetadata
import androidx.media3.common.util.UnstableApi
import dev.brahmkshatriya.echo.data.models.ImageHolder
import dev.brahmkshatriya.echo.data.models.Track
import java.nio.ByteBuffer


interface PlayerHelper {
companion object {

@SuppressLint("UnsafeOptInUsageError")
fun Track.toMetaData() = MediaMetadata.Builder()
.setTitle(title)
.setArtist(artists.firstOrNull()?.name)
.setArtwork(cover)
.build()

@UnstableApi
private fun MediaMetadata.Builder.setArtwork(cover: ImageHolder?): MediaMetadata.Builder {
cover?.let {
return when (it) {
is ImageHolder.UrlHolder -> setArtworkUri(it.url.toUri())
is ImageHolder.UriHolder -> setArtworkUri(it.uri)
is ImageHolder.BitmapHolder -> setArtworkData(
it.bitmap.toByteArray(),
MediaMetadata.PICTURE_TYPE_FILE_ICON
)
}
}
return this
}

private fun Bitmap.toByteArray(): ByteArray {
val size: Int = rowBytes * height
val byteBuffer = ByteBuffer.allocate(size)
copyPixelsToBuffer(byteBuffer)
return byteBuffer.array()
}


fun Long.toTimeString(): String {
val seconds = this / 1000
val minutes = seconds / 60
val hours = minutes / 60
return if (hours > 0) {
String.format("%02d:%02d:%02d", hours, minutes % 60, seconds % 60)
} else {
String.format("%02d:%02d", minutes, seconds % 60)
}
}
}
}

115 changes: 115 additions & 0 deletions app/src/main/java/dev/brahmkshatriya/echo/ui/player/PlayerListener.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package dev.brahmkshatriya.echo.ui.player

import android.annotation.SuppressLint
import android.os.Handler
import android.os.Looper
import androidx.media3.common.C
import androidx.media3.common.MediaMetadata
import androidx.media3.common.Player
import androidx.media3.session.MediaController
import dev.brahmkshatriya.echo.data.models.Track
import dev.brahmkshatriya.echo.databinding.BottomPlayerBinding
import dev.brahmkshatriya.echo.ui.player.PlayerHelper.Companion.toTimeString
import dev.brahmkshatriya.echo.ui.utils.loadInto

class PlayerListener(val player: MediaController, val binding: BottomPlayerBinding) :
Player.Listener {
init {
//Poll each second to update the seekbar
val handler = Handler(Looper.getMainLooper())
val runnable = object : Runnable {
override fun run() {
binding.collapsedSeekBar.progress = player.currentPosition.toInt()
binding.expandedSeekBar.value = player.currentPosition.toFloat()

binding.trackCurrentTime.text = player.currentPosition.toTimeString()

handler.postDelayed(this, 1000)
}
}
handler.post(runnable)
}
@SuppressLint("SwitchIntDef")
override fun onPlaybackStateChanged(playbackState: Int) {
when (playbackState) {
Player.STATE_BUFFERING -> {
binding.trackPlayPause.isEnabled = false
binding.collapsedTrackPlayPause.isEnabled = false
}

Player.STATE_READY -> {
binding.trackPlayPause.isEnabled = true
binding.collapsedTrackPlayPause.isEnabled = true
}
}
}

override fun onIsPlayingChanged(isPlaying: Boolean) {
binding.trackPlayPause.isEnabled = false
binding.collapsedTrackPlayPause.isEnabled = false

binding.trackPlayPause.isChecked = isPlaying
binding.collapsedTrackPlayPause.isChecked = isPlaying

binding.trackPlayPause.isEnabled = true
binding.collapsedTrackPlayPause.isEnabled = true
}


private val tracks = mutableMapOf<MediaMetadata, Track>()
fun map(metadata: MediaMetadata, track: Track) {
tracks[metadata] = track
}

override fun onMediaMetadataChanged(mediaMetadata: MediaMetadata) {

val track = tracks[mediaMetadata] ?: return
binding.collapsedTrackTitle.text = track.title
binding.expandedTrackTitle.text = track.title

track.artists.joinToString(" ") { it.name }.run {
binding.collapsedTrackAuthor.text = this
binding.expandedTrackAuthor.text = this
}
track.cover?.run {
loadInto(binding.collapsedTrackCover)
loadInto(binding.expandedTrackCover)
}

binding.collapsedSeekBar.isIndeterminate = true
binding.expandedSeekBar.isEnabled = false

binding.trackTotalTime.text = null
}

override fun onPositionDiscontinuity(
oldPosition: Player.PositionInfo,
newPosition: Player.PositionInfo,
reason: Int
) {
if (reason != Player.DISCONTINUITY_REASON_SEEK && reason != Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT) {
binding.collapsedSeekBar.progress = newPosition.positionMs.toInt()
binding.expandedSeekBar.value = newPosition.positionMs.toFloat()

binding.trackCurrentTime.text = newPosition.positionMs.toTimeString()
}
}

override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) {
if (!playWhenReady) return
if(player.duration == C.TIME_UNSET) return

binding.collapsedSeekBar.isIndeterminate = false
binding.expandedSeekBar.isEnabled = true

binding.collapsedSeekBar.max = player.duration.toInt()
binding.expandedSeekBar.valueTo = player.duration.toFloat()

binding.trackTotalTime.text = player.duration.toTimeString()
}

override fun onPlaylistMetadataChanged(mediaMetadata: MediaMetadata) {
super.onPlaylistMetadataChanged(mediaMetadata)
println(mediaMetadata)
}
}
75 changes: 27 additions & 48 deletions app/src/main/java/dev/brahmkshatriya/echo/ui/player/PlayerView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import android.view.View
import androidx.activity.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.media3.common.MediaItem
import androidx.media3.common.Player
import androidx.media3.common.Player.Listener
import androidx.media3.session.MediaController
import com.google.android.material.bottomnavigation.BottomNavigationView
import com.google.android.material.bottomsheet.BottomSheetBehavior
Expand All @@ -17,15 +15,15 @@ import dev.brahmkshatriya.echo.MainActivity
import dev.brahmkshatriya.echo.R
import dev.brahmkshatriya.echo.data.models.StreamableAudio
import dev.brahmkshatriya.echo.databinding.BottomPlayerBinding
import dev.brahmkshatriya.echo.ui.player.PlayerHelper.Companion.toMetaData
import dev.brahmkshatriya.echo.ui.utils.dpToPx
import dev.brahmkshatriya.echo.ui.utils.loadInto
import dev.brahmkshatriya.echo.ui.utils.updatePaddingWithSystemInsets
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch

class PlayerView(
private val activity: MainActivity,
player: MediaController,
private val player: MediaController,
private val view: View,
private val binding: BottomPlayerBinding
) {
Expand All @@ -34,7 +32,7 @@ class PlayerView(

init {
applyView()
connect(player)
connect()
}

private fun applyView() {
Expand Down Expand Up @@ -75,7 +73,7 @@ class PlayerView(
}
}

private fun connect(player: MediaController) {
private fun connect() {
binding.trackPlayPause.addOnCheckedStateChangedListener { _, state ->
when (state) {
STATE_CHECKED -> player.play()
Expand All @@ -88,64 +86,45 @@ class PlayerView(
STATE_UNCHECKED -> player.pause()
}
}
player.addListener(object : Listener {
override fun onPlaybackStateChanged(playbackState: Int) {
when (playbackState) {
Player.STATE_IDLE, Player.STATE_BUFFERING -> {
binding.trackPlayPause.isEnabled = false
binding.collapsedTrackPlayPause.isEnabled = false
}

Player.STATE_READY, Player.STATE_ENDED -> {
binding.trackPlayPause.isEnabled = true
binding.collapsedTrackPlayPause.isEnabled = true
}
}
}

override fun onIsPlayingChanged(isPlaying: Boolean) {
binding.trackPlayPause.isEnabled = false
binding.collapsedTrackPlayPause.isEnabled = false
binding.trackNext.setOnClickListener {
player.seekToNextMediaItem()
}

binding.trackPlayPause.isChecked = isPlaying
binding.collapsedTrackPlayPause.isChecked = isPlaying
binding.trackPrevious.setOnClickListener {
player.seekToPreviousMediaItem()
}

binding.trackPlayPause.isEnabled = true
binding.collapsedTrackPlayPause.isEnabled = true
binding.expandedSeekBar.addOnChangeListener { _, value, fromUser ->
if (fromUser) {
player.seekTo(value.toLong())
}
}

})
val listener = PlayerListener(player, binding)
player.addListener(listener)

activity.lifecycleScope.launch {
viewModel.audioFlow.collectLatest {
it?.run {
viewModel.audioFlow.collectLatest { pair ->
pair?.run {
val track = this.first
val metadata = track.toMetaData()
listener.map(metadata, track)

binding.collapsedTrackTitle.text = track.title
binding.expandedTrackTitle.text = track.title

track.artists.joinToString { " " }.run {
binding.collapsedTrackAuthor.text = this
binding.expandedTrackAuthor.text = this
}
track.cover?.run {
loadInto(binding.collapsedTrackCover)
loadInto(binding.expandedTrackCover)
}

val builder = MediaItem.Builder()
.setMediaMetadata(metadata)
val item = when (val audio = this.second) {
is StreamableAudio.StreamableFile -> {
MediaItem.fromUri(audio.uri)
builder.setUri(audio.uri)
}

is StreamableAudio.ByteStreamAudio -> TODO()
is StreamableAudio.StreamableUrl -> {
MediaItem.Builder()
.setUri(audio.url.url)
.build()
builder.setUri(audio.url.url)
}

is StreamableAudio.ByteStreamAudio -> TODO()
}
player.setMediaItem(item)
player.setMediaItem(item.build())
player.prepare()
player.play()
}
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/color/bottom_item_icon.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true" android:color="?colorOnPrimary" />
<item android:state_checked="true" android:color="?colorOnSurfaceVariant" />
<item android:color="?attr/colorPrimary" />
</selector>
5 changes: 5 additions & 0 deletions app/src/main/res/color/button_play_pause.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false" android:color="?color" />
<item android:color="?attr/colorPrimary" />
</selector>
Loading

0 comments on commit 474b259

Please sign in to comment.