Initial MVP
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
# 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
|
||||
|
||||
---
|
||||
|
||||
## [ ] 1. Proper icons
|
||||
|
||||
Replace the placeholder unicode glyphs (`GlyphText` in `NowPlayingScreen.kt` —
|
||||
`⏮ ▶ ⏸ ⏭ 🔈`) with real Material icons.
|
||||
|
||||
- Preferred source: **Google Material Symbols / Icons**. In Compose the usual
|
||||
route is the `androidx.compose.material:material-icons-*` artifacts
|
||||
(`Icons.Filled.PlayArrow`, `SkipNext`, `VolumeUp`, …). `material-icons-core`
|
||||
covers the common set; `material-icons-extended` has everything but is large —
|
||||
prefer core, or import only the specific vector assets we use.
|
||||
- Platform-bundled drawables (`android.R.drawable.ic_media_*`) exist but look
|
||||
dated and vary by OEM — avoid; bundle our own for a consistent look.
|
||||
- Adding the dependency means a `deps.json` regen (see README).
|
||||
|
||||
## [ ] 2. Settings menu
|
||||
|
||||
A dedicated settings screen where the user can view every setting and reset it.
|
||||
|
||||
- Currently settings (host/port) are only editable by hitting **Disconnect** to
|
||||
get back to the connect form. Add a real settings route.
|
||||
- Include a **Reset** action that clears DataStore (add a `clear()` to
|
||||
`SettingsRepository`).
|
||||
- Will likely want simple navigation (Navigation-Compose, or a screen enum in
|
||||
`PlayerViewModel`/`AppScreen`).
|
||||
- Room to grow: password field, connection timeout, theme, keep-screen-on.
|
||||
|
||||
## [ ] 3. Cast-style volume control
|
||||
|
||||
When the MPD server is playing, present the volume control as a **"casting"
|
||||
style** remote-volume control — the way MALP (and Google Cast) do — making it
|
||||
visually clear you're controlling the *server's* output, not the phone's.
|
||||
|
||||
- Functionally we already send `setvol` to the server (`MpdClient.setVolume`);
|
||||
this is mostly a UX/affordance change: a cast icon, "Casting to <host>" label,
|
||||
distinct styling for remote vs local volume.
|
||||
- Reference behaviour: MALP.
|
||||
|
||||
## [ ] 4. Library browse — albums
|
||||
|
||||
A screen to browse all albums on the server (artists can come later).
|
||||
|
||||
- MPD commands: `list album group albumartist` (or `list album`), and
|
||||
`find album "<name>"` to fetch an album's tracks; add to the queue with
|
||||
`add`/`findadd`. Extend `MpdCommands` + `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>` 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.
|
||||
|
||||
## [ ] 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.
|
||||
@@ -0,0 +1,88 @@
|
||||
# Testing on a physical Android device
|
||||
|
||||
The fastest inner loop for this app is building the debug APK and installing it
|
||||
straight onto a real device over USB (an Android phone, or an Android-based DAP
|
||||
like the FiiO M33). This is quicker than `nix run .#emulate` and tests real
|
||||
networking to your MPD server.
|
||||
|
||||
## One-time device setup
|
||||
|
||||
1. **Enable Developer Options**: Settings → About phone → tap **Build number**
|
||||
seven times.
|
||||
2. **Enable USB debugging**: Settings → System → Developer options → **USB
|
||||
debugging**.
|
||||
3. **Use a data-capable USB cable** and plug the device directly into the
|
||||
machine. Many cables are charge-only — if the device never appears in
|
||||
`lsusb`, suspect the cable or the USB mode first.
|
||||
4. **Unlock the device screen.** The *"Allow USB debugging?"* authorization
|
||||
dialog only appears while unlocked — this is the usual reason "no popup shows
|
||||
up". Tap **Allow** and check **Always allow from this computer**.
|
||||
|
||||
### NixOS note (this machine)
|
||||
|
||||
`adb` is provided by the dev shell (`nix develop`). On this NixOS host it talks
|
||||
to devices **without** needing `programs.adb.enable`, custom udev rules, or an
|
||||
`adbusers` group: a plugged-in, authorized device shows up directly as `device`.
|
||||
|
||||
If a device instead shows as:
|
||||
|
||||
- `unauthorized` → you haven't accepted the on-device prompt yet (unlock the
|
||||
screen and tap Allow).
|
||||
- `no permissions` → *then* you'd need udev rules; add
|
||||
`programs.adb.enable = true;` and your user to the `adbusers` group in the
|
||||
NixOS config, `nixos-rebuild switch`, re-login, and `adb kill-server`. (Not
|
||||
currently needed here.)
|
||||
|
||||
## Verify the device is connected
|
||||
|
||||
```sh
|
||||
adb devices -l # should list the device as `device` (not unauthorized)
|
||||
adb shell getprop ro.product.model
|
||||
```
|
||||
|
||||
If `adb devices` is empty, check `lsusb` for the device (Pixels show as
|
||||
`Google Inc. …` / vendor `18d1`), then re-check the cable and USB debugging.
|
||||
|
||||
## Build, install, launch
|
||||
|
||||
From the repo root, inside `nix develop`:
|
||||
|
||||
```sh
|
||||
./gradlew assembleDebug
|
||||
adb install -r app/build/outputs/apk/debug/app-debug.apk
|
||||
adb shell am start -n ca.ksamad.musicremote/.MainActivity # optional auto-launch
|
||||
```
|
||||
|
||||
`-r` reinstalls over the existing app **keeping its data** (so persisted
|
||||
connection settings survive). To simulate a clean first run:
|
||||
|
||||
```sh
|
||||
adb shell pm clear ca.ksamad.musicremote
|
||||
```
|
||||
|
||||
## Screenshots
|
||||
|
||||
```sh
|
||||
adb exec-out screencap -p > screenshot.png
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **"failed to connect to <host>:<port>"** — the device can't reach the MPD
|
||||
server. Check the device is on the **same WiFi/LAN** as the server. A common
|
||||
gotcha: WiFi is off on a portable DAP, so `adb shell ip route` shows no route
|
||||
and `adb shell ping <server>` returns *"Network is unreachable"*.
|
||||
- **App looks stale after changes** — reinstall the APK; restarting the device
|
||||
or emulator alone does not update the installed app.
|
||||
|
||||
## Wireless alternative (no cable)
|
||||
|
||||
Android 11+ supports wireless debugging (Developer options → **Wireless
|
||||
debugging** → *Pair device with pairing code*), then on the host:
|
||||
|
||||
```sh
|
||||
adb pair <ip>:<pair-port> # enter the code shown on-device
|
||||
adb connect <ip>:<debug-port>
|
||||
```
|
||||
|
||||
This needs no cable and no udev rules; the device must share your LAN.
|
||||
Reference in New Issue
Block a user