Skip to content

perf(home): load TMDB artwork at the size each slot draws - #761

Open
ReichiMD wants to merge 1 commit into
ProdigyV21:mainfrom
ReichiMD:claude/hopeful-ramanujan-5roog1
Open

ReichiMD wants to merge 1 commit into
ProdigyV21:mainfrom
ReichiMD:claude/hopeful-ramanujan-5roog1

Conversation

@ReichiMD

Copy link
Copy Markdown
Contributor

Summary

Home cards requested every TMDB backdrop as original (1.0–2.3 MB each, measured) although a card is only ~420–550 px wide. Each load site now asks for the smallest official TMDB width that covers what it draws.

Measured on a phone, cold start (cache cleared), 3 runs each, before/after builds with temporary logcat lines on test branches only:

before after
first row on screen 3.6 s 3.8 s (unchanged)
images of the first screen 17.4 MB 10.1 MB (−42 %)
image responses done, counted from the first row 2.3 s 1.1 s
scrolling once to the bottom of Home 17.1 MB 1.7 MB (10× less)

Times are when the response headers arrived, so the real gain for large bodies is rather bigger. Checked by eye on a phone and a TV: cards, the hero (portrait and landscape) and addon rows with non-TMDB posters look as before; on the TV everything on the first screen is there after ~4 s.

Changes

  • New TmdbImageSizing.forSlot(url, widthPx, heightPx, kind): rewrites only image.tmdb.org/t/p/<wN|original>/…, never to a larger size than the stored URL; other hosts, h632, w…_and_h… stay untouched. Stored URLs, the data model, cloud state and the Home cache are unchanged – only the load sites pick the size.
  • Rule: smallest official width ≥ the slot (a crop in a tall slot needs height × aspect), original only above 1280 px. Backdrops/stills w300/w780/w1280, posters w92…w780. The idea comes from Moviebase's TmdbImageSize (MoviebaseApp/tmdb-kotlin, Apache 2.0) – no code copied.
  • Sized: MediaCard (so Search, Watchlist, View all and Collections benefit too), the featured trailer card (sized from its 380 dp end width, so the spring never switches files), the TV hero, the phone hero carousel, the details backdrop (TV + phone) and the Home backdrop preload.
  • The preload now uses the row's card width (TV 210 dp, phone 200 dp – shared constants) and Compose's rounding, so preload and card hit the same URL and memory key (test over 9 densities). Without that, 1.5x phones would have downloaded two different files per card.
  • A portrait card without a poster (image falls back to the backdrop file) is sized for the 16:9 image it shows.
  • The focused hero logo moves from BACKGROUND to DEFERRED once startup has settled, so it no longer waits behind the single-slot card-logo fan-out; during startup it stays BACKGROUND, so the initial rows keep their two DEFERRED slots ("Initial rows must not wait for speculative artwork").

Trade-offs

  • Card and hero no longer share one file. On a 1080p TV the hero is 1920 px wide, so it stays original, and a newly focused title downloads its hero image instead of reusing the card's. The TV details backdrop is sized exactly like the hero, so hero and details share one file again.
  • Collection tiles and IPTV channel logos keep their own artwork rules.
  • The image disk cache is 96 MB on phones (128 MB on TV): ~60 originals vs ~1200 w780 cards, so far more covers survive between sessions.

Not in this PR (happy to follow up)

  • After this change, 7–9 MB of the remaining ~10 MB on a phone's first screen are the portrait hero carousel's original backdrops (a 3:4 card showing a 16:9 image needs ~1760 px), and the same file is sometimes downloaded twice at the same time. That was the same before; a poster in the portrait hero or coalescing the duplicate request would be the next big step – your call on the look.
  • Card-image preview in the TV hero while its full image loads (placeholderMemoryCacheKey), which would hide the hero download mentioned above.
  • getLogoUrl does not coalesce in-flight requests: the hero logo and a card logo for the same title can now run at the same time.
  • The logo preload still rounds with toInt() while the card uses roundToPx() (memory-cache miss on 1.33x TVs).

Testing

  • TmdbImageSizingTest (12 tests); full testSideloadDebugUnitTest: 1830 tests, 0 failures; detekt: no new findings.
  • Staging (R8) builds before/after on a phone with logcat, see the table above; visual check on phone and TV.

created by Claude (Anthropic) on behalf of @ReichiMD

Cards, the hero (TV and phone), the featured trailer card, the Home
backdrop preload and the details backdrop rewrite image.tmdb.org URLs to
the smallest official TMDB width that covers the slot instead of
'original' (1-2 MB per card). Stored URLs stay unchanged, other hosts
are untouched, and a URL is never rewritten to a larger size. A portrait
card without a poster is sized for the backdrop it shows.

The preload now uses the row's card width (TV 210 dp, phone 200 dp) and
Compose's rounding, so preload and card share one cache entry. The TV
details backdrop is sized like the Home hero, so both share one file.

Once startup has settled, the focused item's hero logo moves from
BACKGROUND to DEFERRED so it no longer waits behind the single-slot
card-logo fan-out; during startup the initial rows keep DEFERRED.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qy4WX7LN7PngMAhsMQCJC8
@github-actions github-actions Bot added the area: android Changes to the Android app or Gradle build label Sep 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: android Changes to the Android app or Gradle build

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants