diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/MainActivity.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/MainActivity.kt index 7317327..20808da 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/MainActivity.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/MainActivity.kt @@ -81,7 +81,6 @@ import it.vfsfitvnm.vimusic.utils.colorPaletteNameKey import it.vfsfitvnm.vimusic.utils.forcePlay import it.vfsfitvnm.vimusic.utils.getEnum import it.vfsfitvnm.vimusic.utils.intent -import it.vfsfitvnm.vimusic.utils.listener import it.vfsfitvnm.vimusic.utils.preferences import it.vfsfitvnm.vimusic.utils.thumbnailRoundnessKey import it.vfsfitvnm.innertube.Innertube @@ -366,7 +365,7 @@ class MainActivity : ComponentActivity() { } } - player.listener(object : Player.Listener { + val listener = object : Player.Listener { override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) { if (reason == Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED && mediaItem != null) { if (mediaItem.mediaMetadata.extras?.getBoolean("isFromPersistentQueue") != true) { @@ -376,7 +375,11 @@ class MainActivity : ComponentActivity() { } } } - }) + } + + player.addListener(listener) + + onDispose { player.removeListener(listener) } } } } diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/player/Controls.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/player/Controls.kt index ce03e3f..07cea13 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/player/Controls.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/player/Controls.kt @@ -45,10 +45,10 @@ import it.vfsfitvnm.vimusic.ui.components.SeekBar import it.vfsfitvnm.vimusic.ui.components.themed.IconButton import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance import it.vfsfitvnm.vimusic.ui.styling.favoritesIcon +import it.vfsfitvnm.vimusic.utils.DisposableListener import it.vfsfitvnm.vimusic.utils.bold import it.vfsfitvnm.vimusic.utils.forceSeekToNext import it.vfsfitvnm.vimusic.utils.forceSeekToPrevious -import it.vfsfitvnm.vimusic.utils.rememberRepeatMode import it.vfsfitvnm.vimusic.utils.secondary import it.vfsfitvnm.vimusic.utils.semiBold import kotlinx.coroutines.Dispatchers @@ -70,7 +70,17 @@ fun Controls( val binder = LocalPlayerServiceBinder.current binder?.player ?: return - val repeatMode by rememberRepeatMode(binder.player) + var repeatMode by remember { + mutableStateOf(binder.player.repeatMode) + } + + binder.player.DisposableListener { + object : Player.Listener { + override fun onRepeatModeChanged(newRepeatMode: Int) { + repeatMode = newRepeatMode + } + } + } var scrubbingPosition by remember(mediaId) { mutableStateOf(null) diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/player/Player.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/player/Player.kt index 84f544c..4ff6569 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/player/Player.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/player/Player.kt @@ -29,6 +29,8 @@ import androidx.compose.foundation.text.BasicText import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.neverEqualPolicy +import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment @@ -44,6 +46,7 @@ import androidx.compose.ui.unit.dp import androidx.media3.common.MediaItem import androidx.media3.common.Player import coil.compose.AsyncImage +import it.vfsfitvnm.innertube.models.NavigationEndpoint import it.vfsfitvnm.route.OnGlobalRoute import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder import it.vfsfitvnm.vimusic.R @@ -58,16 +61,15 @@ import it.vfsfitvnm.vimusic.ui.styling.Dimensions import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance import it.vfsfitvnm.vimusic.ui.styling.collapsedPlayerProgressBar import it.vfsfitvnm.vimusic.ui.styling.px +import it.vfsfitvnm.vimusic.utils.DisposableListener import it.vfsfitvnm.vimusic.utils.forceSeekToNext import it.vfsfitvnm.vimusic.utils.isLandscape -import it.vfsfitvnm.vimusic.utils.rememberMediaItem -import it.vfsfitvnm.vimusic.utils.rememberPositionAndDuration -import it.vfsfitvnm.vimusic.utils.rememberShouldBePlaying +import it.vfsfitvnm.vimusic.utils.positionAndDurationState import it.vfsfitvnm.vimusic.utils.seamlessPlay import it.vfsfitvnm.vimusic.utils.secondary import it.vfsfitvnm.vimusic.utils.semiBold +import it.vfsfitvnm.vimusic.utils.shouldBePlaying import it.vfsfitvnm.vimusic.utils.thumbnail -import it.vfsfitvnm.innertube.models.NavigationEndpoint import kotlin.math.absoluteValue @ExperimentalFoundationApi @@ -84,12 +86,33 @@ fun Player( binder?.player ?: return - val nullableMediaItem by rememberMediaItem(binder.player) + var nullableMediaItem by remember { + mutableStateOf(binder.player.currentMediaItem, neverEqualPolicy()) + } + + var shouldBePlaying by remember { + mutableStateOf(binder.player.shouldBePlaying) + } + + binder.player.DisposableListener { + object : Player.Listener { + override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) { + nullableMediaItem = mediaItem + } + + override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) { + shouldBePlaying = binder.player.shouldBePlaying + } + + override fun onPlaybackStateChanged(playbackState: Int) { + shouldBePlaying = binder.player.shouldBePlaying + } + } + } val mediaItem = nullableMediaItem ?: return - val shouldBePlaying by rememberShouldBePlaying(binder.player) - val positionAndDuration by rememberPositionAndDuration(binder.player) + val positionAndDuration by binder.player.positionAndDurationState() val windowInsets = WindowInsets.systemBars diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/player/Queue.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/player/Queue.kt index f5adffa..0ea128e 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/player/Queue.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/player/Queue.kt @@ -31,6 +31,9 @@ import androidx.compose.foundation.text.BasicText import androidx.compose.material.ripple.rememberRipple import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha @@ -40,6 +43,9 @@ import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp +import androidx.media3.common.MediaItem +import androidx.media3.common.Player +import androidx.media3.common.Timeline import com.valentinilk.shimmer.shimmer import it.vfsfitvnm.reordering.ReorderingLazyColumn import it.vfsfitvnm.reordering.animateItemPlacement @@ -61,12 +67,12 @@ import it.vfsfitvnm.vimusic.ui.styling.Dimensions import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance import it.vfsfitvnm.vimusic.ui.styling.onOverlay import it.vfsfitvnm.vimusic.ui.styling.px +import it.vfsfitvnm.vimusic.utils.DisposableListener import it.vfsfitvnm.vimusic.utils.medium -import it.vfsfitvnm.vimusic.utils.rememberMediaItemIndex -import it.vfsfitvnm.vimusic.utils.rememberShouldBePlaying -import it.vfsfitvnm.vimusic.utils.rememberWindows +import it.vfsfitvnm.vimusic.utils.shouldBePlaying import it.vfsfitvnm.vimusic.utils.shuffleQueue import it.vfsfitvnm.vimusic.utils.smoothScrollToTop +import it.vfsfitvnm.vimusic.utils.windows import kotlinx.coroutines.launch @ExperimentalFoundationApi @@ -112,19 +118,52 @@ fun Queue( binder?.player ?: return@BottomSheet + val player = binder.player + val menuState = LocalMenuState.current val thumbnailSizeDp = Dimensions.thumbnails.song val thumbnailSizePx = thumbnailSizeDp.px - val mediaItemIndex by rememberMediaItemIndex(binder.player) - val windows by rememberWindows(binder.player) - val shouldBePlaying by rememberShouldBePlaying(binder.player) + var mediaItemIndex by remember { + mutableStateOf(if (player.mediaItemCount == 0) -1 else player.currentMediaItemIndex) + } + + var windows by remember { + mutableStateOf(player.currentTimeline.windows) + } + + var shouldBePlaying by remember { + mutableStateOf(binder.player.shouldBePlaying) + } + + player.DisposableListener { + object : Player.Listener { + override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) { + mediaItemIndex = + if (player.mediaItemCount == 0) -1 else player.currentMediaItemIndex + } + + override fun onTimelineChanged(timeline: Timeline, reason: Int) { + windows = timeline.windows + mediaItemIndex = + if (player.mediaItemCount == 0) -1 else player.currentMediaItemIndex + } + + override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) { + shouldBePlaying = binder.player.shouldBePlaying + } + + override fun onPlaybackStateChanged(playbackState: Int) { + shouldBePlaying = binder.player.shouldBePlaying + } + } + } val reorderingState = rememberReorderingState( lazyListState = rememberLazyListState(initialFirstVisibleItemIndex = mediaItemIndex), key = windows, - onDragEnd = binder.player::moveMediaItem, + onDragEnd = player::moveMediaItem, extraItemCount = 0 ) @@ -219,13 +258,13 @@ fun Queue( onClick = { if (isPlayingThisMediaItem) { if (shouldBePlaying) { - binder.player.pause() + player.pause() } else { - binder.player.play() + player.play() } } else { - binder.player.playWhenReady = true - binder.player.seekToDefaultPosition(window.firstPeriodIndex) + player.playWhenReady = true + player.seekToDefaultPosition(window.firstPeriodIndex) } } ) @@ -266,7 +305,7 @@ fun Queue( reorderingState.coroutineScope.launch { reorderingState.lazyListState.smoothScrollToTop() }.invokeOnCompletion { - binder.player.shuffleQueue() + player.shuffleQueue() } } ) diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/player/StatsForNerds.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/player/StatsForNerds.kt index 2d65349..fb1ca11 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/player/StatsForNerds.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/player/StatsForNerds.kt @@ -26,8 +26,12 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp +import androidx.media3.common.Player import androidx.media3.datasource.cache.Cache import androidx.media3.datasource.cache.CacheSpan +import it.vfsfitvnm.innertube.Innertube +import it.vfsfitvnm.innertube.models.bodies.PlayerBody +import it.vfsfitvnm.innertube.requests.player import it.vfsfitvnm.vimusic.Database import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder import it.vfsfitvnm.vimusic.models.Format @@ -35,12 +39,9 @@ import it.vfsfitvnm.vimusic.query import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance import it.vfsfitvnm.vimusic.ui.styling.onOverlay import it.vfsfitvnm.vimusic.ui.styling.overlay +import it.vfsfitvnm.vimusic.utils.DisposableListener import it.vfsfitvnm.vimusic.utils.color import it.vfsfitvnm.vimusic.utils.medium -import it.vfsfitvnm.vimusic.utils.rememberVolume -import it.vfsfitvnm.innertube.Innertube -import it.vfsfitvnm.innertube.models.bodies.PlayerBody -import it.vfsfitvnm.innertube.requests.player import kotlin.math.roundToInt import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.distinctUntilChanged @@ -70,7 +71,17 @@ fun StatsForNerds( Database.format(mediaId).distinctUntilChanged() }.collectAsState(initial = null, context = Dispatchers.IO) - val volume by rememberVolume(binder.player) + var volume by remember { + mutableStateOf(binder.player.volume) + } + + binder.player.DisposableListener { + object : Player.Listener { + override fun onVolumeChanged(newVolume: Float) { + volume = newVolume + } + } + } DisposableEffect(mediaId) { val listener = object : Cache.Listener { @@ -193,7 +204,8 @@ fun StatsForNerds( onClick = { query { runBlocking(Dispatchers.IO) { - Innertube.player(PlayerBody(videoId = mediaId)) + Innertube + .player(PlayerBody(videoId = mediaId)) ?.map { response -> response.streamingData?.adaptiveFormats ?.findLast { format -> @@ -205,7 +217,9 @@ fun StatsForNerds( itag = format.itag, mimeType = format.mimeType, bitrate = format.bitrate, - loudnessDb = response.playerConfig?.audioConfig?.loudnessDb?.toFloat()?.plus(7), + loudnessDb = response.playerConfig?.audioConfig?.loudnessDb + ?.toFloat() + ?.plus(7), contentLength = format.contentLength, lastModified = format.lastModified ) diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/player/Thumbnail.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/player/Thumbnail.kt index a7ffd99..6356562 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/player/Thumbnail.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/player/Thumbnail.kt @@ -17,12 +17,18 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.dp +import androidx.media3.common.MediaItem +import androidx.media3.common.PlaybackException +import androidx.media3.common.Player import coil.compose.AsyncImage import it.vfsfitvnm.vimusic.Database import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder @@ -34,9 +40,8 @@ import it.vfsfitvnm.vimusic.service.VideoIdMismatchException import it.vfsfitvnm.vimusic.ui.styling.Dimensions import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance import it.vfsfitvnm.vimusic.ui.styling.px -import it.vfsfitvnm.vimusic.utils.rememberError -import it.vfsfitvnm.vimusic.utils.rememberMediaItem -import it.vfsfitvnm.vimusic.utils.rememberMediaItemIndex +import it.vfsfitvnm.vimusic.utils.currentWindow +import it.vfsfitvnm.vimusic.utils.DisposableListener import it.vfsfitvnm.vimusic.utils.thumbnail import java.net.UnknownHostException import java.nio.channels.UnresolvedAddressException @@ -57,17 +62,38 @@ fun Thumbnail( it to (it - 64.dp).px } - val mediaItemIndex by rememberMediaItemIndex(player) - val mediaItem by rememberMediaItem(player) + var nullableWindow by remember { + mutableStateOf(player.currentWindow) + } - val error by rememberError(player) + var error by remember { + mutableStateOf(player.playerError) + } + + player.DisposableListener { + object : Player.Listener { + override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) { + nullableWindow = player.currentWindow + } + + override fun onPlaybackStateChanged(playbackState: Int) { + error = player.playerError + } + + override fun onPlayerError(playbackException: PlaybackException) { + error = playbackException + } + } + } + + val window = nullableWindow ?: return AnimatedContent( - targetState = mediaItemIndex to mediaItem, + targetState = window, transitionSpec = { val duration = 500 val slideDirection = - if (targetState.first > initialState.first) AnimatedContentScope.SlideDirection.Left else AnimatedContentScope.SlideDirection.Right + if (targetState.firstPeriodIndex > initialState.firstPeriodIndex) AnimatedContentScope.SlideDirection.Left else AnimatedContentScope.SlideDirection.Right ContentTransform( targetContentEnter = slideIntoContainer( @@ -92,9 +118,7 @@ fun Thumbnail( ) }, contentAlignment = Alignment.Center - ) { (_, currentMediaItem) -> - val currentMediaItem = currentMediaItem ?: return@AnimatedContent - + ) {currentWindow -> Box( modifier = modifier .aspectRatio(1f) @@ -102,7 +126,7 @@ fun Thumbnail( .size(thumbnailSizeDp) ) { AsyncImage( - model = currentMediaItem.mediaMetadata.artworkUri.thumbnail(thumbnailSizePx), + model = currentWindow.mediaItem.mediaMetadata.artworkUri.thumbnail(thumbnailSizePx), contentDescription = null, contentScale = ContentScale.Crop, modifier = Modifier @@ -116,23 +140,23 @@ fun Thumbnail( ) Lyrics( - mediaId = currentMediaItem.mediaId, + mediaId = currentWindow.mediaItem.mediaId, isDisplayed = isShowingLyrics && error == null, onDismiss = { onShowLyrics(false) }, onLyricsUpdate = { areSynchronized, mediaId, lyrics -> query { if (areSynchronized) { if (Database.updateSynchronizedLyrics(mediaId, lyrics) == 0) { - if (mediaId == currentMediaItem.mediaId) { - Database.insert(currentMediaItem) { song -> + if (mediaId == currentWindow.mediaItem.mediaId) { + Database.insert(currentWindow.mediaItem) { song -> song.copy(synchronizedLyrics = lyrics) } } } } else { if (Database.updateLyrics(mediaId, lyrics) == 0) { - if (mediaId == currentMediaItem.mediaId) { - Database.insert(currentMediaItem) { song -> + if (mediaId == currentWindow.mediaItem.mediaId) { + Database.insert(currentWindow.mediaItem) { song -> song.copy(lyrics = lyrics) } } @@ -141,12 +165,12 @@ fun Thumbnail( } }, size = thumbnailSizeDp, - mediaMetadataProvider = currentMediaItem::mediaMetadata, + mediaMetadataProvider = currentWindow.mediaItem::mediaMetadata, durationProvider = player::getDuration, ) StatsForNerds( - mediaId = currentMediaItem.mediaId, + mediaId = currentWindow.mediaItem.mediaId, isDisplayed = isShowingStatsForNerds && error == null, onDismiss = { onShowStatsForNerds(false) } ) diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/Player.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/Player.kt index 43e8130..db99676 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/Player.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/Player.kt @@ -5,6 +5,9 @@ import androidx.media3.common.MediaItem import androidx.media3.common.Player import androidx.media3.common.Timeline +val Player.currentWindow: Timeline.Window? + get() = if (mediaItemCount == 0) null else currentTimeline.getWindow(currentMediaItemIndex, Timeline.Window()) + val Timeline.mediaItems: List get() = List(windowCount) { getWindow(it, Timeline.Window()).mediaItem diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/PlayerState.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/PlayerState.kt index 3d55986..6229dcb 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/PlayerState.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/PlayerState.kt @@ -2,127 +2,33 @@ package it.vfsfitvnm.vimusic.utils import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.DisposableEffectResult -import androidx.compose.runtime.DisposableEffectScope +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.neverEqualPolicy -import androidx.compose.runtime.produceState import androidx.compose.runtime.remember import androidx.media3.common.MediaItem -import androidx.media3.common.PlaybackException import androidx.media3.common.Player -import androidx.media3.common.Timeline import kotlinx.coroutines.delay import kotlinx.coroutines.isActive import kotlinx.coroutines.launch +import kotlinx.coroutines.suspendCancellableCoroutine -context(DisposableEffectScope) -fun Player.listener(listener: Player.Listener): DisposableEffectResult { - addListener(listener) - return onDispose { - removeListener(listener) +@Composable +inline fun Player.DisposableListener(crossinline listenerProvider: () -> Player.Listener) { + DisposableEffect(this) { + val listener = listenerProvider() + addListener(listener) + onDispose { removeListener(listener) } } } @Composable -fun rememberMediaItemIndex(player: Player): State { - val mediaItemIndexState = remember(player) { - mutableStateOf(if (player.mediaItemCount == 0) -1 else player.currentMediaItemIndex) +fun Player.positionAndDurationState(): State> { + val state = remember { + mutableStateOf(currentPosition to duration) } - DisposableEffect(player) { - player.listener(object : Player.Listener { - override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) { - mediaItemIndexState.value = - if (player.mediaItemCount == 0) -1 else player.currentMediaItemIndex - } - - override fun onTimelineChanged(timeline: Timeline, reason: Int) { - mediaItemIndexState.value = - if (player.mediaItemCount == 0) -1 else player.currentMediaItemIndex - } - }) - } - - return mediaItemIndexState -} - -@Composable -fun rememberMediaItem(player: Player): State { - val state = remember(player) { - mutableStateOf(player.currentMediaItem, neverEqualPolicy()) - } - - DisposableEffect(player) { - player.listener(object : Player.Listener { - override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) { - state.value = mediaItem - } - }) - } - - return state -} - -@Composable -fun rememberWindows(player: Player): State> { - val windowsState = remember(player) { - mutableStateOf(player.currentTimeline.windows) - } - - DisposableEffect(player) { - player.listener(object : Player.Listener { - override fun onTimelineChanged(timeline: Timeline, reason: Int) { - windowsState.value = timeline.windows - } - }) - } - - return windowsState -} - -@Composable -fun rememberShouldBePlaying(player: Player): State { - val state = remember(player) { - mutableStateOf(!(player.playbackState == Player.STATE_ENDED || !player.playWhenReady)) - } - - DisposableEffect(player) { - player.listener(object : Player.Listener { - override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) { - state.value = !(player.playbackState == Player.STATE_ENDED || !playWhenReady) - } - - override fun onPlaybackStateChanged(playbackState: Int) { - state.value = !(playbackState == Player.STATE_ENDED || !player.playWhenReady) - } - }) - } - - return state -} - -@Composable -fun rememberRepeatMode(player: Player): State { - val state = remember(player) { - mutableStateOf(player.repeatMode) - } - - DisposableEffect(player) { - player.listener(object : Player.Listener { - override fun onRepeatModeChanged(repeatMode: Int) { - state.value = repeatMode - } - }) - } - - return state -} - -@Composable -fun rememberPositionAndDuration(player: Player): State> { - val state = produceState(initialValue = player.currentPosition to player.duration) { + LaunchedEffect(this) { var isSeeking = false val listener = object : Player.Listener { @@ -133,7 +39,7 @@ fun rememberPositionAndDuration(player: Player): State> { } override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) { - value = player.currentPosition to value.second + state.value = currentPosition to state.value.second } override fun onPositionDiscontinuity( @@ -143,65 +49,29 @@ fun rememberPositionAndDuration(player: Player): State> { ) { if (reason == Player.DISCONTINUITY_REASON_SEEK) { isSeeking = true - value = player.currentPosition to player.duration + state.value = currentPosition to duration } } } - player.addListener(listener) + addListener(listener) val pollJob = launch { while (isActive) { delay(500) if (!isSeeking) { - value = player.currentPosition to player.duration + state.value = currentPosition to duration } } } - awaitDispose { + try { + suspendCancellableCoroutine { } + } finally { pollJob.cancel() - player.removeListener(listener) + removeListener(listener) } } return state } - -@Composable -fun rememberVolume(player: Player): State { - val volumeState = remember(player) { - mutableStateOf(player.volume) - } - - DisposableEffect(player) { - player.listener(object : Player.Listener { - override fun onVolumeChanged(volume: Float) { - volumeState.value = volume - } - }) - } - - return volumeState -} - -@Composable -fun rememberError(player: Player): State { - val errorState = remember(player) { - mutableStateOf(player.playerError) - } - - DisposableEffect(player) { - player.listener(object : Player.Listener { - override fun onPlaybackStateChanged(playbackState: Int) { - errorState.value = player.playerError - } - - override fun onPlayerError(playbackException: PlaybackException) { - errorState.value = playbackException - } - }) - } - - return errorState -}