Files
encore/docs/TODO.md
T
2026-07-27 00:16:46 -04:00

140 lines
7.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Roadmap / TODO
Near-term work after the working MVP (connect + auto-connect, now-playing with
transport/volume/options, DataStore-persisted settings). Roughly ordered by
priority; not a commitment.
Status legend: `[ ]` todo · `[~]` in progress · `[x]` done
---
## [x] 1. Proper icons
Done. Replaced the placeholder unicode glyphs in `NowPlayingScreen.kt` with real
Material icons (`Icons.Filled.SkipPrevious/PlayArrow/Pause/SkipNext/VolumeUp`,
plus `Repeat`/`Shuffle` leading icons on the chips) from
`androidx.compose.material:material-icons-extended`.
- That artifact bundles thousands of vectors, so the **debug** APK grew ~7 MB
(expected — debug can't shrink). Enabled **R8 + resource shrinking** on the
`release` build type, which strips the unused icons: release APK is ~1.2 MB.
- Considered `material-icons-core` (too small — no media icons) and bundling
individual vector drawables (leaner but manual); the extended dep + R8 was the
best effort/quality trade.
## [x] 2. Settings menu
Done. `SettingsScreen` (reached via a gear icon on the now-playing screen) shows
the current server (host/port) with **Change server** (disconnect → connect
form) and **Reset settings** (confirm dialog → clears DataStore + disconnects).
- `SettingsRepository.clear()` wipes DataStore; `MpdConnectionManager.resetSettings()`
clears + 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.
## [x] 3. Cast-style volume + OS media integration
Done — full MALP-style **OS integration**, not just in-app. Architecture:
- **`MpdConnectionManager`** (app-scoped, held by `MusicRemoteApplication`) now
owns the single `MpdClient`, so the connection survives Activity recreation and
runs while the service is up. `PlayerViewModel` is a thin delegate over it.
- **`PlaybackService`** — a `mediaPlayback` foreground service hosting a
`MediaSessionCompat`:
- **Cast-style volume**: `setPlaybackToRemote(VolumeProviderCompat)` (absolute,
0100). The OS routes hardware volume keys to the *server* volume
**system-wide, even when the app is backgrounded** (verified: 50→40 via
injected keys from the launcher), and shows the remote-volume UI. Replaced
the earlier in-app `onKeyDown` hack. Rapid presses accumulate via
`pendingVolume`.
- **Now-playing notification / QS / lock-screen** via a `MediaStyle`
notification bound to the session, with prev/play-pause/next actions
(`MediaButtonReceiver`) and metadata/position mirrored from the flows.
- Needs `POST_NOTIFICATIONS` (requested in `MainActivity`) +
`FOREGROUND_SERVICE[_MEDIA_PLAYBACK]`.
- In-app **`CastIndicator`** ("Controlling <host>") still shown above the slider.
- **Foreground key handling** (`VolumeKeyDispatcher`, delegated from
`MainActivity.dispatchKeyEvent`): while focused, volume keys are consumed and
drive the server silently — no system volume slider (the app's own slider is
the feedback). Backgrounded, the `VolumeProvider` takes over (system UI is fine
there). Gated by `MpdConnectionManager.isControllingVolume`; passes through to
local volume when not connected.
Notes: verified on the FiiO via injected key events (a rotary volume knob may not
emit `VOLUME_UP/DOWN` — hardware-dependent). Album art in the notification/session
is pending item #5.
## [x] 4. Library browse — albums
Done. `AlbumsScreen` (library icon on the player) lists every album via
`list album group albumartist` (`MpdCommands.listAlbums``MpdAlbum.listFrom`),
sorted by artist then title. Tapping an album replaces the queue and plays it
(`MpdClient.playAlbum` = `clear` + `findadd album … albumartist …` + `play`),
then returns to the now-playing screen.
- Verified live: 149 albums listed and played correctly on the FiiO.
- Placeholder disc icon per row (real art is item #5).
- Interaction choice: tap = **replace queue + play** (the direct "play this
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
Pull artwork for the now-playing track and for the album browse grid.
- 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.
## [x] 6. BUG: idle connection drops after a few minutes → kicked to connect page
After a few minutes idling, the app surfaces **"connection closed mid-response"**
and falls back to the connect screen. MALP does not do this.
- Error origin: `MpdConnection.readResponse()` hits EOF and throws
`MpdConnectionException("connection closed mid-response")`; the idle loop's
`catch (IOException)` calls `failAndClose()``MpdConnectionState.Error`
UI shows the connect form.
- Likely causes to investigate:
- **Android Doze / WiFi power-save** tearing down sockets when the screen is
off or the app is backgrounded (most likely on a portable DAP/phone).
- MPD's `connection_timeout` (default 60s) closing a connection it considers
idle — a parked `idle` should count as active, but the *command* connection
sits silent; a periodic `ping` keepalive may be needed.
- NAT/router idle-connection reaping (less likely on LAN).
- Fix direction: don't treat an idle-connection drop as a fatal error — instead
**auto-reconnect transparently** (re-open connections, re-issue `idle`, resync
state) and keep showing the player. Consider a keepalive ping and, for
backgrounded playback control, a foreground service / partial wakelock.
- PARTIALLY ADDRESSED by item #3: the `mediaPlayback` foreground service keeps
the process/connection alive in the background.
**FIXED.** Root cause was two-fold: (a) the quiet command connection was reaped
by MPD's `connection_timeout`, and (b) the idle loop treated *any* `IOException`
(including that reap) as fatal → `Error` → connect page. Plus a Pi-Zero overload
angle: volume-key floods + 2-round-trip refreshes. Fixes, all in `MpdClient`
/`MpdConnectionManager`/`MpdConnection`:
- **Keepalive `ping`** every 25s on the command connection — prevents the
`connection_timeout` reap and detects a dead socket early.
- **Transparent auto-reconnect** (`triggerReconnect`/`reconnectLoop`): a transport
drop reopens both sockets in the background (state → `Connecting`, "Connecting…"
splash, player stays), retrying with backoff; only after
`MAX_RECONNECT_ATTEMPTS` does it surface `Error`. `MpdAckException` is
explicitly *not* treated as a connection failure.
- **Volume throttle**: `setvol` writes are coalesced through a conflated channel
(~1 per 120 ms, latest value) — kills the flood from holding volume keys while
keeping accurate accumulation via `pendingVolume`.
- **Command-list refresh**: `status`+`currentsong` fetched in one round-trip
(`MpdConnection.executeList`, `command_list_ok_begin`).
Verified on the FiiO: Wi-Fi drop → "Connecting…" → auto-recovers to the player
(no connect-page bounce); 6 rapid volume presses = exactly 30, throttled.