Turning a Raspberry Pi 2 into a shared Spotify Jukebox

Introduction

In the past role we had music playing in the office through some wall-mounted power speakers. We wanted our staff to be able to control what is playing, skip tracks, change volume, etc.

Initially we had Spotify running on a Mac Mini, using a home-made Rails website and some Apple Script commands to control skip, pause, play and volume change but naturally that was not very flexible, or reliable and created a maintenance burden when any new features needed adding.

While looking into creating a better solution using libspotify came across the Pi Musicbox project which promoted the use of Mopidy.

Mopidy is essentially a music server which supports plugins to add functionality, for example music services like Spotify & Soundcloud but also for control through web interfaces.

We started running Mopidy on a Linux Server with the Music Box web client (of the Pi Musicbox project).

This has served us very well but is not the best use of a Dell server, so to minimise waste (and downtime should we need to tinker with the server) we are moving the Mopidy setup to a Raspberry Pi 2. This post outlines the steps to make that happen...

What You'll Need

  • Raspberry Pi 2
  • Micro SD Card (ideally 8 GB or larger)
  • Another Computer (to download/copy the OS image and ssh)
  • A way to read/write the micro SD card on the computer
  • Powered Speakers?
    I have heard that headphones are not a good way to test volume change etc
  • If all goes well you shouldn't need a keyboard or mouse for the Pi!

Guide

This guide is based on running Linux on the computer used to prepare the SD card however there are many guides around the web on how to prepare the SD using Mac OS or Windows. Mac is fairly similar, most notably the devices are identified a little differently and you use diskutil to find and unmount your device before using dd.

Step 1 - Install Raspbian

First download Raspbian Jessie Lite from https://www.raspberrypi.org/downloads/raspbian/ and extract the img file within.

2015-11-21-raspbian-jessie-lite.zip
paul@box [09:35:00] [~/Downloads] 
-> % unzip 2015-11-21-raspbian-jessie-lite.zip
Archive:  2015-11-21-raspbian-jessie-lite.zip
  inflating: 2015-11-21-raspbian-jessie-lite.img  

Then 'copy' the image to the SD card. This is done at the block level rather than the file level using the dd command. This step (usually requiring sudo) is destructive so you need to be sure which device is your SD card - using fdisk one can check.

fdisk -l will list all disks, partitions, sizes. Again sudo is likely required.

fdisk will list entries that look similar to:

Disk /dev/sda: 894.3 GiB, 960197124096 bytes, 1875385008 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: 62FB3A67-F01B-4A6A-903A-A5573C2A7E96

Device          Start        End    Sectors   Size Type
/dev/sda1        2048    1050623    1048576   512M EFI System
/dev/sda2     1050624 1808625663 1807575040 861.9G Linux filesystem
/dev/sda3  1808625664 1875384319   66758656  31.9G Linux swap

So we can either look for a disk the same size as the SD card, in my case it's /dev/sde (which already has two partitions in this case):

Disk /dev/sde: 59.5 GiB, 63864569856 bytes, 124735488 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xea0e7380

Device     Boot  Start       End   Sectors  Size Id Type
/dev/sde1         8192    131071    122880   60M  c W95 FAT32 (LBA)
/dev/sde2       131072 124735487 124604416 59.4G 83 Linux

Or we can check the device list before, and after inserting the device.

Before:

paul@box [09:41:17] [~/Downloads] 
-> % sudo fdisk -l | grep 'Disk /dev/'
Disk /dev/sda: 894.3 GiB, 960197124096 bytes, 1875385008 sectors
Disk /dev/sdb: 232.9 GiB, 250059350016 bytes, 488397168 sectors
Disk /dev/sdc: 931.5 GiB, 1000204886016 bytes, 1953525168 sectors
Disk /dev/sdd: 931.5 GiB, 1000204886016 bytes, 1953525168 sectors

After:

paul@box [09:41:21] [~/Downloads] 
-> % sudo fdisk -l | grep 'Disk /dev/'
Disk /dev/sda: 894.3 GiB, 960197124096 bytes, 1875385008 sectors
Disk /dev/sdb: 232.9 GiB, 250059350016 bytes, 488397168 sectors
Disk /dev/sdc: 931.5 GiB, 1000204886016 bytes, 1953525168 sectors
Disk /dev/sdd: 931.5 GiB, 1000204886016 bytes, 1953525168 sectors
Disk /dev/sde: 59.5 GiB, 63864569856 bytes, 124735488 sectors

It's quite likely it will come after the last used letter, for example if you last device is /dev/sdc then it's likely to be /dev/sdd.

Now that we know the device, copying the data is simple -

paul@box [09:51:19] [~/Downloads] 
-> % sudo dd if=2015-11-21-raspbian-jessie-lite.img of=/dev/sde bs=1M
1391+0 records in
1391+0 records out
1458569216 bytes (1.5 GB) copied, 93.7754 s, 15.6 MB/s

Now the card should boot Raspbian in the Raspberry Pi 2.

Step 2 - SSH Access

When powering up the Pi with a monitor connected it should boot into Raspbian and tell you it's IP. The Lite version does not boot into a graphical (X) environment so you will be presented with a login prompt.

[TODO: Image Here]

Once you have the IP you should be able to manage it externally:

paul@box [04:54:04] [~] 
-> % ssh pi@192.168.1.57                                        
The authenticity of host '192.168.1.57 (192.168.1.57)' can't be established.
ECDSA key fingerprint is SHA256:2pI1YWSU6pjrr0Q15kriKfuFjgIL4GwnWmDtLY1eyCA.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.1.57' (ECDSA) to the list of known hosts.
pi@192.168.1.57's password: 

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
pi@raspberrypi:~ $ 

The default username is pi and password is raspberry.

Step 3 - Configuration & Security

Set the hostname:

sudo vi /etc/hostname

I set it to musicbox.

It will pick up after a reboot:

pi@musicbox:~ $ hostname
musicbox

When running commands like sudo you may see:

sudo: unable to resolve host musicbox

If your hostname does not match a valid DNS entry you'll need to update the /etc/hosts file. Note the last entry probably read raspberrypi which I have changed to musicbox.

127.0.0.1       localhost
::1             localhost ip6-localhost ip6-loopback
ff02::1         ip6-allnodes
ff02::2         ip6-allrouters

127.0.1.1       musicbox   

Add your management user:

pi@raspberrypi:~ $ sudo adduser paul
Adding user `paul' ...
Adding new group `paul' (1001) ...
Adding new user `paul' (1001) with group `paul' ...
Creating home directory `/home/paul' ...
Copying files from `/etc/skel' ...
Enter new UNIX password: 
Retype new UNIX password: 
passwd: password updated successfully
Changing the user information for paul
Enter the new value, or press ENTER for the default
	Full Name []: Paul
	Room Number []: 
	Work Phone []: 
	Home Phone []: 
	Other []: 
Is the information correct? [Y/n] 

Allow the new user sudo access:

sudo usermod -aG sudo paul

Login as the new user and remove the pi user:

paul@musicbox:~ $ sudo userdel pi

Step 4 - Install Mopidy

The steps here are based on the Official Install Guide for Debian.

paul@musicbox:~ $ wget -q -O - https://apt.mopidy.com/mopidy.gpg | sudo apt-key add -
OK
paul@musicbox:~ $ sudo wget -q -O /etc/apt/sources.list.d/mopidy.list https://apt.mopidy.com/jessie.list
paul@musicbox:~ $ sudo apt-get update

[ long output removed ]

Fetched 9,299 kB in 20s (455 kB/s)                                                                                  
Reading package lists... Done
paul@musicbox:~ $ sudo apt-get install mopidy

[ long output removed ]

0 upgraded, 145 newly installed, 0 to remove and 21 not upgraded.
Need to get 39.3 MB of archives.
After this operation, 130 MB of additional disk space will be used.
Do you want to continue? [Y/n] y

[ long output removed ]

paul@musicbox:~ $ 

The install should only take a few minutes - though that depends in part on the speed of your internet connection.

Once complete, install the Spotify plug-in:

paul@musicbox:~ $ sudo apt-get install mopidy-spotify

[ long output removed ]

0 upgraded, 7 newly installed, 0 to remove and 21 not upgraded.
Need to get 1,141 kB of archives.
After this operation, 3,551 kB of additional disk space will be used.
Do you want to continue? [Y/n] 

[ long output removed ]

paul@musicbox:~ $

And the last piece to install is the musicbox plug-in. The project's GitHub Page suggests using pip to install - which is generally easier and what I'd recommend:

paul@musicbox:~ $ sudo apt-get install python-pip

[ long output removed ]

paul@musicbox:~ $ sudo pip install Mopidy-MusicBox-Webclient

[ long output removed ]

paul@musicbox:~ $

Step 5 - Configuring Mopidy

If you run mopidy without any configuration (in the foreground) you'll likely get output that looks similar to this:

paul@musicbox:~ $ mopidy
INFO     Starting Mopidy 1.1.1
INFO     Loading config from builtin defaults
INFO     Loading config from command line options
INFO     Creating dir /home/paul/.cache/mopidy
INFO     Creating dir /home/paul/.config/mopidy
INFO     Creating dir /home/paul/.local/share/mopidy
INFO     Loading config from builtin defaults
INFO     Loading config from command line options
INFO     Creating file /home/paul/.config/mopidy/mopidy.conf
INFO     Initialized /home/paul/.config/mopidy/mopidy.conf with default config
INFO     Enabled extensions: mpd, http, stream, m3u, softwaremixer, file, musicbox_webclient
INFO     Disabled extensions: spotify, local
WARNING  Found local configuration errors, the extension has been automatically disabled:
WARNING    local/media_dir must be set.
WARNING  Found spotify configuration errors, the extension has been automatically disabled:
WARNING    spotify/username must be set.
WARNING    spotify/password must be set.
WARNING  Please fix the extension configuration errors or disable the extensions to silence these messages.
INFO     Starting Mopidy mixer: SoftwareMixer
INFO     Starting Mopidy audio
INFO     Starting Mopidy backends: StreamBackend, M3UBackend, FileBackend
INFO     Creating dir /home/paul/.local/share/mopidy/m3u
INFO     Loaded 0 M3U playlists from /home/paul/.local/share/mopidy/m3u
INFO     Audio output set to "autoaudiosink"
INFO     Starting Mopidy core
INFO     Starting Mopidy frontends: MpdFrontend, HttpFrontend
INFO     MPD server running at [::ffff:127.0.0.1]:6600
INFO     HTTP server running at [::ffff:127.0.0.1]:6680

The Warnings tell us that Spotify is not configured (and disabled) as is local media (which I'm not bothered about for now).

The configuration for mopidy is stored within /etc/mopidy/mopidy.conf.

Add the following configuration:

[spotify]
username = xxxxx
password = xxxxx
bitrate = 320
volume_normalization = true

The GitHub Page for the Spotify Plugin lists the configuration options available - as we pay for Premium we might as well have the highest bit rate!

Then we can test by running mopidy in the foreground again, to tell it to use the default config file we use the --config argument, and to avoid changing permissions on the config file it is run using sudo as by default only the mopidy user has access:

paul@musicbox:~ $ sudo mopidy --config /etc/mopidy/mopidy.conf 
INFO     Starting Mopidy 1.1.1
INFO     Loading config from builtin defaults
INFO     Loading config from /etc/mopidy/mopidy.conf
INFO     Loading config from command line options
INFO     Enabled extensions: spotify, mpd, http, stream, m3u, softwaremixer, file, musicbox_webclient, local
INFO     Disabled extensions: none
INFO     Starting Mopidy mixer: SoftwareMixer
INFO     Starting Mopidy audio
INFO     Starting Mopidy backends: SpotifyBackend, StreamBackend, M3UBackend, FileBackend, LocalBackend
INFO     Creating dir /var/cache/mopidy/spotify
INFO     Creating dir /var/lib/mopidy/spotify
INFO     Loaded 0 M3U playlists from /var/lib/mopidy/playlists
INFO     Audio output set to "autoaudiosink"
INFO     No local library metadata cache found at /var/lib/mopidy/local/library.json.gz. Please run `mopidy local scan` to index your local music library. If you do not have a local music collection, you can disable the local backend to hide this message.
INFO     Loaded 0 local tracks using json
INFO     Starting Mopidy core
INFO     Starting Mopidy frontends: MpdFrontend, HttpFrontend
INFO     MPD server running at [::ffff:127.0.0.1]:6600
INFO     HTTP server running at [::ffff:127.0.0.1]:6680
INFO     Logged in to Spotify in online mode

Now Spotify has logged in.

Note: If you specify bad credentials you will likely get an error like:

ERROR    Spotify login error: <ErrorType.BAD_USERNAME_OR_PASSWORD: 6>

While Mopidy started you may have noticed two log entries of interest:

INFO     MPD server running at [::ffff:127.0.0.1]:6600
INFO     HTTP server running at [::ffff:127.0.0.1]:6680

The MPD server is Mopidy's own API (for which there are various clients).

The HTTP server is where the musicbox web plugin is mounted. As the log line suggests it is bound only to the loopback interface, which can be verified using lsof.

paul@musicbox:~ $ sudo lsof -i | grep 6680
mopidy    8993  root   20u  IPv4  17770      0t0  TCP localhost:6680 (LISTEN)

This means that right now no clients on the same network as the Pi can access mopidy.

The HTTP Configuration of the Mopidy docs explains the config that can be specified - I changed to the following:

[http]
enabled = true
hostname = 0.0.0.0  
port = 6680
zeroconf = Mopidy HTTP server on $hostname

The Port is unchanged (but I'd rather have it explicity in the config), and it now binds to all interfaces. On restarting Mopidy we should see:

INFO     MPD server running at [::ffff:127.0.0.1]:6600
INFO     HTTP server running at [::ffff:0.0.0.0]:6680

And if I browse to http://192.168.1.57:6680/ from another computer on the nextwork I am redirected to http://192.168.1.57:6680/mopidy/ which presents a page which in turn lets us navigate to MusicBox.

Now we can see if it works.

It doesn't. Upon trying to play a track there is no sound, and the following message shows in the log:

WARNING  Element doesn't implement handling of this stream. Please file a bug.

It seems that by default the Pi sends audio out over HDMI, the following command forces it to use the Headphone Jack:

sudo amixer cset numid=3 1

The final argument in the above command is the output:

0=auto
1=analog
2=HDMI

TODO: sudo apt-get install mopidy-spotify mopidy-alsamixer

Additionally, OK AT FULL NOISY LOWER, KNOWN ISSUE - USB

[audio]
mixer = alsamixer
output = alsasink

[alsamixer]
card = 0
control = PCM

[softwaremixer]
enabled = false

Final for USB:

[spotify]
username = xxx
password = xxx
bitrate = 320
volume_normalization = true

[http]
enabled = true
hostname = 0.0.0.0
port = 6680
zeroconf = Mopidy HTTP server on $hostname

[audio]
mixer = alsamixer
output = alsasink device=plughw:1,0

[alsamixer]
card = 1
control = PCM

[softwaremixer]
enabled = false

Step 6 - Automation

Pre clean up (made as root)

sudo rm /var/log/mopidy/mopidy.log

START ON BOOT

sudo dpkg-reconfigure mopidy

Start on boot

  • Reboot to test

Once booted you can tail the logs with tail -f /var/log/mopidy/mopidy.log to check everything is working as expected.

We found at times the service got a bit stuck so we have a cron job to restart it at 7 PM which conveniently also stops the music should anyone forget.

The job is in root's crontab - sudo nano crontab -e

0 19 * * * /etc/init.d/mopidy restart

Note: The Raspian Jessie Lite image seems to have NTP installed as default so the time syncs regularly.