Files
lintunes/andtunes/TASKS.md
T
travandClaude Opus 5 81b7f6767c v0.20.0: the Rabbit's plays come home
andTunes phase 3. The R1 is now one more machine in the Round 38 play-count
model, and nothing lands on it that Android can't play.

Play counts: andTunes 0.2.0 counts a play on a natural finish (LinTunes'
rule) and keeps Music/andTunes/plays/andtunes-<install id>.json in exactly
the per-machine-totals journal shape, written beside the old one and renamed
over it. Each sync first folds it into <data_dir>/plays/ with a new
play_journal.merge_totals, the per-track max: the file now has two homes and
both desktops may bring it back, so the max converges and an older copy can
never pull a count down. Nothing is written when nothing moved, and
PlayJournal.load needed no change.

Format gate: the planner reuses export/web_support.conversion_for outright,
since Android's MediaPlayer decodes the browser's set. FairPlay is refused
and reported; ALAC, AIFF and oddities land as FLAC, converted once into a
per-track cache and size-diffed after that. With no ffmpeg, the export's
"sync without them?" question. A file ffmpeg can't read costs that song,
not the sync.

"Sync Playlist to Rabbit (Auxio)" is retired from the menu; device_sync's
helpers stay because export and andTunes import them.

The dev fixture grew a real ALAC track, a Protected AAC .m4p and an "Odd
Formats" playlist. Verified on the R1 with it: AIFF and ALAC arrived as
FLAC and played, the FairPlay track was refused, four plays came back on the
next sync and each track's effective count rose by exactly one, and a third
sync copied nothing.

Found along the way: USB re-enumeration can wedge gvfsd-mtp, after which
anything touching the mount (find_device, the GUI tests) hangs in an
uninterruptible wait. CLAUDE.md now has the recovery.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018ZCVBTJRFJ2XfMshu2gtUv
2026-09-11 15:20:44 -05:00

6.1 KiB
Raw Blame History

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.

Status: phases 1–3 done (2026-09-11, LinTunes rounds 50–51, andTunes 0.2.0). What's left is the Parked list, pulled in as trav finds he needs it. Built with python3 andtunes/build.py — plain Java, no Gradle, nothing downloaded (see README.md, Toolchain). Phases are andTunes' own; which LinTunes round each lands in is decided when it starts.

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, mapped by the stock Generic.kl (checked 2026-09-11 via dumpsys input) to KEYCODE_DPAD_UP / DPAD_DOWN — not volume. Side/PTT button sits on mtk-kpd with KEY_VOLUMEDOWN + KEY_POWER. Headset exposes KEY_PLAYPAUSE, so a MediaSession gets media keys for free.
  • USB: set to come up as mtp,adb whenever the screen is unlocked (adb shell svc usb setScreenUnlockedFunctions mtp, 2026-09-11), so the gvfs mount sync uses and the adb install uses are both there at once.

Phase 1 — plays a song (v0.1.0) ✔

  • java-21-openjdk-devel — installed, javac on PATH. (Java 25 is also present; build.py points at 21.)
  • Android SDK: platform 33 + build-tools 34.0.0 were already in ~/Android/Sdk. No Gradle, no Kotlin — build.py runs aapt2 → javac → R8 → zipalign → apksigner directly (trav, 2026-09-11: don't pull a GB over cell if it isn't needed; it isn't).
  • Project, package me.teafry.andtunes, minSdk 26 / targetSdk 33, framework only: Activity, ListView, MediaPlayer, MediaSession, Notification.MediaStyle, R8. The APK is 61 KiB.
  • MANAGE_EXTERNAL_STORAGE + a first-run screen that sends the user to the toggle (LinTunes' installer grants it over adb, so it rarely shows).
  • Library loader: android.util.JsonReader streaming parse off the main thread into a process singleton; artists/albums grouped in one pass; an unknown format gets a readable message; reloads when library.json changes (a re-sync) the next time the menu resumes.
  • 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, repeat. No back button.
  • Audio focus, ACTION_AUDIO_BECOMING_NOISY, MediaSession + notification.
  • End of queue pauses unless repeat is on. Repeat: off / all / one.
  • adb shell am start -W: 313–338 ms cold to the menu (551 ms on the very first launch after install, which includes dex verification).

Phase 2 — the rest of the screens ✔

  • Playlists, artists (→ "All songs" + albums; all songs has its own shuffle), albums, search (artists, albums and songs as you type).
  • Album art thumbs in album rows, full art on now playing.
  • The "Now playing" bar at the bottom of every list screen; the playing song is drawn inverted in any list it appears in.
  • Scroll wheel → list scrolling (one row per detent), volume on the menu and now-playing screens. Taken in dispatchKeyEvent, DPAD and VOLUME codes, down and up, so the search box can't swallow it.
  • Replaceable button images: Buttons/<name>.png beats the bundled drawable, for the six menu tiles plus ◀ ▶ ⏯. LinTunes ships the defaults into Buttons/ only where the file is missing.
  • Missing file (synced then deleted) skips with a toast, doesn't stop the queue.
  • Resume last queue + track + position on launch (paused).

Phase 3 — install from LinTunes, counts coming back

  • Connections → Install andTunes on Rabbit… (LinTunes 0.19.0): adb install -r + the two permission grants; says Install / Update / Reinstall from the version on the device; with no adb, copies the APK to Download/ over MTP for a manual tap. APK committed at lintunes/android/andTunes.apk (+ andTunes.json, its version).
  • Format gate (LinTunes 0.20.0): sync reuses export/web_support.conversion_for outright, because Android's MediaPlayer decodes the same set a browser does. Protected AAC is refused and reported; ALAC, AIFF and anything else unplayable becomes FLAC (never a lossy re-encode). Conversions are cached per track, so they're converted once and size-diffed after that.
  • Play counts back (andTunes 0.2.0): a natural finish counts a play, as it does in LinTunes. The app keeps plays/andtunes-<install id>.json in the per-machine-totals shape of storage/play_journal.py. Sync folds it into <data_dir>/plays/ with the per-track max before copying anything, so the R1 is one more machine in the Round 38 model and there are no new merge rules.
  • Retired Sync Playlist to Rabbit (Auxio) from the menu. (trav's R1 has no old Music/<Playlist>/ folders, so no cleanup was needed.)

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.