feat: art images and queue
This commit is contained in:
+66
-10
@@ -6,6 +6,25 @@ priority; not a commitment.
|
||||
|
||||
Status legend: `[ ]` todo · `[~]` in progress · `[x]` done
|
||||
|
||||
## [x] Queue / "Up Next" view (post-roadmap)
|
||||
|
||||
Done. `QueueScreen` (queue icon on the player) shows **"Up Next"** — the tracks
|
||||
that will auto-play from the current one to the end of the queue, so the count
|
||||
reflects "how many will play before MPD stops."
|
||||
|
||||
- Reads the full queue via `playlistinfo` (`MpdClient.queue`), then slices from
|
||||
`status.song` (current queue index) to the end. The already-played prefix is
|
||||
dropped, and the slice re-derives from the live current position, so the list
|
||||
shrinks as playback advances **even without consume mode** (MPD keeps played
|
||||
tracks in the queue unless `consume` is on; only the current-song pointer
|
||||
moves — that's why a plain queue view looked static).
|
||||
- Current track is first, highlighted (bold + tinted + speaker icon). Tapping any
|
||||
track jumps to it (`playid`). Full queue re-fetched on queue-version change.
|
||||
- Captions the cases where the count isn't literally "tracks until stop":
|
||||
repeat (loops), single (stops after current), repeat+single (repeats current).
|
||||
- Rows show art thumbnails (disk-cached); art-less tracks show the disc
|
||||
placeholder. Overlay nav via `PlayerOverlay.Queue`.
|
||||
|
||||
---
|
||||
|
||||
## [x] 1. Proper icons
|
||||
@@ -33,8 +52,13 @@ form) and **Reset settings** (confirm dialog → clears DataStore + disconnects)
|
||||
- Navigation is lightweight state (`showSettings` in `MusicRemoteApp`, only over
|
||||
the player screen; a `LaunchedEffect` drops it when leaving the player) — no
|
||||
Navigation-Compose dependency yet.
|
||||
- Room to grow: password field, connection timeout, theme, keep-screen-on — and
|
||||
if screens multiply, revisit adopting Navigation-Compose.
|
||||
- **Playback section**: a **Consume mode** toggle (live-bound to `status.consume`,
|
||||
writes via the `consume` command). Consume is a dynamic MPD playback option, not
|
||||
static config — toggling it persists server-side (MPD state file). Verified the
|
||||
switch round-trips to the server (`consume: 0 → 1`).
|
||||
- Room to grow: password field, connection timeout, theme, keep-screen-on, and
|
||||
the other playback toggles (single/repeat/random) could join the Playback
|
||||
section. If screens multiply, revisit adopting Navigation-Compose.
|
||||
|
||||
## [x] 3. Cast-style volume + OS media integration
|
||||
|
||||
@@ -82,16 +106,48 @@ then returns to the now-playing screen.
|
||||
album" gesture). Could later add a long-press / menu for "add to queue" and an
|
||||
album-detail/track view. Artist browse is a future extension.
|
||||
|
||||
## [ ] 5. Album / artist images
|
||||
## [x] 5. Album / artist images
|
||||
|
||||
Pull artwork for the now-playing track and for the album browse grid.
|
||||
Done — art on the now-playing screen, the album grid, and the media
|
||||
notification / lock-screen / QS card.
|
||||
|
||||
- MPD serves art over the protocol via `albumart <uri> <offset>` and
|
||||
`readpicture <uri> <offset>` (embedded art). **`MpdConnection` already handles
|
||||
binary responses**, so the transport groundwork is done — add the commands,
|
||||
loop over offsets to fetch the whole image, and decode.
|
||||
- Needs an image loader + caching. Coil (`io.coil-kt`) is the standard Compose
|
||||
choice; a custom `MpdArtFetcher` could feed it. Dependency → `deps.json` regen.
|
||||
- `MpdClient.songArt(uri)` / `albumArt(album, artist)` loop the chunked art
|
||||
protocol: try `albumart` (folder cover), fall back to `readpicture` (embedded);
|
||||
`albumArt` resolves a track via `find … window 0:1` first. `binarylimit` is
|
||||
raised on connect so covers transfer in ~1 round-trip.
|
||||
- **Coil** (`io.coil-kt.coil3`) with a custom `Fetcher`/`Keyer` over the
|
||||
`MpdArtData` model (`SongArt`/`AlbumArt`), wired as the app's
|
||||
`SingletonImageLoader.Factory`. **Disk cache** means each cover is fetched from
|
||||
the server once ever — important for the Pi. `ArtImage` composable shows a disc
|
||||
placeholder underneath. Service pulls the bitmap via Coil (shared cache) into
|
||||
`METADATA_KEY_ALBUM_ART`.
|
||||
- The now-playing screen is now vertically scrollable so art doesn't clip.
|
||||
- **Art connection pool** (`MpdClient`, `ART_POOL_SIZE = 4`): covers are fetched
|
||||
on a dedicated pool of connections, not the command connection — so they load
|
||||
~4× in parallel while scrolling *and* never block play/pause/volume. (Measured:
|
||||
a batch of 8 covers went from ~1.8s serialized on the tail to ~4-way parallel.)
|
||||
Each album cover is 3 round-trips (`find` → `albumart` ACK → `readpicture`), so
|
||||
the Pi is the remaining limit; disk cache means it's a one-time cost per cover.
|
||||
- **Disk-cache persistence:** Coil's disk cache is only auto-managed by its
|
||||
*network* fetchers — a custom fetcher must read/write `imageLoader.diskCache`
|
||||
itself, or art is only memory-cached (re-fetched every cold start). `MpdArtFetcher`
|
||||
now serves from the disk snapshot when present and write-through-caches misses.
|
||||
Verified: after force-stop + relaunch, 19/19 covers loaded as DISK hits, 0
|
||||
server fetches. (Known minor gap: art-*less* albums aren't negative-cached, so
|
||||
they re-probe the server each cold start — future optimization if it matters.)
|
||||
|
||||
Gotchas hit & fixed:
|
||||
- **Portability bug (not art-specific):** `MpdAckException`'s ACK regex had bare
|
||||
`]`/`}` — fine on the JVM (tests passed) but Android's ICU engine rejects them,
|
||||
so the first ACK parsed on-device threw `ExceptionInInitializerError`. Escaped
|
||||
them. This had masked all art (albumart's "no cover" ACK).
|
||||
- Coil's reified `components { add(factory) }` didn't match the sealed-interface
|
||||
subtypes; had to register with the explicit `add(factory, MpdArtData::class)`.
|
||||
- Coil pinned to **3.0.4**: 3.5+ requires `compileSdk 36` (we're on 35). Bumping
|
||||
later means updating `flake.nix` platform + AGP.
|
||||
|
||||
Future: online art fallback (MusicBrainz/Last.fm) for albums with no local art,
|
||||
like MALP; artist images.
|
||||
|
||||
## [x] 6. BUG: idle connection drops after a few minutes → kicked to connect page
|
||||
|
||||
|
||||
Reference in New Issue
Block a user