Add app-private offline caching for cloud music - #68
Conversation
|
| Filename | Overview |
|---|---|
| app/src/main/java/com/lostf1sh/pixelplayeross/data/offline/OfflineMediaRepository.kt | Core offline cache orchestrator: download lifecycle, quota enforcement, and metadata persistence. Minor ordering issue in resumePendingDownloads (enforceRuntimeLimits is fire-and-forget before startForeground). StatFs called on main thread in enforceRuntimeLimits. |
| app/src/main/java/com/lostf1sh/pixelplayeross/data/service/player/PlaybackAudioCache.kt | Well-structured dual-cache abstraction; CacheRoutingDataSource correctly routes by cache-key prefix; isAvailableOffline uses content-length metadata for a reliable offline check. |
| app/src/main/java/com/lostf1sh/pixelplayeross/data/database/OfflineMediaEntities.kt | Room entities, DAO, and FK constraints look correct; tracksExclusiveToCollection uses correlated subqueries instead of IN lists, avoiding the SQLite variable-count limit. |
| app/src/main/java/com/lostf1sh/pixelplayeross/data/database/Migrations.kt | MIGRATION_3_4 DDL matches Room entity annotations; unique nullable index on cache_key is correct for SQLite NULL semantics; FK cascade order is consistent with DAO deletion order. |
| app/src/main/java/com/lostf1sh/pixelplayeross/data/service/player/DualPlayerEngine.kt | Cache-key injection in resolveDataSpec and offline-first routing in resolveMediaItem are correct; shouldUsePlaybackCache guard prevents accidental key overwrite for already-keyed items. |
| app/src/main/java/com/lostf1sh/pixelplayeross/data/offline/OfflineMediaDownloadService.kt | Standard Media3 DownloadService subclass; correctly returns the singleton DownloadManager from the repository; RESTART intent-filter ensures the service can be restarted by the system. |
| app/src/main/java/com/lostf1sh/pixelplayeross/presentation/screens/LibraryCachedTab.kt | Composable cache management tab; uses AlertDialog for removal confirmation; progress indicator only shown for non-COMPLETE items; filter state is local and reset-safe. |
| app/src/main/java/com/lostf1sh/pixelplayeross/data/preferences/UserPreferencesRepository.kt | Adds OFFLINE_CACHE_LIMIT_BYTES preference key, clamped between 1 GB and 100 GB (or 0 for unlimited); migration correctly appends CACHED tab to existing tab order lists. |
| app/src/main/java/com/lostf1sh/pixelplayeross/presentation/screens/SettingsCategoryScreen.kt | Adds offline cache limit slider backed by OFFLINE_CACHE_LIMIT_OPTIONS; index-based mapping is safe given the fixed-length array and coerceIn guard. |
Sequence Diagram
sequenceDiagram
participant Player as ExoPlayer
participant RDS as ResolvingDataSource
participant CRD as CacheRoutingDataSource
participant OC as OfflineCache (filesDir)
participant TC as TransientCache (cacheDir)
participant Net as Network
Player->>RDS: "open(dataSpec, navidrome://... key=pixelplayer:audio:v1:)"
RDS->>RDS: shouldUsePlaybackCache(key) true, resolve URI
RDS->>CRD: open(dataSpec with cache key)
CRD->>CRD: shouldUsePlaybackCache true, select cacheDataSource
CRD->>OC: open / read
alt Fully cached offline
OC-->>CRD: bytes
CRD-->>Player: bytes
else Not in offline cache
OC->>TC: open upstream (cache miss fallback)
alt In transient cache
TC-->>OC: bytes
OC-->>CRD: bytes
else Cache miss
TC->>Net: fetch
Net-->>TC: bytes stored in transient cache
TC-->>OC: bytes
OC-->>CRD: bytes
end
CRD-->>Player: bytes
end
Note over Player,Net: Non-cloud URIs use directDataSource, bypassing both caches
Reviews (3): Last reviewed commit: "Fix large offline collection removal" | Re-trigger Greptile
|
LGTM |
Summary
Why
Issue #47 asks for server music to be available offline. This keeps offline audio inside the PixelPlayer Media3 cache instead of exporting user-visible files, while preserving a small transient cache for ordinary streaming.
Testing
JAVA_HOME=/usr/lib/jvm/java-21-openjdk ./gradlew :app:compileDebugKotlinJAVA_HOME=/usr/lib/jvm/java-21-openjdk ./gradlew :app:testDebugUnitTestJAVA_HOME=/usr/lib/jvm/java-21-openjdk ./gradlew :app:lintDebugJAVA_HOME=/usr/lib/jvm/java-21-openjdk ./gradlew :app:assembleDebug -Ppixelplayer.enableAbiSplits=falseScreenshots
Not included; no device or emulator runtime pass was performed for this change.
Closes #47