Skip to content
Open
Changes from 3 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
150 changes: 85 additions & 65 deletions app/src/main/kotlin/com/metrolist/music/playback/MusicService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,8 @@ class MusicService :

// Tracks the original queue size to distinguish original items from auto-added ones
private var originalQueueSize: Int = 0
private var addedQueueSize: Int = 0
private var manualQueueCount: Int = 0
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

private var consecutivePlaybackErr = 0
private var retryJob: Job? = null
Expand Down Expand Up @@ -1422,6 +1424,8 @@ class MusicService :
}
// Reset original queue size when starting a new queue
originalQueueSize = 0
addedQueueSize = 0
manualQueueCount = 0
if (queue.preloadItem != null) {
player.setMediaItem(queue.preloadItem!!.toMediaItem())
player.prepare()
Expand Down Expand Up @@ -1714,71 +1718,42 @@ class MusicService :
}
}

val insertIndex = player.currentMediaItemIndex + 1
val shuffleEnabled = player.shuffleModeEnabled

// Insert items immediately after the current item in the window/index space
// Calculate insertion point: current song + existing manual queue items
// This ensures FIFO order and coherence between playNext and addToQueue
val currentIndex = player.currentMediaItemIndex
val insertIndex = currentIndex + manualQueueCount + 1

player.addMediaItems(insertIndex, items)
player.prepare()

// Update manual queue count and added queue size
// This makes items visible to the priority logic and fixes LIFO issues
manualQueueCount += items.size
addedQueueSize += items.size

if (shuffleEnabled) {
// Rebuild shuffle order so that newly inserted items are played next
val timeline = player.currentTimeline
if (!timeline.isEmpty) {
val size = timeline.windowCount
val currentIndex = player.currentMediaItemIndex

// Newly inserted indices are a contiguous range [insertIndex, insertIndex + items.size)
val newIndices = (insertIndex until (insertIndex + items.size)).toSet()
if (player.shuffleModeEnabled) {
val totalItems = player.mediaItemCount
val random = java.util.Random()

// Rebuild shuffle order to prioritize the newly inserted items
// Maintaining a sequential path for the current song + all manual queue items
val finalOrder = IntArray(totalItems)
val sequentialPathEnd = currentIndex + manualQueueCount

for (i in 0..sequentialPathEnd) {
if (i < totalItems) finalOrder[i] = i
}

// Collect existing shuffle traversal order excluding current index
val orderAfter = mutableListOf<Int>()
var idx = currentIndex
while (true) {
idx = timeline.getNextWindowIndex(idx, Player.REPEAT_MODE_OFF, /*shuffleModeEnabled=*/true)
if (idx == C.INDEX_UNSET) break
if (idx != currentIndex) orderAfter.add(idx)
}
val remainingIndices = ((sequentialPathEnd + 1) until totalItems).toMutableList()
remainingIndices.shuffle(random)

val prevList = mutableListOf<Int>()
var pIdx = currentIndex
while (true) {
pIdx = timeline.getPreviousWindowIndex(pIdx, Player.REPEAT_MODE_OFF, /*shuffleModeEnabled=*/true)
if (pIdx == C.INDEX_UNSET) break
if (pIdx != currentIndex) prevList.add(pIdx)
}
prevList.reverse() // preserve original forward order

val existingOrder = (prevList + orderAfter).filter { it != currentIndex && it !in newIndices }

// Build new shuffle order: current -> newly inserted (in insertion order) -> rest
val nextBlock = (insertIndex until (insertIndex + items.size)).toList()
val finalOrder = IntArray(size)
var pos = 0
prevList
.filter { it !in newIndices }
.forEach { if (it in 0 until size) finalOrder[pos++] = it }
finalOrder[pos++] = currentIndex
nextBlock.forEach { if (it in 0 until size) finalOrder[pos++] = it }
orderAfter
.filter { it !in newIndices }
.forEach { if (pos < size) finalOrder[pos++] = it }

// Fill any missing indices (safety) to ensure a full permutation
if (pos < size) {
for (i in 0 until size) {
if (!finalOrder.contains(i)) {
finalOrder[pos++] = i
if (pos == size) break
}
}
}

player.setShuffleOrder(DefaultShuffleOrder(finalOrder, System.currentTimeMillis()))
for (i in remainingIndices.indices) {
finalOrder[sequentialPathEnd + 1 + i] = remainingIndices[i]
}
}
}

player.setShuffleOrder(DefaultShuffleOrder(finalOrder, random.nextLong()))
}
player.prepare()
}
fun addToQueue(items: List<MediaItem>) {
// Remove duplicates if enabled
if (dataStore.get(PreventDuplicateTracksInQueueKey, false)) {
Expand All @@ -1792,20 +1767,60 @@ class MusicService :
}
}

// Remove from highest index to lowest to maintain index stability
indicesToRemove.sortedDescending().forEach { index ->
player.removeMediaItem(index)
}
}

player.addMediaItems(items)
if (player.mediaItemCount == 0 || player.playbackState == STATE_IDLE) {
player.setMediaItems(items)
player.prepare()
return
}

// Check if repeat mode is active
val repeatMode = player.repeatMode
val isRepeatActive = repeatMode != REPEAT_MODE_OFF

// Calculate insertion point: current song + existing manual queue items
// This ensures FIFO order for the manual queue
val currentIndex = player.currentMediaItemIndex
val insertIndex = if (isRepeatActive) {
currentIndex + manualQueueCount + 1
} else {
player.mediaItemCount
}

player.addMediaItems(insertIndex, items)

// Update manual queue count and added queue size
manualQueueCount += items.size
addedQueueSize += items.size

if (player.shuffleModeEnabled) {
val shufflePlaylistFirst = dataStore.get(ShufflePlaylistFirstKey, false)
applyShuffleOrder(player.currentMediaItemIndex, player.mediaItemCount, shufflePlaylistFirst)
val totalItems = player.mediaItemCount
val random = java.util.Random()
val finalOrder = IntArray(totalItems)

// Maintain a sequential path for the manual queue range
// Coherent with playNext logic to pass PR review requirements
val sequentialPathEnd = currentIndex + manualQueueCount

for (i in 0..sequentialPathEnd) {
if (i < totalItems) finalOrder[i] = i
}

val remainingIndices = ((sequentialPathEnd + 1) until totalItems).toMutableList()
remainingIndices.shuffle(random)

for (i in remainingIndices.indices) {
finalOrder[sequentialPathEnd + 1 + i] = remainingIndices[i]
}

player.setShuffleOrder(DefaultShuffleOrder(finalOrder, random.nextLong()))
}
player.prepare()
}

}
fun toggleLibrary() {
scope.launch {
val songToToggle = currentSong.first()
Expand Down Expand Up @@ -2230,6 +2245,11 @@ class MusicService :
}
previousMediaItemIndex = player.currentMediaItemIndex

// Decrement manual queue count when transitioning to next song
if (reason == Player.MEDIA_ITEM_TRANSITION_REASON_AUTO || reason == Player.MEDIA_ITEM_TRANSITION_REASON_SEEK) {
if (manualQueueCount > 0) manualQueueCount--
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

lastPlaybackSpeed = -1.0f // force update song

setupLoudnessEnhancer()
Expand Down
Loading