mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-11 07:29:14 +01:00
bound artwork seeds and size the now-playing prefetch
This commit is contained in:
@@ -12,4 +12,16 @@ interface ArtworkSeedDao {
|
||||
|
||||
@Upsert
|
||||
suspend fun upsert(seed: ArtworkSeed)
|
||||
|
||||
@Query("UPDATE artwork_seeds SET lastUsed = :lastUsed WHERE cacheKey = :cacheKey")
|
||||
suspend fun touch(
|
||||
cacheKey: String,
|
||||
lastUsed: Long,
|
||||
)
|
||||
|
||||
@Query(
|
||||
"DELETE FROM artwork_seeds WHERE cacheKey NOT IN " +
|
||||
"(SELECT cacheKey FROM artwork_seeds ORDER BY lastUsed DESC LIMIT :keep)",
|
||||
)
|
||||
suspend fun prune(keep: Int)
|
||||
}
|
||||
|
||||
@@ -112,6 +112,21 @@ val MIGRATION_11_12 =
|
||||
}
|
||||
}
|
||||
|
||||
val MIGRATION_12_13 =
|
||||
object : Migration(12, 13) {
|
||||
override suspend fun migrate(connection: SQLiteConnection) {
|
||||
connection.execSQL("DROP TABLE IF EXISTS `artwork_seeds`")
|
||||
connection.execSQL(
|
||||
"CREATE TABLE IF NOT EXISTS `artwork_seeds` (`cacheKey` TEXT NOT NULL, " +
|
||||
"`primary` INTEGER NOT NULL, `secondary` INTEGER, `nameBusy` INTEGER, " +
|
||||
"`sourceId` INTEGER NOT NULL, `lastUsed` INTEGER NOT NULL DEFAULT 0, " +
|
||||
"PRIMARY KEY(`cacheKey`), " +
|
||||
"FOREIGN KEY(`sourceId`) REFERENCES `sources`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
)
|
||||
connection.execSQL("CREATE INDEX IF NOT EXISTS `index_artwork_seeds_sourceId` ON `artwork_seeds` (`sourceId`)")
|
||||
}
|
||||
}
|
||||
|
||||
val MIGRATIONS: Array<Migration> =
|
||||
arrayOf(
|
||||
MIGRATION_1_2,
|
||||
@@ -125,4 +140,5 @@ val MIGRATIONS: Array<Migration> =
|
||||
MIGRATION_9_10,
|
||||
MIGRATION_10_11,
|
||||
MIGRATION_11_12,
|
||||
MIGRATION_12_13,
|
||||
)
|
||||
|
||||
@@ -32,7 +32,7 @@ import com.subtracks.data.model.SubsonicSource
|
||||
ShuffleOrder::class,
|
||||
ArtworkSeed::class,
|
||||
],
|
||||
version = 12,
|
||||
version = 13,
|
||||
exportSchema = true,
|
||||
)
|
||||
@ColumnTypeConverters(QueueKindConverter::class)
|
||||
|
||||
@@ -237,12 +237,25 @@ data class ShuffleOrder(
|
||||
val flatPosition: Long,
|
||||
)
|
||||
|
||||
@Entity(tableName = "artwork_seeds")
|
||||
@Entity(
|
||||
tableName = "artwork_seeds",
|
||||
foreignKeys = [
|
||||
ForeignKey(
|
||||
entity = Source::class,
|
||||
parentColumns = ["id"],
|
||||
childColumns = ["sourceId"],
|
||||
onDelete = ForeignKey.CASCADE,
|
||||
),
|
||||
],
|
||||
indices = [Index("sourceId")],
|
||||
)
|
||||
data class ArtworkSeed(
|
||||
@PrimaryKey val cacheKey: String,
|
||||
val primary: Int,
|
||||
val secondary: Int?,
|
||||
val nameBusy: Boolean? = null,
|
||||
val sourceId: Long = 0,
|
||||
@ColumnInfo(defaultValue = "0") val lastUsed: Long = 0,
|
||||
)
|
||||
|
||||
class QueueKindConverter {
|
||||
|
||||
@@ -3,6 +3,8 @@ package com.subtracks.data.repo
|
||||
import com.subtracks.data.db.SubtracksDatabase
|
||||
import com.subtracks.data.model.ArtworkSeed
|
||||
|
||||
private const val MAX_STORED_SEEDS = 1024
|
||||
|
||||
interface ArtworkSeedStore {
|
||||
suspend fun seed(cacheKey: String): ArtworkSeed?
|
||||
|
||||
@@ -11,10 +13,19 @@ interface ArtworkSeedStore {
|
||||
|
||||
class ArtworkSeedRepository(
|
||||
private val db: SubtracksDatabase,
|
||||
private val maxStoredSeeds: Int = MAX_STORED_SEEDS,
|
||||
) : ArtworkSeedStore {
|
||||
override suspend fun seed(cacheKey: String): ArtworkSeed? = db.artworkSeedDao().seed(cacheKey)
|
||||
override suspend fun seed(cacheKey: String): ArtworkSeed? {
|
||||
val dao = db.artworkSeedDao()
|
||||
val row = dao.seed(cacheKey) ?: return null
|
||||
dao.touch(cacheKey, System.currentTimeMillis())
|
||||
return row
|
||||
}
|
||||
|
||||
override suspend fun save(seed: ArtworkSeed) {
|
||||
db.artworkSeedDao().upsert(seed)
|
||||
val sourceId = seed.cacheKey.substringBefore(':').toLongOrNull() ?: return
|
||||
val dao = db.artworkSeedDao()
|
||||
dao.upsert(seed.copy(sourceId = sourceId, lastUsed = System.currentTimeMillis()))
|
||||
dao.prune(maxStoredSeeds)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -580,11 +580,10 @@ class PlaybackController(
|
||||
thumbnail: Boolean = false,
|
||||
): CoverArtRef? = sourceRepository.coverArt(item?.coverArtId, thumbnail)
|
||||
|
||||
suspend fun upcomingCoverArt(thumbnail: Boolean = false): CoverArtRef? {
|
||||
suspend fun upcomingItem(): QueueItem? {
|
||||
val snap = snapshot ?: return null
|
||||
val position = currentPosition() ?: return null
|
||||
val next = queueRepository.itemAt(snap, position + 1) ?: return null
|
||||
return sourceRepository.coverArt(next.toQueueItem().coverArtId, thumbnail)
|
||||
return queueRepository.itemAt(snap, position + 1)?.toQueueItem()
|
||||
}
|
||||
|
||||
private fun play(
|
||||
|
||||
@@ -98,8 +98,10 @@ fun NowPlayingRoute(
|
||||
}
|
||||
LaunchedEffect(state.item?.id, state.hasNext) {
|
||||
if (!state.hasNext) return@LaunchedEffect
|
||||
controller.upcomingCoverArt(thumbnail = true)?.let { ArtworkSeedCache.prefetch(context, it) }
|
||||
controller.upcomingCoverArt()?.let { prefetchImage(context, it) }
|
||||
controller.upcomingItem()?.let { next ->
|
||||
controller.coverArt(next, thumbnail = true)?.let { ArtworkSeedCache.prefetch(context, it) }
|
||||
controller.coverArt(next)?.let { prefetchImage(context, it) }
|
||||
}
|
||||
}
|
||||
NowPlayingScreen(
|
||||
state = state,
|
||||
@@ -120,18 +122,26 @@ fun NowPlayingRoute(
|
||||
)
|
||||
}
|
||||
|
||||
private fun prefetchImage(
|
||||
internal fun prefetchImage(
|
||||
context: Context,
|
||||
ref: CoverArtRef,
|
||||
) {
|
||||
SingletonImageLoader.get(context).enqueue(
|
||||
ImageRequest
|
||||
.Builder(context)
|
||||
.data(ref.url)
|
||||
.memoryCacheKey(ref.cacheKey)
|
||||
.diskCacheKey(ref.cacheKey)
|
||||
.build(),
|
||||
)
|
||||
SingletonImageLoader.get(context).enqueue(prefetchImageRequest(context, ref))
|
||||
}
|
||||
|
||||
internal fun prefetchImageRequest(
|
||||
context: Context,
|
||||
ref: CoverArtRef,
|
||||
): ImageRequest {
|
||||
val metrics = context.resources.displayMetrics
|
||||
val maxDimension = maxOf(metrics.widthPixels, metrics.heightPixels)
|
||||
return ImageRequest
|
||||
.Builder(context)
|
||||
.data(ref.url)
|
||||
.memoryCacheKey(ref.cacheKey)
|
||||
.diskCacheKey(ref.cacheKey)
|
||||
.size(maxDimension)
|
||||
.build()
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
|
||||
Reference in New Issue
Block a user