Open-source macOS menubar app for experimental multi-device audio routing.
Local Stereo is the stable path today. Local + AirPlay sync is active R&D.
You have a HomePod in the living room, an AirPlay speaker in the kitchen, and a USB DAC in the bedroom. You want to play one song everywhere from your Mac, without giving up local speakers.
macOS gives you two half-solutions:
- Audio MIDI Setup → Multi-Output Device — works for local outputs, but AirPlay 2 receivers drift and there's no per-device volume.
- Control Center AirPlay multi-room — works for AirPlay 2 receivers only. The moment you AirPlay anywhere, you lose your local speakers.
Neither gives you a dependable Local + AirPlay mix with per-device control. SyncCast is an alpha attempt at that, with a stable local Stereo mode and an experimental AirPlay mode.
- Captures the system audio stream on macOS for AirPlay/capture-dependent paths. Local Stereo now defaults to a Direct Stereo CoreAudio output path so local video playback does not need ScreenCaptureKit or Screen Recording.
- Routes the captured stream to multiple destinations simultaneously:
- Local CoreAudio outputs (built-in speakers, USB / HDMI / Thunderbolt DACs)
- AirPlay 2 receivers (HomePod, Apple TV, Xiaomi Sound, third-party speakers, other Macs running AirPlay Receiver)
- Two mutually-exclusive modes, swapped in one click:
- AirPlay experimental mode — local + AirPlay routing through the OwnTone-backed AirPlay pipeline. Multiple AirPlay receivers are handled by AirPlay's own timing domain; the local leg is slaved to that same clock domain by a ring-level control loop, with a per-output millisecond trim for listening-position differences.
- Stereo mode — local CoreAudio outputs only, defaulting to Direct Stereo. This is the currently stable path and is suitable for video.
- Acoustic (microphone) measurement was retired on 2026-08-09. SyncCast never opens the microphone and never plays calibration tones; alignment comes from the OwnTone clock domain instead.
- Lives quietly in the menubar. Pure user-space Swift + a small Python sidecar.
┌─────────────────────────────────────────────────────────────┐
│ Any macOS app (Music, Spotify, Safari, Mpv, …) │
└────────────────────────────┬────────────────────────────────┘
│ System audio
▼
┌─────────────────────────────────────────────────────────────┐
│ Capture backend ── SCK today, Process Tap in progress │
└────────────────────────────┬────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ SyncCast Router (Swift, user-space actor) │
│ • Ring buffer + per-mode reconciliation │
│ • Mode toggle: whole-home (AirPlay) vs stereo (local) │
│ • IPC bridge to Python sidecar over Unix socket │
└──────┬─────────────────────────────────┬────────────────────┘
│ CoreAudio │ Unix socket + PCM FIFO
▼ ▼
┌──────────────────────┐ ┌──────────────────────────────────┐
│ Aggregate Device │ │ Python sidecar (pyatv + OwnTone) │
│ → built-in speakers │ │ AirPlay 2 RTSP / PTP sender │
│ → USB / HDMI DACs │ └────────────┬─────────────────────┘
└──────────────────────┘ │ AirPlay 2
▼
HomePod · Apple TV · 3rd-party AirPlay · other Macs
Sub-components:
| Component | What it does |
|---|---|
apps/menubar/ |
SwiftUI menubar app: device picker, mode toggle, volume controls. |
core/router/ |
Audio capture, ring buffer, routing actor. |
core/discovery/ |
CoreAudio + Bonjour device enumeration. |
sidecar/ (Python) |
Wraps pyatv (discovery / pairing) and OwnTone (PTP-locked multi-target AirPlay 2 sender). |
proto/ |
JSON-RPC schemas exchanged over the Unix socket. |
- macOS 14 (Sonoma) or later — required for the current alpha.
- Screen Recording permission — not required for the default local Stereo path. It is still required for ScreenCaptureKit fallback/capture-dependent paths such as AirPlay unless Process Tap is selected.
- Microphone permission — never requested. No code path opens the microphone, and the app bundle carries no
NSMicrophoneUsageDescription. - Xcode 15+ and Python 3.11+ — only if you're building from source.
- An AirPlay 2 receiver and/or a CoreAudio output device — preferably both, that's the point.
Pre-built .app bundles are published as GitHub Releases:
github.com/vcxzvfe/syncast/releases
The latest alpha is signed with a self-signed certificate. To run it:
unzip SyncCast.app.zip
mv SyncCast.app /Applications/
xattr -dr com.apple.quarantine /Applications/SyncCast.app
open /Applications/SyncCast.appOr build from source — see below.
SyncCast isn't notarized yet, so you build it locally. Three steps:
# 1) Clone and bootstrap (BlackHole + OwnTone + Python deps)
git clone https://github.com/<your-user>/syncast.git
cd syncast
./scripts/bootstrap.sh
# 2) Build the Swift menubar binary
( cd apps/menubar && swift build -c release )
# 3) Package as a .app bundle and install to /Applications
bash scripts/package-app.sh # produces dist/SyncCast.app
bash scripts/install-app.sh # copies to /Applications/SyncCast.app and re-signsThen launch:
open /Applications/SyncCast.appWhy install to
/Applications? macOS Tahoe's TCC silently denies capture permissions for apps running from arbitrary paths.install-app.shalso re-signs in place so the signature matches the final bundle path.
Development installs use ad-hoc signing by default. That is fine for the default local Stereo / Direct Stereo path, which does not need Screen Recording. If you need stable TCC grants while testing SCK fallback or other capture-dependent paths, create a self-signed code-signing identity named SyncCast Dev and run package/install with SYNCAST_USE_SYNCCAST_DEV=1.
- Launch SyncCast. Look for the icon in the macOS menubar.
- Grant Screen Recording only if you use an SCK capture path and macOS prompts for it, then quit and reopen once.
- Pick a mode in the popover:
- AirPlay experimental — AirPlay receivers plus selected local outputs. Expect added latency; per-output delay trims are available for fine alignment.
- Stereo — local outputs only, low-latency aggregate device, suitable for video.
- Tick the devices you want. Discovery runs continuously; new AirPlay receivers and audio devices appear within a few seconds.
- Play music from anything — Music.app, Spotify, a browser tab, mpv. In Stereo, macOS routes audio through the Direct Stereo output; capture-dependent modes use the selected capture backend.
Alpha. Experimental. Use at your own risk.
What works:
- Local Stereo default path that bypasses ScreenCaptureKit
- System audio capture via ScreenCaptureKit for fallback/capture-dependent paths
- Local Stereo routing through an Aggregate Device
- AirPlay 2 multi-target streaming via the OwnTone-backed sidecar
- Mode switching, device discovery, per-device volume
- Local
.appbundling with self-signed codesigning
What's still rough:
- Local + AirPlay alignment now rides the OwnTone clock domain and has been verified by ear on one setup only; it has not been validated across a range of receivers or long sessions.
- ScreenCaptureKit can trigger DRM playback blocks; Local Stereo now defaults to Direct Stereo, while Process Tap / AirPlay capture validation remains in progress.
- Not notarized — Gatekeeper warnings are normal on first launch.
- No first-run wizard yet;
bootstrap.shis the on-ramp. - AirPlay device pairing flow is minimal (relies on
pyatv). - Architecture is stable but tests against real receivers are still partly manual.
See docs/ROADMAP.md for what's next.
docs/ARCHITECTURE.md— full system design.docs/ROADMAP.md— phased plan and current status.docs/adr/— Architecture Decision Records (one per cross-cutting choice).docs/HANDOFF.md— current state and open threads.proto/— IPC schemas between the Swift router and the Python sidecar.sidecar/README.md— sidecar internals and protocol.
Issues, ADRs, and PRs are welcome. See CONTRIBUTING.md. Code style is SwiftPM defaults for Swift and ruff + mypy --strict for Python; one ADR per cross-cutting design change.
MIT © 2026 Zifan and SyncCast contributors.
- ScreenCaptureKit — Apple's modern system-wide audio capture API.
- OwnTone (forked-daapd) — the only open-source AirPlay 2 sender today that can PTP-lock multiple receivers to a single master.
- pyatv — AirPlay 2 / HAP discovery and pairing.
- Built with the Claude Code multi-agent workflow — planning, implementation, review, and packaging coordinated across parallel worktrees.