Files
lintunes/andtunes/TASKS.md
T
travandClaude Opus 5 cc1ca5ef78 v0.17.0: the desktop already knows what the songs are
The first half of andTunes, a music player for the Rabbit R1. Auxio re-reads
Android's MediaStore on every launch, which is the "your songs will show up
here" hang — so the answer isn't a faster scanner, it's not scanning at all.
LinTunes already knows the artist, album and year of every file it just
copied, so it writes them into Music/andTunes/library.json and the app reads
one file instead of indexing a filesystem.

New lintunes/andtunes/: layout.py (a de-duplicated Media/<Artist>/<Album>/
tree, so a song in three playlists is stored once), manifest.py (pure, Qt-free),
art.py (one 480 px cover per album via QImage, cached and invalidated by the
audio file's mtime — which is exactly what embedding new art moves), and
sync.py, a third sibling of plan_export/ExportWorker.

Three rules the code depends on: planning never renders art (a mutagen open
per album, and planning runs on the GUI thread); the index is written last and
after a cancel lists only tracks whose files actually landed; and every delete
goes through layout.assert_inside, which is why the old Music/<Playlist>/
folders are structurally unreachable rather than merely un-referenced.

Connections gains "Sync to Rabbit" and "Rabbit Sync Settings…" — the ticked
set is the device's contents, so unticking is how a playlist comes off, which
nothing could do before. The selection rides preferences.json so both machines
agree. The old per-playlist sync stays, renamed "(Auxio)": removing it now
would leave the device full of files and nothing able to open them until the
app exists.

The app itself is rounds 48-50; its board is andtunes/TASKS.md, including the
measured screen facts (480x640 px at density override 160 — one dp is half its
usual physical size on a 2.88" panel).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015wrTys5U1fLb4pBD2LKWzV
2026-09-06 03:25:40 -04:00

87 lines
4.7 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.
# andTunes — task board
A music player for the Rabbit R1 that never scans anything: LinTunes writes
`Music/andTunes/library.json` during sync and the app just reads it. See
`../andTunes spec.txt` for the original brief.
Legend: `[ ]` todo · `[~]` in progress · `[x]` done.
## Device facts (measured 2026-09-06, `adb shell wm size` etc.)
- Panel **480 × 640 px**, physical density 320, **override density 160** — so
the app sees a 480 × 640 **dp** canvas on a 2.88" screen (~278 real ppi).
- **One dp is about half its usual physical size here.** Double every stock
value: body text 28–32 sp, list rows ≥ 88 dp, menu tiles ~213 dp. A 48 dp
row would be 4.4 mm tall. Do not "fix" this by changing the device density —
trav likes it where it is.
- Android 13 / API 33, `gsi_r1-userdebug`, arm64-v8a, 65 GB free.
- Scroll wheel = input device `och1970_holl_key` → **KEY_UP / KEY_DOWN**
(`KEYCODE_DPAD_UP` / `DPAD_DOWN`). Side/PTT button sits on `mtk-kpd` with
KEY_VOLUMEDOWN + KEY_POWER. Headset exposes KEY_PLAYPAUSE, so a MediaSession
gets media keys for free.
## Round 48 — plays a song (v0.1.0)
- [ ] Toolchain: `sudo dnf install java-21-openjdk-devel`, Android
commandline-tools + platform 33 + build-tools into `~/Android/Sdk`.
(Java 25 is installed but AGP won't run on it.) Gradle via the wrapper.
- [ ] Gradle project, package `me.teafry.andtunes`, minSdk 26 / targetSdk 33.
**No Compose, no androidx, no Media3** — framework `Activity`,
`ListView`, `MediaPlayer`, `MediaSession`, `Notification.MediaStyle`,
R8 minify. Cold start is the whole feature; there is nothing to
initialize if nothing is linked in.
- [ ] `MANAGE_EXTERNAL_STORAGE` + a first-run screen that sends the user to
the toggle. `READ_MEDIA_AUDIO` grants the audio files but **not**
`library.json`, which isn't media — that's the whole reason.
- [ ] Library loader: parse `library.json` off the main thread into a
singleton that survives between activities. Refuse an unknown `format`
with a readable message rather than half-reading it.
- [ ] `MenuActivity`: six tiles, 2 × 3, **loads nothing**. The sixth reads
"Now Playing" or "Shuffle All" depending on the service.
- [ ] `ListActivity` in songs mode + "Shuffle all" header row.
- [ ] `PlaybackService` (foreground) + `NowPlayingActivity`: white
background, square art, slim scrubber, ◀ ⏯ ▶, shuffle. **No back
button** — Android's own is always on screen.
- [ ] Audio focus, ACTION_AUDIO_BECOMING_NOISY, MediaSession + notification.
- [ ] End of queue **pauses** unless repeat is on. Repeat: off / all / one.
- [ ] Verify with `adb shell am start -W` — target: menu visible < 400 ms.
## Round 49 — the rest of the screens
- [ ] Playlists, artists (→ albums + "All songs" + shuffle for that artist),
albums, search.
- [ ] Album art thumbs in album rows, full art on now playing.
- [ ] The "Now playing" bar at the bottom of every list screen.
- [ ] Scroll wheel → list scrolling, and volume on the now-playing screen.
- [ ] Replaceable button images: load `Buttons/<name>.png` from the device
folder when present, else the bundled drawable. LinTunes ships defaults
into `Buttons/` only when the file is missing, so the user's own
artwork is never overwritten.
- [ ] Missing file (synced then deleted) skips with a toast, doesn't crash
the queue.
- [ ] Resume last track + position on launch.
## Round 50 — install from LinTunes, counts coming back
- [ ] `Connections → Install andTunes on Rabbit…`: `shutil.which("adb")`
(the `ffmpeg_available()` runtime-tool pattern — never a pip dep), then
`adb install -r`; fall back to copying the APK over MTP for a manual
tap. APK committed at `lintunes/android/andTunes.apk` with
`package_data`, read via `importlib.resources` like `export/templates`.
- [ ] Format gate shaped like `export/web_support.py`: deny-by-default on
suffix, AIFF and protected AAC refused, anything else unplayable
converted to **FLAC** (never a lossy re-encode).
- [ ] Play counts back: the app writes `plays/andtunes-<installid>.json` in
the per-machine-totals shape of `storage/play_journal.py`; sync copies
it back and folds it in. The R1 becomes one more machine in the Round 38
model — no new merge rules.
- [ ] Retire `Sync Playlist to Rabbit (Auxio)` once andTunes covers it, and
offer a one-off cleanup of the old `Music/<Playlist>/` folders.
## Parked
- Gapless, crossfade, EQ, ratings, folder browsing, sleep timer, Android Auto.
- Deleting the old per-playlist folders automatically. They're outside the
andTunes root and the containment guard makes them unreachable on purpose;
removing them should be an explicit, separately-confirmed action.