OwnTone Multiroom Audio

December 19, 2025

Christmas time is also the time to do stuff with my family, especially once the festivities are actually happening.
One thing that you apparently cannot miss out on is playing Christmas music in the background. But I find that as people often move between the kitchen and the living room, not everyone can enjoy the music if it is only played in one room.
Luckily, we have multiple devices that can act as AirPlay receivers. and AirPlay supports synchronous multiroom audio.
So let’s hack something together, shall we?

OwnTone

One open-source software I found that supports multiroom-audio via AirPlay is OwnTone, an open-source audio media server.

As we own digital copies of the Christmas albums, I’m mostly interested in its ability to scan the local music collection and stream them to one or more AirPlay receivers, with no noticeable delay between the receivers.
By spreading the receivers throughout the house, everyone can enjoy the music, no matter where they are.

Basic Setup

I’ll be using Alpine Linux on a Raspberry Pi (again), as it will come in handy later.
The official Raspberry Pi OwnTone builds don’t support musl libc, so we’ll be using Docker.

First, we have to create the directories where the container can store its data.

# create folder for manifest and data
mkdir owntone && cd owntone
# create mount points
mkdir cache
mkdir config

Next, create a compose.yaml file with the following content, making sure to replace ./media with the path to your music collection.

services:
  owntone:
    image: docker.io/owntone/owntone:latest
    container_name: OwnTone
    network_mode: host
    environment:
      - UID=1000
      - GID=1000
    volumes:
      - ./config:/etc/owntone
      - ./media:/srv/media:ro
      - ./cache:/var/cache/owntone
    restart: unless-stopped/etc/shairport-sync.conf

Now simply start the Docker container using docker compose up and you should be able to access the WebUI on http://<IP>:3689.
You can find all available receivers in Settings > Remotes & Outputs. Either enable them using the sliders or from the menu in the navigation bar on the bottom.

Finally, select an album to listen to and enjoy the music as you walk between rooms.

Pipes

If, like me, you would like to be a little more dynamic with your music but don’t want to use Spotify, you might feel out of luck. But I’ve glanced over one pretty amazing feature so far: Pipes.
Pipes allow you to output audio data from one program into a pipe file, which is then played back by OwnTone and can thus be streamed to multiple receivers.

Stop the OwnTone container using Ctrl-C or docker compose down.
Before we configure pipe sources, we’ll set up a place for the pipes to go.

mkdir pipes

Edit the compose.yaml and include the following line in the volumes section.

      - ./pipes:/srv/pipes

Lastly, we have to edit the OwnTone configuration file in ./config/owntone.conf. In the library section, we want to register the pipes directory.
You probably also want to enable pipe_autostart, to automatically start playing new pipes. In my experience this only works sometimes, but it is pretty nice if it does.

library {
    // ...
    directories = { "/srv/media", "/srv/pipes" }
    // ...
    pipe_autostart = true
    // ...
}

With all OwnTone-related changes done, we can start the Ddocker container again, telling it to run in the background so we can continue working in the current shell session.

docker compose up -d

AirPlay

I’ve mentioned Shairport-Sync a couple of times in previous blog posts, and today we are finally making actual use of it.
Shairport-Sync is an AirPlay compatible audio player, which allows streaming audio from AirPlay sources.

On Alpine Linux, it can simply be installed using the following command.

doas apk add shairport-sync

By default, Shairport-Sync is intended to output the audio directly to the hardware output devices. However, we want it to output the data to a pipe so we can forward it to OwnTone.
To customize the default behaviour, we want to edit the config file in /etc/shairport-sync.conf.

In the general section, uncomment the name line, or insert a new one with your name of choice (it uses the hostname by default).
You should also uncomment the output_backend and set it to pipe.

general =
{
    // ...
    name = "AirPlay Bridge";
    output_backend = "pipe";
    // ...
};

Further down in the file you will find the pipe section, which can be used to configure the pipe output.
You’ll want to change the path so it points to a file in the pipes folder we created earlier. In my case it looks like this:

pipe = 
{
    name = "/home/<my username>/owntone/pipes/airplay";
};

To forward the AirPlay metadata, you can enable the metadata extension in the metadata section,and create a separate pipe for it.
OwnTone expects this to have the same name as the actual pipe, but with a .metadata extension; otherwise it won’t be picked up correctly.

metadata =
{
    enabled = "yes";
    include_cover_art = "yes";
    // ...
    pipe_name = "/home/<my username>/owntone/pipes/airplay.metadata";
    // ...
};

To be able to reliably play the pipes, I also had to change the user who runs the shairport-sync service. In /etc/init.d/shairport-sync you can simply add command_user="<your username>" below the command_background line.
I also had to make sure the log file is owned by my user.

doas touch /var/log/shairport-sync.log
doas chown <your username>:<your-usergroup> /var/log/shairport-sync.log`

Afterwards, you can start the service and enable it on boot if you want to.

# start service
doas rc-service shairport-sync start
# enable on boot
doas rc-update add shairport-sync

Normally OwnTone runs its own discoverability service, but if the Shairport-Sync receiver doesn’t show up, you can start it yourself.

# install Avahi discoverability service
doas apk add avahi
# start service
doas rc-service avahi-daemon start
# enable on boot
doas rc-update add avahi-daemon

When you connect a device to the receiver, you should notice OwnTone starting the pipe playback.
If it doesn’t, try manually starting it by selecting the file in the WebUI file browser.

Bluetooth

If you’ve followed my DIY Bluetooth receiver post, you might remember how we used PipeWire to stream to an AirPlay receiver.
Adding another hop would add unnecessary delay here, so instead we’ll have PipeWire output to a Pipe Tunnel directly.

We’ll register a new module in .config/pipewire/pipewire.conf.d/pipe.conf and add the following config.
Remember to change the filename to point to a file in your pipes folder.

context.modules = [
{   name = libpipewire-module-pipe-tunnel
    args = {
	    node.name = "OwnTone Pipe"
	    node.description = "OwnTone Pipe"
	    tunnel.mode = sink
	    pipe.filename = "/home/<username>/owntone/pipes/bluetooth"
    }
}
]

After restarting PipeWire (rc-service -U pipewire restart), you can check the available sinks and adjust the default to point to the pipe output.

# list available nodes
wpctl status
# change default node to
wpctl set-default <ID>

Lastly, connect your phone via Bluetooth and select the pipe in OwnTone. And that’s it - you should be ready to stream your music.

Summing up

Well - now you essentially have three different methods to play an album.
Either select it from your local collection or have a family member stream it from their device using AirPlay or Bluetooth.

Merry Christmas ๐ŸŽ„.
Enjoy your music.