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:
@@ -209,6 +209,55 @@ The headless equivalent runs as part of `meson test` (`udp loopback` test):
|
||||
synthetic frames → encode → packetize → localhost UDP → depacketize →
|
||||
decode, no portal or window involved.
|
||||
|
||||
## Android receiver app (Phase 8)
|
||||
|
||||
Toolchain: JDK 21, system `gradle` (no wrapper), Android SDK with android-36.
|
||||
AGP 9 has **built-in Kotlin** — do not apply `org.jetbrains.kotlin.android`
|
||||
and do not use `kotlinOptions {}`.
|
||||
|
||||
```sh
|
||||
cd android
|
||||
gradle :app:assembleDebug # -> app/build/outputs/apk/debug/app-debug.apk
|
||||
gradle :app:testDebugUnitTest # 25 JVM tests (rtp, jitter, depacketizer)
|
||||
```
|
||||
|
||||
Device test (validated on a Fairphone 6, Android 16 / API 36):
|
||||
|
||||
```sh
|
||||
adb install -r app/build/outputs/apk/debug/app-debug.apk
|
||||
adb shell am start -n screen_cast.receiver/screen_cast.ReceiverActivity
|
||||
# activity FQN is screen_cast.ReceiverActivity (namespace), not
|
||||
# screen_cast.receiver.ReceiverActivity
|
||||
adb shell uiautomator dump /sdcard/ui.xml && adb shell cat /sdcard/ui.xml # status text
|
||||
adb exec-out screencap -p > phone.png # visual check
|
||||
```
|
||||
|
||||
On the sender: `screencast --send --peer <phone-ip>:5005` (add `--target
|
||||
window` to pick a specific window; the portal's monitor source picks the
|
||||
first output on multi-monitor Hyprland/GTK-portal setups).
|
||||
|
||||
Platform quirks found while validating (Android 16 / API 36):
|
||||
|
||||
- `android.permission.INTERNET` is required — NsdService rejects
|
||||
registration without it.
|
||||
- `DatagramSocket.localPort` gives the bound port; `.port` is -1 for
|
||||
unconnected datagram sockets, and `.localAddress` is an `InetAddress`
|
||||
(Inet6Address on Android), not an `InetSocketAddress`.
|
||||
- `MediaCodec`: pass the output Surface to `configure()` (`setOutputSurface`
|
||||
is illegal after configure); call `start()`; render with
|
||||
`releaseOutputBuffer(index, render=true)`; the C2 AVC decoder requires a
|
||||
concrete width/height at configure (use a placeholder — the in-band SPS
|
||||
reconfigures it).
|
||||
- `MediaFormat.format()` / `KEY_MIME_TYPE` are not in the API-36 public
|
||||
surface.
|
||||
- NSD: API 36 replaced `ResolutionListener` registration with
|
||||
`registerService(info, flags, RegistrationListener)`; the app falls back
|
||||
to reflection on older devices.
|
||||
- The C2 decoder scales its output to the Surface (`android._video-scaling`)
|
||||
— letterbox by **sizing the TextureView to the video aspect** (centered
|
||||
on a black window background), not with a transform matrix (double scale).
|
||||
- mDNS does not cross subnets; `--peer` is the reliable path.
|
||||
|
||||
## Formatting
|
||||
|
||||
```sh
|
||||
|
||||
Reference in New Issue
Block a user