6.0 KiB
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
releasebuild 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 (
showSettingsinMusicRemoteApp, only over the player screen; aLaunchedEffectdrops 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 byMusicRemoteApplication) now owns the singleMpdClient, so the connection survives Activity recreation and runs while the service is up.PlayerViewModelis a thin delegate over it.PlaybackService— amediaPlaybackforeground service hosting aMediaSessionCompat:- Cast-style volume:
setPlaybackToRemote(VolumeProviderCompat)(absolute, 0–100). 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-apponKeyDownhack. Rapid presses accumulate viapendingVolume. - Now-playing notification / QS / lock-screen via a
MediaStylenotification bound to the session, with prev/play-pause/next actions (MediaButtonReceiver) and metadata/position mirrored from the flows. - Needs
POST_NOTIFICATIONS(requested inMainActivity) +FOREGROUND_SERVICE[_MEDIA_PLAYBACK].
- Cast-style volume:
- In-app
CastIndicator("Controlling ") still shown above the slider. - Foreground key handling (
VolumeKeyDispatcher, delegated fromMainActivity.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, theVolumeProvidertakes over (system UI is fine there). Gated byMpdConnectionManager.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.
[ ] 4. Library browse — albums
A screen to browse all albums on the server (artists can come later).
- MPD commands:
list album group albumartist(orlist album), andfind album "<name>"to fetch an album's tracks; add to the queue withadd/findadd. ExtendMpdCommands+MpdClient. - Grid or list of albums → tap to view/queue tracks.
[ ] 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>andreadpicture <uri> <offset>(embedded art).MpdConnectionalready 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 customMpdArtFetchercould feed it. Dependency →deps.jsonregen.
[ ] 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 throwsMpdConnectionException("connection closed mid-response"); the idle loop'scatch (IOException)callsfailAndClose()→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 parkedidleshould count as active, but the command connection sits silent; a periodicpingkeepalive 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
mediaPlaybackforeground service now keeps the process/connection alive in the background, which should stop Doze / wifi power-save from tearing the sockets down (the most likely cause). Still worth verifying over a long idle, and adding transparent auto-reconnect + keepalive ping as defense-in-depth (e.g. against MPDconnection_timeout).