Files
flamenet-messenger-android/app
Paul HittandClaude Opus 5 4728928d98 Android: voice notes
The engine and the client already carried audio — `Attachment.AUDIO`, a
`duration` field, and a `sendAttachment` that takes both. What was missing was
entirely the phone: something to record with, something to play with, and two
controls. So this is app-layer only, and adds no dependency: `MediaRecorder`
and `MediaPlayer` are platform APIs, and a voice note is an ordinary encrypted
attachment the moment it is bytes.

AAC in an MP4 container, mono, 32 kbit/s. Speech rather than music: clearly
intelligible, and roughly a tenth the size of the stereo default nobody asked
for — which matters when every byte is uploaded encrypted and stored by
somebody paying for it.

⚠️ **Tap to start, tap to send — not hold-to-talk.** Hold-to-talk demonstrates
well and behaves badly on a real phone: the gesture competes with scrolling,
and letting go early silently discards what was said. Two deliberate taps
cannot be lost by accident.

⚠️ **`MediaRecorder.stop()` throws when it captured no frames** — a tap instead
of a hold, or a permission granted and then denied by policy. The exception is
a bare `RuntimeException` and it reads as "recording is broken" when the truth
is "there was nothing to record". Treated as an empty recording, and anything
under half a second is dropped rather than delivered: a note that plays silence
looks like a delivery bug, and the person who fumbled the button already knows
they did.

⚠️ **The microphone permission is requested when somebody asks to record**, not
at startup. A microphone prompt on first launch, before anything has been said,
is one people deny on reflex — and a denied permission is far harder to recover
from than one that was never asked for.

⚠️ Received notes are fetched and decrypted **on tap, never on render**. A
thread of twenty would otherwise download and decrypt all twenty on scroll:
bytes nobody asked for, and twenty decrypted files in the cache for a list
nobody has listened to. Playback stops on leaving the thread, because a
MediaPlayer that outlives its composable holds audio focus and a file handle.

Every temporary file — the recording, and the decrypted copy playback needs
because `MediaPlayer` cannot take a byte array — is deleted on both the success
and the failure path. Plaintext audio left in a cache directory is the exact
thing this product is not supposed to do.

`imageBytes` became `attachmentBytes`: photos and voice notes take the same
path, and a method called `imageBytes` that audio also calls is a name that has
quietly stopped being true.

Also retires buddy commands in the parity table — no longer a feature.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 14:37:31 -04:00
..
2026-08-25 14:37:31 -04:00