Chat Controlled Music With Mopidy and Hedwig

Prelude

Tres Trantham
6 min readJul 2, 2016

One of the things I miss most about an open workspace is sharing music with colleagues. It’s also one the things I miss least — especially when someone just has to share their favorite tune for the 11th time. The worst part is that you are typically at the mercy of who’s controlling the music. And remote team members? They are resigned to never being able to participate in an office’s music culture outside of the occasional “I love this song!” in a chat room.

Let’s change that. Let’s setup a music server for distributed teams. And let’s control it via chat. Just like your favorite all-star jam band, we’ll piece together the best parts from Mopidy, Icecast, Spotify, Hedwig, and Slack in an opinionated way, but we’ll still leave room for those gnarly solos.

Setting Up Mopidy

Mopidy is an extensible music server written in Python. At its core, Mopidy is a great MPD server, but through numerous extensions it’s a music hub. Mopidy can simultaneously pull music from multiple web and local sources as well as push audio through local and streaming outputs. After having written a basic music player from scratch before, I’m glad to have the firepower of Mopidy and MPD to do the heavy lifting so we can focus on the Elixir side of things.

But before we do that, let’s get our music server up and running. Mopidy has great installation instructions for Mac and if you’re on a different system, they have you covered there too. But, we’ll be using Docker containers to make setup as easy as possible.

Getting Docker setup and running is outside the scope of this article, but they also have great installation instructions as well as a Mac installer. Once you have a working Docker installation, all you have to do is run the `docker-mopidy-spotify` container. This will setup Mopidy, add Spotify integration, and start the Spotmop web UI.

$ docker run \
-p 6600:6600 \
-p 6680:6680 \
trestrantham/docker-mopidy-spotify \
mopidy \
-o spotify/username=USERNAME \
-o spotify/password=PASSWORD

You will notice that we are exposing the MPD and web server ports of 6600 and 6680 repectively external to the container. This is so we can access the container services from our localhost (or from a remote server).

In order to access the Mopidy web UI, you will need to first get the Docker virtual machine IP address.

$ docker-machine ip
192.168.99.100

Then go to the web URL in a browser.

At this point, you have a fully functional Mopidy server running locally via a Docker container. However, since we are setting up a music server for distributed listeners, we’ll need to provide a way for multiple people to listen in at once. Luckily, this problem has been solved with the excellent Icecast server library.

Setting Up Icecast

Icecast allows you to create a streaming music station that enables you to send audio to a socket and in turn stream that socket to multiple listeners. Setting it up and integrating with our Mopidy server is a snap. The first thing we’ll do is setup the Icecast server and start listening on a socket. While you can easily install Icecast with a simple brew install icecast, we’re going to use another Docker container to get up and running quickly.

$ docker run \
-p 8000:8000 \
-e ICECAST_SOURCE_PASSWORD=mopidy \
-e ICECAST_ADMIN_PASSWORD=mopidy \
-e ICECAST_PASSWORD=mopidy \
-e ICECAST_RELAY_PASSWORD=mopidy \
trestrantham/docker-icecast

Like our Mopidy container, this starts our service — Icecast in this case — and exposes its port, 8000. If you go to the same docker-machine IP as before with this port, you’ll see the default Icecast web UI.

The default configuration of our container creates a Mopidy mount point that listeners can tune in to. But before they can do that, we’ll need to start sending audio to the socket our Icecast server is listening on. We’ll accomplish this by modifying our Mopidy docker command to leverage shout2send to specify that the audio output be pushed to our Icecast server. This will use our same docker-machine IP address and the source password used in our Icecast container invocation.

$ docker run \
-p 6600:6600 \
-p 6680:6680 \
trestrantham/docker-mopidy-spotify \
mopidy \
-o spotify/username=USERNAME \
-o spotify/password=PASSWORD \
-o audio/output="lamemp3enc ! shout2send mount=mopidy ip=192.168.99.100 port=8000 password=mopidy"

Now we’re jamming! Playing music from the Mopidy web UI will now stream via Icecast to anyone tuning in to the Mopidy mount point — http://192.168.99.100:8000/mopidy.m3u in our case.

Setting up Hedwig

To control our music server, we’ll lean on Hedwig to setup a chat bot with a custom responder that can talk to Mopidy. Since we’re integrating with Slack, let’s use the hedwig_slack adapter to generate our bot. Following the getting started instructions for the Slack adapter will have you walk through a few steps:

  1. Create a new Elixir application
  2. Add hedwig_slack as a dependency
  3. Generate a bot using the Slack adapter
  4. Setup the bot by supervising the process and adding configuration

The provided documentation walks through these in detail, but one of the important parts is ensuring that you have the configuration options setup correctly — namely the Slack adapter and Slack API token. You can also optionally set what rooms your bot should join when connecting. Otherwise, you can invite your bot to a channel and it will automatically join.

use Mix.Config

config :jarvis, Jarvis.Robot,
adapter: Hedwig.Adapters.Slack,
name: "jarvis",
aka: "/",
token: System.get_env("SLACK_API_TOKEN"),
rooms: [],
responders: [
{Hedwig.Responders.Help, []},
{Hedwig.Responders.GreatSuccess, []},
{Hedwig.Responders.ShipIt, []}
]

Now you can start your bot and start chatting with it via any Slack client.

$ mix run --no-halt

Integrating Hedwig with Mopidy

The last step is to integrate our chat bot with our jukebox. To do this, we will use the hedwig_mopidy responder for Hedwig. This allows you to control Mopidy via chat by issuing play commands via your bot. To install hedwig_mopidy, it will need to be added as a dependency to your bot.

def deps do
[{:hedwig_mopidy, "~> 0.0.2"}]
end

And started with your application.

def application do
[applications: [:hedwig_mopidy]]
end

The last thing to do is setup configuration for the responder. The first thing we’ll do is add hedwig_mopidy to the bot’s list of responders so that it will be available when on start. Secondly, because hedwig_mopidy uses the mopidy library as a dependency, we’ll have to set configuration for both libraries. Our new config.exs should look like this in its entirety.

use Mix.Configconfig :jarvis, Jarvis.Robot,
adapter: Hedwig.Adapters.Slack,
name: "jarvis",
aka: "/",
token: System.get_env("SLACK_API_TOKEN"),
rooms: [],
responders: [
{Hedwig.Responders.Help, []},
{Hedwig.Responders.GreatSuccess, []},
{Hedwig.Responders.ShipIt, []},
{HedwigMopidy.Responders.Mopidy, []}
]
config :mopidy,
api_url: System.get_env("MOPIDY_API_URL")
config :hedwig_mopidy,
web_url: Regex.replace(~r/\/rpc/, System.get_env("MOPIDY_API_URL") || "", ""),
icecast_url: System.get_env("HEDWIG_MOPIDY_ICECAST_URL")

Let’s walk through all the configuration we’ve added. The SLACK_API_TOKEN is obtained from Slack when creating a new bot user. The MOPIDY_API_URL is the HTTP JSON-RPC API URL from the Mopidy server we setup above. In our example, this would be 192.168.99.100:6680/mopidy/rpc. And the HEDWIG_MOPIDY_ICECAST_URL is just the Icecast URL we setup previously— 192.168.99.100:8000.

With this configuration in place, starting our bot will now connect to our Mopidy server and allow us to control our music from any Slack channel our bot is a member of.

Conclusion

And there you have it! You can check out the hedwig_mopidy source for a full list of commands currently supported out of the box. Future versions will include Spotify’s recommendations engine to start stations based on artists or songs as well as adding more of the MPD functionality provided by the mopidy library like persisted playlists, listening history, and more.

If you found this useful then all I ask is that you pay it forward. And if you have any questions or comments, feel free to leave them here or on Twitter. Happy hacking!

--

--

Tres Trantham

Developer. Husband. Father. Beer connoisseur. NFL geek. Data enthusiast.