perf(playback): size the video cache adaptively instead of fixed 1 GB
With multi-rendition HLS renditions in the tens-to-low-hundreds of MB each, the previous 1 GB fixed budget held only ~10-20 videos and evicted everything not currently on screen. A Nostr timeline is append-only so LRU degenerates to FIFO here, which is actually fine — the bottleneck was the budget, not the policy. Mirror the image cache pattern: target 20% of currently-available disk, clamped between a 256 MB floor (low-storage devices) and a 4 GB cap. That typically lets the evictor keep an order of magnitude more videos without notably affecting disk pressure on modern devices. https://claude.ai/code/session_01W8yGieuEyMKeKY9vU9zjKH
This commit is contained in:
+34
-4
@@ -22,6 +22,7 @@ package com.vitorpamplona.amethyst.service.playback.diskCache
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.os.StatFs
|
||||
import androidx.media3.database.StandaloneDatabaseProvider
|
||||
import androidx.media3.datasource.DataSource
|
||||
import androidx.media3.datasource.cache.CacheDataSource
|
||||
@@ -31,9 +32,14 @@ import java.io.File
|
||||
|
||||
@SuppressLint("UnsafeOptInUsageError")
|
||||
class VideoCache {
|
||||
var exoPlayerCacheSize: Long = 1000 * 1024 * 1024 // 1GB
|
||||
|
||||
var leastRecentlyUsedCacheEvictor = LeastRecentlyUsedCacheEvictor(exoPlayerCacheSize)
|
||||
companion object {
|
||||
// Target fraction of currently-available disk space.
|
||||
private const val CACHE_SIZE_PERCENT = 0.20
|
||||
// Hard cap so we never consume more than this even on large devices.
|
||||
private const val CACHE_SIZE_MAX_BYTES = 4L * 1024 * 1024 * 1024 // 4 GB
|
||||
// Floor so the cache is still useful on low-storage devices.
|
||||
private const val CACHE_SIZE_MIN_BYTES = 256L * 1024 * 1024 // 256 MB
|
||||
}
|
||||
|
||||
lateinit var exoDatabaseProvider: StandaloneDatabaseProvider
|
||||
lateinit var simpleCache: SimpleCache
|
||||
@@ -46,14 +52,38 @@ class VideoCache {
|
||||
) {
|
||||
exoDatabaseProvider = StandaloneDatabaseProvider(context)
|
||||
|
||||
val cacheSize = calculateCacheSize(cachePath)
|
||||
|
||||
simpleCache =
|
||||
SimpleCache(
|
||||
cachePath,
|
||||
leastRecentlyUsedCacheEvictor,
|
||||
LeastRecentlyUsedCacheEvictor(cacheSize),
|
||||
exoDatabaseProvider,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Adaptive cache sizing: target [CACHE_SIZE_PERCENT] of currently-available
|
||||
* disk, clamped between [CACHE_SIZE_MIN_BYTES] and [CACHE_SIZE_MAX_BYTES].
|
||||
*
|
||||
* A Nostr timeline is append-only — users rarely rewatch older videos — so
|
||||
* LRU approximates FIFO here. That's actually fine as long as the budget is
|
||||
* large enough to cover a useful scroll window. With multi-rendition HLS
|
||||
* renditions at ~20-100 MB each, 1 GB held only a handful of videos and
|
||||
* evicted anything not visible on screen. 4 GB keeps roughly an order of
|
||||
* magnitude more without meaningfully impacting disk pressure on modern
|
||||
* devices (Android can reclaim cache dirs when storage gets tight).
|
||||
*/
|
||||
private fun calculateCacheSize(cachePath: File): Long {
|
||||
cachePath.mkdirs()
|
||||
val availableBytes =
|
||||
runCatching {
|
||||
StatFs(cachePath.absolutePath).availableBytes
|
||||
}.getOrDefault(0L)
|
||||
val target = (availableBytes * CACHE_SIZE_PERCENT).toLong()
|
||||
return target.coerceIn(CACHE_SIZE_MIN_BYTES, CACHE_SIZE_MAX_BYTES)
|
||||
}
|
||||
|
||||
// This method should be called when proxy setting changes.
|
||||
fun renewCacheFactory(dataSourceFactory: DataSource.Factory) {
|
||||
cacheDataSourceFactory =
|
||||
|
||||
Reference in New Issue
Block a user