Skip to content

fix: feed first-paint latency, MMAP 44.1 fallback, canvas/static cover handover - #315

Merged
ajisth69 merged 3 commits into
mainfrom
fix/feed-canvas-and-mmap-rates
Oct 13, 2026
Merged

ajisth69 merged 3 commits into
mainfrom
fix/feed-canvas-and-mmap-rates

Conversation

@ajisth69

@ajisth69 ajisth69 commented Oct 13, 2026 •

Copy link
Copy Markdown
Member

Three independent fixes, one commit each (plus a follow-up repair commit — the first feed commit had an add(it to ...) typo inside a forEachIndexed lambda; CI caught it, now green on a732c68):

1. Feed page load time — ea6a5e8

The feed's only mid-load emission (onUpdate) publishes Quick Picks, and since 8335ccb blended discovery candidates into that list — so the first paint waited on three related-songs round-trips of 30 tracks each. On a cold start (no cached feed) the whole page sat blank until the slowest calls in the load finished.

Not one of the recent USB/local-songs PRs — the discovery change was already in main's base.

Fix: two emissions — familiar-only picks paint immediately (no network beyond the parallel batch already in flight), the discovery-blended picks republish the same sections once the round-trips land, overlapping with the artist fetches that follow. Discovery feature fully intact; Fresh Finds / Mixes / final FeedData unchanged.

2. Internal MMAP resampling to 44.1 — 8b528c4

negotiate() opened the internal-direct stream at the probe's rate when the HAL refused the source rate, and DirectOutputStream::probe() asks for no rate at all — so it returns the device's native primary output rate. On a 44.1-primary HAL, every refused source (48 kHz YouTube included) resampled down to 44.1.

Fix: fallbackRates() orders attempts source → 48 kHz → device native. 48 is tried first; the probe rate stays as the last resort, so a device that can't open 48 behaves exactly as before. One extra failed open per format, cached per rate+encoding per route. Signal Path verdict/pill need no change — conversion was already reported honestly.

Tests: fallbackRatesPrefer48kHzOverTheDeviceNativeRate, fallbackRatesSkipTheSourceRateAndDuplicates (7 cases).

3. Canvas artwork vs static cover — 9a5dfb6

The full-screen hero (12114f3, merged with #301/#302 today) stacks StaticArtworkHero and CanvasArtworkPlayer in identical bounds with the hero at full alpha, so the cover bled through wherever the canvas is transparent — its bottom fade, its 320 ms reveal, the repaint window on track change. The coordination the hero originally had was deleted by bef22b9 and never restored by the rebuild.

Fix: the canvas's own onCoverChanged reports its effective alpha and the static art fades out in step, snapping off at ≥95 % coverage. Wired in both the full-bleed hero and the sleeve. A canvas leaving composition reports 0, so the cover returns the moment a track without one starts. Dead canvasRendered state removed.

Verification — CI green on a732c68

Unit tests (incl. the two new ones) and assembleRelease both pass in CI: https://github.com/Clash-Projects/LastWave-Native/actions/runs/38409097146

Still worth a device pass: feed cold start (force-stop, clear recents), a canvas track's cover handover, and — on a 44.1-primary-HAL device — INTERNAL DIRECT (mmap) engaged ... device=48000Hz in logcat.

8335ccb blended discovery candidates into Quick Picks and made the
feed's only mid-load emission (onUpdate) wait on the three
related-songs round-trips behind them, so a cold feed — no cached
feed for this user — showed nothing until the slowest calls in the
whole load finished.

The discovery feature is unchanged: familiar-only picks are emitted
first, needing no network beyond the parallel batch already in
flight, and the blended picks republish the same sections the moment
the round-trips land, overlapping with the artist fetches that
follow instead of blocking the first paint. Fresh Finds, Mixes and
the final returned FeedData keep using the blended picks and
discoveryTracks exactly as before.
…al MMAP path

negotiate() opened the internal-direct stream at the probe's rate
whenever the HAL refused the source rate, and the probe asks for no
rate at all — DirectOutputStream::probe() builds its AAudio stream
without setSampleRate — so it returns the device's native primary
output rate. On a 44.1-primary HAL that made every refused source,
48 kHz YouTube streams included, resample DOWN to 44.1.

fallbackRates() orders the attempts source -> 48 kHz -> device
native: 48 is tried first, the probe rate is kept as the last resort,
and the source rate is never repeated. A device that genuinely cannot
open 48 kHz behaves exactly as before; attempts are cached per
srcRate/encoding per route, so the extra try costs one failed open
per format.

Tests: fallbackRatesPrefer48kHzOverTheDeviceNativeRate,
fallbackRatesSkipTheSourceRateAndDuplicates. The Signal Path verdict
and the quality pill need no change — resampling is already reported
honestly through internalSession.resampled and the pill is
source-measured by design.
The full-screen hero (12114f3) stacks StaticArtworkHero and
CanvasArtworkPlayer in identical bounds with the hero always at full
alpha, so the cover showed through wherever the canvas is
transparent — its bottom fade, its 320 ms reveal, and the repaint
window on every track change — and the two fought for the same
pixels. The coordination the hero originally had is restored via the
API that survived bef22b9: the canvas reports its effective alpha
(onCoverChanged) and the static art's alpha follows it, snapping off
once coverage reaches 95%. Wired in both the full-bleed hero and the
sleeve (PlayerArtwork), and a canvas leaving composition reports 0,
so the cover returns the moment a track without one starts. The dead
canvasRendered state (written, never read) is removed with it.
@ajisth69
ajisth69 merged commit a14ff39 into main Oct 13, 2026
1 check failed
ajisth69 added a commit that referenced this pull request Oct 13, 2026
1. FeedRepository: the #315 merge landed ea6a5e8 without its fixup
   commit a732c68, so the add(it to ...) typo inside the
   forEachIndexed lambdas is live on main and the app does not compile
   from it. Restored here: forEachIndexed blocks use add(t to ...), the
   unnamed forEach blocks below keep add(it to seedScore(...)).

2. ModernLyricsView: Modifier.blur has no clip parameter in this Compose
   version — its second parameter is edgeTreatment. The glow uses
   BlurredEdgeTreatment.Unbounded so the halo is not cut at the Text's
   own bounds.
ajisth69 added a commit that referenced this pull request Oct 13, 2026
Identical to the repair in #317: the #315 merge landed the feed change without its fixup commit, so main does not compile from add(it to ...) inside the forEachIndexed lambdas. This branch needs main to compile; once #317 merges, the two sides carry identical content for these lines.
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.

1 participant