Skip to content
llmassistedPublic

About

AirPlay receiver for Android TV — GPL-3.0, 4K HEVC + HDR10, extended-display mode. Work in progress.

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

Tarmac

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.

Architecture

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.

Build

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:installDebug

Release build

A 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)

Install / sideload

Grab the signed APK from the latest release and sideload it:

adb install tarmac-v1.0.0.apk

Or, for a local debug build:

adb install app/build/outputs/apk/debug/app-debug.apk

Open 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.

Diagnostics

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_STATS

StrictMode thread + VM policies are installed on debuggable builds so disk/network on main, leaked closables, and activity leaks show up in logcat.

Known issues

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.

Remaining (deliberate deferrals)

  • 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.

By design (not bugs)

  • AudioPipeline uses WRITE_BLOCKING deliberately (back-pressures decode rather than dropping PCM). Only revisit as part of a receive/playback decoupling refactor.

License

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.

Status

  • 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.

About

AirPlay receiver for Android TV — GPL-3.0, 4K HEVC + HDR10, extended-display mode. Work in progress.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages