Skip to content

feat: instant home screen loading via two-level cache and lazy hero banner - #3142

Open
iAm-an-iA wants to merge 8 commits into
recloudstream:masterfrom
iAm-an-iA:feat/home-provider-cache
Open

feat: instant home screen loading via two-level cache and lazy hero banner#3142
iAm-an-iA wants to merge 8 commits into
recloudstream:masterfrom
iAm-an-iA:feat/home-provider-cache

Conversation

@iAm-an-iA

@iAm-an-iA iAm-an-iA commented Aug 27, 2026

Copy link
Copy Markdown

Summary

Implemented an intelligent Two-Level Provider & Homepage Cache Architecture (In-Memory ConcurrentHashMap + Persistent Disk SharedPreferences JSON serialization) combined with Lazy Hero Banner Loading to accelerate home screen rendering, eliminate cold-start blank screens, and allow movie lists to populate instantly via a Stale-While-Revalidate pattern.


Code Changes & Architecture

1. Two-Level Caching Engine (HomeCache.kt)

  • L1 In-Memory Cache: Thread-safe ConcurrentHashMap for sub-millisecond retrieval of active feeds during session navigation.
  • L2 Persistent Disk Cache: JSON-serialized disk storage surviving process kills and cold app restarts.
  • Data Models: Added CachedHomeData, CachedHomePageResponse, and CachedSearchResponse supporting serialization across all TvTypes (Movie, TvSeries, Anime, Live, Torrent).

2. ViewModel & Lazy Hero Banner Integration (HomeViewModel.kt)

  • Instant Feed Hydration: Modified load() to immediately dispatch cached data via HomeCache.getHomeCache(api.name), rendering movie lists directly from local disk cache.
  • Decoupled Lazy Hero Banner: Moved hero banner trailer and metadata fetching into an asynchronous background coroutine (addJob = ioSafe { ... }), preventing network calls from blocking initial feed appearance.
  • Background Revalidation: Saves fresh network data to L1 & L2 cache on getMainPage() success without causing UI flicker or focus resets.

3. API Load Response Caching (APIRepository.kt)

  • Extended API load response caching to respect user-configured TTL (cacheTimeSeconds) and enabled/disabled state.

4. User Preferences & Storage Management (SettingsProviders.kt & DataStoreHelper.kt)

  • Cache Duration Setting: Added user-configurable TTL (Off / Always Reload, 10m, 30m, 1h, 3h, 6h, 1d, 3d, 1w) persisted in DataStoreHelper.cacheTimeMinutes.
  • Clear Provider Cache Action: Added one-tap cache invalidation across home and provider caches with dynamic disk usage calculation (formatShortFileSize).

5. Automated Unit Tests (HomeCacheTest.kt)

  • Comprehensive test suite covering JSON serialization/deserialization, TTL expiration, memory eviction, multi-threaded access, and cache clearance.

Performance Impact (Xiaomi TV - Real Data)

Test Environment: Xiaomi TV (MiTV-MZTU0 / Android TV 14, Quad-Core Cortex-A55, Mali GPU, 2 GB RAM, 1080p@60Hz UI)
Target Provider: StreamPlay (20 Home Sections / Movie Lists)
Benchmark Flow: Cold Start ➔ Time to First Movie List (TTFML) ➔ Home Feed Render (via adb logcat, dumpsys gfxinfo, and dumpsys meminfo)

Metric Without Cache (master) With Two-Level Cache + Lazy Banner (feat/home-provider-cache) Improvement
Time to First Movie List (TTFML) ~70,000 ms (70s) (Full network & JSON parsing) 2,348 ms (2.3s) (Direct from L2 Disk Cache) -96.6% (67.6s faster)
Hero Banner Hydration Blocking main flow 8,050 ms (Lazy in Background) Instant list, zero UI blocking
Startup Screen State Blank / Loading spinner 20 Movie sections rendered immediately Instant 10-foot interaction
Total Memory PSS 175.24 MB (Total RSS: 281.01 MB) 124.37 MB (Total RSS: 234.71 MB) -29.0% (~50.9 MB saved)
Java Heap Allocated 50.37 MB 34.06 MB -32.4% Lower heap allocation
D-pad Frame Pacing (P50) 31 ms 27 ms Smoother scrolling on TV CPU

Validation

  • ./gradlew testPrereleaseDebugUnitTest (All HomeCacheTest cases passed)
  • Verified on real Xiaomi TV device (MiTV-MZTU0 / Android TV 14) via ADB instrumentation

AI Disclosure

  • AI Usage: ~45% of the cache serialization boilerplate, ViewModel wiring, and unit test cases were generated/assisted by AI.
  • Verification: The code was verified via automated unit tests and profiled on physical Android TV hardware.

Screenshots

Before (Without Cache / Eager Network Load) After (Two-Level Cache + Lazy Hero Banner)
record record1

Provider Cache Settings & Duration Selector

screen

Phone

telegram-cloud-document-5-6093647703474970867

@iAm-an-iA
iAm-an-iA force-pushed the feat/home-provider-cache branch from d6b3f66 to ef98bba Compare August 27, 2026 07:41

@fire-light42 fire-light42 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, and great performance testing but this pull request is way too bloated, with cache logic interspersed with normal code. Surely you can move the cache logic to APIRepository to reduce the complexity and boilerplate by a lot.

This whole pull request (excluding tests) ought to be achievable in ~200 lines, not 700.

Comment thread app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeCache.kt Outdated
Comment thread app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeCache.kt Outdated
Comment thread app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt Outdated
Comment thread app/src/main/res/values/strings.xml Outdated
@iAm-an-iA

Copy link
Copy Markdown
Author

@fire-light42 My bad for the messy diff before — had some messy merge/cherry-pick conflicts from my dev branch that pulled in unrelated stuff. It's clean and minimal now.

@iAm-an-iA
iAm-an-iA marked this pull request as draft August 27, 2026 15:37
@fire-light42

fire-light42 commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

This looks great now! I will review it soon, unless it's not ready for review?

@iAm-an-iA
iAm-an-iA force-pushed the feat/home-provider-cache branch from 71a2720 to fbe5b34 Compare August 27, 2026 16:45
@iAm-an-iA
iAm-an-iA marked this pull request as ready for review August 27, 2026 17:39
@iAm-an-iA

Copy link
Copy Markdown
Author

@fire-light42 Sorry for the delay, had a small issue with the hero banner when switching providers earlier.
It's fixed now and ready for review!

@iAm-an-iA iAm-an-iA changed the title Instant feed rendering via two-level cache and lazy hero banner feat: instant home screen loading via two-level cache and lazy hero banner Aug 28, 2026
@iAm-an-iA
iAm-an-iA requested a review from fire-light42 August 28, 2026 11:27

@fire-light42 fire-light42 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking a lot better, just needs some changes to account for provider specific cache needs.

<Preference
android:icon="@drawable/ic_baseline_clock_24"
android:key="@string/cache_time_key"
android:title="@string/cache_time_settings" />

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cache time is a tricky problem.

Some providers generate ephemeral homepage data, for instance temporary page URLs with a lifetime of an hour or so, after that the items cannot be opened. This is currently avoided by always fetching new home pages, but creating a caching system would screw over all developers with those types of extensions. Users will most certainly not know to change this setting to fix the issue.

Therefore the cache time needs to be adjustable by extension developers themselves, something like a field in MainAPI: open var maxHomepageCacheTime: Long? = null (ms). Null will correspond to some app-wide default (we can test to find a good default and adjust it after implementation).

android:title="@string/cache_time_settings" />

<Preference
android:icon="@drawable/ic_baseline_delete_outline_24"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer if you move this cache cleaning logic to the cache clean in player settings to remove clutter in settings.

}

suspend fun getMainPage(page: Int, nameIndex: Int? = null): Resource<List<HomePageResponse?>> {
suspend fun getMainPage(page: Int, nameIndex: Int? = null, forceReload: Boolean = false): Resource<List<HomePageResponse?>> {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This system can be further streamlined by returning the cached response, but simultaneously fetching the home page in the background to update the cache. This lets users get an up to date cache without sacrificing load times.

However, feature is not necessary in this pull request.


if (isCacheEnabled && !forceReload) {
val cached = homeCache.withLock {
var found: List<HomePageResponse?>? = null

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recommend you take advantage of Kotlin functions to make your code simpler, this could be:

val found = homeCache.firstOrNull { item -> 
    item.hash == lookingForHash && unixTime - item.unixTime < cacheTtl
}?.response

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants