Skip to content

Takiyo0/kei

Repository files navigation

Kei

Kei is a multi-room AirPlay 2 Server in a single process written in Go. Kei is a heavily modified version of on Goplay by openairplay. Kei is not perfect yet, and that's exactly why I'll never stop improving it. See Screenshots

Home page

Kei on a happy family

Table of Contents

Quick Start

  1. Read How to use
  2. If 24/7 usage, read Run Kei 24/7 (Recommended)
  3. If the sender cannot discover Kei, read I can't find the AirPlay server on my device
  4. Read Kei's Current Limitation Summary

Requirements

  • A device that can run portaudio backend
  • Linux audio backend (PulseAudio / PipeWire-Pulse / ALSA)*
  • AirPlay sender (iPhone/iPad/Mac)
  • ~20–50mb RAM approx. per AirPlay server (15mb for base Kei)

Note:

  • *: On Linux, Kei uses what PortAudio can open on your system. I tested mainly with PulseAudio (and PipeWire via pipewire-pulse compatibility). ALSA also works (including -tags=embedded build path).

Features

Main Features

  • PTP synced, aka. audio sync based on sent timing + 0ms latency
  • multi-AirPlay/room server with just a single process
  • Run the whole home speaker system from a centralized machine with no cost
  • Configurable channel playback per AirPlay server, allowing synced stereo setup
  • supports any audio playing app that supports audio AirPlay
  • Configurable audio, net. interface per AirPlay server
  • Kiosk mode
  • web interface

Other features

  • Play AAC and ALAC
  • Play/Pause/Stop/Seek/Volume
  • Homekit pairing with iPhone Home App (it just appears, not controllable)
  • Using portaudio and support alsa for minimal linux (for alsa, you build yourself go build -tags=embedded -o kei .)
  • Supports RTP over UDP and TCP
  • Windows, MacOS, Linux compatible
  • Track information available

May Come Soon

  • sync!!!
  • Multi-instance sync between Kei(s) where one instance becomes leader
  • Run on OpenWRT routers (I'm trying)
  • Video (will take a while)

Not Yet Supported

  • Screen, Video, ... (other Airplay Features)
  • Airplay 1
  • Sample rate other than 44,100 Hz

How to use

Installation

  1. Download the latest release from GitHub Releases for your OS/arch.
  2. Install runtime dependencies:
    • Darwin (arm64/amd64):
      brew install portaudio fdk-aac
    • Linux (amd64/arm64):
      # Debian/Ubuntu example
      sudo apt update
      # runtime dependencies for release binary
      sudo apt install -y libportaudio2 libfdk-aac2
      # optional: only if you build Kei from source
      sudo apt install -y portaudio19-dev libfdk-aac-dev
    • Windows (amd64): no extra dependency needed

Run

  1. Create a folder for Kei and place the binary there (Kei stores config/state beside runtime).
  2. Run based on your OS:
    • Windows:
      • Run kei-<version>-windows-amd64.exe
      • Allow firewall access when prompted
    • macOS:
      chmod +x kei-<version>-darwin-<arch>
      ./kei-<version>-darwin-<arch>
    • Linux:
      chmod +x kei-<version>-linux-<arch>
      # allow Kei to bind to port 319 and 320 for ptp
      sudo setcap cap_net_bind_service=+ep kei-<version>-linux-<arch>
      ./kei-<version>-linux-<arch>
      if you're planning to run 24/7, see Run Kei 24/7 (Recommended).
  3. Open dashboard in browser: http://localhost:6769 (you can change the port in the config file)
  4. First run setup:
    • Set an admin password
    • Create your first AirPlay server
    • Pick a network interface and audio output device
    • Keep playback mode at 1 (recommended)
  5. On a sender device (iPhone/iPad/Mac), open the AirPlay picker and select your Kei server.

FAQ for running Kei:

Architecture

kei architecture

Kei Architecture

Feature Details

Multi-AirPlay Servers

Kei has the ability to run multiple AirPlay servers within a single process, meaning it’s enough to run once and a multi-AirPlay server is deployed. Each AirPlay instance has a separated configurable audio device and network interface.

True Stereo Modes

With two AirPlay servers, you can configure them to run as a stereo setup by setting one to play left channel and the other to play right channel through web interface, and make sure the Playback Mode to be at least 1 or 2. With this setup, you can achieve true stereo AirPlay.

Possible issues:

Playback Modes

idx label explanation
0 flow - Kei still anchor on START/SETRATEANCHORTIME
- after that Kei will flow with incoming buffer and ignore progress seek/strict timeline guardrail
1 semi strict - Kei will resync when sender send progress event
- Kei does not use it's timeline feature
2 strict - kei will resync when sender send progress event
- Kei will use it's timeline feature

Recommended

  • recommended default (most users): mode 1
  • if you prioritize smooth over sync: mode 0 (*sometimes what sender sends is not smooth)
  • if you prioritize sync over smooth: mode 2

How Timing Works

  • SETRATEANCHORTIME is used as the main anchor when playback starts / sender switches timeline.
  • SETPARAMETER (progress/current) is used for seek/progress correction depending on playback mode.
    • mode 0 still anchor at start, but after that it flows.
    • mode 1 follows seek/progress correction without strict timeline feature.
    • mode 2 follows seek/progress correction with strict timeline feature.

Best Playback Mode by Use Case

use case best mode note
Apple Music AutoMix 1 most balanced between sync and smooth
daily listening (mixed apps) 1 safest default
prioritize smoothest playback 0 can drift more
prioritize strictest sync 2 can get chunky during sender inconsistency

Playback Channels

idx label explanation
0 both - Kei will play both channel of audio
1 left - Kei will play just the left channel of audio
2 right - Kei will play just the right channel of audio

Clock Offset

Kei has a built-in clock offset feature that allows you to adjust the time difference between sender and Kei during playback. The default offset is 350ms because it is what works best on my machine. You will feel the difference when you seek or start playing, aka during anchoring. Setting it higher will make Kei play earlier, while setting it lower will make Kei play later.

Environments

Tested Working

  • Apps
    • TCP: Apple Music, Apple TV, Netflix, Podcasts, Radio Garden, X, YouTube
    • UDP: Spotify
  • Sender Devices
    • iPhone 15 Plus (iOS 26) (0ms latency***)
    • iPhone 11 (iOS 26) (0ms latency***)
    • iPhone 8 (iOS 16) (0ms latency***)
    • MacBook Pro 2017 (macOS Sequoia)*

Untested

  • Multi-room with other AirPlay speakers (e.g., Apple TV, HomePod, etc.)**
  • AirPlay from Apple TV**
  • Kei hosted on ARM devices (e.g., Raspberry Pi, Apple Silicon, etc.)**

Won't work

  • Microcontrollers (e.g., ESP32, Arduino, etc.) due to small memory and processing power

Note:

  • *: Kei works with both AirPlay-ing from direct video / audio or as an output device. But when you use Kei as an output device and play video / audio, you'll notice Kei plays ~1-1.5s ahead of the sender. I'm assuming it's necessary during SETUP or mDNS to tell "hardware delay", but I haven't found a way to fix it.
  • **: I don't have those devices to test, but if you have and want to test, please let me know the result!
  • ***: The latency we can't feel that there is a latency. Perfect for watching on media streaming platform

Web Interface

Kei has a web interface that allows you to configure each AirPlay server configuration and manage AirPlay servers. During first initialization, you'll get asked to set the admin password. With the admin password, you can log in to the web interface and manage AirPlay servers and also set the Kiosk password. See Dashboard Screenshots

Kiosk Mode

Kei has Kiosk mode implemented by default. It is useful when you want to put it on TV or other devices that you wish to show aesthetic yet useful playback information for an AirPlay server.

kiosk mode

Kiosk mode page

If you want to use Kiosk mode on unsecure devices, you can set the Kiosk password in the web interface logged in as admin. Once the password is set, you can log in with the Kiosk password to access the web interface. This locks all endpoints but getting airplay information. Kiosk password has an advantage to not get logged out when you restart Kei.

Setup Example

Worth to Try Configuration for Kei on Multiple Devices

Note: this configuration is based on my device and might be different for your device. But you can try it

Device Playback Mode Clock Offset
Windows (20@4.9ghz 32G laptop) 1 350ms
MBP 2017 (4@3.6ghz 8G laptop) 1 170ms
Old Mini PC (4@1.7ghz 2G laptop) 1 70ms

These measurements are fine to my ear, but there might be a slight ~10ms delay. You can try to adjust it to your devices.

Single speaker

  • Create one AirPlay server
  • Set playback channel to both

Multi-room Setup

  • Create as many AirPlay servers as you want

multi-room example setup

Multi-room example setup

True stereo

  • Create two AirPlay servers
  • Server A: playback channel left
  • Server B: playback channel right
  • Set both server playback modes to the same value (recommended mode 1)

Run Kei 24/7 (Recommended)

Linux (systemd)

If you run Kei on Linux continuously, it is recommended to use systemd:

  • auto start on boot
  • auto restart on a crash (it happens)
  • centralized logs with journalctl
  1. create /opt/kei if not exists
  2. move Kei binary to /opt/kei, and rename it to kei
  3. if you are already using Kei before, move the storage folder to /opt/kei ( see Where Kei stores data)
  4. /opt/kei should look like this:
    $ ls /opt/kei
    kei  storage
  5. download kei.service to your machine (anywhere)
  6. open it and edit these values:
    • YOUR_USER: your linux user
    • YOUR_GROUP: your linux group. usually the same as YOUR_USER
    • 1000 on both Environment if your user id is not 1000. Leave it if you are 1000. (Check by id -u YOUR_USER)
  7. move the file to /etc/systemd/system/kei.service
  8. make sure you are the owner of /opt/kei
    sudo chown -R $USER:$USER /opt/kei
  9. Enable and run:
    sudo systemctl daemon-reload
    sudo systemctl enable --now kei
    sudo systemctl status kei
  10. Check the logs realtime and past 20 lines:
    journalctl -u kei -f -n 20

If you get PulseAudio/PipeWire session errors in system service mode, use a user service (systemctl --user) or set XDG_RUNTIME_DIR and PULSE_SERVER correctly in kei.service.

macOS (launchd)

Use launchd for boot/login auto-start on macOS.

  1. create /opt/kei if not exists
  2. move Kei binary to /opt/kei, and rename it to kei
  3. if you are already using Kei before, move the storage folder to /opt/kei ( see Where Kei stores data)
  4. make binary executable:
    chmod +x /opt/kei/kei
  5. download com.takiyo0.kei.plist and move to ~/Library/LaunchAgents/com.takiyo0.kei.plist
  6. make sure you are the owner of /opt/kei:
    sudo chown -R $(whoami) /opt/kei
  7. load and start:
    launchctl unload ~/Library/LaunchAgents/com.takiyo0.kei.plist 2>/dev/null || true
    launchctl load ~/Library/LaunchAgents/com.takiyo0.kei.plist
    launchctl start com.takiyo0.kei
  8. check status:
    launchctl list | grep com.takiyo0.kei

Notes:

  • logs are written to /opt/kei/kei.out.log and /opt/kei/kei.err.log
  • for machine-wide daemon (all users), use /Library/LaunchDaemons/ with root-managed plist

Windows (Task Scheduler)

Use Task Scheduler to auto-run Kei at login.

  1. put kei.exe in a stable folder, e.g. C:\kei\kei.exe
  2. if you are already using Kei before, move the storage folder beside kei.exe
  3. open Task Scheduler -> Create Task
  4. General:
    • Name: Kei
    • Run only when user is logged on
  5. Triggers:
    • New -> Begin the task: At log on
  6. Actions:
    • New -> Action: Start a program
    • Program/script: C:\kei\kei.exe
    • Start in: C:\kei
  7. Conditions:
    • uncheck power/network restrictions if you want it always on
  8. Save task and test with Run

CLI alternative:

schtasks /Create /TN "Kei" /SC ONLOGON /TR "C:\kei\kei.exe" /RL LIMITED /F

Updating

  1. Stop Kei:
    # Linux (systemd)
    sudo systemctl stop kei
    
    # macOS (launchd)
    launchctl stop com.takiyo0.kei
    
    # Windows (Task Scheduler): end current task instance from Task Scheduler UI
  2. Download the latest binary on releases
  3. Replace the existing binary (/opt/kei/kei on Linux/macOS, or your exe path on Windows)
  4. Start Kei:
    # Linux (systemd)
    sudo systemctl start kei
    
    # macOS (launchd)
    launchctl start com.takiyo0.kei
    
    # Windows (Task Scheduler): run task manually once from UI (or log off/on)

Known Issues

Current limitations summary

area current behavior
Apple Music AutoMix mostly smooth on mode 1, but temporary drift can still happen
Strict mode (2) can sound chunky when sender frame metadata is inconsistent
Sender metadata quality some apps can send invalid/inconsistent progress info
adding/removing audio device If you're adding, Kei won't detect it until restart. If you remove, it has a chance to crash
sync when connecting There's a high chance of music won't sync when you connect the second server and so on. Seek or pause/play the playback. Although it should always get synced after connect. Desync Problem
unstable hardware environment If you somehow host this on laptop and got into sleep, hibernate or anything interfere with running applications, it may break or crash

Apple Music Automix ⭐

Apple launched a new AutoMix on Apple Music during the new iOS 26 update. This new feature sometimes sends an inconsistent audio frame, e.g., (1) frame sequence jumps when AutoMix is starting to play; (2) the same goes with frame timestamps; (3) they sometimes send a broken frame and sequence. By this problem, Kei can't rely on fully by just frame metadata, and or its built-in timestamp. That's why Kei implements the Playback Mode feature.

if you use then Best?
flow Kei will blindly follow what sender sends. This is the raw frame sequence that sender sends.
Once sender sends a broken frame, the whole playback will gets desync
semi When sender starting the AutoMix, sender will send progress which Kei will use as an anchor. But Kei does not use it's timeline feature.
This means playback always synced, and Kei will play the frame sequence as is during AutoMix
strict Same as 1, but Kei will use it's timeline feature. This means playback always synced, but sometimes audio will be chunky (sometimes a lot) during AutoMix due to missing sequence on the timeline

I recommend you to use semi playback mode. If drifting still happens, check your Clock Offset because it's also applied during seek, otherwise it may be because the sender sends different progress used to seek for correction. There can still be a temporary slight drift after some transitions (around ~500ms, fixed when next progress comes)

No Progress & Playback Info

I got this issue on YouTube App on my iPhone iOS 26. As of now, there's no way I can tell the progress because what they send is:

{
  "start": 2238230511,
  "current": 2248994366,
  "end": 2246884474,
  "last_update": 1778754751158563300
}

as you can see, current is higher than end, so I don't know how to measure it. Sender also did not send application/x-dmap-tagged thus track is null.

Stereo Setup/Multi-room gets desynced on playback

  • make sure you set up both Clock Offset correctly. First, try to match them. If still desync, use this YouTube Video to check the clock offset
  • try to seek or restart playback
  • wait until the sender sends progress again (usually when changing track, sometimes can happen in the middle of playback)

The root cause that I found is that the sender sends different progress used to seek for correction.

Log & Explanation
```shell
[a.1] now playing progress 2576460144/2576587487/2587395135
[a.2] received SET_RTP_TIME, cseq=1282879 crtp=2576603697
[a.3] received SET_RTP_TIME, seeking to RTP time 2576587487

[b.1] now playing progress 2576460144/2576587815/2587395135
[b.2] received SET_RTP_TIME, cseq=1282879 crtp=2576603697
[b.3] received SET_RTP_TIME, seeking to RTP time 2576587815

[a.4] considering seek candidate seq=1282878 ts=2576602673 target=2576587487 targetOffset=2576602922 distance=249 from=-1 offsetTicks=15435
[a.5] applied SET_RTP_TIME seek from played history targetRtp=2576587487 targetOffset=2576602922 replayedFrames=1 nextRtp=2576603697 nextSeq=1282879

[b.4] considering seek candidate seq=1282878 ts=2576602673 target=2576587815 targetOffset=2576603250 distance=577 from=-1 offsetTicks=15435
[b.5] considering seek candidate seq=1282879 ts=2576603697 target=2576587815 targetOffset=2576603250 distance=447 from=1 offsetTicks=15435
[b.6] applied SET_RTP_TIME seek targetRtp=2576587815 targetOffset=2576603250 rtp=2576603697 seq=1282879
[b.7] seek lock acquired at seq=1282879 ts=2576603697, releasing enforceExpected
```

*: [x.y] where x is the AirPlay server and y is log sequence of that server

As you can see, sender sends progress of 2576587487 to server a and 2576587815 to server b at the same time. Upon this request, Kei will start looking on its buffers and it found:

  • 2576602673 for a
  • 2576603697 for b that which b happen to get one frame ahead of a because what a gets is the second-nearest frame from b [b.4]. This 1-frame difference can make the playback desync, and you might feel off but not much. There's a high chance that it'll be fixed on the next track without intervention.

Networking and Ports

Kei relies on LAN connectivity between sender and Kei host. In restrictive firewall environments, ensure local network traffic is allowed for:

  • mDNS discovery (5353/udp)
  • PTP timing (319/udp and 320/udp)
  • RTSP control and RTP audio streams (dynamic ports selected by runtime)

If discovery works but playback fails (or vice versa), firewall/routing is usually the first thing to verify.

Data and Config

Where Kei stores data

Kei stores persistent runtime data in the storage folder beside the executable working directory. The config is named config.json. If you want to edit config.json, turn off Kei first, change it, then turn it back on.

Backup before update

Before replacing the binary, back up:

  • storage/ directory (all of it)

Then replace binary and restart Kei.

Support Policy

  • Current stage: early release / active tuning
  • Behavior and tuning defaults may change between releases as timing logic improves

Security Notes

  • Use a strong admin password.
  • Kiosk login is intentionally restricted to limited read-only endpoints.
  • Kiosk login persistence survives restart by design; treat kiosk-enabled devices as semi-trusted display clients.
  • Keep contents inside storage folder safe, as it contains sensitive data.

Troubleshooting

No sound

  • check the selected output device in web UI
  • check volume from sender and from Kei
  • check if the sender is connected to the expected AirPlay server

Continuous desync and or chunky audio after AutoMix (Apple Music)

  • try to not use playback mode 2, try with 1, or 0 if same result
  • restart Apple Music. Yes, it happens to me. Restarting Apple Music helps a lot
  • note: mode 1 is the recommended default for AutoMix and mixed daily usage

Output device glitching / unstable audio

  • Kei currently supports only 44100Hz playback
  • make sure the output device /backend can run stable at 44,100 Hz

Network interface changed / disconnected

Kei has an automatic interface IPv4 watcher per AirPlay server.

what will happen:

  • if interface IPv4 changes (DHCP renew, move room, reconnect with different IP), Kei auto-restarts only that AirPlay server
  • if the interface temporarily has no IPv4 (cable unplugs / Wi-Fi down), Kei waits and does not spam restart
  • once IPv4 appears again, Kei restarts the server and rebinds RTSP/mDNS on new IP
  • restart has cooldown (10s) to avoid flapping loop

what user will feel:

  • an active playback session on that AirPlay server will drop during the interface break
  • sender usually needs to reconnect / restart playback after the interface comes back
  • other AirPlay servers (different interface) are not restarted

FAQ

Why does mode 2 sound chunky?

Mode 2 is strict timeline mode. When the sender emits missing/jumped timeline frames (intentionally/unintentionally), strict correction can produce chunky output. E.g., now playing sequence 123, but suddenly the sender skips to 20 or 300, when the difference is small, Kei will try to fill the hole with blank. If big, Kei will seek to a new sequence.

Why can mode 0 drift?

mode 0 flows after anchor and ignores strict progress/timeline guardrail, so long-run drift risk is higher.

Is slight drift still possible on mode 1?

Yes. mode 1 is currently the best balance and recommended default, but in sender-side transition edge cases (especially AutoMix), small temporary drift can still happen (roughly ~500 ms).

Why can fast switching stall initial sync sometimes?

Sender can send a mixed / late frame timeline around quick track changes. Kei tries to filter/reset, but transition artifacts can still happen.

I can't find the AirPlay server on my device

First, make sure you are in the same network as the sender, not behind routing. E.g.,

  • if Kei runs on LAN and your device is on Wi-Fi, try using Wi-Fi on the device that runs Kei
  • If Kei and sender are on different networks, you should bridge mDNS traffic between them. On OpenWRT, you can use avahi-daemon to do that.
  • Make sure that ports are freely open between devices, because Kei open servers on random open ports.

I use PulseAudio but ALSA error is everywhere during startup

Example warning
2026/05/14 09:27:17 [CONFIG] -> configuration loaded
ALSA lib pcm_dsnoop.c:567:(snd_pcm_dsnoop_open) unable to open slave
ALSA lib pcm_dmix.c:1000:(snd_pcm_dmix_open) unable to open slave
ALSA lib pcm.c:2722:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.rear
ALSA lib pcm.c:2722:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.center_lfe
ALSA lib pcm.c:2722:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.side
snd_pcm_jack_open: WARNING: JACK client name 'kei-v2.0.6-linux-amd64.C.714007' truncated to 31 characters, might not be unique
Cannot connect to server socket err = No such file or directory
Cannot connect to server request channel
jack server is not running or cannot be started
JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock
JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock
snd_pcm_jack_open: WARNING: JACK client name 'kei-v2.0.6-linux-amd64.P.714007' truncated to 31 characters, might not be unique
Cannot connect to server socket err = No such file or directory
Cannot connect to server request channel
jack server is not running or cannot be started
JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock
JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock
ALSA lib pcm_oss.c:404:(_snd_pcm_oss_open) Cannot open device /dev/dsp
ALSA lib pcm_oss.c:404:(_snd_pcm_oss_open) Cannot open device /dev/dsp
ALSA lib pulse.c:242:(pulse_connect) PulseAudio: Unable to connect: Connection refused

ALSA lib pulse.c:242:(pulse_connect) PulseAudio: Unable to connect: Connection refused

ALSA lib pcm_a52.c:1036:(_snd_pcm_a52_open) a52 is only for playback
ALSA lib confmisc.c:160:(snd_config_get_card) Invalid field card
ALSA lib pcm_usb_stream.c:481:(_snd_pcm_usb_stream_open) Invalid card 'card'
ALSA lib confmisc.c:160:(snd_config_get_card) Invalid field card
ALSA lib pcm_usb_stream.c:481:(_snd_pcm_usb_stream_open) Invalid card 'card'
ALSA lib pcm_dsnoop.c:567:(snd_pcm_dsnoop_open) unable to open slave
ALSA lib pcm_dmix.c:1000:(snd_pcm_dmix_open) unable to open slave
ALSA lib pcm_dmix.c:1000:(snd_pcm_dmix_open) unable to open slave
Cannot connect to server socket err = No such file or directory
Cannot connect to server request channel
jack server is not running or cannot be started
JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock
JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock
2026/05/14 09:27:17 [AIRPLAY HANDLER] -> starting AirPlay handler. 0 AirPlay configurations found.
2026/05/14 09:27:17 [HTTP] -> (KEI) starting HTTP server on port 6769

It's okay, make sure Kei can get your audio devices by checking audio device selection on dashboard, and you can play audio just fine.

Support

Remember to star the repo if you like it! I will really appreciate it, and I will be happy to hear your feedback.

About

A lightweight multi-room AirPlay audio server in Go with PTP-based sync, true stereo pairing, and web control

Topics

Resources

License

Stars

Watchers

Forks

Contributors