mirror of
https://github.com/narrrl/proton-stream.git
synced 2026-11-03 16:41:26 +01:00
The app is listed in the TV launcher with a banner, and neither a touch screen nor leanback is required. While the controls are down the player takes D-pad left and right as a ten-second seek, the middle button as bringing the controls up, and play/pause and space as they say.
128 lines
6.3 KiB
Markdown
128 lines
6.3 KiB
Markdown
# Android Development
|
|
|
|
The Android client lives in `android/`; its Rust boundary is
|
|
`crates/pstr-android`. It targets Android 12 (API 31) and newer on
|
|
`arm64-v8a` phones/tablets and `x86_64` emulators. Compose owns navigation and
|
|
screens, while UniFFI exposes the existing catalog, share, streaming, watch-state,
|
|
and offline logic. Keep reusable behavior in the Rust crates rather than
|
|
reimplementing it in Kotlin.
|
|
|
|
## Android TV
|
|
|
|
The same build installs on Android TV: it is listed in the TV launcher
|
|
(`LEANBACK_LAUNCHER`, with the banner `scripts/render-tv-banner.sh` renders)
|
|
and requires neither a touch screen nor leanback. Every screen is Compose
|
|
`clickable`s, which the D-pad already moves between. The player takes a
|
|
remote's keys while its controls are down: left and right seek ten seconds,
|
|
the middle button brings the controls up, and play/pause works as on a
|
|
keyboard. There is no TV-specific layout yet, and none of it has been run on a
|
|
TV. Signing in to an account, so that shares arrive through sync, is the way
|
|
to set one up without typing a share link on a remote.
|
|
|
|
## Toolchain
|
|
|
|
Install JDK 17, Android SDK Platform/Build Tools 36, NDK
|
|
`30.0.16248370`, Rust 1.96, Gradle 8.13, and the native helpers:
|
|
|
|
```bash
|
|
rustup target add aarch64-linux-android x86_64-linux-android
|
|
cargo install cargo-ndk --version 4.1.2 --locked
|
|
```
|
|
|
|
Set `ANDROID_HOME` (or `ANDROID_SDK_ROOT`); `cargo-ndk` discovers the NDK below
|
|
that SDK. The workspace-provided `uniffi-bindgen` binary keeps binding generation
|
|
on the same UniFFI version as the bridge. Use `bash scripts/build-android.sh debug` for an installable debug APK,
|
|
`bash scripts/build-android.sh check` for Android lint/unit tests, and
|
|
`bash scripts/build-android.sh release` for release APK/AAB output. Gradle first
|
|
generates UniFFI Kotlin into `android/app/build/generated/source/uniffi`, then
|
|
builds both Rust ABIs into `android/app/build/generated/jniLibs`. Generated files
|
|
belong under `build/` and must not be committed.
|
|
|
|
`versionName` always comes from `[workspace.package].version` in `Cargo.toml`, so
|
|
a tagged build cannot drift from the desktop release. The default `versionCode`
|
|
is `major * 1,000,000 + minor * 1,000 + patch` (`0.1.1` becomes `1001`). Set
|
|
`ANDROID_VERSION_CODE` only when Play requires a higher monotonic code; Gradle
|
|
rejects non-integers, zero, and values above Android's limit. The Android
|
|
workflow tests both the default derivation and the override path.
|
|
|
|
## Local Run and Verification
|
|
|
|
Start an API 31+ emulator or attach a device, then run:
|
|
|
|
```bash
|
|
bash scripts/build-android.sh debug
|
|
adb install -r android/app/build/outputs/apk/debug/app-debug.apk
|
|
```
|
|
|
|
Before submitting Android work, run the Rust workspace gate from the repository
|
|
root, then:
|
|
|
|
```bash
|
|
bash scripts/build-android.sh check # ktlint, Android lint, host and screenshot tests
|
|
bash scripts/android-acceptance.sh --release # the on-device matrix, minified APK
|
|
```
|
|
|
|
`check` needs no device. Run the matrix with `--release` where you can: it
|
|
installs the signed minified APK and runs instrumentation against it, which is
|
|
the only path that exercises R8 — the step that has broken this app five distinct
|
|
ways that a debug build cannot reproduce. Without the `ANDROID_RELEASE_*`
|
|
variables below, drop `--release` and the debug APK is used instead. The matrix — phone and tablet layouts, rotation,
|
|
process recreation, playback and background controls,
|
|
download cancellation/resume, and an offline launch with networking disabled —
|
|
is `scripts/android-acceptance.sh`; `--list` prints every case and what it
|
|
needs, and `PSTR_ACCEPTANCE_ONLY=name` runs one. Five cases need a real share,
|
|
given as `PSTR_ACCEPTANCE_SHARE_URL` and `PSTR_ACCEPTANCE_SHARE_PASSWORD`; they
|
|
reach the device as instrumentation arguments and are stored nowhere. Without
|
|
them those cases report `skip`. There is no Picture-in-Picture: it was removed
|
|
after a PiP window that never sized its video right proved less use than
|
|
background audio and the lock-screen transport. `docs/TESTING.md` has the layer-by-layer
|
|
detail, including why lint runs against a baseline.
|
|
|
|
Requires JDK 17. `build-android.sh` locates it, because a newer JVM does not
|
|
fail with a version message — the Kotlin compiler throws
|
|
`IllegalArgumentException: 26.0.2` from an IntelliJ version parser.
|
|
|
|
## Release Signing
|
|
|
|
Release signing is optional locally and never stored in the repository. Set all
|
|
four variables before `bash scripts/build-android.sh signed`:
|
|
|
|
```text
|
|
ANDROID_RELEASE_STORE_FILE
|
|
ANDROID_RELEASE_STORE_PASSWORD
|
|
ANDROID_RELEASE_KEY_ALIAS
|
|
ANDROID_RELEASE_KEY_PASSWORD
|
|
```
|
|
|
|
GitHub Actions expects the keystore itself as the base64-encoded
|
|
`ANDROID_RELEASE_KEYSTORE_BASE64` secret and the other three values as secrets.
|
|
Keep the keystore and passwords in a durable external secret manager; losing the
|
|
key prevents users from upgrading an installed APK.
|
|
|
|
A `v*` tag runs `release.yml`, which calls `android.yml` with
|
|
`signed_release: true` and publishes the signed APK as
|
|
`proton-stream-<version>-android.apk` next to the desktop artifacts, together
|
|
with the `mpv-android-source-<rev>.tar.gz` corresponding source for the bundled
|
|
libmpv. The AAB is kept as the `android-bundle` workflow artifact only; nothing
|
|
uploads it to Play. A manual `android.yml` run with `signed_release` builds the
|
|
same signed files without publishing them.
|
|
|
|
## Licensing and Native Playback
|
|
|
|
The Android application is GPL-3.0-or-later; the shared Rust crates remain MIT.
|
|
See `android/LICENSE.md` and `android/THIRD_PARTY_NOTICES.md`. Before distributing
|
|
a binary with bundled libmpv, pin its exact source revision/build configuration,
|
|
package the notices, and retain the corresponding source plus build scripts
|
|
required by the GPL. The current playback service scaffold is not a substitute
|
|
for the final bundled libmpv integration.
|
|
|
|
`bash scripts/build-libmpv-android.sh` pins and stages libmpv for both supported
|
|
ABIs, plus a source archive and revision record, under
|
|
`android/app/build/generated/mpv`. Gradle runs that task before native builds,
|
|
packages the staged libraries, and compiles `pstr_mpv`, the JNI/EGL adapter. It
|
|
feeds libmpv through the Rust stream C ABI; decrypted bytes remain in native
|
|
memory. `PlaybackService` owns the mpv core for background audio and media
|
|
controls, while the activity supplies the current `Surface`.
|
|
Do not publish an Android binary until this exact pipeline is verified on real
|
|
arm64 hardware and an x86_64 emulator.
|