Android: album art pops in while scrolling — preload ahead of the viewport #65
Closed
opened 2026-08-18 06:00:07 +00:00 by logan
·
2 comments
No Branch/Tag Specified
main
fix/146-stub-etxtbsy
fix/175-wizard-follows-the-library
fix/231-setter-rollback
fix/197-duplicate-column-label
docs/225-fixtures-wav-tags
docs/220-skill-check-scope
test/217-fixture-names-in-queue-selection
fix/216-riff-parse-allocation
fix/170-queue-header-action-names
fix/210-nav-sheet-scroll-affordance
docs/50-readme-landing-page
feat/65-art-prefetch-ahead
feat/71-more-as-a-bottom-sheet
feat/54-native-touch-feel
feat/67-entity-links-into-menus
test/196-visual-tier-gates
fix/138-ui-test-storage-leak
fix/104-wav-tags-read
fix/207-sheet-scroll-affordance
fix/204-ui-visual-update-filter
pi-agent-backlog-automation
63-touch-model-phase-2
63-android-touch-model
186-touch-targets-settings
186-touch-targets-page-header
187-seek-bar-hit-area
189-190-explore-correctness
135-android-underrun-instrumentation
51-android-small-screens
fix/171-phone-queue-scrim
fix/137-touch-only-affordances
fix/154-nested-css-check
feat/58-mini-player-progress-line
fix/66-album-page-scrolls-as-one
60-context-menu-action-sheet
64-android-system-volume
59-slim-the-mini-player
55-queue-as-a-screen
feat/57-drop-the-android-top-bar
feat/62-jobs-as-a-notification
fix/53-seek-bar-never-moves
fix/159-android-task-app-id
fix/52-android-activity-recreation-restarts-the-process
fix/150-expand-button-under-the-art
feat/42-inline-volume-and-centred-transport
fix/156-queue-selection-fixture-order
fix/151-fuse-the-scroll-guard-and-the-write
fix/43-queue-panel-selection
fix/143-top-bar-fits-its-window
feat/27-jobs-into-settings
feat/25-configurable-sidebar-tabs
feat/6-global-back-forward
fix/72-active-view-broadcast
fix/69-page-header-action-overflow
fix/quick-wins-batch
fix/118-in-library-clear
fix/61-mini-player-plain-text
fix/68-hover-affordances-pointer
fix/119-dev-headless-port
fix/130-issue-claim-user
fix/131-codegen-check-scope
feat/28-autotag-match-on-album
feat/17-demote-version-selector
feat/38-ownership-visibility
ci/115-manual-release
feat/34-icon-language
feat/7-full-tracklist-toggle
fix/16-tagwriter-totals
fix/unclaim-ca-certs
fix/unclaim-shell
ci/unclaim-on-close
docs/closing-keyword
docs/retire-stale-planning-docs
docs/issue-driven-workflow
integration/small-fixes
fix/small-issue-batch
fix/queue-toggle-state
fix/drag-count-badge
fix/album-card-year
fix/album-tracklist-heading
fix/seek-bar-clock-width
fix/explore-art-scanner-requests
chore/workflow-guardrails
v0.7.0
v0.6.0
v0.5.0
v0.4.0
v0.3.1
v0.3.0
v0.2.3
v0.2.2
v0.2.1
v0.2.0
v0.1.0
v0.0.1
v0.0.0
Labels
Clear labels
Area/Design
Area/Downloads
Area/Explore
Area/Library-UI
Area/Metadata
Area/Packaging
Area/Player
Area/Queue
Area/Settings
Area/Shell-Nav
Compat/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Platform/Android
Platform/Desktop
Breaking change that won't be backward compatible
Something is not working
Documentation changes
Improve existing functionality
New functionality
This is security issue
Issue or pull request related to testing
Priority
Critical
1
The priority is critical
Priority
High
2
The priority is high
Priority
Medium
3
The priority is medium
Priority
Low
4
The priority is low
Reviewed
Confirmed
1
Issue has been confirmed
Reviewed
Duplicate
2
This issue or pull request already exists
Reviewed
Invalid
3
Invalid issue
Reviewed
Won't Fix
3
This issue won't be fixed
Status
Blocked
1
Something is blocking this issue or pull request
Status
Need More Info
2
Feedback is required to reproduce issue or to continue work
Status
Abandoned
3
Somebody has started to work on this but abandoned work
Status
In Progress
Somebody is actively working on this right now
Milestone
No items
No Milestone
Projects
Clear projects
No projects
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: yonlu/yellowjacket#65
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Report
Scrolling through albums, the art pops in. It should be preloaded so scrolling looks smooth.
Findings
loading="lazy" decoding="async"by rule, which is what produces the pop-in at speed.cover-grid'sgetCoverUrl()is the model) — a card drawing the_lgtier into a small box will pop in far more than one drawing_sm.LRUMaps with measured ceilings; any prefetch must go through those caches and respect the caps, or it becomes an unbounded prefetch of the whole library.Direction
Increase the virtualizer's overscan on the grids, make sure the smallest adequate tier is used, and consider
fetchpriority/eager loading for the first screenful. Measure withwindow.__yjCacheStats()before and after so the ceiling is shown to still hold — the caps exist because twenty-four searches retained 20.58MB and were still accelerating.Picking this up on branch
feat/65-art-prefetch-ahead.Taking it as Phase 4's remaining independent item in #73 (#66 and #64
are closed; #63/#67/#71/#54 are merged or in flight). Everything at
Priority/Highis unactionable from here — #212, #203 and #194 end at adevice I do not have, #183 is claimed, #53 sits at
Status/Need More Infobehind an existing branch, and #73 is this list itself.Skipping ahead of two Medium items deliberately, and saying why.
Six PRs from earlier runs are open and unmerged against a
mainthathas not moved, so this run is asked to avoid another branch over the
same files. #200 (dismissible surfaces answer back) is
queue-panel,menu-surfaceandindex.ts— all three already edited by #208/#209/#211/#214 — and #39 (column config from the track list) is
track-list, same problem. #85 opens with "re-measure before building",which needs a real tagged library and MusicBrainz, not a scheduled run.
Direction I intend to take, since the issue offers three.
The tier work is already done:
cover-grid'sgetCoverUrl()picks_sm/_md/_lgfrom the card size and DPR, andartists-viewdoesthe same for its avatars. So that half of the Direction is a check, not
a change.
The overscan half cannot be done as written:
@lit-labs/virtualizer's_overhangis a hard-coded 1000pxprotectedfield onBaseLayoutwith no config surface, so "ask the virtualizer for a larger overscan"
would mean monkey-patching a private. That leaves the thing the
overhang is actually standing in for — the image is not fetched until
the card exists, and 1000px is about two screens on a 439px viewport.
So: prefetch the covers for the entries just past the rendered window,
off the
visibilityChangedevent both grids already listen to,bounded and de-duplicated. Cover URLs are served by
coverart.HandlerwithCache-Control: public, max-age=31536000, immutable(the filenames are content hashes), so a prefetched image isa browser-cache hit when the card is finally drawn and costs nothing on
the second pass. Note that this is not the
LRUMappath the Findingswarn about — that ceiling is Explore's base64 data URLs; library covers
are plain URLs and never enter it — so the bound here is the request
window and the dedup set, and I will say what both measure.
PR #215 —
feat/65-art-prefetch-ahead, CI green (checkande2e,the latter carrying WebKit).
Three notes on how the Direction's three suggestions came out, since
two of them changed shape:
change:
cover-grid.getCoverUrl()andartists-view's avatar bothpick
_sm/_md/_lgfrom the card size and the device pixel ratio,and both images are already
loading="lazy" decoding="async".@lit-labs/virtualizer's_overhangis a hard-coded 1000pxprotectedfield onBaseLayout,read by every layout, with no option on
grid()and no property onthe element — so this would be monkey-patching a private. The
request is issued ahead of the element instead, which is what that
overhang was standing in for.
fetchpriority/eager for the first screenful is not done. Thatscreenful renders on mount and asks for its art immediately, so it is
a different question from the one reported. Saying so rather than
leaving it ambiguous.
Measured on
make dev-headless SEED=bulk(4 988 albums), ten 2 400pxjumps, counting covers in the viewport with
naturalWidth === 0:mainwindow.__yjCacheStats().imagePrefetchafter that run is 497entries, 15 407 chars, cap 512 — the ceiling this issue asks to be
shown still holding. Worth recording that it is a different ceiling
from the one the Findings name: the
LRUMapcaps bound Explore'sbase64 data URLs, whereas a library cover is a plain URL under
Cache-Control: immutable, so the bytes are the browser's cache andwhat is capped here is the record of URLs already asked for.
Not verified on the device: the numbers above are a desktop engine
against the bulk library's 3.7 kB covers, which is why both builds are
clean by 50 ms there.