Android remote control for a running Feishin desktop player. It connects directly to Feishin's Remote WebSocket API; audio continues to play on the computer.
- Play, pause, previous, next, seek, volume, shuffle, and repeat controls
- Live artwork, favorite state, rating, and playback position
- Track year, genre, format, bitrate, sample rate, bit depth, track/disc number, and play count
- Multiple saved Feishin server profiles
- Password encryption through Android Keystore
- Automatic reconnect with bounded exponential backoff
- Android notification, lock-screen, headset, and Bluetooth media controls
- Persistent light and dark themes
- Android device and Feishin computer on the same network
- Feishin desktop application; Remote is not exposed by the browser build
- Feishin Remote enabled and reachable through its configured port (
4333by default)
- Open Settings → Window in Feishin.
- Scroll to the Remote section.
- Enable Remote control server.
- Note the port and optionally configure a username and unique password.
- Find the computer's LAN address, such as
192.168.1.20.
The resulting address is normally:
http://192.168.1.20:4333
- Enter the Remote URL and optional credentials.
- Tap Save server to retain the profile. The address and username use app preferences; the password is stored separately using Android Keystore encryption.
- Tap Connect.
- Select another saved profile after disconnecting. The last selected profile reconnects automatically when the app starts.
Unexpected disconnects retry after 2, 4, 8, 16, and then 30 seconds, stopping after eight attempts. A manual disconnect cancels retries.
Android media controls become available when Feishin reports a current track. They control Feishin remotely and do not play audio on the phone.
Feishin serves HTTP and WebSocket traffic without TLS by default. Basic Auth credentials can therefore be observed by other devices on an untrusted network.
- Use a unique Remote password.
- Use the app only on a trusted LAN, or place Feishin behind an HTTPS/WSS reverse proxy.
- Passwords are never written to SharedPreferences or application logs.
- Android cloud backup is disabled to avoid restoring encrypted values without their Keystore keys.
Prerequisites:
- Flutter 3.44 or newer
- Android SDK
- Java 17
Install packages and run:
flutter pub get
flutter runRun checks:
dart format --output=none --set-exit-if-changed lib test
flutter analyze
flutter testBuild a debug APK:
flutter build apk --debugOutput:
build/app/outputs/flutter-apk/app-debug.apk
.github/workflows/android-ci.yml validates every relevant push and pull request. It checks formatting, runs analysis and tests with coverage, builds a debug APK, and uploads the APK and coverage report as workflow artifacts.
.github/workflows/android-release.yml creates signed GitHub Releases from v*.*.* tags or a manual workflow dispatch. Release builds are obfuscated, signature-verified, and published with a SHA-256 checksum. Obfuscation symbols are retained as a private workflow artifact for crash symbolication.
Generate the key once and keep an offline backup:
keytool -genkeypair -v \
-keystore release.jks \
-alias feishin-remote \
-keyalg RSA \
-keysize 4096 \
-validity 10000Add these encrypted repository secrets under GitHub → Settings → Secrets and variables → Actions:
| Secret | Value |
|---|---|
ANDROID_KEYSTORE_BASE64 |
Base64-encoded contents of release.jks |
ANDROID_KEYSTORE_PASSWORD |
Keystore password |
ANDROID_KEY_ALIAS |
Key alias, such as feishin-remote |
ANDROID_KEY_PASSWORD |
Private-key password |
Create the Base64 value on Linux with:
base64 -w 0 release.jksNever commit the keystore or passwords. The workflow decodes the key only for the build and removes it afterward.
Create and push a SemVer tag:
git tag -a v1.0.0 -m "Feishin Remote 1.0.0"
git push origin v1.0.0The tag determines the Android version name. The GitHub run number becomes the monotonically increasing version code. A release can also be started manually from Actions → Android Release → Run workflow.
export ANDROID_KEYSTORE_PATH="$PWD/android/app/release.jks"
export ANDROID_KEYSTORE_PASSWORD='your-keystore-password'
export ANDROID_KEY_ALIAS='feishin-remote'
export ANDROID_KEY_PASSWORD='your-key-password'
flutter build apk \
--release \
--obfuscate \
--split-debug-info=build/symbols/local
unset ANDROID_KEYSTORE_PASSWORD ANDROID_KEY_PASSWORDA release build intentionally fails when any signing variable is absent rather than producing a debug-signed release.
lib/app_storage.dart Saved profiles, theme preference, secure passwords
lib/feishin_remote.dart WebSocket protocol, state parsing, reconnect logic
lib/remote_audio_handler.dart Android media-session bridge
lib/remote_app.dart Material UI
assets/logo.svg Editable logo source
assets/logo.png In-app and README logo
