Merry Christmas! 🎄

This year, Santa brought me a set of big speakers and a receiver for our living room. It’s the setup I’ve wanted for years - primarily to be used for listening to music. Since our living room is open to other parts of the house, music played there easily fills our primary living spaces.

We have a couple of kids who are starting to get in to music - as avid performers and listeners. They’re individually exploring their own musical tastes, while also sharing in some of our family favorites. For a while, we’ve been trying to enjoy communal listening experiences, especially as the kids discover new sub-genres and artists and want to share. Before the new living room system, these (brief) sessions were usually AirPlayed through a HomePod Mini in our kitchen… not exactly a stellar experience.

Our music primarily comes from Spotify, and we’ve recently settled in to a family plan. Everyone has their own profile, allowing them to build their own libraries and feed the AI recommendation beast separately. I also have a collection of digital purchases and ripped media I enjoy with Jellyfin, but that’s a different blog post.

Spotify is mired in controversy, but the alternative was very disappointing, so it’s what we use.

Listening Together

Spotify Connect is what drives the integration in many smart devices. TVs, speakers, and other Spotify-enabled gadgets (like my Onkyo receiver) have a built-in Spotify client that streams audio directly from their servers.

Unlike AirPlay, where data passes through another device (either an iPhone or Macintosh), data is delivered directly from Spotify’s servers to the playback device. The Spotify apps shift into a remote-control mode transparently, as if it was playing audio locally. That makes it easy to use either the phone or desktop apps to control music on the stereo.[1]

A Spotify Jam playing through our Living Room receiver

With Spotify’s Jam feature, it’s also possible to share the queue of these playback sessions, allowing anyone who is participating to add, skip, or control tracking on songs in the queue. Participants can use their own device, so their libraries (including favorite albums, songs, and playlists) are readily available and can easily be referenced when adding music to the shared playback queue.

This really comes to life when you have multiple people sitting down and enjoying music together. They’re able to use their own phones to choose what’s next, and be inspired by what’s playing right now.

It’s pretty cool, and it Just Works.

The Internet of (Dumb) Things

Folks who have known me a while might be surprised that I connected my receiver to the internet. I’m not a fan of services and devices that unnecessarily decay over time because of the diverging needs of users and businesses. As more manufacturers subsidize low-cost electronics with on-device advertising and analytics, I try to avoid purchasing those devices. When I can’t avoid it (like TVs), I simply don’t connect them to a network.

I just don’t think everything needs an IP address. I’ve come to really prefer what have become colloquially known as “dumb” devices - those without network capabilities that retain their original design, function, and performance.

I’ve opted to not connect my Onkyo receiver to the internet. Like I’ve done with my TVs in the past, I’m leaving it off my home network, and instead connecting devices to it I have more trust and confidence in (and that I can easily remove or replace).

For Spotify Connect, the device I’m using is a Raspberry Pi 3B.

Wait… what? 👀

Raspotify To The Rescue

It probably deserves its own post, but the TL;DR version of my solution is that I installed Raspotify on a Raspberry Pi connected to my receiver.

Raspotify is powered by librespot, an open source implementation of the Spotify Connect receiver. Installation is fairly straight-forward for those who are comfortable in a Linux/Unix command line environment, and things work pretty well out of the box.

When it’s up and running, it behaves just like a device with Spotify built-in. It shows up in the Spotify apps, and can be accessed by any Spotify Premium user on the same local network.[2]

I’ve opted to do some customization to the service. The audio level that comes out of the 3.5mm audio jack on my Raspberry Pi was much weaker than other devices I tested.[3] After confirming the output levels were properly maxed out in alsamixer, I switched to the HDMI port on the Raspberry Pi, which required a simple update to the /etc/raspotify/conf file:

1
LIBRESPOT_DEVICE="hdmi:CARD=vc4hdmi,DEV=0"

I used the following command to figure out which devices librespot was able to detect, then dropped the identifier into the config above:

1
$ librespot -d ?

After that change (and tracking down a suitable HDMI cable), things worked great.

Nerding Out

Of course, I had to nerd out a bit. librespot supports event notifications by calling a provided executable whenever when events fire in the service. There are quite a few different types of events produced, so I started experimenting.

I created a script at /usr/local/bin/librespot-event.sh, made it executable, and updated /etc/raspotify/conf with the following:

1
LIBRESPOT_ONEVENT=/usr/local/bin/librespot-event.sh

After some initial experimentation with simple things like piping env to logger and peeking at the values, I realized there were a lot of possibilities (and quite a bit of potential complexity). After some Googling, I stumbled on this Python script with some thorough logic around event types that produced JSON accordingly.

Then it clicked! I could publish that JSON on my local MQTT broker, making the data accessible to other integrations in my home. However, I wasn’t excited about futzing with python, and liked the idea of keeping things in a shell script.

Soooo… I asked ChatGPT to convert the python to bash and use jq for the JSON parts. It did it almost perfectly, and I asked for a simple correction, which it was successful in making.

After that, I added some environment variables for MQTT and watched the events start flowing through my network.

Here’s my librespot-event.sh script if you’re interested in taking a look.

And here’s what some of the events look like when I subscribe with mosquitto_sub

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
{
"event_time": "2024-12-24 23:05:30",
"event": "track_changed",
"common_metadata_fields": {
"item_type": "Track",
"track_id": "06jYhkarVbcRsKcQaTZXJz",
"uri": "spotify:track:06jYhkarVbcRsKcQaTZXJz",
"name": "Winter Blessings",
"duration_ms": "250713",
"is_explicit": "false",
"language": [
"zxx",
],
"covers": [
"https://i.scdn.co/image/ab67616d0000b27306afd8641a8e72354c5b01eb",
"https://i.scdn.co/image/ab67616d00001e0206afd8641a8e72354c5b01eb",
"https://i.scdn.co/image/ab67616d0000485106afd8641a8e72354c5b01eb",
]
},
"track_metadata_fields": {
"number": "1",
"disc_number": "1",
"popularity": "63",
"album": "Winter Blessings",
"artists": [
"The Three Queens",
],
"album_artists": [
"The Three Queens",
]
}
}

I’m not doing anything with the events right now, but I can envision a bunch of different things for the future.

Wrapping Up

So there you have it - our roundabout way of getting Spotify Connect on a receiver that has the capability natively. 🙃

If you have any questions or ideas, I’m always happy to chat. You can find me at @thaddeus@mastodon.social or other places online.


  1. You can also use both simultaneously, which is pretty rad. ↩︎

  2. The Spotify apps have to be granted Local Network permissions on recent versions of iOS and macOS, which is a tradeoff I’m willing to make for the convenience. I wish companies weren’t abusing mDNS, but alas… ↩︎

  3. I initially tried using AirPlay with my 2012 AirPort Express, but the overall experience was pretty poor for our family needs. However, the audio quality and levels were great. I still love the hardware so much. ↩︎