High-quality AirPlay receiver for Android TV. Point your MacBook's Screen Mirroring at Tarmac and get:
- 1080p60 and 4K (HEVC) mirroring
- HDR10 passthrough
- Hardware-tunneled HEVC decode for drift-free A/V sync
- Extended-display mode — "Use As Separate Display" in macOS's AirPlay menu uses the TV as a second monitor
- AirPlay Video streaming (Photos, Apple TV app, etc.)
Built for the Android TV boxes you actually use — Chromecast with Google TV, NVIDIA Shield, Fire TV Cube — without the stutter and sync problems of AirScreen.
Latest release: v1.0.0 — signed APK available on the release page.
Android TV app (Kotlin + Leanback)
└─ TarmacService (foreground)
└─ JNI → native AirPlay server (UxPlay lib/)
└─ MediaCodec (tunneled) + AudioTrack
The AirPlay protocol stack is UxPlay's lib/ directory (RTSP + RAOP + FairPlay SAP + pair-setup), pulled in as a git subtree. UxPlay's GStreamer-based renderers are replaced with Android MediaCodec and AudioTrack to get hardware A/V sync, low latency, and HDR passthrough.
Requires Android Studio Hedgehog (2023.1.1) or newer, NDK 26.3.11579264, CMake 3.22. The native RAOP/RTSP protocol stack (UxPlay's lib/) and libplist are vendored under native/, and OpenSSL ships via a prefab AAR — no subtree pull is needed.
# Debug build (Android Studio debug keystore; fine for local install):
./gradlew :app:assembleDebug
# Install on a connected Android TV device:
./gradlew :app:installDebugA release build is signed with a keystore referenced from ~/.gradle/gradle.properties (or -P flags). Missing properties cleanly degrade to an unsigned APK.
# ~/.gradle/gradle.properties
TARMAC_RELEASE_STORE_FILE=/absolute/path/to/tarmac-release.jks
TARMAC_RELEASE_STORE_PASSWORD=...
TARMAC_RELEASE_KEY_ALIAS=tarmac
TARMAC_RELEASE_KEY_PASSWORD=..../gradlew :app:assembleRelease
# → app/build/outputs/apk/release/app-release.apk (signed, APK Signature Scheme v2)Grab the signed APK from the latest release and sideload it:
adb install tarmac-v1.0.0.apkOr, for a local debug build:
adb install app/build/outputs/apk/debug/app-debug.apkOpen Tarmac on the TV. It advertises itself via Bonjour as Tarmac by default (configurable under Settings). On your Mac, open Control Center → Screen Mirroring, pick Tarmac, enter the on-screen PIN.
For extended desktop, open the Screen Mirroring submenu again while mirroring and choose Use As Separate Display.
Debug builds expose runtime state two ways:
# Full diagnostics report (sessions, codec stats, wake-lock state, network callback):
adb shell dumpsys activity service com.tarmac/.service.TarmacService
# Same report mirrored to logcat via a NOT_EXPORTED broadcast:
adb shell am broadcast -p com.tarmac -a com.tarmac.action.DUMP_STATSStrictMode thread + VM policies are installed on debuggable builds so disk/network on main, leaked closables, and activity leaks show up in logcat.
A full-codebase review (2026-06-02, source-verified) targeted the three reported symptoms — first connection failing, mid-session stall requiring an AirPlay restart, and choppy A/V. All confirmed findings were fixed in v1.0.25: the VideoPipeline codec-lifecycle races (atomic teardown with callback-thread join and generation-guarded buffers, synchronized pre-configure state, GOP-safe overflow, eager recoverable-error reset), the AudioPipeline codec lock and failed-create retry, the reconnect-flap mirror teardown, main-thread ANR on restart, session-state binder IPC under the JNI lock, JNI exception safety, and the wake-lock / max-picture-size hardening.
Fixes are verified by static analysis and build; not yet reproduced/validated on Android TV hardware.
- MEDIUM (bounded) — NTP/PTS timing uses
CLOCK_REALTIME(native/libairplay/lib/raop_ntp.c:504); a system clock step causes a one-time judder burst, not a permanent freeze (the pacing anchor re-anchors). Wants a monotonic clock for the local pacing timeline — a wall-vs-monotonic split across the JNI boundary.
AudioPipelineusesWRITE_BLOCKINGdeliberately (back-pressures decode rather than dropping PCM). Only revisit as part of a receive/playback decoupling refactor.
GPL-3.0-only. UxPlay is GPL-3.0, so any build that includes the subtree must also be distributed under GPL-3.0. The UI/service layers (Kotlin code in app/) are GPL-3.0 for consistency.
See LICENSE for the full text.
- Phase 1 — Android TV scaffolding + native build skeleton
- Phase 2 — JNI bridge, MediaCodec video, AudioTrack audio, Bonjour
- Phase 3 — Leanback UX (pairing PIN, status, settings)
- Phase 4 — Extended display + AirPlay Video (ExoPlayer)
- Phase 5a — 4K HEVC + HDR10 static metadata + tunneled playback
- Phase 5b — Endurance hardening (session-scoped wake-lock, Wi-Fi roam recovery, pipeline fault self-healing, JNI callback lifecycle safety, dumpsys + adb diagnostics)
Shipped as v1.0.0.