Our office jukebox is a big part of the culture in our organisation. So much so that when the jukebox isn't running in the office, it feels very strange and eerily quiet. This post is about how we replaced the ten-year-old existing version with a more modern approach. It gives an overview of the technologies used and why we chose them.
TL;DR
If you're interested in an office jukebox powered by Mopidy and written in JavaScript/TypeScript, with features like:
-
Global play/pause/skip/volume
-
Tracking user plays
-
User Voting on music played
-
Track Search with highlighting popular office tracks
-
Track curation (choose a mix of tracks to add at once)
-
AutoPlay (choosing tracks that you'll like based on previous plays)
-
AutoStop at the end of the day
Go ahead and try it: https://github.com/kyan/jukebox-js
The Old
Jukebox was first built ten years ago using Ruby on Rails, with a hand rolled Javascript frontend and music coming from the wonderful Mopidy. This has worked fantastically over the years but has many moving parts due to limitations at the time, many of which are no longer supported. Developer confidence working on it is very low due to lack of tests. Basically people are scared to go near it.
The New
Web tech has moved fast over the last few years, and while Ruby on Rails is still one of our tools of choice, it felt like like for this project it wasn't the perfect fit. We still wanted to use Mopidy, as that serves us well in the basic requirement of playing music, but with the other parts we could perhaps find a better fit. We needed something that was good for building fast and complex frontend interfaces, good with events, good with JSON and something that we all knew and had a good development toolchain. How about JavaScript (plus TypeScript)? We were already using it on the front-end, so we decided to extend this to the rest of the app. We've ended up with:
-
Front-end: ReactJS – Mostly JavaScript, slowly moving over to TypeScript
-
Back-end API: NodeJS – TypeScript
-
MongoDB: Used for storage and state and exposed as JSON
-
Mopidy – Specifically the WebSocket interface which exposes live data in JSON
Now, you may be thinking "Why didn't you just pick one of the many Mopidy clients already out there?"
Well, we want our jukebox to do a lot more than just play music. We want users to be able to search/choose/curate tracks to play and to be able to track that. We want other people to be able to vote on whether they like that music, so that when there is no music queued up, the jukebox will choose its own music based on what we all like and have played before. We use the MopidySpotify plugin and Recommendations API to help seed our initial recommendations and then filter that down.
Under the hood, what we've essentially built is a WebSocket proxy. Something that sits in the middle of the conversation between the Client and Mopidy and injects extra content, plus enabling extra functionality on top of that. Here's a quick overview of each moving part:
Front-end
First a disclaimer. The current UI looks like it was designed by a developer. That's because it was – me! So whilst it works and is functional, a new design is in progress by the expert designers here at Kyan. I've seen the mockups and it looks slick.
The front-end is a React JS app created using the create-react-app project. This is a pretty standard React app. It connects to the backend API via a socketio client and waits for messages. These come in a defined format and are JSON. The Client can also send messages down that socket. These can optionally be secured via a JWT token (users need to login to do certain tasks). The socket is the only communication mechanism to the backend. The information coming into the frontend comes via different channels. For instance, a channel for Mopidy information, or a channel for Search information or State. So data coming from Mopidy would be parsed and stored in Redux which in turn triggers the UI to rerender when required.
The front-end is flat files served from Github using Github pages.
Back-end
A NodeJS app. This backend is essentially a WebSocket proxy, allowing us to enrich data that comes in from Mopidy, adding our own information before it is broadcast to all the connected clients. We add things like voting information and metrics about when things have been played. The same goes for any messages sent to Mopidy. The API also handles extra communications directly with Spotify via their Web API. Using ExpressJS, the API starts a socketio server, as well as connecting to Mopidy using the Mopidy JS Library.
The back-end is served from a Raspberry Pi Model B that lives in our office.
Mopidy
We're using the exposed Websocket interface which we connect to using mopidy.js in the API. We run Mopidy on a Raspberry Pi Model B with the addition of a HIFIBerry Sound card. That sits on top of our amp, which powers four large speakers that are positioned around the office.
MongoDB
We use an instance of MongoDB to store information about tracks that have been played as well as user information to link with and app settings. We also use it to store the current state of the jukebox. Oh, and we use it to also cache album art urls so we don't hammer the Spotify API.
Make Developers Happy
Something really important for our new version of our jukebox was it should be developer friendly. Like all projects really, a developer should be able to get up and running as quickly as possible. It's taken quite a while to refine for this project, but I think we're in a pretty good place.
Someone can come along and check-out the project and have a fully working jukebox in minutes. We use Docker for this. The stumbling block was always Mopidy. For some time you had to have a working version of Mopidy running somewhere to connect to when developing. Whilst you can still do that, we've made it even easier by replacing this with a docker container running a silent version of Mopidy, so you can still run/interact with it all in a self container environment.
Deployment
We've tried to keep it simple. We currently deploy the back-end with shipIt and the front-end just pushes to Github Pages using gh-pages.
Tests
Another thing that makes developers happy is confidence. We have 100% test coverage throughout the project. It also uses ESLint for linting and prettier for a consistent style. CI is using Github Actions. Doing this from the start has meant we can now confidently make changes without the fear of breaking things.
That's all. I hope this was an interesting overview of our office jukebox. If you'd like to try the jukebox in your own office you can download the project from Github: https://github.com/kyan/jukebox-js.
Thanks!
Previously from our Engineering Team:
How to manage a project-specific PATH with direnv by Andrew Peng
Why do we love Next.js so much? by Dave Quilter
Dev Talks: End-to-end testing with Cypress by Damian Boni
Things I wish I’d known before I started with React Native by Carmen Lopez Guerra
How Ruby if statements can help you write better code by Dave Cocks
How do you solve a problem like caching in Progressive Web Apps? by Dave Quilter