docs: document the Android receiver app (Phase 8)

README: phone-as-second-screen section (build, install, sender usage,
multi-monitor caveat). RUNBOOK: Android build/test procedure, device-test
commands, and the API-36 platform quirks found during validation.
PHASES: Phase 8 milestones, all validated. MEMORY: current phase state
and the hard-won Android/portal lessons.
This commit is contained in:
2026-09-10 11:38:16 +02:00
parent 455ba6055d
commit bbe4f21a3b
4 changed files with 135 additions and 8 deletions
+25 -2
View File
@@ -1,10 +1,33 @@
# Project Memory — screen_cast
Last updated: Phase 6 (discovery + signaling) complete and validated over
loopback; current phase is Phase 7.
Last updated: Phase 8 (Android receiver app) complete and validated on a
Fairphone 6; all prior phases done.
## Project state
- **Phase 8 done: Android receiver app** (`android/`, Kotlin, minSdk 30,
app id `screen_cast.receiver`): phone as second receiver (screen → HDMI via
USB-C DP-alt-mode). Speaks the existing signaling+RTP protocol — no C++
changes. AGP 9 **built-in Kotlin** (no kotlin plugin, no `kotlinOptions`;
Kotlin targets `compileOptions`, Java 17); no androidx (framework +
org.json + JUnit). 25 JVM tests green. Validated end-to-end on a
Fairphone 6 (API 36): offer/answer → MediaCodec → fullscreen letterboxed
render; PLI recovery on real Wi-Fi loss. Hard-won API-36 quirks (in
RUNBOOK): `INTERNET` permission is REQUIRED for NsdService;
`DatagramSocket.localPort` (`.port` is -1 unconnected; `.localAddress` is
Inet6Address, not InetSocketAddress); pass the Surface to
`MediaCodec.configure()` + `start()`; render via
`releaseOutputBuffer(render=true)`; C2 AVC needs a concrete size at
configure (in-band SPS reconfigures); `MediaFormat.format()`/
`KEY_MIME_TYPE` not public in API 36; NSD `RegistrationListener` replaced
`ResolutionListener` (reflection fallback for older devices);
**`android._video-scaling`: C2 scales output to the Surface** → letterbox
by sizing the TextureView to the video aspect, NOT a transform matrix
(double scale). Sender-side: Hyprland + GTK portal's `--target monitor`
picks the FIRST output (eDP-1, wrong display) — use `--target window`
(hyprland-share-picker). Activity FQN for `am start`:
`screen_cast.receiver/screen_cast.ReceiverActivity` (Kotlin package =
namespace `screen_cast`).
- **Phase 6 done**: Avahi mDNS discovery (`_screencast._tcp` — receiver
announces its signaling port via a threaded-poll Avahi client; senders
browse+resolve) plus JSON session signaling (offer/answer) over TCP with