9c8fa9340c
apple / swift (push) Failing after 40s
audit / cargo-audit (push) Failing after 1m12s
windows-msix / package (push) Successful in 1m37s
windows / build (push) Successful in 1m14s
android / android (push) Successful in 4m48s
ci / web (push) Successful in 27s
ci / rust (push) Successful in 4m21s
ci / docs-site (push) Successful in 31s
ci / bench (push) Successful in 4m39s
decky / build-publish (push) Successful in 11s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 5s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 4s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 4s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 4s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 19s
deb / build-publish (push) Successful in 6m3s
flatpak / build-publish (push) Successful in 4m13s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 8m15s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 8m16s
docker / deploy-docs (push) Successful in 18s
Two bodies of work in one commit (the rename moved files the fixes also touched). Naming/structure cleanup (pre-launch): - Host modules m3.rs->punktfunk1.rs, m0.rs->spike.rs; CLI m3-host->punktfunk1-host, m0->spike; bare `punktfunk-host` now prints help. Types M3Options/M3Source-> Punktfunk1Options/Punktfunk1Source. - Clients consolidated out of crates/ into clients/: punktfunk-client-rs-> clients/probe (crate punktfunk-probe), client-linux->clients/linux, client-windows->clients/windows, punktfunk-android->clients/android/native (crate punktfunk-client-android; kept [lib] name=punktfunk_android so the JNI contract is unchanged). crates/ now holds only core + host. - Milestone codes M0-M4 purged from code/CLI/CLAUDE.md/README/docs/docs-site, kept only in docs/implementation-plan.md. docs/m2-plan.md-> docs/gamestream-host-plan.md. CI/gradle/flatpak paths updated. Client loss-recovery (video froze and never recovered after a brief drop): - Export punktfunk_connection_frames_dropped through the C ABI (the core already tracked it for the client keyframe-recovery loop; it was never reachable from the ABI clients). Regenerated punktfunk_core.h. - Apple (StreamPump + Stage2Pipeline) and Android (decode.rs) now poll frames_dropped and request a keyframe when it climbs -- the same loss-driven recovery Linux/Windows already had. Under infinite GOP the decoder silently conceals reference-missing frames, so the decode-error trigger rarely fires. Apple rumble robustness (worked then went spotty -- DualSense + Xbox): - Add CHHapticEngine stopped/reset handlers (rebuild on app background / audio interruption / server reset) and drop the permanent `broken` latch on a transient drive failure; latch only when the controller truly has no haptics. - Surface swallowed SDL set_rumble errors on Linux/Windows + diagnostic logging. Verified: cargo build/clippy/fmt --workspace, C-ABI harness, header drift. Not runnable on this box (verify in CI): Gitea workflows, gradle/Android, flatpak, Swift/decky. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
70 lines
3.5 KiB
Markdown
70 lines
3.5 KiB
Markdown
# punktfunk Android client
|
|
|
|
Native Android client for **punktfunk/1**, targeting **phone + TV** (Compose, D-pad + touch).
|
|
|
|
## Architecture — Rust-heavy (like the Linux client, not thin-native like Apple)
|
|
|
|
Kotlin cannot `import` the cbindgen C header the way Swift can, so a native bridge is unavoidable.
|
|
We write it in **Rust** and link `punktfunk-core` directly — so the Android client reuses the Linux
|
|
client's orchestration (audio jitter ring, VK keymap inverse, latency/skew math, capture state
|
|
machine, trust logic) instead of re-porting it into Kotlin.
|
|
|
|
| Side | Owns |
|
|
|------|------|
|
|
| **Rust** (`clients/android/native` → `libpunktfunk_android.so`) | the JNI seam, `NativeClient` (QUIC control + UDP data plane), AnnexB→`AMediaCodec` decode, Opus+Oboe audio, VK keymap, latency math, trust/pairing |
|
|
| **Kotlin** (`clients/android`) | Compose UI (host grid / settings / stream), `SurfaceView` lifecycle, input capture, `NsdManager` discovery, Keystore identity, permissions |
|
|
|
|
The single seam is `io.unom.punktfunk.kit.NativeBridge` ⇄ `Java_io_unom_punktfunk_kit_NativeBridge_*`.
|
|
|
|
## Layout
|
|
|
|
```
|
|
clients/android/native/ Rust cdylib (workspace member)
|
|
src/lib.rs JNI_OnLoad + abiVersion/coreVersion (native-link proof)
|
|
src/session.rs session handle lifecycle (connect/close); plane pumps = TODO
|
|
|
|
clients/android/ Gradle project (this dir)
|
|
settings.gradle.kts · build.gradle.kts · gradle.properties · gradlew
|
|
app/ :app — Compose application (MainActivity)
|
|
kit/ :kit — Android library: NativeBridge + the cargo-ndk build
|
|
build.gradle.kts cargoNdk{Debug,Release} → src/main/jniLibs/<abi>/*.so
|
|
```
|
|
|
|
## Prerequisites (already set up on the dev Mac)
|
|
|
|
- Android SDK + **NDK r30** (`30.0.14904198`), `platforms;android-37.0`, `build-tools;37.0.0`
|
|
- **JDK 21** for Gradle/AGP (the machine default JDK 25 is too new for AGP 9.2)
|
|
- Rust + `rustup target add aarch64-linux-android x86_64-linux-android` + `cargo install cargo-ndk`
|
|
|
|
Toolchain pinned: AGP 9.2.0 · Gradle 9.4.1 · Kotlin 2.3.21 · Compose BOM 2026.05.01 ·
|
|
compileSdk 37 · targetSdk 36 · minSdk 31 · ABIs arm64-v8a + x86_64.
|
|
|
|
## Build & run
|
|
|
|
**Android Studio:** open `clients/android` — it uses its bundled JBR 21 automatically. The
|
|
`cargoNdk*` task builds the `.so` as part of the normal build.
|
|
|
|
**CLI** (the machine default is JDK 25, so point Gradle at JDK 21):
|
|
|
|
```sh
|
|
export JAVA_HOME="$(brew --prefix openjdk@21)/libexec/openjdk.jdk/Contents/Home"
|
|
cd clients/android
|
|
./gradlew :app:assembleDebug # cargo-ndk cross-compiles libpunktfunk_android.so first
|
|
./gradlew :app:installDebug # onto a running emulator/device
|
|
|
|
# Emulators (created during env setup): emulator -avd pf_phone | emulator -avd pf_tv
|
|
```
|
|
|
|
The debug APK lands in `app/build/outputs/apk/debug/`. The scaffold screen calls
|
|
`NativeBridge.abiVersion()` across JNI — a live ABI version proves the whole native stack is wired.
|
|
|
|
## Status
|
|
|
|
- **Scaffold (done):** Gradle modules, cargo-ndk wiring, JNI native-link proof, phone+TV-installable
|
|
manifest. `crates/punktfunk-core` `rcgen` switched to the `ring` backend so the client `.so` is
|
|
aws-lc-free.
|
|
- **Next (Android stage 1):** video decode (`AMediaCodec` async → `SurfaceView`), audio
|
|
(Opus + Oboe + jitter ring), input capture → `send_input`, pairing/identity (Keystore-wrapped),
|
|
mDNS discovery, the phone/TV Compose UI. The Rust-side homes are stubbed in
|
|
`clients/android/native/src/session.rs` with port pointers to `clients/linux`.
|