Cast it, it's there. Kick back with CastBay. · 一投即达,自在映湾
Website: castbay.weenas.com · User guide · How it works · Download: CastBay.apk (all releases) · Privacy policy
CastBay (Chinese: 映湾) turns an Android TV, TV box, car display or tablet into a receiver for iPhone, iPad and Mac: AirPlay screen mirroring, music and video, plus the cast button in video and music apps (DLNA). Free, open source, no ads.
- Screen mirroring from iPhone, iPad and Mac, up to 60 fps; H.265 up to 4K on 4K TVs with hardware HEVC decoding.
- Music: a blurred-cover backdrop, optional synced lyrics (via lrclib.net) and round playback controls; AirPlay music is lossless ALAC, played when the phone means it heard (as on an Apple TV), so lyrics stay in sync and pausing and resuming pick up where they left off.
- Video casting: apps' AirPlay video (e.g. YouTube, iQiyi) plays straight from the source, with audio track and subtitle choices.
- DLNA: the cast button in apps such as Bilibili, iQiyi, NetEase Cloud Music and QQ Music, from iPhone and Android phones.
- Remote or touch screen: a quick menu while playing (picture fit, playback stats, audio/subtitles, pause and skip for videos), opened with Down on a remote or a tap on a touch screen; media keys (a steering wheel's too) pause and change tracks; Back twice to stop, Home keeps playing. For mirroring, the stats include the phone's own report: frames sent and dropped, round trip, packet loss and bandwidth.
- Private: choose who can cast (anyone, devices allowed on the TV, a PIN shown on the TV the first time as on an Apple TV, or a password), allow or block each device, refuse or allow a second device; no account, and nothing is collected unless you choose to: problem reports (personal details taken out first) and anonymous usage statistics are both opt-in.
- English and Chinese.
- An Android device on Android 6.0 (API 23) or later: a TV, TV box, car display or tablet
- The sender on the same local network
Tested on TCL (Android 9) and Sony BRAVIA (Android 12) TVs and a BYD car display with iPhones.
Mac screen mirroring works, but music from the Mac's Music app can't be sent to CastBay: it uses a FairPlay type (2) that UxPlay can't handle (UxPlay#570).
AirPlay and DLNA video are fetched by the TV itself (as on an Apple TV), so the TV must be able to reach the video source directly. For YouTube that means googlevideo.com; on networks where the phone only reaches it through a proxy or VPN, the TV needs one too.
- AirPlay Protocol: UxPlay's
lib/(RAOP/RTSP/RTP, pairing, FairPlay, AirPlay video with FCUP) - JNI Bridge: Native C code interfaces with Android Java/Kotlin layer
- Video Decoding: Android MediaCodec (hardware accelerated)
- Audio Output: AudioTrack; AirPlay music is played at each frame's NTP time, mirroring audio as it arrives
- Video casting: Media3/ExoPlayer for AirPlay (HLS) and DLNA video and music
- DLNA: an in-app UPnP media renderer (SSDP, AVTransport/RenderingControl, GENA events)
- mDNS Discovery: Android NsdManager, publishing the TXT records UxPlay builds
# Clone, then the pinned dependencies: UxPlay, libplist, ALAC and OpenSSL
git clone git@github.com:weenas/castbay.git
cd castbay
git submodule update --init # top level only: OpenSSL's own submodules aren't needed
# Build (requires JDK 17-21, Android SDK platform 37, NDK 27.0.12077973, CMake 3.22.1, perl
# and make). The first build also compiles OpenSSL's libcrypto
# for each ABI (tools/build-openssl.sh, about a minute), then reuses it.
./gradlew assembleDebugFor an existing clone, initialize the pinned dependencies before building:
git submodule update --initDebug builds (com.weenas.castbay.debug, listed as "CastBay Dev") install beside the release app
and carry a simulated AirPlay sender. It feeds the app through the same callbacks the protocol
library uses, so admission, decoding, timed music playback, the screens, menus and stats all run
for real; only the network, pairing and encryption are skipped (those still need a real iPhone or
Mac). tools/sim drives it and the TV's remote over adb:
tools/sim install # build, install and open the debug app
tools/sim mirror 30 # 30 s of a test picture: a clock, a frame counter, a moving block
tools/sim music 60 "My Song" # a generated tune with title, artist, album and cover
tools/sim pause # pause / resume, as the phone would
tools/sim video # an HLS video (Apple's sample, with audio tracks and subtitles)
tools/sim stop # the sender disconnects
tools/sim crash # the app crashes (to test error reports)
tools/sim key down right ok # remote keys: up down left right ok back home menu playpause
tools/sim shot screen.png # screenshot
tools/sim log # CastBay's recent logUse -s SERIAL (or ANDROID_SERIAL) to pick the TV or an emulator. The debug app doesn't start
at boot, so it only runs while you test.
Versions follow semantic versioning: the middle number goes up for a release with new features (1.1.0), the last for a release that only fixes things (1.1.1), the first for big changes. versionName and versionCode (one more each release) in app/build.gradle.kts change only when releasing; a debug build's version names the commit it was built from (1.1.0-dev+6228385).
CI builds only what a change touches: the app (Android workflow) for app/, airplay/, third_party/ and the Gradle files, the website (Website workflow) for web/. The other job shows as skipped.
castbay/
├── app/ # Android application
│ ├── src/main/
│ │ ├── java/com/weenas/castbay/
│ │ │ ├── MainActivity.kt
│ │ │ ├── service/ # Service lifecycle and bridge
│ │ │ ├── ui/ # Compose UI screens
│ │ │ ├── viewmodel/ # MVVM ViewModels
│ │ │ └── receiver/ # BroadcastReceivers
│ │ ├── cpp/ # C++ native code (CMake)
│ │ └── res/ # Android resources
│ └── build.gradle.kts
├── airplay/ # Android library, JNI bridge, and native protocol build
├── third_party/ # Pinned UxPlay, libplist, ALAC and OpenSSL submodules
├── web/ # castbay.weenas.com (Astro), built and published by Cloudflare
├── branding/ # Logo and wordmark masters (SVG and PNG)
└── README.md
The website is built with Astro from web/ (Node 22.12 or later):
web/src/pages/: the pages. The user guide, How it works and the privacy policy are Markdown (guide.md,tech.md,privacy.md, and the same underzh/); the home pages areindex.astro.web/src/layouts/: the<head>, header, navigation and footer every page shares (Page.astro), and the layout of the Markdown pages (Doc.astro).web/src/i18n.ts: the words the shared parts use in each language.web/src/content/releases/<en|zh>/<version>.md: the release notes, shown on the changelog pages, used as the GitHub release notes, and, as plain text, in the app (About → What's New, from the latest ten versions inlatest.json): keep each bullet one self-contained point. Add both before tagging a release (vX.Y.Z); the release workflow refuses a full release without them. Add the same points, shorter (500 characters at most), asfastlane/metadata/android/<en-US|zh-CN>/changelogs/<versionCode>.txt, which F-Droid shows. The changelog also lists each APK's SHA-256, fetched from GitHub when the site is built; with a Cloudflare deploy hook URL in theCLOUDFLARE_DEPLOY_HOOKsecret, the release workflow rebuilds the site so a new release's appears at once. Each release also carriesCastBay-<version>-mapping.txt, R8's name map:retrace(Android SDK command-line tools) turns a release build's obfuscated crash stack trace back into source names with it.web/public/: styles, images, scripts,robots.txtandsitemap.xml, published as they are.
cd web
npm install # once
npm run dev # preview at http://localhost:4321, updating as you edit
npm run build # build into dist/, as Cloudflare doesCloudflare builds and publishes the site from web/ whenever main changes (web/wrangler.jsonc); CI checks that it builds. A new page also goes in public/sitemap.xml.
web/worker/index.js answers /api/*: problem reports (R2 bucket castbay-reports) and opt-in usage statistics (D1 database castbay-stats). For the maintainer, behind Cloudflare Access (an email login):
- https://castbay.weenas.com/stats: the usage statistics' totals (data:
/api/stats/summary). Also listed in the Access app launcher, https://easonxiang.cloudflareaccess.com. - A problem report by its ID:
cd web && npx wrangler r2 object get castbay-reports/reports/CB-XXXXXX.txt --remote --pipe(afternpx wrangler login). - Any question the page doesn't answer:
npx wrangler d1 execute castbay-stats --remote --command "SELECT …"; seedocs/design/usage-stats-and-reports.md.
fastlane/metadata/android/ holds the store listing (title, descriptions, icon, TV screenshots, per-version changelogs) in English and Chinese, which F-Droid reads. Store builds pass -Pcastbay.selfUpdate=false: no in-app updater and no update check, since the store updates the app.
Contributions are welcome: see CONTRIBUTING.md. Questions and ideas go to Discussions; security issues are reported privately (SECURITY.md). Everyone is expected to follow the code of conduct.
This project is licensed under the GNU General Public License v3.0. See the LICENSE file for details.
- UxPlay - AirPlay protocol library (mirroring, audio, HLS video)
- RPiPlay - the original core UxPlay's library grew from
- libplist - binary plist support
- Apple ALAC - the ALAC decoder for AirPlay music
- Media3 ExoPlayer - video and DLNA playback
- LRCLIB - synced lyrics
- Android Open Source Project - Base platform
This project is not affiliated with or endorsed by Apple Inc. or Google LLC. AirPlay, iPhone, iPad and Mac are trademarks of Apple Inc.; Android TV and Google TV are trademarks of Google LLC.

