diff --git a/app/src/main/kotlin/com/subtracks/data/repo/QueueRepository.kt b/app/src/main/kotlin/com/subtracks/data/repo/QueueRepository.kt index c112654a..fe625a48 100644 --- a/app/src/main/kotlin/com/subtracks/data/repo/QueueRepository.kt +++ b/app/src/main/kotlin/com/subtracks/data/repo/QueueRepository.kt @@ -10,6 +10,7 @@ import com.subtracks.data.model.QueueEntry import com.subtracks.data.model.QueueKind import com.subtracks.data.model.ShuffleOrder import com.subtracks.data.model.SongListItem +import java.util.concurrent.atomic.AtomicLong const val QUEUE_CHUNK = 60 @@ -62,8 +63,7 @@ class QueueRepository( @Volatile private var cachedShuffleOrder: LongArray? = null - @Volatile - private var queueVersion = 0L + private val queueVersion = AtomicLong() @Volatile private var cachedFlatIds: List? = null @@ -98,6 +98,7 @@ class QueueRepository( } suspend fun snapshot(): QueueSnapshot { + val version = queueVersion.get() val loaded = db.useReaderConnection { transactor -> transactor.deferredTransaction { @@ -110,7 +111,7 @@ class QueueRepository( } val (entries, order, shuffled) = loaded val valid = if (shuffled && order == null) null else order - return QueueSnapshot(entries, valid, queueVersion) + return QueueSnapshot(entries, valid, version) } suspend fun modes(): QueueModes { @@ -138,7 +139,7 @@ class QueueRepository( fun invalidateLibraryCache() { cachedFlatIds = null - queueVersion++ + queueVersion.incrementAndGet() } suspend fun setRepeat(mode: Int) = dao.setCursor(cursorRow().copy(repeatMode = mode)) @@ -173,6 +174,15 @@ class QueueRepository( return rows(entry, entry.offset + offset, 1).firstOrNull() } + suspend fun flatIndexOf( + snapshot: QueueSnapshot, + songId: String, + ): Long? { + if (!snapshot.shuffled) return null + val index = flatIds(snapshot).indexOf(songId) + return if (index >= 0) index.toLong() else null + } + suspend fun range( snapshot: QueueSnapshot, first: Long, @@ -271,7 +281,7 @@ class QueueRepository( dao.insert(entries.mapIndexed { index, entry -> entry.copy(id = 0, position = index.toLong()) }) } } - queueVersion++ + queueVersion.incrementAndGet() cachedFlatIds = null } diff --git a/app/src/main/kotlin/com/subtracks/playback/PlaybackController.kt b/app/src/main/kotlin/com/subtracks/playback/PlaybackController.kt index 956ab215..4b4663e8 100644 --- a/app/src/main/kotlin/com/subtracks/playback/PlaybackController.kt +++ b/app/src/main/kotlin/com/subtracks/playback/PlaybackController.kt @@ -398,8 +398,10 @@ class PlaybackController( fun previous() { val player = player ?: return if (player.currentPositionMs > RESTART_THRESHOLD_MS) { - player.seekTo(0) - refresh() + scope.launch { + startLock.withLock { player.seekTo(0) } + refresh() + } return } val snapshot = snapshot ?: return @@ -657,9 +659,20 @@ class PlaybackController( private suspend fun shiftWindowLocked(center: Long) { val player = player ?: return + val previous = snapshot val snapshot = readSnapshot() this.snapshot = snapshot if (snapshot.size == 0L) return + if (previous != null && previous.shuffled != snapshot.shuffled) { + val currentId = player.currentItem?.id + val position = + (currentId?.let { queueRepository.flatIndexOf(snapshot, it) } ?: center) + .coerceIn(0, snapshot.size - 1) + queueRepository.setCursor(position) + rebuildWindow(queueRepository.window(snapshot, position, QUEUE_WINDOW_RADIUS), position) + refresh(position) + return + } val target = center.coerceIn(0, snapshot.size - 1) val desiredStart = (target - QUEUE_WINDOW_RADIUS).coerceAtLeast(0) val desiredEnd = (target + QUEUE_WINDOW_RADIUS).coerceAtMost(snapshot.size - 1) diff --git a/app/src/test/kotlin/com/subtracks/playback/PlaybackControllerTest.kt b/app/src/test/kotlin/com/subtracks/playback/PlaybackControllerTest.kt index d2bda52c..4346b3c0 100644 --- a/app/src/test/kotlin/com/subtracks/playback/PlaybackControllerTest.kt +++ b/app/src/test/kotlin/com/subtracks/playback/PlaybackControllerTest.kt @@ -680,7 +680,7 @@ class PlaybackControllerTest { handle.positionMs = 5_000L controller.previous() - assertTrue(handle.operations.contains("seekTo(0)")) + await { handle.operations.contains("seekTo(0)") } assertEquals( "s2", controller.state.value.item