Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ fun AlbumCarouselSection(
currentSong: Song?,
queue: ImmutableList<Song>,
expansionFraction: Float,
currentMediaItemIndex: Int = -1,
requestedScrollIndex: Int? = null,
onSongSelected: (Song) -> Unit,
onAlbumClick: (Song) -> Unit = {},
Expand All @@ -50,13 +51,12 @@ fun AlbumCarouselSection(
if (queue.isEmpty()) return

// Mantiene compatibilidad con tu llamada actual
val initialIndex = remember(currentSong?.id, queue) {
val songId = currentSong?.id ?: return@remember 0
queue.indexOfFirst { it.id == songId }
.takeIf { it >= 0 }
?: queue.indexOf(currentSong)
.takeIf { it >= 0 }
?: 0
val initialIndex = remember(currentSong?.id, currentMediaItemIndex, queue) {
resolveCurrentQueueIndex(
currentSong = currentSong,
currentMediaItemIndex = currentMediaItemIndex,
queue = queue
)
}

val carouselState = rememberRoundedParallaxCarouselState(
Expand All @@ -71,18 +71,20 @@ fun AlbumCarouselSection(
}

// Player -> Carousel
val currentSongIndex = remember(currentSong?.id, queue) {
val songId = currentSong?.id ?: return@remember 0
queue.indexOfFirst { it.id == songId }
.takeIf { it >= 0 }
?: queue.indexOf(currentSong)
.takeIf { it >= 0 }
?: 0
val currentSongIndex = remember(currentSong?.id, currentMediaItemIndex, queue) {
resolveCurrentQueueIndex(
currentSong = currentSong,
currentMediaItemIndex = currentMediaItemIndex,
queue = queue
)
}
val requestedTargetIndex = remember(requestedScrollIndex, queue) {
requestedScrollIndex?.takeIf { it in queue.indices }
}
val effectiveTargetIndex = requestedTargetIndex ?: currentSongIndex
val carouselItemKeys = remember(queue) {
buildQueueOccurrenceKeys(queue)
}

PrefetchAlbumNeighbors(
isActive = expansionFraction > 0.08f,
Expand Down Expand Up @@ -156,7 +158,7 @@ fun AlbumCarouselSection(
suppressNoPeekSettleCorrection = requestedTargetIndex != null || programmaticScrollInProgress,
carouselStyle = if (carouselState.pagerState.pageCount == 1) CarouselStyle.NO_PEEK else carouselStyle, // Handle single-item case
carouselWidth = availableWidth, // Pass the full width for layout calculations
itemKey = { index -> queue.getOrNull(index)?.id ?: index },
itemKey = { index -> carouselItemKeys.getOrNull(index) ?: "queue_item_$index" },
content = { index ->
val song = queue[index]
val isFocusedItem = carouselState.pagerState.currentPage == index
Expand Down Expand Up @@ -185,3 +187,28 @@ fun AlbumCarouselSection(
)
}
}

private fun resolveCurrentQueueIndex(
currentSong: Song?,
currentMediaItemIndex: Int,
queue: ImmutableList<Song>
): Int {
val songId = currentSong?.id ?: return 0
if (currentMediaItemIndex in queue.indices && queue[currentMediaItemIndex].id == songId) {
return currentMediaItemIndex
}
return queue.indexOfFirst { it.id == songId }
.takeIf { it >= 0 }
?: queue.indexOf(currentSong)
.takeIf { it >= 0 }
?: 0
}

private fun buildQueueOccurrenceKeys(queue: ImmutableList<Song>): List<String> {
val occurrencesBySongId = HashMap<String, Int>()
return queue.mapIndexed { index, song ->
val occurrence = occurrencesBySongId.getOrDefault(song.id, 0)
occurrencesBySongId[song.id] = occurrence + 1
"queue_carousel_${song.id}_${occurrence}_${song.albumArtUriString.orEmpty().hashCode()}_$index"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ internal fun BoxScope.UnifiedPlayerMiniAndFullLayers(
currentSong = currentSongNonNull,
currentPlaybackQueue = currentPlaybackQueue,
currentQueueSourceName = currentQueueSourceName,
currentMediaItemIndex = infrequentPlayerState.currentMediaItemIndex,
isShuffleEnabled = infrequentPlayerState.isShuffleEnabled,
shuffleTransitionInProgress = infrequentPlayerState.isShuffleTransitionInProgress,
repeatMode = infrequentPlayerState.repeatMode,
Expand Down Expand Up @@ -296,6 +297,7 @@ internal fun UnifiedPlayerPrewarmLayer(
currentSong = currentSong,
currentPlaybackQueue = currentPlaybackQueue,
currentQueueSourceName = currentQueueSourceName,
currentMediaItemIndex = infrequentPlayerState.currentMediaItemIndex,
isShuffleEnabled = infrequentPlayerState.isShuffleEnabled,
shuffleTransitionInProgress = infrequentPlayerState.isShuffleTransitionInProgress,
repeatMode = infrequentPlayerState.repeatMode,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ fun FullPlayerContent(
currentSong: Song?,
currentPlaybackQueue: ImmutableList<Song>,
currentQueueSourceName: String,
currentMediaItemIndex: Int = -1,
isShuffleEnabled: Boolean,
shuffleTransitionInProgress: Boolean,
repeatMode: Int,
Expand Down Expand Up @@ -406,12 +407,13 @@ fun FullPlayerContent(
}
}

var pendingCarouselSongId by remember { mutableStateOf<String?>(null) }
val pendingCarouselIndex = remember(pendingCarouselSongId, currentPlaybackQueue) {
pendingCarouselSongId?.let { targetSongId ->
currentPlaybackQueue.indexOfFirst { it.id == targetSongId }
.takeIf { it >= 0 }
}
var pendingCarouselIndex by remember { mutableStateOf<Int?>(null) }
val currentQueueIndex = remember(song.id, currentMediaItemIndex, currentPlaybackQueue) {
resolveQueueIndex(
queue = currentPlaybackQueue,
songId = song.id,
currentMediaItemIndex = currentMediaItemIndex
)
}
val skipRequests = remember {
MutableSharedFlow<SkipDirection>(
Expand All @@ -421,51 +423,51 @@ fun FullPlayerContent(
}
val latestQueue by rememberUpdatedState(currentPlaybackQueue)
val latestSongId by rememberUpdatedState(song.id)
val latestCurrentQueueIndex by rememberUpdatedState(currentQueueIndex)
val latestRepeatMode by rememberUpdatedState(repeatMode)
val latestIsRemotePlaybackActive by rememberUpdatedState(isRemotePlaybackActive)
val latestCurrentPositionProvider by rememberUpdatedState(currentPositionProvider)
val latestOnNext by rememberUpdatedState(onNext)
val latestOnPrevious by rememberUpdatedState(onPrevious)

LaunchedEffect(song.id, pendingCarouselSongId) {
if (pendingCarouselSongId == song.id) {
pendingCarouselSongId = null
LaunchedEffect(currentQueueIndex, pendingCarouselIndex) {
if (pendingCarouselIndex == currentQueueIndex) {
pendingCarouselIndex = null
}
}

LaunchedEffect(pendingCarouselSongId, song.id) {
val targetSongId = pendingCarouselSongId ?: return@LaunchedEffect
LaunchedEffect(pendingCarouselIndex, currentQueueIndex) {
val targetIndex = pendingCarouselIndex ?: return@LaunchedEffect
kotlinx.coroutines.delay(900)
if (pendingCarouselSongId == targetSongId && song.id != targetSongId) {
pendingCarouselSongId = null
if (pendingCarouselIndex == targetIndex && currentQueueIndex != targetIndex) {
pendingCarouselIndex = null
}
}

LaunchedEffect(skipRequests) {
skipRequests.collect { direction ->
val queueSnapshot = latestQueue
val baseSongId = pendingCarouselSongId ?: latestSongId
val baseIndex = pendingCarouselIndex
?: latestCurrentQueueIndex
?: queueSnapshot.indexOfFirst { it.id == latestSongId }.takeIf { it >= 0 }
val predictedTargetIndex = when (direction) {
SkipDirection.NEXT -> predictSkipNextCarouselIndex(
currentSongId = baseSongId,
currentIndex = baseIndex,
queue = queueSnapshot,
repeatMode = latestRepeatMode,
isRemotePlaybackActive = latestIsRemotePlaybackActive
)
SkipDirection.PREVIOUS -> predictSkipPreviousCarouselIndex(
currentSongId = baseSongId,
currentIndex = baseIndex,
queue = queueSnapshot,
currentPositionMs = latestCurrentPositionProvider(),
repeatMode = latestRepeatMode,
isRemotePlaybackActive = latestIsRemotePlaybackActive
)
}
val predictedTargetSongId = predictedTargetIndex
?.let(queueSnapshot::getOrNull)
?.id

if (predictedTargetSongId != null) {
pendingCarouselSongId = predictedTargetSongId
if (predictedTargetIndex != null) {
pendingCarouselIndex = predictedTargetIndex

// Start the pager motion before MediaController listeners fan out
// the full track transition state updates through the player UI.
Expand All @@ -478,7 +480,7 @@ fun FullPlayerContent(
}

kotlinx.coroutines.delay(
if (predictedTargetSongId != null) SPAM_SKIP_SERIALIZATION_MS
if (predictedTargetIndex != null) SPAM_SKIP_SERIALIZATION_MS
else NO_TARGET_SKIP_SERIALIZATION_MS
)
}
Expand All @@ -498,6 +500,7 @@ fun FullPlayerContent(
FullPlayerAlbumCoverSection(
song = song,
currentPlaybackQueue = currentPlaybackQueue,
currentMediaItemIndex = currentQueueIndex ?: currentMediaItemIndex,
carouselStyle = carouselStyle,
loadingTweaks = loadingTweaks,
isSheetDragGestureActive = isSheetDragGestureActive,
Expand Down Expand Up @@ -994,6 +997,7 @@ fun FullPlayerContent(
private fun FullPlayerAlbumCoverSection(
song: Song,
currentPlaybackQueue: ImmutableList<Song>,
currentMediaItemIndex: Int,
carouselStyle: String,
loadingTweaks: FullPlayerLoadingTweaks,
isSheetDragGestureActive: Boolean,
Expand Down Expand Up @@ -1072,6 +1076,7 @@ private fun FullPlayerAlbumCoverSection(
currentSong = song,
queue = currentPlaybackQueue,
expansionFraction = 1f,
currentMediaItemIndex = currentMediaItemIndex,
requestedScrollIndex = requestedScrollIndex,
onSongSelected = { newSong ->
if (newSong.id != song.id) {
Expand Down Expand Up @@ -1242,39 +1247,46 @@ private fun FullPlayerProgressSection(
)
}

private fun resolveQueueIndex(
queue: ImmutableList<Song>,
songId: String,
currentMediaItemIndex: Int
): Int? {
if (currentMediaItemIndex in queue.indices && queue[currentMediaItemIndex].id == songId) {
return currentMediaItemIndex
}
return queue.indexOfFirst { it.id == songId }.takeIf { it >= 0 }
}

private fun predictSkipNextCarouselIndex(
currentSongId: String,
currentIndex: Int?,
queue: ImmutableList<Song>,
repeatMode: Int,
isRemotePlaybackActive: Boolean
): Int? {
if (isRemotePlaybackActive || queue.size <= 1) return null

val currentIndex = queue.indexOfFirst { it.id == currentSongId }
if (currentIndex == -1) return null
val safeCurrentIndex = currentIndex?.takeIf { it in queue.indices } ?: return null

return when {
currentIndex < queue.lastIndex -> currentIndex + 1
safeCurrentIndex < queue.lastIndex -> safeCurrentIndex + 1
repeatMode == Player.REPEAT_MODE_ALL -> 0
else -> null
}
}

private fun predictSkipPreviousCarouselIndex(
currentSongId: String,
currentIndex: Int?,
queue: ImmutableList<Song>,
currentPositionMs: Long,
repeatMode: Int,
isRemotePlaybackActive: Boolean
): Int? {
if (isRemotePlaybackActive || queue.size <= 1) return null
if (currentPositionMs > PREVIOUS_TRACK_RESTART_THRESHOLD_MS) return null

val currentIndex = queue.indexOfFirst { it.id == currentSongId }
if (currentIndex == -1) return null
val safeCurrentIndex = currentIndex?.takeIf { it in queue.indices } ?: return null

return when {
currentIndex > 0 -> currentIndex - 1
safeCurrentIndex > 0 -> safeCurrentIndex - 1
repeatMode == Player.REPEAT_MODE_ALL -> queue.lastIndex
else -> null
}
Expand Down
Loading