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

4.7 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.

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.