Replacing librespot with Spotify's Soloist
I was starting to have several issues with librespot as a core part of my multi-room audio setup. The most frustrating being that it wasn’t updating the Spotify backend about songs changing, so although the music changed nothing updated in the UI. It seemingly got stuck one day and no matter what I did it couldn’t become unstuck. To be clear I’m not blaming librespot, those guys are awesome - Spotify have clearly changed something on their backend.
Whilst reading through another issue report I noticed that someone linked to Introducing Spotify Soloist, and after further reading it seemed to be an ‘official’ librespot.
Spotify Soloist is a new Spotify Connect terminal client for headless and DIY setups. It runs on Raspberry Pis and Linux, works with both Free and Premium Spotify accounts, and is available for download today.
Setting up Spotify Soloist as a Snapcast Source
Soloist can output to named pipewire device, so all that needs is hooking into Snapcast as a pipe to capture the audio. These are the instructions that I’ve cobbled together in an evening’s worth of experimentation.
The most frustrating thing is that clients seemingly expire after 90 days - so you’ll need to automate the replacement of the soloist binary. A simple cron or systemd service should suffice.
These instructions were written for a Raspberry Pi running Ubuntu 24.04 LTS; I’m sure they can be adapted to your Linux flavour of choice!
Dedicated user
Setup a dedicated system user with systemd lingering enabled - lingering is required so that systemd can setup the user session, and everything that depends on it, like letting PipeWire start at boot without an interactive login needing to happen. Usually PipeWire only starts when you login graphically, which wouldn’t happen.
sudo useradd -m -s /usr/sbin/nologin soloist
sudo loginctl enable-linger soloist
You may already be using systemctl linger for Raspberry Pi Connect!
XDG_RUNTIME_DIR
A gotcha that you might hit is that anytime you run a command as the soloist user - systemctl --user, pactl, or anything from a root shell it’ll need XDG_RUNTIME_DIR=/run/user/$(id -u soloist) set explicitly first. You’ll see the XDG_RUNTIME_DIR set throughout this tutorial.
sudo -u soloist XDG_RUNTIME_DIR=/run/user/$(id -u soloist) systemctl --user status soloist.service
Interactive logins get this set automatically via PAM, however this is a headless user so without it you’ll just see ‘Connection refused’.
Installing PipeWire
sudo apt update
sudo apt install pipewire pipewire-pulse wireplumber pipewire-audio-client-libraries pulseaudio-utils
Installing Soloist
Soloist provides installation instructions; but for brevity, I’ve copied them here.
Firstly, identifying the architecture that your system is running on, this is usually x86_64 or arm64.
uname -m # aarch64 is arm64
Then download soloist:
curl --fail --location -o soloist.tar.gz \
https://soloist-builds.spotifycdn.com/soloist_release_<arch>.tar.gz
tar -xzf soloist.tar.gz
sudo install -m 755 soloist /usr/local/bin/soloist
Setting Soloist up
You’ll need an API key, which comes from the Spotify for Developers dashboard. We’re going to store it in a environment file that only the soloist user can read.
sudo mkdir -p /etc/soloist
sudo nano /etc/soloist/soloist.env # SOLOIST_API_KEY=your_key_here
sudo chown soloist:soloist /etc/soloist/soloist.env
sudo chmod 600 /etc/soloist/soloist.env
Setting up a systemd service and audio sink
The audio sink is the service that redirects the output of soloist to a file, which Snapcast can pick up.
sudo -u soloist mkdir -p /home/soloist/.config/systemd/user
sudo nano /home/soloist/.config/systemd/user/soloist.service
Copy and paste the systemd unit file below into your terminal. You may want to change the --device-name from Soloist to something more appropriate.
[Unit]
Description=Spotify Soloist (Spotify Connect device)
After=pipewire-pulse.service
Wants=pipewire-pulse.service
[Service]
Type=simple
EnvironmentFile=/etc/soloist/soloist.env
ExecStartPre=/bin/sh -c 'pactl list short modules | grep -q module-pipe-sink || pactl load-module module-pipe-sink file=/tmp/soloist sink_name=soloist_sink format=s16le rate=48000'
ExecStart=/usr/local/bin/soloist \
--device-name "Soloist" \
--api-key "${SOLOIST_API_KEY}" \
--pipewire-device soloist_sink
Restart=on-failure
RestartSec=5
RestartPreventExitStatus=10
[Install]
WantedBy=default.target
sudo chown -R soloist:soloist /home/soloist/.config
sudo -u soloist XDG_RUNTIME_DIR=/run/user/$(id -u soloist) \
systemctl --user enable --now soloist.service
We’re ignoring ExitStatus=10 here as that’ll only show up when the build expires - I’m hoping that when this is released into production they’ll drop this ‘feature’.
Plugging into Snapcast
In snapserver.conf, add a stream pointing to the fifo pipe.
[stream]
source = pipe:///tmp/soloist?name=Soloist&sampleformat=48000:16:2&mode=create
sudo systemctl restart snapserver
Keeping Soloist up to date
Full transparency, this script was written by Claude. I’ve cast an eye over it, but don’t blindly trust AI code!
#!/bin/sh
set -e
case "$(uname -m)" in
aarch64) ARCH="arm64" ;;
armv7l) ARCH="arm32" ;;
x86_64) ARCH="x86_64" ;;
*) echo "Unsupported architecture: $(uname -m)" >&2; exit 1 ;;
esac
TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT
curl --fail --location -o "$TMP/soloist.tar.gz" \
"https://soloist-builds.spotifycdn.com/soloist_release_${ARCH}.tar.gz"
tar -xzf "$TMP/soloist.tar.gz" -C "$TMP"
test -x "$TMP/soloist"
if ! cmp -s "$TMP/soloist" /usr/local/bin/soloist 2>/dev/null; then
install -m 755 "$TMP/soloist" /usr/local/bin/soloist
echo "$(date -Iseconds): installed new soloist build"
SOLOIST_UID="$(id -u soloist)"
sudo -u soloist XDG_RUNTIME_DIR=/run/user/${SOLOIST_UID} systemctl --user restart soloist.service
else
echo "$(date -Iseconds): No update needed"
fi
Install it as a script.
sudo install -m 755 update-soloist.sh /usr/local/sbin/update-soloist.sh
sudo /usr/local/sbin/update-soloist.sh # test it once
And setup a weekly cron, this should fit nicely inside of the 90 day expiry:
0 4 * * 0 /usr/local/sbin/update-soloist.sh >> /var/log/soloist-update.log 2>&1