<div align="center">
# shairplay-rust
**Pure Rust AirPlay server library**
[](https://github.com/metaneutrons/shairplay-rust/actions/workflows/ci.yml)
[](https://crates.io/crates/shairplay)
[](https://docs.rs/shairplay)
[](LICENSE)
[](https://www.rust-lang.org)
A complete AirPlay audio and video receiver as a Rust library. Supports both classic AirPlay (AP1) and AirPlay 2 (AP2) with buffered audio, encrypted transport, HomeKit pairing, and screen mirroring. `#![forbid(unsafe_code)]`.
**This is a clean-room Rust implementation β not a wrapper or FFI binding.** Every protocol handler, codec, and cryptographic primitive has been reimplemented from scratch in safe Rust. No C code from shairplay, shairport-sync, or any other project is linked or called.
</div>
---
> β οΈ **Pre-1.0 notice:** This crate is under active development. Minor version bumps (e.g. 0.1 β 0.2) may include breaking API changes. Pin your dependency to a specific minor version (`shairplay = "0.2"`) and review the [CHANGELOG](CHANGELOG.md) before upgrading.
## Features
| π΅ | **AirPlay 1 (Classic)** | ALAC decode, AES encryption, DACP remote control β rock solid |
| π΅ | **AirPlay 2** | Buffered AAC, SRP-6a pairing, ChaCha20-Poly1305 encrypted RTSP |
| π | **Multichannel** | 5.1 and 7.1 AAC decode with ITU-R BS.775 mixdown to stereo |
| π | **Resampling** | Automatic sample rate conversion via rubato |
| π | **HomeKit pairing** | Transient (PIN 3939) and normal (persistent key storage) |
| πΊ | **Video** | Screen mirroring β work in progress, behind `video` feature gate |
| π¬ | **HLS Video** | YouTube and other HLS streams β receiver relays URL to app |
| π | **Cross-platform** | macOS (native Bonjour) + Linux (pure Rust mDNS) |
| π | **Pure safe Rust** | `#![forbid(unsafe_code)]`, no C code in this crateΒΉ |
| β‘ | **Async** | Built on [tokio](https://tokio.rs) |
> ΒΉ The crate itself contains no `unsafe` code. On macOS, mDNS service registration uses [astro-dnssd](https://crates.io/crates/astro-dnssd), which internally calls Apple's Bonjour C API via FFI. On Linux, [mdns-sd](https://crates.io/crates/mdns-sd) provides a pure Rust mDNS implementation with no native dependencies.
## Quick Start
### AirPlay 1 (Classic)
```rust
use std::sync::Arc;
use shairplay::{RaopServer, AudioHandler, AudioSession, AudioFormat};
struct MyHandler;
impl AudioHandler for MyHandler {
fn audio_init(&self, format: AudioFormat) -> Box<dyn AudioSession> {
println!("Stream: {}ch {}bit {}Hz", format.channels, format.bits, format.sample_rate);
Box::new(MySession)
}
}
struct MySession;
impl AudioSession for MySession {
fn audio_process(&mut self, samples: &[f32]) {
// F32 interleaved PCM β same format for AP1 and AP2
}
}
#[tokio::main]
async fn main() -> Result<(), shairplay::ShairplayError> {
let mut server = RaopServer::builder()
.name("My Speaker")
.build(Arc::new(MyHandler))?;
server.start().await?;
tokio::signal::ctrl_c().await.unwrap();
server.stop().await;
Ok(())
}
```
### AirPlay 2
```rust
// AP2 adds resampling and multichannel mixdown.
// Output is always f32 interleaved PCM β same as AP1.
let mut server = RaopServer::builder()
.name("My Speaker")
.output_sample_rate(48000)
.output_max_channels(2)
.build(Arc::new(MyHandler))?;
```
## Builder Options
| `.name()` | `"Shairplay"` | | AirPlay display name |
| `.hwaddr()` | random locally administered address | | 6-byte MAC address for mDNS |
| `.port()` | `5000` | | RTSP listening port |
| `.password()` | none | | HTTP Digest auth password |
| `.max_clients()` | `10` | | Maximum concurrent connections |
| `.bind()` | all interfaces | | Bind to specific IPs (multi-interface) |
| `.output_sample_rate()` | source rate | `resample` | Resample all audio to this rate |
| `.output_max_channels()` | source channels | `resample` | Mix down to this channel count |
| `.pin()` | `"3939"` | `ap2` | PIN for HomeKit pairing |
| `.pairing_store()` | `MemoryPairingStore` | `ap2` | Persistent key storage |
| `.video_handler()` | none | `video` | Video session factory |
| `.hls_handler()` | none | `hls` | HLS video playback handler |
## Feature Flags
| *(default)* | β | AirPlay 1 only |
| `resample` | rubato | Sample rate conversion + channel mixdown |
| `ap2` | chacha20poly1305, hkdf, symphonia, β¦ (implies `resample`) | Full AirPlay 2 audio |
| `video` | (implies `ap2`) | Legacy feature set for screen mirroring (`0x527FFEE6`) |
| `hls` | (implies `video`) | HLS video playback (YouTube, etc.) β receiver relays URL to app |
## Implementation Status
### β
AirPlay 1 β Production Ready
Rock solid. ALAC decoding, AES encryption, DACP remote control, metadata (artwork, progress, track info). Works with iPhone, iPad, Mac, iTunes.
### β
AirPlay 2 Audio β Production Ready
Full pipeline: SRP-6a pairing β encrypted RTSP β FairPlay β PTP timing β buffered AAC decode β f32 PCM output. Multichannel 5.1/7.1 with ITU-R BS.775 mixdown. Automatic resampling. Both stream types implemented:
- **Type 103 (buffered)** β AAC over TCP with timed playout buffer. Used for music.
- **Type 96 (realtime)** β ALAC over UDP with immediate delivery. Used for Siri, phone calls, system sounds.
Connections are **instant** (starts streaming in under 50ms) using deterministic key persistence and correct `eventPort` handling on `isRemoteControlOnly` channels, matching commercial receiver capabilities.
### π§ͺ Video (Screen Mirroring) β Work in Progress
Behind the `video` feature gate. Screen mirroring uses the legacy/UxPlay-compatible
feature set; AP2 buffered audio is intentionally disabled while video is enabled.
The video feature switches to a UxPlay-compatible legacy feature set
(`0x527FFEE6`) to receive screen mirroring data from iOS 18+. AP2
buffered audio is not available β the iPhone falls back to legacy ALAC
(type 96) which is fully supported with FairPlay key decryption and
NTP timing.
Video decryption is implemented for the legacy feature-set path; AP2+video
hybrid operation remains research work. See
[AP2-STATUS.md](AP2-STATUS.md) and [VIDEO-RESEARCH.md](VIDEO-RESEARCH.md)
for details.
### π¬ Remote Control
AP1 DACP remote control is fully implemented and works.
Third-party AP2 receivers cannot send playback commands (play/pause/skip) to the iPhone. All paths require Apple ecosystem trust:
- **Type 130 MRP data channel** β requires HomeKit seed (Home app pairing)
- **Companion-link protocol** β requires same Apple ID
- **DACP** β iPhone doesn't send Active-Remote header in AP2
See [AP2-STATUS.md](AP2-STATUS.md) for the full research. If you have further insights or want to help investigate, please reach out!
## Example Player
The included example plays AirPlay audio through the system's default output device:
```bash
# AirPlay 1
cargo run --example player
# AirPlay 2
cargo run --example player --features ap2
# With resampling to match output device rate (e.g. 44100β96000 Hz)
cargo run --example player --features ap2 -- --resample
# With persistent device identity (stable MAC + paired keys across restarts)
cargo run --example player --features ap2 -- --persist state.json
# Custom name and interface binding
cargo run --example player --features ap2 -- --name "Kitchen" --bind 192.168.1.100 --persist state.json --resample
# HLS video (YouTube) β requires mpv installed
cargo run --example player --features hls -- --resample
```
Without `--persist`, the device gets a random MAC each run and the iPhone treats it as a new device. With `--persist`, the MAC and paired keys are saved to a JSON file.
Without `--resample`, audio is delivered at the source's native rate (44100 Hz). If your output device runs at a different rate (e.g. 96000 Hz), use `--resample` to convert in the example app.
## Architecture
``` plain
src/
βββ raop/ RAOP server, RTSP handlers, RTP streaming
β βββ buffered_audio AP2 timed playout buffer with decrypt/decode/resample
β βββ event_channel AP2 encrypted event channel
β βββ video Video handler traits (experimental)
β βββ video_stream Video stream receiver (experimental)
βββ crypto/ RSA, Ed25519+Curve25519, AES, FairPlay
β βββ pairing_homekit AP2 SRP-6a + HomeKit pairing + pair-verify
β βββ chacha_transport AP2 ChaCha20-Poly1305 encrypted RTSP
β βββ video_cipher AES-128-CTR streaming cipher for video
β βββ tlv AP2 TLV codec for pairing messages
βββ codec/ Audio decoders
β βββ alac ALAC decoder (AP1)
β βββ aac AAC decoder via symphonia (AP2)
β βββ resample Sample rate conversion + channel mixdown
βββ proto/ SDP, HTTP/RTSP, binary plist, HTTP Digest auth
βββ net/ Async TCP server, mDNS, PTP timing, feature flags
βββ dacp/ DACP remote control client (AP1)
βββ error/ Error types
```
## Test Coverage
152 tests including 17 C-verified pairing vectors from [pair_ap](https://github.com/ejurgensen/pair_ap) and 10 C-verified FairPlay vectors generated from the original [shairplay](https://github.com/juhovh/shairplay) C source:
```plain
cargo test # AP1 tests
cargo test --features ap2 # AP1 + AP2 tests
cargo test --features video # All tests
```
## Development
```sh
git config core.hooksPath .githooks
```
This enables pre-commit (auto-format) and pre-push (fmt check + clippy + audit) hooks.
## Acknowledgments
This project builds on the work of many contributors to the AirPlay open-source ecosystem:
- **[shairplay](https://github.com/juhovh/shairplay)** β Original C library this project is a complete rewrite of
- **[shairport-sync](https://github.com/mikebrady/shairport-sync)** β AirPlay 2 C reference implementation by Mike Brady
- **[pair_ap](https://github.com/ejurgensen/pair_ap)** β HomeKit pairing reference (SRP-6a, TLV, HKDF) by Scott Ickle / ejurgensen. 17 C-verified test vectors generated from this codebase
- **[AirPlay 2 Internals](https://emanuelecozzi.net/docs/airplay2/)** β Protocol documentation by Emanuele Cozzi
- **[Unofficial AirPlay Specification](https://openairplay.github.io/airplay-spec/)** β Legacy protocol documentation
- **[rairplay](https://github.com/r4v3n6101/rairplay)** β Rust AirPlay 2 receiver with video support
- **[pyatv](https://github.com/postlund/pyatv)** β Python Apple TV library (companion-link protocol research)
- **[Dissecting the Media Remote Protocol](https://edc.me/posts/dissecting-the-media-remote-protocol/)** β Evan Coleman's Apple TV reverse engineering
## License
LGPL-3.0-or-later
## Disclaimer
All resources in this repository are written using only freely available information from the internet. The code and related resources are meant for educational purposes only. It is the responsibility of the user to make sure all local laws are adhered to. Parts of this project are generated with the help of AI and the expert-in-the-loop approach.