Handling Office Music Politics
…with a Sonos / NodeJS Veto button!
We recently upgraded our sound system in the office, with a lovely shiny Sonos system. With our ever-increasing staff numbers we soon discovered that our music taste was broadening too! Our previous music choice was more of a dictatorship than a representation of the whole office's music tastes. We did have a shared Spotify playlist that people could add to, but as it was played from one person's computer it would invariably be monitored and offending songs removed. It became apparent this wouldn't really work
With Sonos everyone can install the Sonos app, and add their own choice of tunes to the playlist. A perfect balance: everyone adds music without the threat of filtration. Well, most of them - a good chunk of our devs run Linux and there's no Linux Sonos client. Then again everyone has a smartphone and there's an app for them, so problem solved, right?
Well, not quite. The Sonos app isn't the nicest in the world, plus having to get your phone out and load the app just to change a track seemed to be a pain. I started looking around to see if anyone had written a webclient that we could all access, and found two options:
- a Perl-based controller called Purple. I'm irrationaly scared of Perl so I kept looking…
- A NodeJS Web controller (Github link. This is more up my street, being a frontend dev!
The original thought was just to have the web player up and running, but then I did some digging and noticed that it's built on top of an API too. (see https://github.com/jishi/node-sonos-discovery)
Inspired by the store opening scene from Empire Records along with a desire to do something tactile and use some of the dev boards I have lying around, I decided to make the office a Veto Button.
The basic idea is simple: a big red button that you wallop if you don't like the song, and it'll skip to the next track.
Technology
I've already mentioned about the Sonos API. It looked like a nice, straightforward answer to my problems. I could write everything in Node for extra hipster points and it'd all be straightforward (I won't have get dirty and write Perl).
To hit Hipster Level: Ninja, I decided to use a Tessel. For those that don't know, Tessel is a microntroller board that runs Javascript. It also has built-in wifi. Perfect for this application: simply connect up a button and tessel push
the code up.
The next go-to board was the Raspberry Pi. Being a fully fledged Linux machine this had no problems. It supports Node and has built in networking so it was all plain sailing. Also the GPIO pins provide a nice easy way of adding a physical buttong. I tested this circuit with a little breadboard push button but I've order a big ass red button which will become the final button
The Code
As I've mentioned above, the project is built on top of Node. I included a couple of packages to help:
-
"sonos-discovery": http://github.com/jishi/node-sonos-discovery
This is a nice package that allows us to find, and talk to, the Sonos system. There are lots of features such as set / get volume, group, etc but for this project I'm just using the discovery and nextTrack method -
"onoff": "~0.3.2"
A little helper utility that makes talking to the GPIO pins on the RaspberryPi (including debouncing for example)
Connecting to Sonos
The code for this is pretty straightforward. Once you've reuquire
d the modules you're dependant on (in my case sonos-discovery
, onoff
and my Veto
library, you can create a new instance with a nice oneliner:
var discovery = new SonosDiscovery();
There are a bunch of mehtods that you can now use to get at your Sonos. For example:
this.discovery.getZones()
this.discovery.getPlayer(playerName)
These should all be self-explanatory so I won't go into it here. There's also some documentation on the Github repo.
Setting up the GPIO
Using the onoff
library you can quickly create a link to one of the Pi's GPIO pins, like this:
var button = new gpio(18, 'input', 'both', {
debounceTimeout: 50
});
Once this is set up you can the watch
method to – yep you guessed it – watch for change in value:
vetoButton.watch(function(err, value) {
if(value == 1) {
vetoSong();
}
});
Here we're simply checking if the value is 1, i.e. the button is pressed, and then calling the vetoSong
method.
Changing tracks
To change the track, we first need to get the player. As the office only has the one group we've hardcoded a player name ("Reception" in our case) and pass that to getPlayer
.
Next, it's a simple case of calling the nextTrack()
and BOOM. Track vetoed.
Other bits
We've written a couple of method, pauseAll
and playAll
, that do what they say. They're not used for anything yet but could be used further down the line (see below for the 'roadmap')
The Button
It's just a straightforward button. For testing I used a little breadboard mounted click switch, but I've ordered a big ass red button that we'll mount properly in the office when it turns up
Future improvements
There's a long list of features that I either want to add or have been suggested to me. A few that we'll add:
- Cross-posting to slack
- Record all vetoed songs and display on a webpage
- If a song has been vetoed more than three times in a week, automatically remove it from the playlist
- Add a big flashing light and play a siren noise when the song's vetoed
- Port over to Tessel (might be possible with Tessel 2 )
- Hook in with our VOIP system to pause on ring / resume on call end
Full Code
All the code is available on Github, at https://github.com/psebborn/sonos-veto-controller:
Feel free to have a look and if you want to leave any comments, go for it!