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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
a213ad8d5d
commit
4728928d98
@@ -3,6 +3,15 @@
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<!--
|
||||
Voice notes. Requested at runtime as well: on this minSdk a dangerous
|
||||
permission declared here is not granted here, and a recorder started
|
||||
without it fails with a RuntimeException carrying no useful message.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<!-- The app is usable without a microphone; it simply cannot record. -->
|
||||
<uses-feature android:name="android.hardware.microphone" android:required="false" />
|
||||
|
||||
<application
|
||||
android:allowBackup="false"
|
||||
android:label="@string/app_name"
|
||||
|
||||
Reference in New Issue
Block a user