Streaming music on Android using MPD

2020-05-18

Most people I know have switched to Spotify for their music needs. I have not. I love having a music library that contains exactly what I want it to contain, and that does not change unless I change it. For a long time I had one problem: I did not have access to my music collection on mobile. Instead I used the Bandcamp app, NewPipe, or listened to podcasts instead. This was doable, since I buy most of my music on Bandcamp, but it was certainly less than ideal.

On desktop I use MPD. I love it, but because it colocates the music library and the player it didn’t work for my mobile needs. I can’t keep a copy of all my music on my phone. MPD can output to a stream over HTTP, but there is a significant delay between controls and actual changes in the playback.

For a few years I stopped trying, but earlier this year I did some digging and managed to get things working very nicely. Music is streamed straight from my desktop and files are transcoded to reduce bandwidth usage. I’ll describe my setup.

MPD on Android

The first step to working with MPD on Android is to install a client. I’ve tried a few over the years, and ended up using MPDroid most of the time. (F-Droid, Play Store), It works fairly well, but I had some random issues every time I used it (not very often). This time I found MAFA, and it is much better. It’s not free, but it has almost every feature I want and is stable, polished, and user-friendly.

For the actual playback I needed MPD itself as well, and it turns out MPD is available for Android. (F-Droid, Play Store.) It’s experimental and very bare-bones, but it works. The app really just runs the daemon, and configuration is done by creating an mpd.conf in the external storage directory.

Satellite setup

To be able to play my entire music collection on my phone, MPD needs to be able to access the music files. Files are read from the music directory. Most often that is a local directory, like ~/music, but it does not have to be. One of the other options is to use an http(s) URL.

An easy way to set this up is to run a web server in the music directory:

python -m http.server --directory ~/music 8000

This web server will serve all files from the music directory to the network on port 8000. Now MPD can read from there:

# /storage/emulated/0/mpd.conf
music_directory "http://192.168.1.100:8000/"

An important caveat is that MPD can not scan for music files in a directory served over HTTP. Instead, we can configure the proxy database plugin. This makes MPD query another MPD instance for all library-related functionality.

# /storage/emulated/0/mpd.conf
database {
  plugin "proxy"
  host "192.168.1.100"
}

And that’s it! The result is that I can play everything in my library on my phone as if it were local, as long as my desktop is reachable (over VPN).

(Satellite setup is described here in the MPD documentation.)

The entire mpd.conf on my phone contains only those two snippets:

# /storage/emulated/0/mpd.conf
music_directory "http://192.168.1.100:8000/

database {
  plugin "proxy"
  host "192.168.1.100"
}

Bandwidth usage

One of the reasons I like Bandcamp is that it provides all music in FLAC format. These files are often very large, in the tens of megabytes. That’s fine on desktop, but mobile internet access still involves data caps. I have a cheap data plan with only a few GB of data per month, and I’d prefer not to upgrade. Streaming FLAC files burns through bandwidth extremely quickly, so after a few days I decided I needed to do something about that.

In the past I sometimes transcoded music to a more space-efficient format for storage on my phone, but because we are using the desktop music library we get all the large files. Luckily, MPD does not actually care whether the file it receives for playback uses the same encoding as the file indexed in the library.

Since I’d prefer not to have to maintain transcoded copies of every single file in my library, I ended up writing a simple web server that transcodes files on the fly (by calling out to FFmpeg). You can find the code and some basic documentation on Sourcehut or GitHub. I used Go for this, primarily because it has a web server with support for things like range requests built in.

transcoding-music-server \
    --target ~/.music.transcoded \
    --origin ~/music \
    --bind :8000

Using this instead of the Python web server, all music is transcoded to bandwidth-efficient Opus. The savings are significant: A random sampling of files shows a 50 MB FLAC ends up transcoded to an Opus file of 4 MB, and an MP3 file of 10 MB ends up transcoded to an Opus file of 2 MB.

Conclusion

The above setup works very well for me. When I want to play music on my phone I open MAFA and select some music or continue the previously playing song, just like I would with a more conventional player. Cover images are also handled automatically.

Setting up the basics was surprisingly easy. The required configuration is minimal, and everything worked immediately. Setting up transcoding obviously took a bit more effort, but the server is only 85 lines long (cloc calls it 69 lines of code).

If you still maintain a music library of your own and use MPD, this is a pretty good way to get some of the benefits of a streaming service.

Not everything is perfect yet:

  • I can’t use headset controls to toggle playback

    I have sent a suggestion for this to the MAFA developer, but because it does not play the actual music this may prove to be hard. MPD itself also can’t handle it without migrating to a different sound API.

  • I need to be connected to my home network through VPN

    I currently use the built-in Android VPN settings, but those are very limited. Using it as an always-on VPN results in degraded performance when connected to my home network, but it’s not possible to automatically toggle the VPN when I (dis)connect to my home network. I have set up Tasker to automatically open the VPN settings at the right time, but toggling involves some manual action.

    The IPSec-based VPN directly to my router also does not seem particularly fast. Since I don’t want to expose MPD to the internet, I will have to find a better way to handle this.


Found a problem? Have a question? Shoot me an email.