Commit Graph
4 Commits
Author SHA1 Message Date
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
travandClaude Opus 5 9483d8951b v0.19.0: andTunes plays music on the Rabbit
The Android half of andTunes had been paused on a 1 GB toolchain download
(Gradle wrapper, Android Gradle Plugin, Kotlin). None of that was needed:
platform 33 and build-tools 34 were already in ~/Android/Sdk, and an app that
links no libraries needs five SDK steps, not a build system. andtunes/build.py
runs aapt2 -> javac -> R8 -> zipalign -> apksigner and produces a 61 KiB APK.

andTunes 0.1.0 is framework-only Java: a six-tile menu that loads nothing, a
streaming library.json parse grouped into artists and albums in one pass, one
ListActivity for every list (songs and artist songs open with Shuffle all,
albums carry art thumbs, search as you type), a Now playing bar under every
list, and a white Now Playing screen with square art, a slim scrubber,
prev/play/next, shuffle and repeat, and no back button. A foreground
PlaybackService owns the queue: audio focus, becoming-noisy, MediaSession and
a MediaStyle notification, end of queue pauses, missing files skip, and the
last queue and position come back on launch. Black on white, and every size
doubled for the R1's override density. Cold start to the menu: 313-338 ms.

The scroll wheel turned out to be DPAD_UP/DOWN (stock Generic.kl), not
volume. The app takes both in dispatchKeyEvent: lists scroll a row per
detent, and the menu and Now Playing get volume.

LinTunes side: Connections > Install andTunes on Rabbit... installs the
committed APK over adb and grants all-files access, or copies it to Download/
over MTP when there's no adb. Sync fills Buttons/ with default artwork only
where a file is missing, so trav's own art is never overwritten.

Verified against the dev library only, synced to the R1 and driven with
adb input + screencap.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KSsYtRn4WZx6xj4GEdVPt9
2026-09-11 14:25:55 -05:00
travandClaude Opus 5 bcba621f3f v0.17.1: a library to break
Round 47 was verified by loading trav's real 21,531-track library. Nothing was
written and nothing was at risk, but it's the wrong habit and he said so. The
alternative is better for development anyway: reproducible, in-repo, and full
of the awkward cases on purpose rather than by luck.

scripts/make_dev_library.py builds a complete synthetic library — data dir and
music tree — from ffmpeg sine waves in six containers, with art on some albums
and not others, every playlist type (regular, folder, live/non-live/
unsupported/limited+nested smart, system, empty, duplicates), a play journal
and a tombstone. ~4 MB, gitignored; the generator is the artifact worth
keeping, not the sine waves.

The edge cases are the point. A collision pair identical in artist/album/title/
number, slashes and colons in every name, a track with no artist, a multi-disc
release, a compilation whose album_artist differs, a dangling location, a
208-character title, and non-ASCII plus an emoji all the way out to the m3u
filename. CLAUDE.md now carries the rule: never develop against the real
library, and when a feature needs a shape the fixture lacks, add it here.

Using it found two bugs in it — a relative --out made every location resolve
against the wrong root, and four-minute uncompressed clips made it 62 MB.

andTunes is paused on a weak connection (the Android SDK is a ~1-1.5 GB
one-time download; after that --offline builds need nothing). andtunes/
README.md now carries everything needed to resume cold: measured device facts,
the dp trap, exact toolchain commands, locked design decisions, and the
library.json contract. JDK 21 is installed and ticked off.

Also answers the scroll wheel question: it reads as volume because the ROM's
key layout maps the wheel's KEY_UP/KEY_DOWN to KEYCODE_VOLUME_UP/DOWN, but a
focused activity sees key events first — so andTunes can claim it by consuming
both those and DPAD_UP/DOWN, in onKeyDown and onKeyUp. No system file touched,
no other app affected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015wrTys5U1fLb4pBD2LKWzV
2026-09-10 00:49:26 -04:00
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