resolve shuffled queue windows in one batched query

This commit is contained in:
austinried
2026-10-04 00:29:55 +00:00
parent 2b59635402
commit 4cad4bc481
2 changed files with 95 additions and 4 deletions
@@ -101,4 +101,43 @@ interface QueueDao {
offset: Long,
limit: Int,
): List<SongListItem>
@Query(
"SELECT songs.id FROM songs WHERE songs.sourceId = :sourceId AND songs.albumId = :albumId " +
"ORDER BY songs.disc, songs.track, songs.id",
)
suspend fun albumSongIds(
sourceId: Long,
albumId: String,
): List<String>
@Query(
"SELECT songs.id FROM songs " +
"LEFT JOIN albums ON albums.sourceId = songs.sourceId AND albums.id = songs.albumId " +
"WHERE songs.sourceId = :sourceId " +
"ORDER BY albums.albumArtist COLLATE NOCASE, songs.album COLLATE NOCASE, songs.disc, songs.track, " +
"songs.title COLLATE NOCASE, songs.id",
)
suspend fun songIds(sourceId: Long): List<String>
@Query(
"SELECT songs.id FROM playlist_songs " +
"JOIN songs ON songs.sourceId = playlist_songs.sourceId AND songs.id = playlist_songs.songId " +
"WHERE playlist_songs.sourceId = :sourceId AND playlist_songs.playlistId = :playlistId " +
"ORDER BY playlist_songs.position",
)
suspend fun playlistSongIds(
sourceId: Long,
playlistId: String,
): List<String>
@Query(
"SELECT songs.*, albums.coverArt AS coverArt FROM songs " +
"LEFT JOIN albums ON albums.sourceId = songs.sourceId AND albums.id = songs.albumId " +
"WHERE songs.sourceId = :sourceId AND songs.id IN (:ids)",
)
suspend fun songsByIds(
sourceId: Long,
ids: List<String>,
): List<SongListItem>
}
@@ -61,6 +61,15 @@ class QueueRepository(
@Volatile
private var cachedShuffleOrder: LongArray? = null
@Volatile
private var queueVersion = 0L
@Volatile
private var cachedFlatIds: List<String>? = null
@Volatile
private var cachedFlatVersion = -1L
fun playlistEntry(
sourceId: Long,
playlistId: String,
@@ -139,6 +148,16 @@ class QueueRepository(
snapshot: QueueSnapshot,
position: Long,
): SongListItem? {
if (snapshot.shuffled) {
val sourceId =
snapshot.entries
.firstOrNull()
?.entry
?.sourceId ?: return null
val flat = snapshot.flatPosition(position) ?: return null
val id = flatIds(snapshot).getOrNull(flat.toInt()) ?: return null
return dao.songsByIds(sourceId, listOf(id)).firstOrNull()
}
val (entry, offset) = snapshot.locate(position) ?: return null
return rows(entry, entry.offset + offset, 1).firstOrNull()
}
@@ -149,12 +168,21 @@ class QueueRepository(
last: Long,
): List<QueueWindowItem> {
if (snapshot.shuffled) {
val items = mutableListOf<QueueWindowItem>()
val sourceId =
snapshot.entries
.firstOrNull()
?.entry
?.sourceId ?: return emptyList()
val ids = flatIds(snapshot)
val requested = mutableListOf<Pair<Long, String>>()
for (position in first..last) {
val (entry, offset) = snapshot.locate(position) ?: continue
rows(entry, entry.offset + offset, 1).firstOrNull()?.let { items += QueueWindowItem(position, it) }
val flat = snapshot.flatPosition(position) ?: continue
val id = ids.getOrNull(flat.toInt()) ?: continue
requested += position to id
}
return items
if (requested.isEmpty()) return emptyList()
val byId = dao.songsByIds(sourceId, requested.map { it.second }.distinct()).associateBy { it.song.id }
return requested.mapNotNull { (position, id) -> byId[id]?.let { QueueWindowItem(position, it) } }
}
val items = mutableListOf<QueueWindowItem>()
var start = 0L
@@ -232,6 +260,30 @@ class QueueRepository(
dao.insert(entries.mapIndexed { index, entry -> entry.copy(id = 0, position = index.toLong()) })
}
}
queueVersion++
cachedFlatIds = null
}
private suspend fun flatIds(snapshot: QueueSnapshot): List<String> {
val cached = cachedFlatIds
if (cached != null && cachedFlatVersion == queueVersion) return cached
val ids = ArrayList<String>(snapshot.size.toInt())
for (resolved in snapshot.entries) {
val entry = resolved.entry
val entryIds =
when (entry.kind) {
QueueKind.Song -> listOf(entry.refId)
QueueKind.Album -> dao.albumSongIds(entry.sourceId, entry.refId)
QueueKind.Songs -> dao.songIds(entry.sourceId)
QueueKind.Playlist -> dao.playlistSongIds(entry.sourceId, entry.refId)
}
val from = entry.offset.toInt().coerceIn(0, entryIds.size)
val to = (entry.offset + resolved.length).toInt().coerceIn(from, entryIds.size)
ids.addAll(entryIds.subList(from, to))
}
cachedFlatIds = ids
cachedFlatVersion = queueVersion
return ids
}
private suspend fun rows(