Using UXplay as an AirPlay server
What is the issue and the solution?
I have a pantry with an A/V receiver with 2 zones - 5.1 in my living room and a stereo setup on my Back screened-in porch. It really only has 2 devices as inputs (why can't they build smaller receivers for simple setups?) - AppleTV 4k and a Mac mini M1 that I have wall-mounted. My receiver doesn't like using just any source as input for Zone 2. Also, Airplay receivers built into consumer grade audio usually aren't all that great, so I came up with this setup.
The Mac mini is running software to act as an AirPlay server. When a client connects, a script I wrote will detect the client (with name resolution), reach out to Home Assistant to power on Zone 2 on the receiver. When the client disconnects from AirPlay, there will be a request to power down Zone 2. If you have any way in which to use rest calls to control your Home Receiver, this could be adapted, but it will be fairly specific to your home setup.
I use the Yamaha legacy integration for Home Assistant to control my receiver.
One additionl inclusion that if not configured, should just be ignored is the use of Pushover for notifications. Each time a client connects and the audio is turned it, I get a notification saying that has happened and if resolvable, what the client's hostname is. Upon disconnect, I get just get a message that it was turned back off. This was largely added as a troubleshooting mechanism as a previous method of detecting audio was much more prone to trigger inadvertantly, I wanted to know when it happened. Still, I find is useful today, so it remains.
Setup Steps
Disable the AirPlay Receiver in System Settings
This may work with the default implementation, but this was originally built out on an older macOS where that wasn't available and I've stuck with what works.
Install HomeBrew
Run the following terminal command to install some necessary components:
brew install cmake libplist openssl@3 pkg-config
Install both the runtime and development installers for GStreamer
Clone the UxPlay git repo
git clone https://github.com/FDH2/UxPlay
Run the following terminal command to build UxPlay:
cd UxPlay; cmake . && make && sudo make install
Setup uxplay to run as a Launch Agent. This is possible to do manually, but I prefer to use Lingon. My config is as follows:
- Run for (Me)
- Set command to be
/usr/local/bin/uxplay -async -n "Back Porch" -nh -vol 0.4
-async
Audio-Only mode: sync audio to client video (default: no)-n
allows for naming, in my case, the speakers are on my screened-in back porch-nh
do not append@hostname
to the name of the Air Play server-vol
allows for a default volume on the streaming client to be set. I calibrated the zone 2 audio level with this to come up with a good minimum volume for spoken word podcasts and with room to increase it for music.
- Toggle At Startup and when saving
- Toggle Launch again if it crashes
- Set the StandardErrorPath to:
<HOMEDIR>/Library/Logs/uxplay/uxplay_err.log
- Set the StandardOutputPath to:
<HOMEDIR>/Library/Logs/uxplay/uxplay_out.log
Utilize my automation script that can be found in my scripts repo. There are 4 necessary files as written, but it would be very easy to strip out some of the extraneous behavior if it isn't relevant to your use.
- detect_audio_stream.py - main script
- home_assistant.py - helper that connects to Home Assistant for powering zone 2 of the receiver on/off
- process_info.py - helper to extract information and host ips/hostnames from processes
- pushover.py - helper to send notifications via Pushover
Setup the script to run, again, as a Launch Agent [1]. My config is as follows:
- Run for (Me)
- Set command to be
<path>/python <path>/detect_audio_stream.py
- Toggle At Startup and when saving
- Toggle Launch again if it crashes
- Configure the schedule. I opted for a Repeat Interval of 5 seconds
- Set the StandardErrorPath to:
<HOMEDIR>/Library/Logs/detect_audio/detect_audio_err.log
- Set the StandardOutputPath to:
<HOMEDIR>/Library/Logs/detect_audio/detect_audio_out.log
- To talk to Home Assistant, set two EnvironmentVariable key pairs:
HASS_TOKEN: <Token String>
HASS_ENTITY: <entitiy_name_in_home_assistant>
PUSHOVER_TOKEN: <pushover token value>
PUSHOVER_USER: <pushover user value>
- If no env var is configured, no automation will be attempted
Launch agents and daemons are mostly interchangable terms, but within the system, a daemon runs at the system level whereas the agent runs within the user's context. In the case of the detect_audio_stream.py, it could likely run as either, but given that it's looking at a process (uxplay) that is run by my user, no elevated permissions are necessary. ↩︎
- ← Previous
Dragon Con 2025