Convert PlayerState to a data class

This commit is contained in:
vfsfitvnm
2022-07-01 22:16:32 +02:00
parent fc9b023174
commit 7e8ec8efdb
2 changed files with 141 additions and 139 deletions

View File

@@ -31,7 +31,6 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.media3.common.C
import androidx.media3.common.MediaItem
import androidx.media3.common.Player
import androidx.media3.datasource.cache.Cache
import androidx.media3.datasource.cache.CacheSpan
@@ -53,7 +52,6 @@ import it.vfsfitvnm.youtubemusic.YouTube
import it.vfsfitvnm.youtubemusic.models.PlayerResponse
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.launch
import kotlin.math.roundToInt
@@ -78,6 +76,7 @@ fun PlayerView(
val coroutineScope = rememberCoroutineScope()
player ?: return
playerState?.mediaItem ?: return
val smallThumbnailSize = remember {
@@ -156,9 +155,9 @@ fun PlayerView(
modifier = Modifier
.clickable {
if (playerState.playbackState == Player.STATE_IDLE) {
player?.prepare()
player.prepare()
}
player?.play()
player.play()
}
.padding(vertical = 8.dp)
.padding(horizontal = 16.dp)
@@ -169,9 +168,7 @@ fun PlayerView(
contentDescription = null,
colorFilter = ColorFilter.tint(colorPalette.text),
modifier = Modifier
.clickable {
player?.pause()
}
.clickable(onClick = player::pause)
.padding(vertical = 8.dp)
.padding(horizontal = 16.dp)
.size(24.dp)
@@ -181,11 +178,8 @@ fun PlayerView(
}
}
) {
val song by remember(playerState.mediaItem?.mediaId) {
playerState.mediaItem?.mediaId?.let(Database::song)?.distinctUntilChanged()
?: flowOf(
null
)
val song by remember(playerState.mediaItem.mediaId) {
playerState.mediaItem.mediaId.let(Database::song).distinctUntilChanged()
}.collectAsState(initial = null, context = Dispatchers.IO)
var isShowingStatsForNerds by rememberSaveable {
@@ -218,7 +212,7 @@ fun PlayerView(
.clickable {
menuState.display {
QueuedMediaItemMenu(
mediaItem = playerState.mediaItem ?: MediaItem.EMPTY,
mediaItem = playerState.mediaItem,
indexInQueue = null,
onDismiss = menuState::hide,
onGlobalRouteEmitted = layoutState.collapse
@@ -247,7 +241,7 @@ fun PlayerView(
.align(Alignment.CenterHorizontally)
) {
val artworkUri = remember(it) {
player?.getMediaItemAt(it)?.mediaMetadata?.artworkUri.thumbnail(
player.getMediaItemAt(it).mediaMetadata.artworkUri.thumbnail(
thumbnailSizePx
)
}
@@ -281,7 +275,7 @@ fun PlayerView(
exit = fadeOut(),
) {
var cachedBytes by remember(song?.id) {
mutableStateOf(binder?.cache?.getCachedBytes(song?.id ?: "", 0, -1) ?: 0L)
mutableStateOf(binder.cache.getCachedBytes(playerState.mediaItem.mediaId, 0, -1))
}
val loudnessDb by remember {
@@ -297,6 +291,8 @@ fun PlayerView(
}
DisposableEffect(song?.id) {
val key = playerState.mediaItem.mediaId
val listener = object : Cache.Listener {
override fun onSpanAdded(cache: Cache, span: CacheSpan) {
cachedBytes += span.length
@@ -313,15 +309,10 @@ fun PlayerView(
) = Unit
}
song?.id?.let { key ->
binder?.cache?.addListener(key, listener)
}
binder.cache.addListener(key, listener)
onDispose {
song?.id?.let { key ->
binder?.cache?.removeListener(key, listener)
}
binder.cache.removeListener(key, listener)
}
}
@@ -368,7 +359,7 @@ fun PlayerView(
Column {
BasicText(
text = playerState.mediaItem?.mediaId ?: "Unknown",
text = playerState.mediaItem.mediaId,
style = typography.xs.semiBold.color(BlackColorPalette.text)
)
BasicText(
@@ -451,11 +442,10 @@ fun PlayerView(
.size(thumbnailSizeDp)
) {
LoadingOrError(
errorMessage = playerState.error?.javaClass?.canonicalName,
errorMessage = playerState.error.javaClass.canonicalName,
onRetry = {
player?.playWhenReady = true
player?.prepare()
playerState.error = null
player.playWhenReady = true
player.prepare()
}
) {}
}
@@ -494,10 +484,7 @@ fun PlayerView(
}
},
onDragEnd = {
scrubbingPosition?.let { scrubbingPosition ->
player?.seekTo(scrubbingPosition)
playerState.currentPosition = scrubbingPosition
}
scrubbingPosition?.let(player::seekTo)
scrubbingPosition = null
},
color = colorPalette.text,
@@ -553,9 +540,7 @@ fun PlayerView(
query {
song?.let { song ->
Database.update(song.toggleLike())
} ?: playerState.mediaItem?.let { mediaItem ->
Database.insert(mediaItem, Song::toggleLike)
}
} ?: Database.insert(playerState.mediaItem, Song::toggleLike)
}
}
.padding(horizontal = 16.dp)
@@ -567,9 +552,7 @@ fun PlayerView(
contentDescription = null,
colorFilter = ColorFilter.tint(colorPalette.text),
modifier = Modifier
.clickable {
player?.seekToPrevious()
}
.clickable(onClick = player::seekToPrevious)
.padding(horizontal = 16.dp)
.size(32.dp)
)
@@ -581,11 +564,11 @@ fun PlayerView(
colorFilter = ColorFilter.tint(colorPalette.text),
modifier = Modifier
.clickable {
if (player?.playbackState == Player.STATE_IDLE) {
if (player.playbackState == Player.STATE_IDLE) {
player.prepare()
}
player?.play()
player.play()
}
.size(64.dp)
)
@@ -594,9 +577,7 @@ fun PlayerView(
contentDescription = null,
colorFilter = ColorFilter.tint(colorPalette.text),
modifier = Modifier
.clickable {
player?.pause()
}
.clickable(onClick = player::pause)
.size(64.dp)
)
}
@@ -606,14 +587,11 @@ fun PlayerView(
contentDescription = null,
colorFilter = ColorFilter.tint(colorPalette.text),
modifier = Modifier
.clickable {
player?.seekToNext()
}
.clickable(onClick = player::seekToNext)
.padding(horizontal = 16.dp)
.size(32.dp)
)
Image(
painter = painterResource(
if (playerState.repeatMode == Player.REPEAT_MODE_ONE) {
@@ -632,10 +610,10 @@ fun PlayerView(
),
modifier = Modifier
.clickable {
player?.repeatMode
?.plus(2)
?.mod(3)
?.let { repeatMode ->
player.repeatMode
.plus(2)
.mod(3)
.let { repeatMode ->
player.repeatMode = repeatMode
preferences.repeatMode = repeatMode
}

View File

@@ -6,107 +6,131 @@ import androidx.compose.runtime.*
import androidx.media3.common.*
import kotlin.math.absoluteValue
@Stable
class PlayerState(private val player: Player) : Player.Listener, Runnable {
private val handler = Handler(Looper.getMainLooper())
var currentPosition by mutableStateOf(player.currentPosition)
var duration by mutableStateOf(player.duration)
private set
data class PlayerState(
val currentPosition: Long,
val duration: Long,
val playbackState: Int,
val mediaItemIndex: Int,
val mediaItem: MediaItem?,
val mediaMetadata: MediaMetadata,
val playWhenReady: Boolean,
val repeatMode: Int,
val error: PlaybackException?,
val mediaItems: List<MediaItem>,
val volume: Float
) {
constructor(player: Player) : this(
currentPosition = player.currentPosition,
duration = player.duration,
playbackState = player.playbackState,
mediaItemIndex = player.currentMediaItemIndex,
mediaItem = player.currentMediaItem,
mediaMetadata = player.mediaMetadata,
playWhenReady = player.playWhenReady,
repeatMode = player.repeatMode,
error = player.playerError,
mediaItems = player.currentTimeline.mediaItems,
volume = player.volume
)
val progress: Float
get() = currentPosition.toFloat() / duration.absoluteValue
var playbackState by mutableStateOf(player.playbackState)
private set
var mediaItemIndex by mutableStateOf(player.currentMediaItemIndex)
private set
var mediaItem by mutableStateOf(player.currentMediaItem)
private set
var mediaMetadata by mutableStateOf(player.mediaMetadata)
private set
var playWhenReady by mutableStateOf(player.playWhenReady)
private set
var repeatMode by mutableStateOf(player.repeatMode)
private set
var error by mutableStateOf(player.playerError)
var mediaItems by mutableStateOf(player.currentTimeline.mediaItems)
private set
var volume by mutableStateOf(player.volume)
private set
override fun onVolumeChanged(volume: Float) {
this.volume = volume
}
override fun onPlaybackStateChanged(playbackState: Int) {
this.playbackState = playbackState
}
override fun onMediaMetadataChanged(mediaMetadata: MediaMetadata) {
this.mediaMetadata = mediaMetadata
}
override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) {
this.playWhenReady = playWhenReady
}
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
this.mediaItem = mediaItem
mediaItemIndex = player.currentMediaItemIndex
}
override fun onRepeatModeChanged(repeatMode: Int) {
this.repeatMode = repeatMode
}
override fun onPlayerError(playbackException: PlaybackException) {
error = playbackException
}
override fun onTimelineChanged(timeline: Timeline, reason: Int) {
mediaItems = timeline.mediaItems
mediaItemIndex = player.currentMediaItemIndex
}
override fun run() {
duration = player.duration
currentPosition = player.currentPosition
handler.postDelayed(this, 500)
}
fun init() {
player.addListener(this)
handler.post(this)
}
fun dispose() {
player.removeListener(this)
handler.removeCallbacks(this)
}
}
@Composable
fun rememberPlayerState(
player: Player?
): PlayerState? {
val playerState = remember(player) {
player?.let(::PlayerState)
var playerState by remember(player) {
mutableStateOf(player?.let(::PlayerState))
}
playerState?.let {
DisposableEffect(Unit) {
playerState.init()
onDispose(playerState::dispose)
DisposableEffect(player) {
if (player == null) return@DisposableEffect onDispose { }
var isSeeking = false
val handler = Handler(Looper.getMainLooper())
val listener = object : Player.Listener, Runnable {
override fun onVolumeChanged(volume: Float) {
playerState = playerState?.copy(volume = volume)
}
override fun onPlaybackStateChanged(playbackState: Int) {
playerState = playerState?.copy(playbackState = playbackState)
if (playbackState == Player.STATE_READY) {
isSeeking = false
}
}
override fun onMediaMetadataChanged(mediaMetadata: MediaMetadata) {
playerState = playerState?.copy(mediaMetadata = mediaMetadata)
}
override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) {
playerState = playerState?.copy(playWhenReady = playWhenReady)
}
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
playerState = playerState?.copy(
currentPosition = player.currentPosition,
mediaItem = mediaItem,
mediaItemIndex = player.currentMediaItemIndex
)
}
override fun onRepeatModeChanged(repeatMode: Int) {
playerState = playerState?.copy(repeatMode = repeatMode)
}
override fun onPlayerError(playbackException: PlaybackException) {
playerState = playerState?.copy(error = playbackException)
}
override fun onTimelineChanged(timeline: Timeline, reason: Int) {
playerState = playerState?.copy(
mediaItems = timeline.mediaItems,
mediaItemIndex = player.currentMediaItemIndex
)
}
override fun onPositionDiscontinuity(
oldPosition: Player.PositionInfo,
newPosition: Player.PositionInfo,
reason: Int
) {
if (reason == Player.DISCONTINUITY_REASON_SEEK) {
isSeeking = true
playerState = playerState?.copy(
duration = player.duration,
currentPosition = player.currentPosition
)
}
}
override fun run() {
if (!isSeeking) {
playerState = playerState?.copy(
duration = player.duration,
currentPosition = player.currentPosition
)
}
handler.postDelayed(this, 500)
}
}
player.addListener(listener)
handler.post(listener)
onDispose {
player.removeListener(listener)
handler.removeCallbacks(listener)
}
}