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

7.5 KiB
Raw Blame History

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 ") 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.listAlbumsMpdAlbum.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.