[SCM] forked-daapd/master: New upstream version 25.0

rbalint at users.alioth.debian.org rbalint at users.alioth.debian.org
Mon Jul 31 00:16:30 UTC 2017


The following commit has been merged in the master branch:
commit 80d97d7e55e6e34c6e836941fb6f3b9d579363c6
Author: Balint Reczey <balint.reczey at canonical.com>
Date:   Thu Jul 27 14:02:43 2017 +0200

    New upstream version 25.0

diff --git a/.gitignore b/.gitignore
index cf5868e..638124c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,6 +29,9 @@ missing
 stamp-h1
 autotools-stamp
 build-stamp
+forked-daapd.spec
+forked-daapd.conf
+forked-daapd.service
 
 # ignore debian packaging for convenience
 debian/
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..8b7e8b0
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,27 @@
+language: c
+sudo: required
+dist: trusty
+env:
+  matrix:
+    - CFG="--disable-verification"
+    - CFG="--enable-lastfm --disable-verification"
+    - CFG="--enable-spotify --disable-verification"
+    - CFG="--enable-chromecast --disable-verification"
+    - CFG="--with-pulseaudio --disable-verification"
+
+script:
+  - autoreconf -fi
+  - ./configure $CFG
+  - make
+  - make clean
+  - scan-build --status-bugs -disable-checker deadcode.DeadStores make
+
+before_install:
+  - wget -q -O - https://apt.mopidy.com/mopidy.gpg | sudo apt-key add -
+  - sudo wget -q -O /etc/apt/sources.list.d/mopidy.list https://apt.mopidy.com/jessie.list
+  - sudo apt-get -qq update
+  - sudo apt-get install -y build-essential clang git autotools-dev autoconf libtool gettext gawk gperf antlr3 libantlr3c-dev libconfuse-dev libunistring-dev libsqlite3-dev libavcodec-dev libavformat-dev libavfilter-dev libswscale-dev libavutil-dev libasound2-dev libmxml-dev libgcrypt11-dev libavahi-client-dev zlib1g-dev libevent-dev libplist-dev libcurl4-openssl-dev libjson-c-dev libspotify-dev libgnutls-dev libprotobuf-c0-dev libpulse-dev
+  
+# Disable email notification
+notifications:
+  email: false
diff --git a/ChangeLog b/ChangeLog
index 2ff4e98..e38ee69 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,6 +1,27 @@
 ChangeLog for forked-daapd
 --------------------------
 
+version 25.0:
+	- improved playback resilience
+	- substitute packet skipping (producing audio "clicks") with start/stop
+	- support for MacOSX with macports and Bonjour mDNS
+	- Airplay device verification for Apple TV 4 w/tvOS 10.2 (uses libsodium)
+	- support for Spotify web api (saved tracks/albums + Discover Weekly)
+	- automatic playback of pipes + support for Shairport metadata pipes
+	- added pipe audio output (fifo)
+	- persistent queues (queue remains across server restart)
+	- support for browser based clients
+	- mpd sendmessage: set individual speaker volume, remote pairing
+	- mpd add http://path-to-radiostream (i.e. add non-library items)
+	- new options, e.g. exclude speakers from list
+	- fix for shuffle mode
+	- fix broken PNG artwork rescaling
+	- use friendly Chromecast name in speaker list
+	- support for libav/ffmpeg dual installs
+	- refactoring - added the input and library interface
+	- performance improvements
+	- and other fixing up...
+
 version 24.2:
 	- Pulseaudio support (can be used for Bluetooth speakers)
 	- new pipe/"fifo" audio output
diff --git a/INSTALL b/INSTALL
index c0a675f..ca31f1b 100644
--- a/INSTALL
+++ b/INSTALL
@@ -31,16 +31,17 @@ libraries:
   antlr3 libantlr3c-dev libconfuse-dev libunistring-dev libsqlite3-dev \
   libavcodec-dev libavformat-dev libavfilter-dev libswscale-dev libavutil-dev \
   libasound2-dev libmxml-dev libgcrypt11-dev libavahi-client-dev zlib1g-dev \
-  libevent-dev
+  libevent-dev libplist-dev libsodium-dev
 
 Optional packages:
 
- Feature    | Configure argument  | Packages
- -----------|---------------------|---------------------------------------------
- Chromecast | --enable-chromecast | libjson-c-dev libgnutls-dev libprotobuf-c-dev
- LastFM     | --enable-lastfm     | libcurl4-gnutls-dev OR libcurl4-openssl-dev
- iTunes XML | --enable-itunes     | libplist-dev
- Pulseaudio | --with-pulseaudio   | libpulse-dev
+ Feature             | Configure argument     | Packages
+ --------------------|------------------------|---------------------------------------------
+ Chromecast          | --enable-chromecast    | libjson-c-dev libgnutls-dev libprotobuf-c-dev
+ LastFM              | --enable-lastfm        | libcurl4-gnutls-dev OR libcurl4-openssl-dev
+ iTunes XML          | --disable-itunes       | libplist-dev
+ Device verification | --disable-verification | libplist-dev libsodium-dev
+ Pulseaudio          | --with-pulseaudio      | libpulse-dev
 
 Note that while forked-daapd will work with versions of libevent between 2.0.0
 and 2.1.3, it is recommended to use 2.1.4+. Otherwise you may not have support
@@ -55,8 +56,9 @@ Then run the following (adding configure arguments for optional features):
  make
  sudo make install
 
-Finally, read the bit in the bottom of this document about init scripts, read
-the README, edit the configuration file and restart the server.
+Finally, read the section 'Long version - after installation' in the bottom of
+this document, which describes configuration, setting up init scripts and adding
+a system user. Also see the README for usage information.
 
 
 Quick version for Fedora
@@ -68,7 +70,8 @@ will need ffmpeg. You can google how to do that. Then run:
 sudo yum install \
  git automake autoconf gettext-devel gperf gawk libtool \
  sqlite-devel libconfuse-devel libunistring-devel mxml-devel libevent-devel \
- avahi-devel libgcrypt-devel zlib-devel alsa-lib-devel ffmpeg-devel
+ avahi-devel libgcrypt-devel zlib-devel alsa-lib-devel ffmpeg-devel \
+ libplist-devel libsodium-devel
 
 Clone the forked-daapd repo:
 
@@ -88,8 +91,9 @@ Then run the following:
  make
  sudo make install
 
-Finally, read the bit in the bottom of this document about init scripts, read
-the README, edit the configuration file and start the server.
+Finally, read the section 'Long version - after installation' in the bottom of
+this document, which describes configuration, setting up init scripts and adding
+a system user. Also see the README for usage information.
 
 
 Quick version for FreeBSD
@@ -101,6 +105,79 @@ the work for you. And should the script not work for you, you can still look
 through it and use it as an installation guide.
 
 
+"Quick" version for macOS (using macports)
+------------------------------------------
+
+Caution: macports requires many downloads and lots of time to install
+(and sometimes build) ports... you'll want a decent network connection
+and some patience!
+
+Install macports (which requires Xcode):
+  https://www.macports.org/install.php
+
+Install Apple's Java (this enables java command on OSX 10.7+):
+  https://support.apple.com/kb/DL1572?locale=en_US
+
+Afterwards, you can optionally install Oracle's newer version, and then
+  choose it using the Java pref in the System Preferences:
+  http://www.oracle.com/technetwork/java/javase/downloads/index.html
+
+sudo port install \
+  autoconf automake libtool pkgconfig git gperf libgcrypt \
+  libunistring libconfuse ffmpeg libevent
+
+Download, configure, build and install the Mini-XML library:
+  http://www.msweet.org/projects.php/Mini-XML
+
+Download, configure, build and install the libinotify library:
+  https://github.com/libinotify-kqueue/libinotify-kqueue
+
+Add the following to .bashrc:
+  # add /usr/local to pkg-config path
+  export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:/opt/local/lib/pkgconfig
+  # libunistring doesn't support pkg-config, set overrides
+  export LIBUNISTRING_CFLAGS=-I/opt/local/include
+  export LIBUNISTRING_LIBS="-L/opt/local/lib -lunistring"
+
+Optional features require the following additional ports:
+
+ Feature             | Configure argument     | Ports
+ --------------------|------------------------|--------------------------------------------
+ Chromecast          | --enable-chromecast    | json-c gnutls protobuf-c
+ LastFM              | --enable-lastfm        | curl
+ iTunes XML          | --disable-itunes       | libplist
+ Device verification | --disable-verification | libplist libsodium
+ Pulseaudio          | --with-pulseaudio      | pulseaudio
+
+Clone the forked-daapd repo:
+  git clone https://github.com/ejurgensen/forked-daapd.git
+  cd forked-daapd
+
+Install antlr3 and library using the included script:
+  scripts/antlr35_install.sh -p /usr/local
+
+Finally, configure, build and install, adding configure arguments for
+  optional features:
+
+ autoreconf -i
+ ./configure
+ make
+ sudo make install
+
+Note: if for some reason you've installed the avahi port, you need to
+  add '--without-avahi' to configure above.
+
+Edit /usr/local/etc/forked-daapd.conf and change the 'uid' to a nice
+system daemon (eg: unknown), and run the following:
+  sudo mkdir -p /usr/local/var/run
+  sudo mkdir -p /usr/local/var/log # or change logfile in conf
+  sudo chown unknown /usr/local/var/cache/forked-daapd # or change conf
+
+Run forked-daapd:
+  sudo /usr/local/sbin/forked-daapd
+
+Verify it's running (you need to Ctrl-C to stop dns-sd):
+  dns-sd -B _daap._tcp
 
 Long version - requirements
 ---------------------------
@@ -147,8 +224,10 @@ Libraries:
 	often already installed as part of your distro
  - libpulse (optional - Pulseaudio local audio)
         from <https://www.freedesktop.org/wiki/Software/PulseAudio/Download/>
- - libplist 0.16+ (optional - iTunes XML support)
+ - libplist 0.16+ (optional - iTunes XML support and Apple TV device verification)
         from <http://github.com/JonathanBeck/libplist/downloads>
+ - libsodium (optional - Apple TV device verification)
+        from <https://download.libsodium.org/doc/>
  - libspotify (optional - Spotify support)
         from <https://developer.spotify.com>
  - libcurl (optional - LastFM support)
@@ -206,12 +285,15 @@ disabled).
 Support for LastFM scrobbling is optional. Use --enable-lastfm to enable this
 feature.
 
-Support for iTunes Music Library XML format is optional. Use --enable-itunes
-to enable this feature.
+Support for iTunes Music Library XML format is optional. Use --disable-itunes
+to disable this feature.
 
 Support for the MPD protocol is optional. Use --disable-mpd to disable this
 feature.
 
+Support for Apple TV device verification is optional. Use --disable-verification
+to disable this feature.
+
 Support for Chromecast devices is optional. Use --enable-chromecast to enable
 this feature.
 
@@ -220,14 +302,16 @@ Building with Pulseaudio is optional. Use --with-pulseaudio to enable.
 Recommended build settings:
  ./configure --prefix=/usr --sysconfdir=/etc --localstatedir=/var
 
-After configure run the usual make, and if that went well, sudo make install
+After configure run the usual make, and if that went well, 'sudo make install'
 
 
 Long version - after installation
 ---------------------------------
 
-After installation, edit the configuration file, /etc/forked-daapd.conf and
-adjust the values at your convenience.
+After installation, edit the configuration file, /etc/forked-daapd.conf.
+
+Note that 'sudo make install' will not install any system files to start the
+service after boot, and it will not setup a system user.
 
 forked-daapd will drop privileges to any user you'll specify in the
 configuration file if it's started as root.
diff --git a/Makefile.am b/Makefile.am
index 1d76649..52d0fa5 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,12 +1,46 @@
 ACLOCAL_AMFLAGS = -I m4
 
-sysconf_DATA = forked-daapd.conf
+RPM_SPEC_FILE = forked-daapd.spec
+CONF_FILE = forked-daapd.conf
+SYSTEMD_SERVICE_FILE = forked-daapd.service
+
+sysconf_DATA = $(CONF_FILE)
+
+BUILT_SOURCES = $(CONF_FILE) $(SYSTEMD_SERVICE_FILE)
 
-EXTRA_DIST = configure
 SUBDIRS = sqlext src
 
-man_MANS = forked-daapd.8
+dist_man_MANS = forked-daapd.8
+
+nobase_dist_doc_DATA = \
+	UPGRADING \
+	README.md \
+	README_PULSE.md \
+	README_SMARTPL.md \
+	scripts/pairinghelper.sh
+
+EXTRA_DIST = \
+	$(CONF_FILE).in \
+	$(SYSTEMD_SERVICE_FILE).in \
+	$(RPM_SPEC_FILE)
 
 install-data-hook:
-	$(MKDIR_P) $(DESTDIR)$(localstatedir)/cache/forked-daapd/libspotify
+	$(MKDIR_P) "$(DESTDIR)$(localstatedir)/cache/$(PACKAGE)/libspotify"
+	$(MKDIR_P) "$(DESTDIR)$(localstatedir)/log"
+
+CLEANFILES = $(BUILT_SOURCES)
+
+do_subst = $(SED) -e 's|@sbindir[@]|$(sbindir)|g' \
+             -e 's|@localstatedir[@]|$(localstatedir)|g' \
+             -e 's|@PACKAGE[@]|$(PACKAGE)|g' \
+             -e 's|@DAAPD_USER[@]|$(DAAPD_USER)|g'
+
+# these files use $prefix, which is determined at build (not configure) time
+$(CONF_FILE) $(SYSTEMD_SERVICE_FILE): Makefile
+	$(AM_V_at)rm -f $@ $@-t
+	$(AM_V_GEN)$(do_subst) "$(srcdir)/$@.in" > $@-t
+	$(AM_V_at)mv $@-t $@
+
+$(CONF_FILE): $(srcdir)/$(CONF_FILE).in
 
+$(SYSTEMD_SERVICE_FILE): $(srcdir)/$(SYSTEMD_SERVICE_FILE).in
diff --git a/README.md b/README.md
index 77689e4..822723c 100644
--- a/README.md
+++ b/README.md
@@ -96,6 +96,9 @@ controllers/remotes (e.g. Retune), but some do not require pairing.
 
 ### Pairing with Remote on iPod/iPhone
 
+NOTE: These are the instructions for the current version of forked-daapd - for
+versions 24.2 and earlier see [here](https://github.com/ejurgensen/forked-daapd/blob/24.2/README.md#using-remote)
+
 If you just started forked-daapd for the first time, then wait til the library
 scan completes before pairing with Remote (see [library](#library)). Otherwise
 you risk timeouts. Then do the following.
@@ -104,6 +107,10 @@ The Quick Way:
 
  1. Download and run the helper script from [here](https://raw.githubusercontent.com/ejurgensen/forked-daapd/master/scripts/pairinghelper.sh)
 
+Another option is to use mpc (MPD command line client):
+
+ 1. `mpc sendmessage pairing 5387` (where 5387 is the 4-digit pairing code displayed by Remote)
+
 Or, if that doesn't work:
 
  1. Start forked-daapd
@@ -121,16 +128,8 @@ Or, if that doesn't work:
     doesn't work properly on your network.
     
  4. Prepare a text file with a filename ending with .remote; the filename
-    doesn't matter, only the .remote ending does. This file must contain
-    two lines: the first line is the name of your iPod/iPhone/iPad, the second
-    is the 4-digit pairing code displayed by Remote.
-    
-    If your iPod/iPhone/iPad is named "Foobar" and Remote gives you the pairing
-    code 5387, the file content must be:
-    ```
-    Foobar
-    5387 
-    ```
+    doesn't matter, only the .remote ending does. This first line in the file
+    must contain the 4-digit pairing code displayed by Remote.
     
  5. Move this file somewhere in your library
 
@@ -147,22 +146,25 @@ forked-daapd does not get notified about new files on network mounts, so the
 Solution: Set two library paths in the config, and add the .remote file to the
 local path. See [Libraries on network mounts](#libraries-on-network-mounts).
 
-#### You did not enter the correct name or pairing code
+#### You did not enter the correct pairing code
 You will see an error in the log about pairing failure with a HTTP response code
 that is *not* 0.
-Solution: Copy-paste the name to be sure to get specials characters right. You
-can also try the pairinghelper script located in the scripts-folder of the
-source.
+Solution: Try again. You can also try the pairinghelper script located in the
+scripts-folder of the source or the mpc method described above.
 
 #### No response from Remote, possibly a network issue
 If you see an error in the log with either:
  - a HTTP response code that is 0
  - "Empty pairing request callback"
 it means that forked-daapd could not establish a connection to Remote. This 
-might be a network issue.
-Solution: Sometimes it resolves the issue if you force Remote to quit, restart
+might be a network issue, your router may not be allowing multicast between the
+Remote device and the host forked-daapd is running on.
+Solution 1: Sometimes it resolves the issue if you force Remote to quit, restart
 it and do the pairing proces again. Another trick is to establish some other
 connection (eg SSH) from the iPod/iPhone/iPad to the host.
+Solution 2: Check your router settings if you can whitelist multicast addresses
+under IGMP settings. For Apple Bonjour, setting a multicast address of
+224.0.0.251 and a netmask of 255.255.255.255 should work.
 
 Otherwise try using avahi-browse for troubleshooting:
  - in a terminal, run `avahi-browse -r -k _touch-remote._tcp`
@@ -180,9 +182,8 @@ Otherwise try using avahi-browse for troubleshooting:
 
 Hit Ctrl-C to terminate avahi-browse.
 
-The name of your iPod/iPhone/iPad is the value of the DvNm field above. In this
-example, the correct value is Foobar. To check for network issues you can try to
-connect to address and port with telnet.
+To check for network issues you can try to connect to address and port with
+telnet.
 
 ### Selecting output devices
 
@@ -206,6 +207,18 @@ devices that are password-protected, the device's AirPlay name and password
 must be given in the configuration file. See the sample configuration file
 for the syntax.
 
+If your Apple TV requires device verification (always required by Apple TV4 with
+tvOS 10.2) then you must select the device for playback, whereafter a PIN will
+be displayed by the Apple TV. The do either of the following:
+
+Alternative 1: Create a file ending with .verification in your music library,
+input the PIN, and save the file. Forked-daapd will now pair with the device,
+and if you select the device again, playback should start.
+Alternative 2: Run "mpc sendmessage verification [PIN]" (requires the mpc tool),
+and then select the device again. Playback should start.
+
+For troubleshooting, see [using Remote](#using-remote).
+
 
 ## Chromecast
 
@@ -323,6 +336,9 @@ configuration file. Here you can also enable/disable support for individual
 file artwork (instead of using the same artwork for all tracks in an entire
 album).
 
+For playlists in your library, say /foo/bar.m3u, then for any http streams in
+the list, forked-daapd will look for /foo/bar.{jpg,png}.
+
 You can use symlinks for the artwork files.
 
 forked-daapd caches artwork in a separate cache file. The default path is 
@@ -351,26 +367,32 @@ configuration file a rescan is required before the new setting will take effect.
 Currently, this will not be done automatically, so you need to trigger the
 rescan as described below.
 
+Symlinks are supported and dereferenced, but it is best to use them for
+directories only.
+
 
-### Symlinks and pipes
+### Pipes (for e.g. multiroom with Shairport-sync)
 
-Symlinks are supported and dereferenced. This does interact in tricky ways
-with the above monitoring and rescanning, so you've been warned. Changes to
-symlinks themselves won't be taken into account, or not the way you'd expect.
+Some programs, like for instance Shairport-sync, can be configured to output
+audio to a named pipe. If this pipe is placed in the library, forked-daapd will
+automatically detect that it is there, and when there is audio being written to
+it, playback of the audio will be autostarted (and stopped).
 
-If you use symlinks, do not move around the target of the symlink. Avoid
-linking files, as files themselves aren't monitored for changes individually,
-so changes won't be noticed unless the file happens to be in a directory that
-is monitored.
+Using this feature, forked-daapd can act as an AirPlay multiroom "router": You
+can have an AirPlay source (e.g. your iPhone) send audio Shairport-sync, which
+forwards it to forked-daapd through the pipe, which then plays it on whatever
+speakers you have selected (through Remote).
 
-Bottom line: symlinks are for directories only.
+The format of the audio being written to the pipe must be PCM16.
 
-Pipes made with mkfifo are also supported. This feature can be useful if you
-have a program that can stream PCM16 audio to a pipe. Forked-daapd can then
-forward the audio to one or more AirPlay speakers.
+You can also start playback of pipes manually. You will find them in remotes 
+listed under "Unknown artist" and "Unknown album". The track title will be the
+name of the pipe.
 
-Pipes have no metadata, so they will be added with "Unknown artist" and "Unknown
-album". The name of the pipe will be used as the track title.
+Shairport-sync can write metadata to a pipe, and forked-daapd can read this.
+This requires that the metadata pipe has the same filename as the audio pipe
+plus a ".metadata" suffix. Say Shairport-sync is configured to write audio to
+"/foo/bar/pipe", then the metadata pipe should be "/foo/bar/pipe.metadata".
 
 
 ### Libraries on network mounts
@@ -435,40 +457,46 @@ curl "http://localhost:3689/logout?session-id=50"
 
 ## Spotify
 
-forked-daapd has *some* support for Spotify. It must be compiled with the
-`--enable-spotify option` (see
+forked-daapd has support for playback of the tracks in your Spotify library. It
+must have been compiled with the `--enable-spotify` option (see
 [INSTALL](https://github.com/ejurgensen/forked-daapd/blob/master/INSTALL)).
-You must have also have libspotify installed, otherwise the Spotify integration
-will not be available. You can get libspotify here:
+You must also have libspotify installed, otherwise Spotify integration will not
+be available. Unfortunately the library is no longer available from Spotify, and
+at the time of writing they have not provided an alternative. You can, however,
+still get libspotify here:
 
-  - Original (binary) tar.gz, see https://developer.spotify.com
-  - Debian package (libspotify-dev), see https://apt.mopidy.com
+- Debian package (libspotify-dev), see https://apt.mopidy.com
   
 You must also have a Spotify premium account. If you normally log into Spotify
 with your Facebook account you must first go to Spotify's web site where you can
 get the Spotify username and password that matches your account. With
 forked-daapd you cannot login into Spotify with your Facebook username/password.
 
-The procedure for logging in to Spotify is very much like the Remote pairing
-procedure. You must prepare a file, which should have the ending ".spotify".
-The file must have two lines: The first is your Spotify user name, and the
-second is your password. Move the file to your forked-daapd library.
-Forked-daapd will then log in and add all the music in your Spotify playlists
-to its database.
+The procedure for logging in to Spotify is a two-step procedure due to the
+current state of libspotify:
+
+1. Put a file in your forked-daapd library containing two lines, the first being
+   your Spotify user name, and the second your password. The filename must have
+   the ending ".spotify"
+2. Delete the file again - forked-daapd will have read it.
+3. forked-daapd will log in and add all music in your Spotify playlists to its
+   database. Wait until completed (follow progress in the log file).
+4. In a browser, go to http://forked-daapd.local:3689/oauth and click the link
+   to authorize forked-daapd with Spotify.
 
 Spotify will automatically notify forked-daapd about playlist updates, so you
-should not need to restart forked-daapd to syncronize with Spotify.
+should not need to restart forked-daapd to syncronize with Spotify. However,
+Spotify only notifies about playlist updates, not new saved tracks/albums, so
+you need to repeat step 4 above to load those.
 
-For safety you should delete the ".spotify" file after first login. Forked-daapd
-will not store your password, but will still be able to log you in automatically
-afterwards, because libspotify saves a login token. You can configure the
-location of your Spotify user data in the configuration file.
+Forked-daapd will not store your password, but will still be able to log you in
+automatically afterwards, because libspotify saves a login token. You can
+configure the location of your Spotify user data in the configuration file.
 
 To permanently logout and remove credentials, delete the contents of
 `/var/cache/forked-daapd/libspotify` (while forked-daapd is stopped).
 
-Limitations: You will only be able to play tracks from your Spotify playlists,
-so you can't search and listen to music from the rest of the Spotify catalogue.
+Limitations:
 You will not be able to do any playlist management through forked-daapd - use
 a Spotify client for that. You also can only listen to your music by letting
 forked-daapd do the playback - so that means you can't stream from forked-daapd
@@ -507,7 +535,7 @@ are supported by forked-daapd.
 Due to some differences between forked-daapd and MPD not all commands will act
 the same way they would running MPD:
 
-- consume, crossfade, mixrampdb, mixrampdelay and replaygain will have no effect
+- crossfade, mixrampdb, mixrampdelay and replaygain will have no effect
 - single, repeat: unlike MPD forked-daapd does not support setting single and repeat separately 
   on/off, instead repeat off, repeat all and repeat single are supported. Thus setting single on 
   will result in repeat single, repeat on results in repeat all.
diff --git a/_config.yml b/_config.yml
new file mode 100644
index 0000000..c741881
--- /dev/null
+++ b/_config.yml
@@ -0,0 +1 @@
+theme: jekyll-theme-slate
\ No newline at end of file
diff --git a/config.rpath b/build-aux/config.rpath
similarity index 100%
rename from config.rpath
rename to build-aux/config.rpath
diff --git a/configure.ac b/configure.ac
index 3657803..5d4d725 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,243 +1,400 @@
 dnl Process this file with autoconf to produce a configure script.
 
-AC_INIT([forked-daapd], [24.2])
+AC_PREREQ([2.60])
+AC_INIT([forked-daapd], [25.0])
+
 AC_CONFIG_SRCDIR([config.h.in])
 AC_CONFIG_MACRO_DIR([m4])
+AC_CONFIG_AUX_DIR([build-aux])
 AC_CONFIG_HEADERS([config.h])
-AC_DEFINE_UNQUOTED([BUILDDATE], ["`date -Idate`"], [Build date])
-AM_INIT_AUTOMAKE([foreign -Wno-portability subdir-objects])
+AM_INIT_AUTOMAKE([foreign subdir-objects 1.11])
+AM_SILENT_RULES([no])
 
+dnl Requires autoconf 2.60
 AC_USE_SYSTEM_EXTENSIONS
 
 dnl Checks for programs.
 AC_PROG_CC
-AM_PROG_CC_C_O
+AC_PROG_CC_C_O
+AC_PROG_SED
+AC_PROG_MKDIR_P
 LT_INIT([disable-static])
 
-AC_CHECK_PROG(GPERF, [gperf], [gperf])
-if test "x$GPERF" = x; then
-	AC_MSG_ERROR([GNU gperf not found, please install it])
-fi
-AC_SUBST(GPERF)
-
-AC_CHECK_PROG(ANTLR, [antlr3], [antlr3])
-if test "x$ANTLR" = x; then
-	if test -d $srcdir/src/pregen; then
-		for f in $srcdir/src/pregen/*; do
-			bf=`basename $f`
-			ln -sf pregen/$bf $srcdir/src/$bf
-		done
-		AC_MSG_NOTICE([antlr3 wrapper not found, using pre-generated files])
-	else
-		AC_MSG_ERROR([antlr3 wrapper not found and pre-generated files not available])
-	fi
-fi
-AC_SUBST(ANTLR)
-AM_CONDITIONAL(COND_ANTLR, test "x$ANTLR" != x)
-
-CFLAGS="$CFLAGS -Wall -D_LARGEFILE_SOURCE"
-
-AC_CHECK_HEADERS([sys/wait.h])
-AC_CHECK_HEADERS([sys/param.h])
-AC_CHECK_HEADERS([sys/select.h])
-AC_CHECK_HEADERS([dirent.h])
-AC_CHECK_HEADERS([regex.h])
-AC_CHECK_HEADERS([pthread_np.h])
-AC_CHECK_FUNCS(posix_fadvise)
-AC_CHECK_FUNCS(strptime)
-AC_CHECK_FUNCS(strtok_r)
-AC_CHECK_FUNCS(timegm)
-AC_CHECK_FUNCS(euidaccess)
-AC_CHECK_FUNCS(pipe2)
+AC_PATH_PROG([GPERF], [[gperf]])
+AS_IF([[test -z "$GPERF"]],
+	[AS_IF([[test -f "$srcdir/src/dmap_fields_hash.h"]],
+		[AM_MISSING_PROG([GPERF], [[gperf]])
+		AC_MSG_NOTICE([[
+
+GNU gperf not found, but it's output appears to be present.
+If you modify any gperf or ANTLR grammar files (.g), you will need
+to install it.]])],
+		[AC_MSG_ERROR([[GNU gperf required, please install it.]])])
+	])
+
+GPERF_TEST="$(echo foo,bar | ${GPERF} -L ANSI-C)"
+
+AC_COMPILE_IFELSE(
+	[AC_LANG_PROGRAM([
+		#include <string.h>
+		const char * in_word_set(const char *, size_t);
+		$GPERF_TEST]
+	)],
+	[GPERF_LEN_TYPE=size_t],
+	[AC_COMPILE_IFELSE(
+		[AC_LANG_PROGRAM([
+			#include <string.h>
+			const char * in_word_set(const char *, unsigned);
+			$GPERF_TEST]
+		)],
+		[GPERF_LEN_TYPE=unsigned],
+		[AC_MSG_ERROR([** unable to determine gperf len type])]
+	)]
+)
+
+AC_DEFINE_UNQUOTED([GPERF_LEN_TYPE], [$GPERF_LEN_TYPE], [gperf len type])
+AC_PATH_PROG([ANTLR], [[antlr3]])
+AS_IF([[test -z "$ANTLR"]],
+	[AS_IF([[test -f "$srcdir/src/SMARTPLLexer.h"]],
+		[AM_MISSING_PROG([ANTLR], [[antlr3]])
+		AC_MSG_NOTICE([[
+
+antlr3 not found, but it's output appears to be present.
+If you modify any ANTLR grammar files (.g), you will need to install it.]])],
+		[AC_MSG_ERROR([[antlr3 wrapper required, please install it.]])])
+	])
+
+dnl Enable all warnings by default.
+AM_CPPFLAGS="-Wall"
+AC_SUBST([AM_CPPFLAGS])
 
+dnl Checks for header files.
+AC_CHECK_HEADERS_ONCE([regex.h pthread_np.h])
+AC_CHECK_HEADERS([sys/wait.h sys/param.h dirent.h getopt.h stdint.h], [],
+	[AC_MSG_ERROR([[Missing header required to build forked-daapd]])])
+AC_CHECK_HEADERS([time.h], [],
+	[AC_MSG_ERROR([[Missing header required to build forked-daapd]])])
+AC_CHECK_FUNCS_ONCE([posix_fadvise euidaccess pipe2])
+AC_CHECK_FUNCS([strptime strtok_r], [],
+	[AC_MSG_ERROR([[Missing function required to build forked-daapd]])])
+
+dnl check for clock_gettime or replace it
+AC_SEARCH_LIBS([clock_gettime], [rt],
+	[AC_DEFINE([HAVE_CLOCK_GETTIME], 1,
+		[Define to 1 if have clock_gettime function])],
+	[AC_CHECK_HEADER([mach/mach_time.h],
+	[AC_DEFINE([HAVE_MACH_CLOCK], 1,
+		[Define to 1 if mach kernel clock replacement available])],
+	[AC_MSG_ERROR([[Missing clock_gettime and any replacement]])])])
+
+dnl check for timer_settime or replace it
+AC_SEARCH_LIBS([timer_settime], [rt],
+	[AC_DEFINE([HAVE_TIMER_SETTIME], 1,
+		[Define to 1 if have timer_settime function])],
+	[AC_CHECK_HEADER([mach/mach_time.h],
+	[AC_DEFINE([HAVE_MACH_TIMER], 1,
+		[Define to 1 if mach kernel clock replacement available])],
+	[AC_MSG_ERROR([[Missing timer_settime and any replacement]])])])
+
+AC_SEARCH_LIBS([pthread_exit], [pthread], [],
+	[AC_MSG_ERROR([[pthreads library is required]])])
 AC_SEARCH_LIBS([pthread_setname_np], [pthread],
-	AC_DEFINE(HAVE_PTHREAD_SETNAME_NP, 1, [Define to 1 if you have pthread_setname_np]),
-	AC_SEARCH_LIBS([pthread_set_name_np], [pthread], AC_DEFINE(HAVE_PTHREAD_SET_NAME_NP, 1, [Define to 1 if you have pthread_set_name_np]))
-)
-AC_SEARCH_LIBS([inotify_add_watch], [inotify], [], AC_MSG_ERROR([inotify not found]))
+	[dnl Validate pthread_setname_np with 2 args (some have 1)
+	 AC_MSG_CHECKING([[for two-parameter pthread_setname_np]])
+	 AC_TRY_LINK([@%:@include <pthread.h>],
+		[pthread_setname_np(pthread_self(), "name");],
+		[AC_MSG_RESULT([yes])
+		 AC_DEFINE([HAVE_PTHREAD_SETNAME_NP], 1,
+			[Define to 1 if you have pthread_setname_np])],
+		[AC_MSG_RESULT([[no]])])],
+	[AC_SEARCH_LIBS([pthread_set_name_np], [pthread],
+		[AC_CHECK_FUNCS([pthread_set_name_np])])])
 
 dnl Large File Support (LFS)
 AC_SYS_LARGEFILE
 AC_TYPE_OFF_T
 
 dnl Checks for libraries.
-gl_LIBUNISTRING
-
-if test x$HAVE_LIBUNISTRING != xyes; then
-   AC_MSG_ERROR([GNU libunistring is required])
-fi
-
-PKG_CHECK_MODULES(ZLIB, [ zlib ])
-PKG_CHECK_MODULES(CONFUSE, [ libconfuse ])
-PKG_CHECK_MODULES(AVAHI, [ avahi-client >= 0.6.24 ])
-PKG_CHECK_MODULES(SQLITE3, [ sqlite3 >= 3.5.0 ])
-
-save_LIBS="$LIBS"
-LIBS="$SQLITE3_LIBS"
-dnl Check that SQLite3 has the unlock notify API built-in
-AC_CHECK_LIB([sqlite3], [sqlite3_unlock_notify], [], AC_MSG_ERROR([SQLite3 was built without unlock notify support]))
-dnl Check that SQLite3 has been built with threadsafe operations
-AC_MSG_CHECKING([if SQLite3 was built with threadsafe operations support])
-AC_LANG_PUSH([C])
-AC_RUN_IFELSE(
-  [AC_LANG_PROGRAM([dnl
-    #include <sqlite3.h>
-    ], [dnl
-    int ret = sqlite3_config(SQLITE_CONFIG_MULTITHREAD);
-    if (ret != SQLITE_OK)
-      return 1;
-    return 0;])],
-  [AC_MSG_RESULT([yes])], [AC_MSG_ERROR([SQLite3 was not built with threadsafe operations support])],
-  [AC_MSG_RESULT([runtime will tell])])
-AC_LANG_POP([C])
-LIBS="$save_LIBS"
-
-PKG_CHECK_MODULES(LIBAV, [ libavformat libavcodec libswscale libavutil libavfilter ])
-
-dnl Checks for misc libav and ffmpeg API differences
-save_LIBS="$LIBS"
-AC_CHECK_LIB([avcodec], [avcodec_find_best_pix_fmt_of_list],
-	AC_DEFINE(HAVE_FFMPEG, 1, [Define to 1 if you have ffmpeg/libav with avcodec_find_best_pix_fmt_of_list]),,[-lavutil])
-
-AC_CHECK_LIB([avfilter], [av_buffersrc_add_frame_flags],
-	AC_DEFINE(HAVE_LIBAV_BUFFERSRC_ADD_FRAME_FLAGS, 1, [Define to 1 if you have ffmpeg/libav with av_buffersrc_add_frame_flags]))
-AC_CHECK_LIB([avfilter], [av_buffersink_get_frame],
-	AC_DEFINE(HAVE_LIBAV_BUFFERSINK_GET_FRAME, 1, [Define to 1 if you have ffmpeg/libav with av_buffersink_get_frame]))
-AC_CHECK_LIB([avfilter], [avfilter_graph_parse_ptr],
-	AC_DEFINE(HAVE_LIBAV_GRAPH_PARSE_PTR, 1, [Define to 1 if you have ffmpeg/libav with avfilter_graph_parse_ptr]))
-AC_CHECK_LIB([avcodec], [av_packet_unref],
-	AC_DEFINE(HAVE_LIBAV_PACKET_UNREF, 1, [Define to 1 if you have ffmpeg/libav with av_packet_unref]),,[-lavutil])
-AC_CHECK_LIB([avcodec], [av_packet_rescale_ts],
-	AC_DEFINE(HAVE_LIBAV_PACKET_RESCALE_TS, 1, [Define to 1 if you have ffmpeg/libav with av_packet_rescale_ts]),,[-lavutil])
-AC_CHECK_LIB([avformat], [avformat_alloc_output_context2],
-	AC_DEFINE(HAVE_LIBAV_ALLOC_OUTPUT_CONTEXT2, 1, [Define to 1 if you have ffmpeg/libav with avformat_alloc_output_context2]))
-AC_CHECK_LIB([avutil], [av_frame_alloc],
-	AC_DEFINE(HAVE_LIBAV_FRAME_ALLOC, 1, [Define to 1 if you have ffmpeg/libav with av_frame_alloc]))
-AC_CHECK_LIB([avutil], [av_frame_get_best_effort_timestamp],
-	AC_DEFINE(HAVE_LIBAV_BEST_EFFORT_TIMESTAMP, 1, [Define to 1 if you have ffmpeg/libav with av_frame_get_best_effort_timestamp]))
-AC_CHECK_LIB([avutil], [av_image_fill_arrays],
-	AC_DEFINE(HAVE_LIBAV_IMAGE_FILL_ARRAYS, 1, [Define to 1 if you have ffmpeg/libav with av_image_fill_arrays]))
-AC_CHECK_LIB([avutil], [av_image_get_buffer_size],
-	AC_DEFINE(HAVE_LIBAV_IMAGE_GET_BUFFER_SIZE, 1, [Define to 1 if you have ffmpeg/libav with av_image_get_buffer_size]))
-
-AC_CHECK_HEADERS([libavutil/channel_layout.h])
-AC_CHECK_HEADERS([libavutil/mathematics.h])
-LIBS="$save_LIBS"
-
-PKG_CHECK_MODULES(MINIXML, [ mxml ])
-
-PKG_CHECK_MODULES(LIBEVENT, [ libevent >= 2 ])
-PKG_CHECK_EXISTS([ libevent >= 2.1.4 ], ,
-	AC_DEFINE(HAVE_LIBEVENT2_OLD, 1, [Define to 1 if you have libevent 2 (<2.1.4)])
-)
-
-AC_CHECK_HEADER(antlr3.h, , AC_MSG_ERROR([antlr3.h not found]))
-AC_CHECK_LIB([antlr3c], [antlr3BaseRecognizerNew], [ANTLR3C_LIBS="-lantlr3c"], AC_MSG_ERROR([ANTLR3 C runtime (libantlr3c) not found]))
-AC_CHECK_LIB([antlr3c], [antlr3NewAsciiStringInPlaceStream],
-	AC_DEFINE(ANTLR3C_NEW_INPUT, 0, [define if antlr3 C runtime uses new input routines]),
-	AC_DEFINE(ANTLR3C_NEW_INPUT, 1, [define if antlr3 C runtime uses new input routines]))
-AC_SUBST(ANTLR3C_LIBS)
 
-AM_PATH_LIBGCRYPT([1:1.2.0], , AC_MSG_ERROR([libgcrypt not found]))
-AM_PATH_GPG_ERROR([1.6], , AC_MSG_ERROR([libgpg-error not found]))
-
-case "$host" in
-  *-*-linux-*)
-	AC_CHECK_HEADERS([sys/eventfd.h])
-	AC_CHECK_FUNC(eventfd_write, AC_DEFINE(HAVE_EVENTFD, 1, [Define to 1 if you have eventfd]))
-
-	AC_CHECK_HEADER(sys/signalfd.h, , AC_MSG_ERROR([signalfd required; glibc 2.9+ recommended]))
-
-	AC_CHECK_HEADER(sys/timerfd.h, , AC_MSG_ERROR([timerfd required; glibc 2.8+ recommended]))
-	AC_CHECK_FUNC(timerfd_create, , AC_MSG_ERROR([timerfd required; glibc 2.8+ recommended]))
-	;;
-esac
-
-AC_CHECK_SIZEOF(void *)
-
-AC_CHECK_HEADERS(getopt.h,,)
-AC_CHECK_HEADERS(stdint.h,,)
+AC_SUBST([COMMON_LIBS])
+AC_SUBST([COMMON_CPPFLAGS])
+AC_SUBST([FORKED_LIBS])
+AC_SUBST([FORKED_CPPFLAGS])
+AC_SUBST([FORKED_OPTS_LIBS])
+AC_SUBST([FORKED_OPTS_CPPFLAGS])
+
+AM_ICONV
+dnl All FORK_ macros defined in m4/fork_checks.m4
+FORK_FUNC_REQUIRE([COMMON], [GNU libunistring], [LIBUNISTRING], [unistring],
+	[u8_strconv_from_locale], [uniconv.h], [],
+	[dnl Retry test with iconv library
+	 FORK_VARS_PREPEND([COMMON], [LIBICONV], [INCICONV])
+	 FORK_FUNC_REQUIRE([COMMON], [GNU libunistring], [LIBUNISTRING],
+		[unistring], [u8_strconv_from_locale], [uniconv.h])])
+
+FORK_MODULES_CHECK([FORKED], [ZLIB], [zlib], [deflate], [zlib.h])
+FORK_MODULES_CHECK([FORKED], [CONFUSE], [libconfuse], [cfg_init], [confuse.h])
+FORK_MODULES_CHECK([FORKED], [MINIXML], [mxml], [mxmlNewElement], [mxml.h],
+	[AC_CHECK_FUNCS([mxmlGetOpaque] [mxmlGetText] [mxmlGetType] [mxmlGetFirstChild])])
+
+dnl SQLite3 requires extra checks
+FORK_MODULES_CHECK([COMMON], [SQLITE3], [sqlite3 >= 3.5.0],
+	[sqlite3_initialize], [sqlite3.h],
+	[dnl Check that SQLite3 has the unlock notify API built-in
+	 AC_CHECK_FUNC([[sqlite3_unlock_notify]], [],
+		[AC_MSG_ERROR([[SQLite3 was built without unlock notify support]])])
+	 dnl Check that SQLite3 has been built with threadsafe operations
+	 AC_MSG_CHECKING([[if SQLite3 was built with threadsafe operations support]])
+	 AC_RUN_IFELSE([AC_LANG_PROGRAM([[#include <sqlite3.h>
+		]], [[
+		int ret = sqlite3_config(SQLITE_CONFIG_MULTITHREAD);
+		if (ret != SQLITE_OK)
+		  return 1;]])],
+		[AC_MSG_RESULT([[yes]])],
+		[AC_MSG_RESULT([[no]])
+		 AC_MSG_ERROR([[SQLite3 was not built with threadsafe operations support]])],
+		[AC_MSG_RESULT([[runtime will tell]])])
+	])
+
+dnl libevent2 requires version checks
+FORK_MODULES_CHECK([FORKED], [LIBEVENT], [libevent >= 2],
+	[event_base_new], [event2/event.h],
+	[dnl check for old version
+	 PKG_CHECK_EXISTS([libevent >= 2.1.4], [],
+		[AC_DEFINE([HAVE_LIBEVENT2_OLD], 1,
+			[Define to 1 if you have libevent 2 (<2.1.4)])])
+	])
+
+dnl antlr version checks
+FORK_FUNC_REQUIRE([FORKED], [ANTLR3 C runtime], [ANTLR3C], [antlr3c],
+	[antlr3BaseRecognizerNew], [antlr3.h],
+	[AC_CHECK_FUNC([[antlr3NewAsciiStringInPlaceStream]],
+		[AC_DEFINE([ANTLR3C_NEW_INPUT], 0,
+			[define if antlr3 C runtime uses new input routines])],
+		[AC_DEFINE([ANTLR3C_NEW_INPUT], 1,
+			[define if antlr3 C runtime uses new input routines])])
+	])
+
+AM_PATH_LIBGCRYPT([1:1.2.0])
+FORK_FUNC_REQUIRE([FORKED], [GNU Crypt Library], [LIBGCRYPT], [gcrypt],
+	[gcry_control], [gcrypt.h])
+AM_PATH_GPG_ERROR([1.6])
+FORK_FUNC_REQUIRE([FORKED], [GNUPG Error Values], [GPG_ERROR_MT], [gpg-error],
+	[gpg_err_init], [gpg-error.h])
+
+AC_CHECK_HEADER([sys/eventfd.h], [AC_CHECK_FUNCS([eventfd])])
+
+AC_CHECK_HEADER([sys/timerfd.h], [AC_CHECK_FUNC([timerfd_create],
+	[AC_DEFINE([HAVE_TIMERFD], 1, [Define to 1 if you have timerfd])])])
+
+FORK_FUNC_REQUIRE([FORKED], [inotify], [INOTIFY], [inotify],
+	[inotify_add_watch], [sys/inotify.h])
+
+have_signal=no
+AC_CHECK_HEADER([sys/signalfd.h], [AC_CHECK_FUNCS([signalfd], [have_signal=yes])])
+AC_CHECK_HEADER([sys/event.h], [AC_CHECK_FUNCS([kqueue], [have_signal=yes])])
+AS_IF([[test "$have_signal" = "no"]],
+	[AC_MSG_ERROR([[Either signalfd or kqueue are required]])])
+
+AC_CHECK_HEADERS_ONCE([endian.h sys/endian.h])
+AC_CHECK_DECL([htobe16], [],
+	[AC_CHECK_HEADERS([libkern/OSByteOrder.h], [],
+		[AC_MSG_ERROR([[Missing functions to swap byte order]])])],
+	[AC_INCLUDES_DEFAULT[
+#ifdef HAVE_ENDIAN_H
+# include <endian.h>
+#elif defined(HAVE_SYS_ENDIAN_H)
+# include <sys/endian.h>
+#endif
+	]])
+
+dnl libav checks should be last, as they are sometimes both installed
+dnl and the CPPFLAGS/LIBS needs to be at the front of the search list.
+dnl Handle alternative package names for libav
+PKG_CHECK_EXISTS([libavcodec-libav], [LIBAV=-libav], [LIBAV=])
+dnl Preference for ffmpeg if we have both (this could be an option...)
+PKG_CHECK_EXISTS([libavcodec], [LIBAV=])
+dnl Option to choose libav even if ffmpeg is detected first
+AC_ARG_WITH([libav], [AS_HELP_STRING([--with-libav],
+	[choose libav even if ffmpeg present (default=no)])],
+	[[LIBAV=-libav]], [[LIBAV=]])
+dnl libav/ffmpeg requires many feature checks
+FORK_MODULES_CHECK([FORKED], [LIBAV],
+	[libavformat$LIBAV libavcodec$LIBAV libswscale$LIBAV libavutil$LIBAV libavfilter$LIBAV],
+	[av_init_packet], [libavcodec/avcodec.h],
+	[dnl Checks for misc libav and ffmpeg API differences
+	 AC_MSG_CHECKING([whether libav libraries are ffmpeg])
+	 AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include <libavcodec/avcodec.h>
+	 ]], [[
+#if LIBAVCODEC_VERSION_MICRO >= 100
+  /* ffmpeg uses 100+ as its micro version */
+#else
+#error libav provider is not ffmpeg
+#endif
+	 ]])], [
+	  [is_ffmpeg=yes]
+	  AC_DEFINE([HAVE_FFMPEG], 1,
+		[Define to 1 if you have ffmpeg (not libav)])],
+	 [[is_ffmpeg=no]])
+	 AC_MSG_RESULT([$is_ffmpeg])
+	 FORK_CHECK_DECLS([av_buffersrc_add_frame_flags],
+		[libavfilter/buffersrc.h])
+	 FORK_CHECK_DECLS([av_buffersink_get_frame],
+		[libavfilter/buffersink.h])
+	 FORK_CHECK_DECLS([avfilter_graph_parse_ptr],
+		[libavfilter/avfilter.h])
+	 FORK_CHECK_DECLS([av_packet_unref], [libavcodec/avcodec.h])
+	 FORK_CHECK_DECLS([av_packet_rescale_ts], [libavcodec/avcodec.h])
+	 FORK_CHECK_DECLS([avformat_alloc_output_context2],
+		[libavformat/avformat.h])
+	 FORK_CHECK_DECLS([av_frame_alloc], [libavutil/frame.h])
+	 FORK_CHECK_DECLS([av_frame_get_best_effort_timestamp],
+		[libavutil/frame.h])
+	 FORK_CHECK_DECLS([av_image_fill_arrays], [libavutil/imgutils.h])
+	 FORK_CHECK_DECLS([av_image_get_buffer_size], [libavutil/imgutils.h])
+	 AC_CHECK_HEADERS([libavutil/channel_layout.h libavutil/mathematics.h])
+	])
+
+AC_CHECK_SIZEOF([void *])
 
 dnl --- Begin configuring the options ---
-dnl iTunes playlists with libplist
-AC_ARG_ENABLE(itunes, AS_HELP_STRING([--enable-itunes], [enable iTunes Music Library XML support (default=no)]))
-AS_IF([test "x$enable_itunes" = "xyes"], [
-	AC_DEFINE(ITUNES, 1, [Define to 1 to enable iTunes XML support])
-	PKG_CHECK_MODULES(LIBPLIST, [ libplist >= 0.16 ])
-])
-AM_CONDITIONAL(COND_ITUNES, [test "x$enable_itunes" = "xyes"])
+dnl ALSA
+FORK_ARG_WITH_CHECK([FORKED_OPTS], [ALSA support], [alsa], [ALSA],
+	[alsa], [snd_mixer_open], [asoundlib.h])
+AM_CONDITIONAL([COND_ALSA], [[test "x$with_alsa" = "xyes"]])
+
+dnl PULSEAUDIO
+FORK_ARG_WITH_CHECK([FORKED_OPTS], [Pulseaudio support], [pulseaudio], [LIBPULSE],
+	[libpulse], [pa_stream_get_state], [pulse/pulseaudio.h],
+	[AC_CHECK_FUNCS([pa_threaded_mainloop_set_name])])
+AM_CONDITIONAL([COND_PULSEAUDIO], [[test "x$with_pulseaudio" = "xyes"]])
+
+dnl Build with libcurl
+FORK_ARG_WITH_CHECK([FORKED_OPTS], [libcurl support], [libcurl], [LIBCURL],
+	[libcurl], [curl_global_init], [curl/curl.h])
+
+dnl Build with libsodium
+FORK_ARG_WITH_CHECK([FORKED_OPTS], [libsodium support], [libsodium], [LIBSODIUM],
+	[libsodium], [sodium_init], [sodium.h])
+
+dnl Build with libplist
+FORK_ARG_WITH_CHECK([FORKED_OPTS], [libplist support], [libplist], [LIBPLIST],
+	[libplist >= 0.16], [plist_dict_get_item], [plist/plist.h])
+
+dnl Build with libevent_pthreads
+FORK_ARG_WITH_CHECK([FORKED_OPTS], [libevent_pthreads support],
+	[libevent_pthreads], [LIBEVENT_PTHREADS], [libevent_pthreads],
+	[evthread_use_pthreads], [event2/thread.h])
+
+dnl Build with json-c
+FORK_ARG_WITH_CHECK([FORKED_OPTS], [json-c support], [json], [JSON_C],
+	[json-c >= 0.11], [json_tokener_parse], [json.h], [],
+	[FORK_MODULES_CHECK([FORKED_OPTS], [JSON_C], [json],
+		[json_tokener_parse], [json.h],
+		[[with_json=yes]
+		 AC_DEFINE([HAVE_JSON_C_OLD], 1,
+			[Define to 1 if you have json-c < 0.11])],
+		[AS_IF([[test "x$with_json" != "xcheck"]],
+			[AC_MSG_FAILURE([[--with-json was given, but test for json-c failed]])])
+		 [with_json=no]]
+	)])
+
+dnl Build with Avahi (or Bonjour if not)
+FORK_ARG_WITH_CHECK([FORKED_OPTS], [Avahi mDNS], [avahi], [AVAHI],
+	[avahi-client >= 0.6.24], [avahi_client_new], [avahi-client/client.h])
+AS_IF([[test "x$with_avahi" = "xno"]],
+	[FORK_FUNC_REQUIRE([FORKED_OPTS], [Bonjour DNS_SD], [DNSSD], [dns_sd],
+		[DNSServiceGetAddrInfo], [dns_sd.h], [],
+		[AC_MSG_ERROR([[Avahi client or Bonjour DNS_SD required, please install one.]])])])
+AM_CONDITIONAL([COND_AVAHI], [[test "x$with_avahi" = "xyes"]])
 
 dnl Spotify with dynamic linking to libspotify
-AC_ARG_ENABLE(spotify, AS_HELP_STRING([--enable-spotify], [enable Spotify support (default=no)]))
-AS_IF([test "x$enable_spotify" = "xyes"], [
-	AC_DEFINE(SPOTIFY, 1, [Define to 1 to enable Spotify support])
-	AC_CHECK_HEADER(libspotify/api.h, , AC_MSG_ERROR([libspotify/api.h not found]))
-	AC_DEFINE(HAVE_SPOTIFY_H, 1, [Define to 1 if you have the <libspotify/api.h> header file.])
-
-	dnl Don't link to libspotify, but instead enable dynamic linking
-	SPOTIFY_CFLAGS="-rdynamic"
-	SPOTIFY_LIBS="-ldl"
-	AC_SUBST(SPOTIFY_CFLAGS)
-	AC_SUBST(SPOTIFY_LIBS)
-])
-AM_CONDITIONAL(COND_SPOTIFY, [test "x$enable_spotify" = "xyes"])
+FORK_ARG_ENABLE([Spotify support], [spotify], [SPOTIFY],
+	[AS_IF([[test "x$with_json" = "xno"]],
+		[AC_MSG_ERROR([[Spotify support requires json-c]])])
+	 AS_IF([[test "x$with_libevent_pthreads" = "xno"]],
+		[AC_MSG_ERROR([[Spotify support requires libevent_pthreads]])])
+	 FORK_MODULES_CHECK([SPOTIFY], [LIBSPOTIFY], [libspotify],
+		[], [libspotify/api.h])
+	 AC_DEFINE([HAVE_SPOTIFY_H], 1,
+		[Define to 1 if you have the <libspotify/api.h> header file.])
+	 dnl Don't link with libspotify, use dynamic linking
+	 AC_SEARCH_LIBS([dlopen], [dl], [],
+		[AC_MSG_ERROR([[Spotify support requires dlopen]])])
+	 FORK_VAR_PREPEND([FORKED_OPTS_CPPFLAGS], [$SPOTIFY_CPPFLAGS])
+	 FORK_VAR_PREPEND([FORKED_OPTS_LIBS], [-rdynamic])
+	])
+AM_CONDITIONAL([COND_SPOTIFY], [[test "x$enable_spotify" = "xyes"]])
 
 dnl LastFM support with libcurl
-AC_ARG_ENABLE(lastfm, AS_HELP_STRING([--enable-lastfm], [enable LastFM support (default=no)]))
-AS_IF([test "x$enable_lastfm" = "xyes"], [
-	AC_DEFINE(LASTFM, 1, [Define to 1 to enable LastFM support])
-	PKG_CHECK_MODULES(LIBCURL, [ libcurl ])
-	AC_CHECK_LIB([mxml], [mxmlGetOpaque], AC_DEFINE(HAVE_MXML_GETOPAQUE, 1, [Define to 1 if your mxml has mxmlGetOpaque.]))
-])
-AM_CONDITIONAL(COND_LASTFM, [test "x$enable_lastfm" = "xyes"])
+FORK_ARG_ENABLE([LastFM support], [lastfm], [LASTFM],
+	[AS_IF([[test "x$with_libcurl" = "xno"]],
+		[AC_MSG_ERROR([[LastFM support requires libcurl]])])])
+AM_CONDITIONAL([COND_LASTFM], [[test "x$enable_lastfm" = "xyes"]])
 
 dnl ChromeCast support with libprotobuf-c
-AC_ARG_ENABLE(chromecast, AS_HELP_STRING([--enable-chromecast], [enable ChromeCast support (default=no)]))
-AS_IF([test "x$enable_chromecast" = "xyes"], [
-	AC_DEFINE(CHROMECAST, 1, [Define to 1 to enable Chromecast support])
-	PKG_CHECK_MODULES(LIBPROTOBUF_C, [ libprotobuf-c >= 1.0.0 ], , [ protobuf_old="yes" ])
-	PKG_CHECK_MODULES(GNUTLS, [ gnutls ])
-	PKG_CHECK_EXISTS([ json-c >= 0.11 ],
-		[ PKG_CHECK_MODULES(JSON_C, [ json-c ]) ],
-		[ PKG_CHECK_MODULES(JSON_C, [ json ], AC_DEFINE(HAVE_JSON_C_OLD, 1, [Define 1 to if you have json-c < 0.11])) ]
-	)
-])
-AS_IF([test "x$protobuf_old" = "xyes"], [
-	AC_DEFINE(HAVE_PROTOBUF_OLD, 1, [Define to 1 if you have libprotobuf < 1.0.0])
-	LDFLAGS="${LDFLAGS} -lprotobuf-c"
-])
-AM_CONDITIONAL(COND_CHROMECAST, [test "x$enable_chromecast" = "xyes"])
-AM_CONDITIONAL(COND_PROTOBUF_OLD, [test "x$protobuf_old" = "xyes"])
+FORK_ARG_ENABLE([Chromecast support], [chromecast], [CHROMECAST],
+	[AS_IF([[test "x$with_json" = "xno"]],
+		[AC_MSG_ERROR([[Chromecast support requires json-c]])])
+	 FORK_MODULES_CHECK([FORKED_OPTS], [LIBPROTOBUF_C],
+		[libprotobuf-c >= 1.0.0], [protobuf_c_message_pack],
+		[protobuf-c/protobuf-c.h], [],
+		[FORK_FUNC_REQUIRE([FORKED_OPTS], [v0 libprotobuf-c],
+			[LIBPROTOBUF_OLD], [protobuf-c],
+			[protobuf_c_message_pack],
+			[google/protobuf-c/protobuf-c.h],
+			[AC_DEFINE([HAVE_PROTOBUF_OLD], 1,
+				[Define to 1 if you have libprotobuf < 1.0.0])
+			 [protobuf_old=yes]],
+			[AC_MSG_ERROR([[Chromecast support requires protobuf-c]])])
+		])
+	 FORK_MODULES_CHECK([FORKED_OPTS], [GNUTLS], [gnutls], [gnutls_init],
+		[gnutls/gnutls.h])
+	])
+AM_CONDITIONAL([COND_CHROMECAST], [[test "x$enable_chromecast" = "xyes"]])
+AM_CONDITIONAL([COND_PROTOBUF_OLD], [[test "x$protobuf_old" = "xyes"]])
 
-dnl MPD support
-AC_ARG_ENABLE(mpd, AS_HELP_STRING([--disable-mpd], [disable MPD client protocol support (default=no)]))
-AS_IF([test "x$enable_mpd" != "xno"], [
-	AC_DEFINE(MPD, 1, [Define to 1 to enable MPD support])
-])
-AM_CONDITIONAL(COND_MPD, [test "x$enable_mpd" != "xno"])
-
-dnl ALSA
-AC_ARG_WITH(alsa, AS_HELP_STRING([--without-alsa], [without ALSA support (default=no)]))
-AS_IF([test "x$with_alsa" != "xno"], [
-	AC_DEFINE(ALSA, 1, [Define to 1 to build with ALSA support])
-	PKG_CHECK_MODULES(ALSA, [ alsa ])
-])
-AM_CONDITIONAL(COND_ALSA, [test "x$with_alsa" != "xno"])
+dnl iTunes playlists with libplist
+FORK_ARG_DISABLE([iTunes Music Library XML support], [itunes], [ITUNES],
+	[AS_IF([[test "x$with_libplist" = "xno"]],
+		[AC_MSG_ERROR([[iTunes Music Library XML support requires libplist]])])
+	])
+AM_CONDITIONAL([COND_ITUNES], [[test "x$enable_itunes" = "xyes"]])
 
-dnl PULSEAUDIO
-AC_ARG_WITH(pulseaudio, AS_HELP_STRING([--with-pulseaudio], [with Pulseaudio support (default=no)]))
-AS_IF([test "x$with_pulseaudio" = "xyes"], [
-	AC_DEFINE(PULSEAUDIO, 1, [Define to 1 to build with Pulseaudio support])
-	PKG_CHECK_MODULES(LIBPULSE, [ libpulse ])
-	AC_SEARCH_LIBS([pa_threaded_mainloop_set_name], [pulse],
-		AC_DEFINE(HAVE_PULSE_MAINLOOP_SET_NAME, 1, [Define to 1 if you have Pulseaudio with pa_threaded_mainloop_set_name])
-	)
-])
-AM_CONDITIONAL(COND_PULSEAUDIO, [test "x$with_pulseaudio" = "xyes"])
+dnl MPD support
+FORK_ARG_DISABLE([MPD client protocol support], [mpd], [MPD])
+AM_CONDITIONAL([COND_MPD], [[test "x$enable_mpd" = "xyes"]])
+
+dnl Apple device verification
+FORK_ARG_DISABLE([Apple TV device verification], [verification], [RAOP_VERIFICATION],
+	[
+	 AS_IF([[test "x$with_libsodium" = "xno"]],
+		[AC_MSG_ERROR([[Apple TV device verification requires libsodium]])])
+	 AS_IF([[test "x$with_libplist" = "xno"]],
+		[AC_MSG_ERROR([[Apple TV device verification requires libplist]])])
+	])
+AM_CONDITIONAL([COND_RAOP_VERIFICATION], [[test "x$enable_verification" = "xyes"]])
+
+dnl Defining users and groups
+AC_ARG_WITH([daapd_user],
+	[AS_HELP_STRING([--with-daapd-user=USER],
+		[User for running forked-daapd (default=daapd)])],
+	[[test x"$withval" = xyes && withval=]], [[withval=]])
+DAAPD_USER=${withval:-daapd}
+AC_SUBST([DAAPD_USER])
+
+AC_ARG_WITH([daapd_group],
+	[AS_HELP_STRING([--with-daapd-group=GROUP],
+		[Group for daapd user (default=USER)])],
+	[[test x"$withval" = xyes && withval=]], [[withval=]])
+DAAPD_GROUP=${withval:-$DAAPD_USER}
+AC_SUBST([DAAPD_GROUP])
 dnl --- End options ---
 
-dnl Checks for header files.
-AC_HEADER_STDC
-AC_HEADER_SYS_WAIT
-
-AC_OUTPUT(src/Makefile sqlext/Makefile Makefile)
+AC_CONFIG_FILES([
+	src/Makefile
+	sqlext/Makefile
+	Makefile
+	forked-daapd.spec
+])
+AC_OUTPUT
diff --git a/forked-daapd.8 b/forked-daapd.8
index e6873b3..04871fd 100644
--- a/forked-daapd.8
+++ b/forked-daapd.8
@@ -21,7 +21,7 @@ Debug domains; available domains are: \fIconfig\fP, \fIdaap\fP,
 \fIrsp\fP, \fIscan\fP, \fIxcode\fP, \fIevent\fP, \fIhttp\fP, \fIremote\fP,
 \fIdacp\fP, \fIffmpeg\fP, \fIartwork\fP, \fIplayer\fP, \fIraop\fP,
 \fIlaudio\fP, \fIdmap\fP, \fIfdbperf\fP, \fIspotify\fP, \fIlastfm\fP,
-\fIcache\fP, \fImpd\fP, \fIstream\fP.
+\fIcache\fP, \fImpd\fP, \fIstream\fP, \fIcast\fP, \fIfifo\fP, \fIlib\fP.
 .TP
 \fB\-c\fR \fIfile\fP
 Use \fIfile\fP as the configuration file.
diff --git a/forked-daapd.conf b/forked-daapd.conf.in
similarity index 91%
rename from forked-daapd.conf
rename to forked-daapd.conf.in
index 2f23e69..6346d62 100644
--- a/forked-daapd.conf
+++ b/forked-daapd.conf.in
@@ -11,14 +11,14 @@ general {
 	# Username
 	# Make sure the user has read access to the library directories you set
  	# below, and full access to the databases, log and local audio
-	uid = "daapd"
+	uid = "@DAAPD_USER@"
 
 	# Database location
-#	db_path = "/var/cache/forked-daapd/songs3.db"
+#	db_path = "@localstatedir@/cache/@PACKAGE@/songs3.db"
 
 	# Log file and level
 	# Available levels: fatal, log, warning, info, debug, spam
-	logfile = "/var/log/forked-daapd.log"
+	logfile = "@localstatedir@/log/@PACKAGE at .log"
 	loglevel = log
 
 	# Admin password for the non-existent web interface
@@ -28,7 +28,7 @@ general {
 	ipv6 = yes
 
 	# Location of cache database
-#	cache_path = "/var/cache/forked-daapd/cache.db"
+#	cache_path = "@localstatedir@/cache/@PACKAGE@/cache.db"
 
 	# DAAP requests that take longer than this threshold (in msec) get their
 	# replies cached for next time. Set to 0 to disable caching.
@@ -37,6 +37,11 @@ general {
 	# When starting playback, autoselect speaker (if none of the previously
 	# selected speakers/outputs are available)
 #	speaker_autoselect = yes
+
+	# Most modern systems have a high-resolution clock, but if you are on an
+	# unusual platform and experience audio drop-outs, you can try changing
+	# this option
+#	high_resolution_clock = yes
 }
 
 # Library configuration
@@ -116,8 +121,9 @@ library {
 	# File types the scanner should ignore
 	# Non-audio files will never be added to the database, but here you
 	# can prevent the scanner from even probing them. This might improve
-	# scan time. By default .db, .ini, .db-journal and .pdf are ignored.
-#	filetypes_ignore = { ".db", ".ini", ".db-journal", ".pdf" }
+	# scan time. By default .db, .ini, .db-journal, .pdf and .metadata are
+	# ignored.
+#	filetypes_ignore = { ".db", ".ini", ".db-journal", ".pdf", ".metadata" }
 
 	# File paths the scanner should ignore
 	# If you want to exclude files on a more advanced basis you can enter
@@ -153,6 +159,11 @@ library {
 #	no_decode = { "format", "format" }
 	# Formats that should always be decoded
 #	force_decode = { "format", "format" }
+
+	# Watch named pipes in the library for data and autostart playback when
+	# there is data to be read. To exclude specific pipes from watching,
+	# consider using the above _ignore options.
+#	pipe_autostart = true
 }
 
 # Local audio output
@@ -160,7 +171,7 @@ audio {
 	# Name - used in the speaker list in Remote
 	nickname = "Computer"
 	
-	# Type of the output (alsa, pulseaudio or dummy)
+	# Type of the output (alsa, pulseaudio, dummy or disabled)
 #	type = "alsa"
 
 	# Audio PCM device name for local audio output - ALSA only
@@ -196,6 +207,10 @@ audio {
 	# you can set a lower value here
 #	max_volume = 11
 
+	# Enable this option to exclude a particular AirPlay device from the
+	# speaker list
+#	exclude = false
+
 	# AirPlay password
 #	password = "s1kr3t"
 #}
@@ -203,7 +218,7 @@ audio {
 # Spotify settings (only have effect if Spotify enabled - see README/INSTALL)
 spotify {
 	# Directory where user settings should be stored (credentials)
-#	settings_dir = "/var/cache/forked-daapd/libspotify"
+#	settings_dir = "@localstatedir@/cache/@PACKAGE@/libspotify"
 
 	# Cache directory
 #	cache_dir = "/tmp"
diff --git a/forked-daapd.service b/forked-daapd.service.in
old mode 100755
new mode 100644
similarity index 60%
rename from forked-daapd.service
rename to forked-daapd.service.in
index d2bccbc..805cfc5
--- a/forked-daapd.service
+++ b/forked-daapd.service.in
@@ -1,9 +1,10 @@
 [Unit]
 Description=DAAP/DACP (iTunes), RSP and MPD server, supports AirPlay and Remote
-After=network.target sound.target
+Documentation=man:forked-daapd(8)
+After=network.target sound.target remote-fs.target pulseaudio.service avahi-daemon.service
 
 [Service]
-ExecStart=/usr/sbin/forked-daapd -f
+ExecStart=@sbindir@/forked-daapd -f
 
 # Restart, but not more than once every 10 minutes
 Restart=on-failure
diff --git a/forked-daapd.spec.in b/forked-daapd.spec.in
new file mode 100644
index 0000000..0f2a1de
--- /dev/null
+++ b/forked-daapd.spec.in
@@ -0,0 +1,174 @@
+# -*- Mode:rpm-spec -*-
+# @configure_input@
+%global username @DAAPD_USER@
+%global groupname @DAAPD_GROUP@
+
+%bcond_without alsa
+%bcond_with pulseaudio
+%bcond_without libcurl
+%bcond_without json
+%bcond_with itunes
+%bcond_with spotify
+%bcond_with lastfm
+%bcond_with chromecast
+%bcond_without mpd
+
+%global _hardened_build 1
+
+Summary: iTunes-compatible DAAP server with MPD and RSP support
+Name: @PACKAGE_NAME@
+Version: @PACKAGE_VERSION@
+Release: 1%{?dist}
+License: GPLv2+
+Group: Applications/Multimedia
+Url: https://github.com/ejurgensen/forked-daapd
+Source: https://github.com/ejurgensen/%{name}/archive/%{version}.tar.gz#/%{name}-%{version}.tar.gz
+%{?systemd_ordering}
+BuildRequires: systemd
+BuildRequires: pkgconfig
+BuildRequires: pkgconfig(sqlite3) >= 3.5.0
+BuildRequires: pkgconfig(libconfuse)
+BuildRequires: libunistring-devel
+BuildRequires: pkgconfig(mxml)
+BuildRequires: pkgconfig(libevent) >= 2.0.0
+BuildRequires: pkgconfig(avahi-client) >= 0.6.24
+BuildRequires: libgcrypt-devel >= 1.2.0
+BuildRequires: libgpg-error-devel >= 1.6
+BuildRequires: pkgconfig(zlib)
+BuildRequires: antlr3-C-devel
+BuildRequires: pkgconfig(libavformat)
+BuildRequires: pkgconfig(libavcodec)
+BuildRequires: pkgconfig(libswscale)
+BuildRequires: pkgconfig(libavutil)
+BuildRequires: pkgconfig(libavfilter)
+Requires(pre): shadow-utils
+Requires: systemd-units
+%if %{with alsa}
+BuildRequires: pkgconfig(alsa)
+%endif
+%if %{with pulseaudio}
+BuildRequires: pkgconfig(libpulse)
+%endif
+%if %{with libcurl}
+BuildRequires: pkgconfig(libcurl)
+%endif
+%if %{with json}
+BuildRequires: pkgconfig(json-c)
+%endif
+%if %{with itunes}
+BuildRequires: pkgconfig(libplist) >= 0.16
+%endif
+%if %{with spotify}
+BuildRequires: libspotify-devel
+%endif
+%if %{with chromecast}
+BuildRequires: pkgconfig(gnutls)
+BuildRequires: pkgconfig(libprotobuf-c)
+%endif
+
+%global homedir %{_localstatedir}/lib/%{name}
+%global gecos %{name} User
+%{!?_pkgdocdir: %global _pkgdocdir %{_docdir}/%{name}-%{version}}
+
+%description
+forked-daapd is a DAAP/DACP (iTunes), MPD (Music Player Daemon)
+and RSP (Roku) media server.
+
+It has support for AirPlay devices/speakers, Apple Remote (and compatibles),
+MPD clients, Chromecast, network streaming, internet radio, Spotify and LastFM.
+
+It does not support streaming video by AirPlay nor Chromecast.
+
+DAAP stands for Digital Audio Access Protocol, and is the protocol used
+by iTunes and friends to share/stream media libraries over the network.
+
+forked-daapd is a complete rewrite of mt-daapd (Firefly Media Server).
+
+%prep
+%if %{with spotify} && %{without json}
+echo "ERROR: Option '-with spotify' cannot be used with '-without json'" >&2 && exit 1
+%endif
+%if %{with lastfm} && %{without libcurl}
+echo "ERROR: Option '-with lastfm' cannot be used with '-without libcurl'" >&2 && exit 1
+%endif
+%if %{with chromecast} && %{without json}
+echo "ERROR: Option '-with chromecast' cannot be used with '-without json'" >&2 && exit 1
+%endif
+
+%setup -q
+
+%build
+%configure \
+%if %{without alsa}
+  --without-alsa \
+%endif
+%if %{without pulseaudio}
+  --without-pulseaudio \
+%endif
+%if %{without libcurl}
+  --without-libcurl \
+%endif
+%if %{without json}
+  --without-json \
+%endif
+%if %{with itunes}
+  --enable-itunes \
+%endif
+%if %{with spotify}
+  --enable-spotify \
+%endif
+%if %{with lastfm}
+  --enable-lastfm \
+%endif
+%if %{with chromecast}
+  --enable-chromecast \
+%endif
+%if %{with mpd}
+  --enable-mpd \
+%endif
+  --with-daapd-user=%{username} \
+  --with-daapd-group=%{groupname}
+make %{?_smp_mflags}
+
+%install
+make install DESTDIR=%{buildroot} docdir=%{_pkgdocdir}
+rm -f %{buildroot}%{_pkgdocdir}/INSTALL
+mkdir -p %{buildroot}%{homedir}
+mkdir -p %{buildroot}%{_localstatedir}/log
+touch %{buildroot}%{_localstatedir}/log/%{name}.log
+mkdir -p %{buildroot}%{_unitdir}
+install -m 0644 forked-daapd.service %{buildroot}%{_unitdir}/%{name}.service
+rm -f %{buildroot}%{_libdir}/%{name}/*.la
+
+%pre
+getent group %{groupname} >/dev/null || groupadd -r %{groupname}
+getent passwd %{username} >/dev/null || \
+    useradd -r -g %{groupname} -d %{homedir} -s /sbin/nologin \
+    -c '%{gecos}' %{username}
+exit 0
+
+%post
+%systemd_post %{name}.service
+
+%preun
+%systemd_preun %{name}.service
+
+%postun
+%systemd_postun_with_restart %{name}.service
+
+%files
+%{!?_licensedir:%global license %%doc}
+%license COPYING
+%{_pkgdocdir}
+%config(noreplace) %{_sysconfdir}/forked-daapd.conf
+%{_sbindir}/forked-daapd
+%{_libdir}/*
+%{_unitdir}/%{name}.service
+%attr(0750,%{username},%{groupname}) %{_localstatedir}/cache/%{name}
+%attr(0750,%{username},%{groupname}) %{homedir}
+%ghost %{_localstatedir}/log/%{name}.log
+%{_mandir}/man?/*
+
+%changelog
+* Tue Dec 20 2016 Scott Shambarger <devel at shambarger.net> - 24.2-1
+   - Initial RPM release candidate.
diff --git a/m4/fork_checks.m4 b/m4/fork_checks.m4
new file mode 100644
index 0000000..47b0f81
--- /dev/null
+++ b/m4/fork_checks.m4
@@ -0,0 +1,218 @@
+# fork_checks.m4 serial 2
+dnl Copyright (c) Scott Shambarger <devel at shambarger.net>
+dnl
+dnl Copying and distribution of this file, with or without modification, are
+dnl permitted in any medium without royalty provided the copyright notice
+dnl and this notice are preserved. This file is offered as-is, without any
+dnl warranty.
+
+dnl _FORK_FUNC_MERGE
+dnl ----------------
+dnl Internal only.  Defines function used by FORK_VAR_PREPEND
+AC_DEFUN([_FORK_FUNC_MERGE], [[
+# fork_fn_merge(before, after)
+# create wordlist removing duplicates
+fork_fn_merge() {
+  fork_fn_var_result=$][1
+  for element in $][2; do
+    fork_fn_var_haveit=
+    for x in $fork_fn_var_result; do
+      if test "X$x" = "X$element"; then
+        fork_fn_var_haveit=1
+	break
+      fi
+    done
+    if test -z "$fork_fn_var_haveit"; then
+      fork_fn_var_result="${fork_fn_var_result}${fork_fn_var_result:+ }$element"
+    fi
+  done
+  echo "$fork_fn_var_result"
+  unset fork_fn_var_haveit
+  unset fork_fn_var_result
+}]])
+
+dnl FORK_VAR_PREPEND(VARNAME, BEFORE)
+dnl ---------------------------------
+dnl Prepends words in BEFORE to the contents of VARNAME, skipping any
+dnl duplicate words.
+AC_DEFUN([FORK_VAR_PREPEND],
+[AC_REQUIRE([_FORK_FUNC_MERGE])dnl
+[ $1=$(fork_fn_merge "$2" "$$1")]])
+
+dnl FORK_VARS_PREPEND(TARGET, LIBS_ENV, CFLAGS_ENV)
+dnl -----------------------------------------------
+dnl Prepend LIBS_ENV to LIBS and TARGET_LIBS
+dnl Append CFLAGS_ENV to CPPFLAGS and TARGET_CPPFLAGS.
+AC_DEFUN([FORK_VARS_PREPEND],
+[[
+  LIBS="$$2 $LIBS"
+  $1_LIBS="$$2 $$1_LIBS"]
+ FORK_VAR_PREPEND([CPPFLAGS], [$$3])
+ FORK_VAR_PREPEND([$1_CPPFLAGS], [$$3])
+])
+
+dnl _FORK_VARS_ADD_PREFIX(TARGET)
+dnl -----------------------------
+dnl Internal use only. Add libdir prefix to {TARGET_}LIBS and
+dnl includedir prefix to {TARGET_}CPPFLAGS as fallback search paths
+dnl expanding all variables.
+AC_DEFUN([_FORK_VARS_ADD_PREFIX],
+[AC_REQUIRE([AC_LIB_PREPARE_PREFIX])
+ AC_LIB_WITH_FINAL_PREFIX([[
+  eval LIBS=\"-L$libdir $LIBS\"
+  eval $1_LIBS=\"-L$libdir $$1_LIBS\"
+  eval fork_tmp_cppflags=\"-I$includedir\"]
+ FORK_VAR_PREPEND([CPPFLAGS], [$fork_tmp_cppflags])
+ FORK_VAR_PREPEND([$1_CPPFLAGS], [$fork_tmp_cppflags])
+ ])
+])
+
+dnl FORK_CHECK_DECLS(SYMBOLS, INCLUDE, [ACTION-IF-FOUND],
+dnl   [ACTION-IF-NOT-FOUND])
+dnl -----------------------------------------------------
+dnl Expands AC_CHECK_DECLS with SYMBOLS and INCLUDE appended to
+dnl AC_INCLUDES_DEFAULT.
+dnl NOTE: Remember that AC_CHECK_DECLS defines HAVE_* to 1 or 0
+dnl (not 1 or undefined!)
+AC_DEFUN([FORK_CHECK_DECLS],
+[AC_CHECK_DECLS([$1], [$3], [$4], [AC_INCLUDES_DEFAULT
+[@%:@include <$2>]])
+])
+
+dnl FORK_FUNC_REQUIRE(TARGET, DESCRIPTION, ENV, LIBRARY, FUNCTION, [HEADER],
+dnl   [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])
+dnl ------------------------------------------------------------------------
+dnl Check for software which lacks pkg-config support, setting TARGET_CPPFLAGS
+dnl and TARGET_LIBS with working values if FUNCTION found, or failing if
+dnl it's not.  If ENV_CFLAGS and ENV_LIBS overrides are set (ENV is prefix),
+dnl tries to link FUNCTION/include HEADER with them.  Without overrides,
+dnl expands like AC_SEARCH_LIBS on FUNCTION (trying without and with LIBRARY),
+dnl adding $prefix paths if necessary.  If FUNCTION found, verifies optional
+dnl HEADER can be included (or fails with error), and expands optional
+dnl ACTION-IF-FOUND with working CPPFLAGS/LIBS for additional checks.
+dnl DESCRIPTION used as friendly name in error messages to help user
+dnl identify software to install.  If FUNCTION not found, either displays
+dnl error suggested use of ENV_* overrides, or if ENV_* were not set
+dnl expands optional ACTION-IF-NOT-FOUND in place of error.
+dnl Restores original CPPFLAGS and LIBS when done.
+AC_DEFUN([FORK_FUNC_REQUIRE],
+[AS_VAR_PUSHDEF([FORK_MSG], [fork_msg_$3])
+ AC_ARG_VAR([$3_CFLAGS], [C compiler flags for $2, overriding search])
+ AC_ARG_VAR([$3_LIBS], [linker flags for $2, overriding search])
+ [fork_save_$3_LIBS=$LIBS; fork_save_$3_CPPFLAGS=$CPPFLAGS
+  fork_found_$3=yes]
+ AS_IF([[test -n "$$3_CFLAGS" && test -n "$$3_LIBS"]],
+	[dnl ENV variables provided, just verify they work
+	 AS_VAR_SET([FORK_MSG], [["
+Library specific environment variables $3_LIBS and
+$3_CFLAGS were used, verify they are correct..."]])
+	 FORK_VARS_PREPEND([$1], [$3_LIBS], [$3_CFLAGS])
+	 AC_CHECK_FUNC([[$5]], [],
+		[AC_MSG_FAILURE([[Unable to link function $5 with $2.$]FORK_MSG])])],
+	[dnl Search w/o LIBRARY, w/ LIBRARY, and finally adding $prefix path
+	 AS_VAR_SET([FORK_MSG], [["
+Install $2 in the default include path, or alternatively set
+library specific environment variables $3_CFLAGS
+and $3_LIBS."]])
+	 AC_MSG_CHECKING([[for library containing $5...]])
+	 AC_TRY_LINK_FUNC([[$5]], [AC_MSG_RESULT([[none required]])],
+		[[LIBS="-l$4 $LIBS"
+		 $1_LIBS="-l$4 $$1_LIBS"]
+		 AC_TRY_LINK_FUNC([[$5]], [AC_MSG_RESULT([[-l$4]])],
+			 [_FORK_VARS_ADD_PREFIX([$1])
+			  AC_TRY_LINK_FUNC([[$5]], [AC_MSG_RESULT([[-l$4]])],
+				[AC_MSG_RESULT([[no]])
+				 fork_found_$3=no])])
+		])
+	])
+ AS_IF([[test "$fork_found_$3" != "no"]],
+	[dnl check HEADER, then expand FOUND
+	 m4_ifval([$6], [AC_CHECK_HEADER([[$6]], [],
+		[AC_MSG_FAILURE([[Unable to find header $6 for $2.$]FORK_MSG])])])
+	 $7])
+ [LIBS=$fork_save_$3_LIBS; CPPFLAGS=$fork_save_$3_CPPFLAGS]
+ dnl Expand NOT-FOUND after restoring saved flags to allow recursive expansion
+ AS_IF([[test "$fork_found_$3" = "no"]],
+	[m4_default_nblank([$8],
+		[AC_MSG_FAILURE([[Function $5 in lib$4 not found.$]FORK_MSG])])])
+ AS_VAR_POPDEF([FORK_MSG])
+])
+
+dnl FORK_MODULES_CHECK(TARGET, ENV, MODULES, [FUNCTION], [HEADER],
+dnl   [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])
+dnl --------------------------------------------------------------
+dnl Expands PKG_CHECK_MODULES, but when found also attempt to link
+dnl FUNCTION and include HEADER.  Appends working package values to
+dnl TARGET_CPPFLAGS and TARGET_LIBS. Expands optional ACTION-IF-FOUND with
+dnl working CPPFLAGS/LIBS for additional checks.  Expands
+dnl ACTION-IF-NOT-FOUND only if package not found (not link/include failures)
+dnl overriding default error.  Restores original CPPFLAGS and LIBS when done.
+AC_DEFUN([FORK_MODULES_CHECK],
+[PKG_CHECK_MODULES([$2], [[$3]],
+	[[fork_save_$2_LIBS=$LIBS; fork_save_$2_CPPFLAGS=$CPPFLAGS]
+	 FORK_VARS_PREPEND([$1], [$2_LIBS], [$2_CFLAGS])
+	 m4_ifval([$4], [AC_CHECK_FUNC([[$4]], [],
+		[AC_MSG_ERROR([[Unable to link function $4]])])])
+	 m4_ifval([$5], [AC_CHECK_HEADER([[$5]], [],
+		[AC_MSG_ERROR([[Unable to find header $5]])])])
+	 $6
+	 [LIBS=$fork_save_$2_LIBS; CPPFLAGS=$fork_save_$2_CPPFLAGS]],
+	 m4_default_nblank_quoted([$7]))
+])
+
+dnl FORK_ARG_WITH_CHECK(TARGET, DESCRIPTION, OPTION, ENV, MODULES, [FUNCTION],
+dnl   [HEADER], [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])
+dnl --------------------------------------------------------------------------
+dnl Create an --with-OPTION with a default of "check" (include MODULES
+dnl if they are available).  Expands FORK_MODULES_CHECK with remaining
+dnl arguments.  Defines HAVE_ENV to 1 if package found.  DESCRIPTION is used
+dnl in option help.  Shell variable with_OPTION set to yes before
+dnl ACTION-IF-FOUND.  Default ACTION-IF-NOT-FOUND will fail
+dnl if --with-OPTION given and MODULES not found, or sets shell var
+dnl with_OPTION to no if option was check.  A non-empty ACTION-IF-NOT-FOUND
+dnl overrides this behavior to allow alternate checks.
+AC_DEFUN([FORK_ARG_WITH_CHECK],
+[AC_ARG_WITH([[$3]], [AS_HELP_STRING([--with-$3],
+	[with $2 (default=check)])], [],
+	[[with_$3=check]])
+ AS_IF([[test "x$with_$3" != "xno"]],
+	[FORK_MODULES_CHECK([$1], [$4], [$5], [$6], [$7],
+		[[with_$3=yes]
+		 AC_DEFINE([HAVE_$4], 1, [Define to 1 to build with $2])
+		 $8],
+		[m4_default_nblank([$9],
+			[AS_IF([[test "x$with_$3" != "xcheck"]],
+				[AC_MSG_FAILURE([[--with-$3 was given, but test for $5 failed]])])
+			 [with_$3=no]])
+		])
+	])
+])
+
+dnl FORK_ARG_ENABLE(DESCRIPTION, OPTION, DEFINE, [ACTION-IF-ENABLE])
+dnl ----------------------------------------------------------------
+dnl Create an --enable-OPTION, setting shell variable enable_OPTION
+dnl to no by default.  If feature is enabled, defines DEFINE to 1
+dnl and expand ACTION-IF_ENABLE.  DESCRIPTION is used in option help.
+AC_DEFUN([FORK_ARG_ENABLE],
+[AC_ARG_ENABLE([[$2]], [AS_HELP_STRING([--enable-$2],
+	[enable $1 (default=no)])])
+ AS_IF([[test "x$enable_$2" = "xyes"]],
+	[AC_DEFINE([$3], 1, [Define to 1 to enable $1])
+	 $4],
+	[[enable_$2=no]])
+])
+
+dnl FORK_ARG_DISABLE(DESCRIPTION, OPTION, DEFINE, [ACTION-IF-ENABLE])
+dnl ----------------------------------------------------------------
+dnl Create an --disable-OPTION, setting shell variable enable_OPTION
+dnl to yes by default.  If feature is enabled, defines DEFINE to 1
+dnl and expand ACTION-IF_ENABLE.  DESCRIPTION is used in option help.
+AC_DEFUN([FORK_ARG_DISABLE],
+[AC_ARG_ENABLE([[$2]], [AS_HELP_STRING([--disable-$2],
+	[disable $1 (default=no)])])
+ AS_IF([[test "x$enable_$2" = "x" || test "x$enable_$2" = "xyes"]],
+	[AC_DEFINE([$3], 1, [Define to 1 to enable $1])
+	 [enable_$2=yes]
+	 $4],
+	[[enable_$2=no]])
+])
diff --git a/m4/libunistring.m4 b/m4/libunistring.m4
deleted file mode 100644
index ac9d11a..0000000
--- a/m4/libunistring.m4
+++ /dev/null
@@ -1,41 +0,0 @@
-# libunistring.m4 serial 4
-dnl Copyright (C) 2009-2010 Free Software Foundation, Inc.
-dnl This file is free software; the Free Software Foundation
-dnl gives unlimited permission to copy and/or distribute it,
-dnl with or without modifications, as long as this notice is preserved.
-
-dnl gl_LIBUNISTRING
-dnl Searches for an installed libunistring.
-dnl If found, it sets and AC_SUBSTs HAVE_LIBUNISTRING=yes and the LIBUNISTRING
-dnl and LTLIBUNISTRING variables and augments the CPPFLAGS variable, and
-dnl #defines HAVE_LIBUNISTRING to 1. Otherwise, it sets and AC_SUBSTs
-dnl HAVE_LIBUNISTRING=no and LIBUNISTRING and LTLIBUNISTRING to empty.
-
-AC_DEFUN([gl_LIBUNISTRING],
-[
-  dnl First, try to link without -liconv. libunistring often depends on
-  dnl libiconv, but we don't know (and often don't need to know) where
-  dnl libiconv is installed.
-  AC_LIB_HAVE_LINKFLAGS([unistring], [],
-    [#include <uniconv.h>], [u8_strconv_from_locale((char*)0);],
-    [no, consider installing GNU libunistring])
-  if test "$ac_cv_libunistring" != yes; then
-    dnl Second try, with -liconv.
-    AC_REQUIRE([AM_ICONV])
-    if test -n "$LIBICONV"; then
-      dnl We have to erase the cached result of the first AC_LIB_HAVE_LINKFLAGS
-      dnl invocation, otherwise the second one will not be run.
-      unset ac_cv_libunistring
-      glus_save_LIBS="$LIBS"
-      LIBS="$LIBS $LIBICONV"
-      AC_LIB_HAVE_LINKFLAGS([unistring], [],
-        [#include <uniconv.h>], [u8_strconv_from_locale((char*)0);],
-        [no, consider installing GNU libunistring])
-      if test -n "$LIBUNISTRING"; then
-        LIBUNISTRING="$LIBUNISTRING $LIBICONV"
-        LTLIBUNISTRING="$LTLIBUNISTRING $LTLIBICONV"
-      fi
-      LIBS="$glus_save_LIBS"
-    fi
-  fi
-])
diff --git a/scripts/antlr35_install.sh b/scripts/antlr35_install.sh
index f7843d7..6df6538 100755
--- a/scripts/antlr35_install.sh
+++ b/scripts/antlr35_install.sh
@@ -1,45 +1,242 @@
 #!/bin/sh
 
 WORKDIR=~/antlr35.tmp
-echo "This script will install antlr 3.5 (and matching libantlr) on your computer."
-read -p "Should the script create $WORKDIR and use it for building? [Y/n] " yn
-if [ "$yn" = "n" ]; then
-	exit
+
+# programs
+MAKE=${MAKE-make}
+DOWNLOAD="wget --no-check-certificate"
+ALTDOWNLOAD="curl -LO"
+SUDO=sudo
+
+# source
+ANTLR_VERSION=3.5
+
+ANTLR3_SOURCE="https://github.com/antlr/website-antlr3/raw/gh-pages/download"
+ANTLR3_JAR="antlr-3.5.2-complete.jar"
+ANTLR3_URL="$ANTLR3_SOURCE/$ANTLR3_JAR"
+
+LIBANTLR3C="libantlr3c-3.4"
+LIBANTLR3C_SOURCE="https://github.com/antlr/website-antlr3/raw/gh-pages/download/C"
+LIBANTLR3C_TAR="${LIBANTLR3C}.tar.gz"
+LIBANTLR3C_URL="$LIBANTLR3C_SOURCE/$LIBANTLR3C_TAR"
+
+usage() {
+  echo
+  echo "This script will download, build and install antlr $ANTLR_VERSION"
+  echo "  (and matching libantlrc) on your computer."
+  echo
+  echo "Usage: ${0##*/} -h | [ -p <prefix> ] [ <build-dir> ]"
+  echo
+  echo "Parameters:"
+  echo "  -h           Show this help"
+  echo "  -p <prefix>  Install to prefix (default: choose /usr or /usr/local)"
+  echo "  <build-dir>  Build directory (default: $WORKDIR)"
+  exit 0
+}
+
+GIVEN_PREFIX=
+case $1 in
+  -h|--help) usage;;
+  -p)
+    shift
+    [ -n "$1" ] || {
+      echo "Option -p requires a argument (try -h for usage)"
+      exit 1
+    }
+    GIVEN_PREFIX=$1
+    shift
+    ;;
+  -*)
+    echo "Unrecognized option $1 (try -h for usage)"
+    exit 1
+    ;;
+esac
+
+# override build directory? (support ~ expansion)
+[ -n "$1" ] && WORKDIR=$1
+ORIG_DIR=`pwd`
+
+err() {
+  echo "$*"
+  if [ -n "$FILES_EXIST" ]; then
+    echo "Files remain in $WORKDIR..."
+  else
+    cd "$ORIG_DIR"
+    rmdir "$WORKDIR"
+  fi
+  exit 1
+}
+
+is_yes() {
+  case "$1" in
+    [N]*|[n]*) return 1;;
+    *) ;;
+  esac
+  return 0
+}
+
+prog_install() {
+  read -p "Would you like to install into $PREFIX now? [Y/n] " yn
+  if ! is_yes "$yn"; then
+    echo "Build left ready to install from $WORKDIR"
+    echo "You can re-run the script (eg. as root) to install into"
+    echo "  $PREFIX later."
+    exit
+  fi
+  if [ `id -u` -ne 0 ]; then
+    echo "Would you like to install with sudo?"
+    read -p "NOTE: You WILL be asked for your password! [Y/n] " yn
+    if ! is_yes "$yn"; then
+      SUDO=
+      read -p "Continue to install as non-root user? [Y/n] " yn
+      is_yes "$yn" || err "Install cancelled"
+    fi
+  else
+    SUDO=
+  fi
+  cd $LIBANTLR3C || err "Unable to cd to build libantlr3c build directory!"
+  echo "Installing libantlr3c to $PREFIX"
+  $SUDO $MAKE install || err "Install of libantlr3c to $PREFIX failed!"
+
+  cd "$ORIG_DIR"
+  cd $WORKDIR
+  echo "Installing antlr3 to $PREFIX"
+  $SUDO mkdir -p "$PREFIX_JAVA" || err "Unable to create $PREFIX_JAVA"
+  $SUDO install "$ANTLR3_JAR" "$PREFIX_JAVA" || \
+    err "Failed to install antlr3 jar to $PREFIX_JAVA"
+  $SUDO mkdir -p "$PREFIX/bin" || err "Unable to create $PREFIX/bin"
+  $SUDO install -m 755 antlr3 "$PREFIX/bin" || \
+    err "Failed to install antlr3 to $PREFIX/bin"
+  echo "Install complete (build remains in $WORKDIR)"
+}
+
+echo "This script will download, build and install antlr $ANTLR_VERSION"
+echo "  (and matching libantlrc) on your computer."
+echo
+
+# check if make works
+ISGNU=`$MAKE --version 2>/dev/null | grep "GNU Make"`
+if [ -z "$ISGNU" ]; then
+  MAKE=gmake
+  ISGNU=`$MAKE --version 2>/dev/null | grep "GNU Make"`
 fi
-mkdir -p $WORKDIR
-if [ ! -d $WORKDIR ]; then
-	echo "Error creating $WORKDIR"
-	exit
+[ -z "$ISGNU" ] && err "Unable to locate GNU Make, set \$MAKE to it's location and re-run"
+
+if [ -f "$WORKDIR/install_env" ]; then
+  echo "Existing build found in $WORKDIR"
+  FILES_EXIST=1
+  cd $WORKDIR || err "Unable to cd to '$WORKDIR'"
+  . install_env
+  [ -n "$PREFIX" ] || err "PREFIX is missing in file 'install_env'"
+  if [ -n "$GIVEN_PREFIX" ] && [ "$GIVEN_PREFIX" != "$PREFIX" ]; then
+    echo "You must rebuild to install into $GIVEN_PREFIX (current build for $PREFIX)"
+    read -p "Would you like to rebuild for ${GIVEN_PREFIX}? [Y/n] " yn
+    if is_yes "$yn"; then
+      rm -f install_env
+      PREFIX=
+    else
+      read -p "Would you like to install to ${PREFIX}? [Y/n] " yn
+      ! is_yes "$yn" && err "Install cancelled"
+    fi
+  fi
+  if [ -n "$PREFIX" ]; then
+    PREFIX_JAVA=$PREFIX/share/java
+    prog_install
+    exit 0
+  fi
 fi
-cd $WORKDIR
 
-read -p "Should the script download and build antlr and libantlr3c? [Y/n] " yn
-if [ "$yn" = "n" ]; then
-	exit
+if [ ! -d "$WORKDIR" ]; then
+  read -p "Should the script create $WORKDIR and use it for building? [Y/n] " yn
+  is_yes "$yn" || exit
 fi
-read -p "Should the script install with prefix /usr or /usr/local? [U/l] " yn
-if [ "$yn" = "l" ]; then
-	PREFIX=/usr/local
+
+if [ -n "$GIVEN_PREFIX" ]; then
+  PREFIX=$GIVEN_PREFIX
 else
-	PREFIX=/usr
+  read -p "Should the script install with prefix /usr or /usr/local? [U/l] " yn
+  if [ "$yn" = "l" ]; then
+    PREFIX=/usr/local
+  else
+    PREFIX=/usr
+  fi
 fi
-read -p "Should the script build libantlr3c for 64 bit? [Y/n] " yn
-if [ "$yn" != "n" ]; then
-	ENABLE64BIT="--enable-64bit"
+PREFIX_JAVA=$PREFIX/share/java
+
+MACHBITS=`getconf LONG_BIT 2>/dev/null`
+[ "$MACHBITS" = "64" ] && DEF_AN="[Y/n]" || DEF_AN="[y/N]"
+read -p "Should the script build libantlr3c for 64 bit? $DEF_AN " yn
+[ -z "$yn" -a "$MACHBITS" != "64" ] && yn=n
+is_yes "$yn" && ENABLE64BIT="--enable-64bit"
+
+mkdir -p "$WORKDIR" || err "Error creating $WORKDIR"
+# don't quote WORKDIR to catch a WORKDIR that will break the build (eg spaces) 
+cd $WORKDIR || err "Unable to cd to '$WORKDIR' (does it include spaces?)"
+
+REMOVE_ON_CANCEL=
+cancel_download() {
+  echo "removing $REMOVE_ON_CANCEL"
+  [ -n "$REMOVE_ON_CANCEL" ] && rm -f "$REMOVE_ON_CANCEL"
+  err "Cancelling download..."
+}
+
+antlr_download() {
+  trap cancel_download SIGINT
+  $DOWNLOAD --help >/dev/null 2>&1 || DOWNLOAD=$ALTDOWNLOAD
+  $DOWNLOAD --help >/dev/null 2>&1 || {
+    echo "Unable to find wget or curl commands to download source,"
+    echo "  please install either one and re-try."
+    exit 1
+  }
+  [ "x$1" = "xreset" ] && rm "$ANTLR3_JAR" "$LIBANTLR3C_TAR"
+  if [ ! -f "$ANTLR3_JAR" ]; then
+    echo
+    echo "Downloading antlr from $ANTLR3_URL"
+    echo "Ctrl-C to abort..."
+    REMOVE_ON_CANCEL=$ANTLR3_JAR
+    $DOWNLOAD "$ANTLR3_URL" || err "Download of $ANTLR3_JAR failed!"
+    FILES_EXIST=1
+  fi
+  if [ ! -f "$LIBANTLR3C_TAR" ]; then
+    echo
+    echo "Downloading libantlr3c from $LIBANTLR3C_URL"
+    echo "Ctrl-C to abort..."
+    REMOVE_ON_CANCEL=$LIBANTLR3C_TAR
+    $DOWNLOAD "$LIBANTLR3C_URL" || err "Download of $LIBANTLR3C_TAR failed!"
+    FILES_EXIST=1
+  fi
+  trap - SIGINT
+}
+
+# retrieve the source
+if [ -f "$ANTLR3_JAR" -a -f "$LIBANTLR3C_TAR" ]; then
+  FILES_EXIST=1
+  read -p "Files appear to already be downloaded, use them? [Y/n] " yn
+  ! is_yes "$yn" && antlr_download reset
+else
+  read -p "Should the script download and build antlr and libantlr3c? [Y/n] " yn
+  is_yes "$yn" || exit
+  antlr_download
 fi
-wget --no-check-certificate https://github.com/antlr/website-antlr3/raw/gh-pages/download/antlr-3.5.2-complete.jar
-wget --no-check-certificate https://github.com/antlr/website-antlr3/raw/gh-pages/download/C/libantlr3c-3.4.tar.gz
-tar xzf libantlr3c-3.4.tar.gz
-cd libantlr3c-3.4
-./configure $ENABLE64BIT --prefix=$PREFIX && make && sudo make install
-cd $WORKDIR
 
-sudo mkdir -p "$PREFIX/share/java"
-sudo install antlr-3.5.2-complete.jar "$PREFIX/share/java"
+# build/install libantlr3c
+[ -d "$LIBANTLR3C" ] && rm -rf "$LIBANTLR3C"
+tar xzf "$LIBANTLR3C_TAR" || err "Uncompress of $LIBANTLR3C_TAR failed!"
+cd $LIBANTLR3C || err "Unable to cd to build $LIBANTLR3C build directory!"
+./configure $ENABLE64BIT --prefix=$PREFIX && $MAKE
+[ $? -ne 0 ] && err "Build of libantlr3c failed!"
+
+# install antlr3 jar and wrapper
+cd "$ORIG_DIR"
+cd $WORKDIR
 printf "#!/bin/sh
 export CLASSPATH
-CLASSPATH=\$CLASSPATH:$PREFIX/share/java/antlr-3.5.2-complete.jar:$PREFIX/share/java
+CLASSPATH=\$CLASSPATH:$PREFIX_JAVA/${ANTLR3_JAR}:$PREFIX_JAVA
 /usr/bin/java org.antlr.Tool \$*
 " > antlr3
-sudo install --mode=755 antlr3 "$PREFIX/bin"
 
+# save for later install attempts
+echo "PREFIX=$PREFIX" > install_env
+echo
+
+prog_install
diff --git a/scripts/freebsd_install_10.1.sh b/scripts/freebsd_install_11.0.sh
old mode 100755
new mode 100644
similarity index 93%
rename from scripts/freebsd_install_10.1.sh
rename to scripts/freebsd_install_11.0.sh
index c097fbb..072e44d
--- a/scripts/freebsd_install_10.1.sh
+++ b/scripts/freebsd_install_11.0.sh
@@ -10,8 +10,8 @@ if [ "$yn" != "y" ]; then
 fi
 
 DEPS="gmake autoconf automake libtool gettext gperf glib pkgconf wget git \
-     ffmpeg libconfuse libevent2 mxml libgcrypt libunistring libiconv \
-     libplist libinotify avahi sqlite3 alsa-lib"
+     ffmpeg libconfuse libevent mxml libgcrypt libunistring libiconv \
+     libplist libinotify avahi sqlite3 alsa-lib libsodium"
 echo "The script can install the following dependency packages for you:"
 echo $DEPS
 read -p "Should the script install these packages? [y/N] " yn
@@ -82,13 +82,13 @@ if [ "$yn" = "y" ]; then
 #These should no longer be required, but if you run into trouble you can try enabling them
 #export CC=cc
 #export LIBUNISTRING_CFLAGS=-I/usr/include
-#export LIBUNISTRING_LIBS=-L/usr/lib
+#export LIBUNISTRING_LIBS="-L/usr/lib -lunistring"
 #export ZLIB_CFLAGS=-I/usr/include
-#export ZLIB_LIBS=-L/usr/lib
+#export ZLIB_LIBS="-L/usr/lib -lz"
 
 	export CFLAGS="-march=native -g -I/usr/local/include -I/usr/include"
 	export LDFLAGS="-L/usr/local/lib -L/usr/lib"
-	./configure --build=i386-portbld-freebsd10.1 && gmake
+	./configure && gmake
 
 	read -p "Should the script install forked-daapd and add service startup scripts? [y/N] " yn
 	if [ "$yn" = "y" ]; then
@@ -135,5 +135,5 @@ fi
 read -p "Should the script (re)start forked-daapd and display the log output? [y/N] " yn
 if [ "$yn" = "y" ]; then
 	sudo service forked-daapd restart
-	tail -f /var/log/forked-daapd.log
+	tail -f /usr/local/var/log/forked-daapd.log
 fi
diff --git a/scripts/pairinghelper.sh b/scripts/pairinghelper.sh
index 94ee275..f844c1b 100755
--- a/scripts/pairinghelper.sh
+++ b/scripts/pairinghelper.sh
@@ -1,64 +1,134 @@
 #!/bin/sh
 
-# Set location of the config file
-conf_path=/etc/forked-daapd.conf
+# Default config file
+conf_path="/etc/forked-daapd.conf"
 
-if [ ! -f $conf_path ]; then
-	echo "Error: Couldn't find $conf_path"
-	echo "Set the correct config file location in the script"
-	exit
+usage() {
+  echo
+  echo "Interactive script pair Remote with forked-daapd"
+  echo
+  echo "Usage: ${0##*/} -h | [ <config-file> ]"
+  echo
+  echo "Parameters:"
+  echo "  -h           Show this help"
+  echo " <config-file> Config file (default: $conf_path)"
+  echo
+  echo "NOTE: forked-daapd needs to be running..."
+  exit 0
+}
+
+case $1 in
+  -h|--help) usage;;
+  -*)
+    echo "Unrecognized option $1 (try -h for usage)"
+    exit 1
+    ;;
+esac
+
+[ -n "$1" ] && conf_path=$1
+
+if [ ! -f "$conf_path" ]; then
+  echo "Couldn't find config file '$conf_path' (try -h for usage)"
+  exit 1
 fi
 
 logfile=`awk '$1=="logfile"{print $3}' $conf_path`
 logfile="${logfile%\"}"
 logfile="${logfile#\"}"
+[ -z "$logfile" ] && logfile="/var/log/forked-daapd.log"
+if [ ! -r "$logfile" ]; then
+  echo "Error: Couldn't read logfile '$logfile'"
+  echo "Verify 'logfile' setting in config file '$conf_path'"
+  exit 1
+fi
+
 library_path=`awk '$1=="directories"{print}' $conf_path`
 library_path="${library_path#*\"}"
 library_path="${library_path%%\"*}"
-
-if [ ! -f $logfile ]; then
-	echo "Error: Couldn't find logfile in $logfile"
-	exit
+if [ -z "$library_path" ]; then
+  echo "Couldn't find 'directories' setting in config file '$conf_path'"
+  exit 1
 fi
-if [ ! -d $library_path ]; then
-	echo "Error: Couldn't find library in $library_path"
-	exit
+if [ ! -d "$library_path" ]; then
+  echo "Error: Couldn't find library '$library_path'"
+  echo "Verify 'directories' setting in config file '$conf_path'"
+  exit 1
 fi
 
+rf="$library_path/pair.remote"
+[ -f "$rf" ] && rm -f "$rf"
+[ -f "$rf" ] && echo "Unable to remove existing pairing file '$rf'" && exit 1
+
 echo "This script will help you pair Remote with forked-daapd"
 echo "Please verify that these paths are correct:"
-echo "  Log file: $logfile"
-echo "  Library:  $library_path"
+echo "  Log file: '$logfile'"
+echo "  Library:  '$library_path'"
 read -p "Confirm? [Y/n] " yn
-if [ "$yn" = "n" ]; then
-	exit
-fi
+case "$yn" in
+  [N]*|[n]*) exit;;
+esac
+
 echo "Please start the pairing process in Remote by selecting Add library"
 read -p "Press ENTER when ready..." yn
-echo -n "Looking in $logfile for Remote announcement..."
-sleep 5
+printf %s "Looking in $logfile for Remote announcement..."
 
-remote=`grep "Discovered remote" $logfile | tail -1 | grep -Po "'.*' \("`
-remote="${remote%\'\ \(}"
-remote="${remote#\'}"
+n=5
+while [ $n -gt 0 ]; do
+  n=`expr $n - 1`
+  remote=`tail -50 "$logfile" | grep "Discovered remote" | tail -1 | grep -o "'.*' ("`
+  remote="${remote%\'\ \(}"
+  remote="${remote#\'}"
+  [ -n "$remote" ] && break
+  sleep 2
+done
 
 if [ -z "$remote" ]; then
-	echo "not found"
-	exit
-else
-	echo "found"
+  echo "not found!"
+  exit 1
 fi
+echo "found"
 
 read -p "Ready to pair Remote '$remote', please enter PIN: " pin
 if [ -z "$pin" ]; then
-	echo "Error: Invalid PIN"
-	exit
+  echo "Error: Invalid PIN"
+  exit 1
 fi
 
 echo "Writing pair.remote to $library_path..."
-printf "$remote\n$pin" > "$library_path/pair.remote"
-sleep 1
-echo "Removing pair.remote from library again..."
-rm "$library_path/pair.remote"
-echo "All done"
+printf "$pin" > "$rf"
+if [ ! -f "$rf" ]; then
+  echo "Unable to create '$rf' - check directory permissions"
+  exit 1
+fi
+
+# leave enough time for deferred file processing on BSD
+n=20
+echo "Waiting for pairing to complete (up to $n secs)..."
+while [ $n -gt 0 ]; do
+  n=`expr $n - 1`
+  result=`tail -1000 "$logfile" | sed -n "/.*remote:/ s,.*remote: ,,p" | awk '/^Discovered remote/{ f="" } /^Kickoff pairing with pin/ { f=$0; } END { print f }'`
+  [ -n "$result" ] && break
+  sleep 1
+done
+if [ -z "$result" ]; then
+  echo "forked-daap doesn't appear to be finding $rf..."
+  echo "Check $logfile, removing pair.remote"
+  rm "$rf"
+  exit 1
+fi
+echo "Pairing file pair.remote read, removing it"
+rm "$rf"
+
+n=5
+while [ $n -gt 0 ]; do
+  n=`expr $n - 1`
+  result=`tail -1000 "$logfile" | sed -n "/.*remote:/ s,.*remote: ,,p" | awk '/^Discovered remote/{ f="" } /^Pairing succeeded/ { f=$0; } END { print f }'`
+  if [ -n "$result" ]; then
+    echo "All done"
+    exit
+  fi
+  sleep 1
+done
+echo "Pairing appears to have failed... check $rf for details"
+exit 1
 
diff --git a/sqlext/Makefile.am b/sqlext/Makefile.am
index 3dde67c..74155a6 100644
--- a/sqlext/Makefile.am
+++ b/sqlext/Makefile.am
@@ -2,4 +2,8 @@ pkglib_LTLIBRARIES = forked-daapd-sqlext.la
 
 forked_daapd_sqlext_la_SOURCES = sqlext.c
 forked_daapd_sqlext_la_LDFLAGS = -avoid-version -module -shared
-forked_daapd_sqlext_la_LIBADD = @LIBUNISTRING@
+AM_CPPFLAGS += \
+	$(COMMON_CPPFLAGS)
+
+forked_daapd_sqlext_la_LIBADD = \
+	$(COMMON_LIBS)
diff --git a/src/.gitignore b/src/.gitignore
index 0770c26..df34249 100644
--- a/src/.gitignore
+++ b/src/.gitignore
@@ -3,13 +3,10 @@ forked-daapd
 *.tokens
 *Lexer.[ch]
 *Parser.[ch]
-DAAP2SQL.[ch]
-RSP2SQL.[ch]
-SMARTPL2SQL.[ch]
-
+*2SQL.[ch]
 *.u
 
-daap_query_hash.c
-rsp_query_hash.c
-dacp_prop_hash.c
-dmap_fields_hash.c
+daap_query_hash.h
+rsp_query_hash.h
+dacp_prop_hash.h
+dmap_fields_hash.h
diff --git a/src/DAAP2SQL.g b/src/DAAP2SQL.g
index 348885a..50a1776 100644
--- a/src/DAAP2SQL.g
+++ b/src/DAAP2SQL.g
@@ -44,7 +44,7 @@ options {
 	};
 
 	/* gperf static hash, daap_query.gperf */
-	#include "daap_query_hash.c"
+	#include "daap_query_hash.h"
 }
 
 query	returns [ pANTLR3_STRING result ]
diff --git a/src/Makefile.am b/src/Makefile.am
index 06d46b4..b27e68d 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -2,11 +2,11 @@
 sbin_PROGRAMS = forked-daapd
 
 if COND_ITUNES
-ITUNES_SRC=filescanner_itunes.c
+ITUNES_SRC=library/filescanner_itunes.c
 endif
 
 if COND_SPOTIFY
-SPOTIFY_SRC=spotify.c spotify.h
+SPOTIFY_SRC=spotify.c spotify.h spotify_webapi.c spotify_webapi.h inputs/spotify.c
 endif
 
 if COND_LASTFM
@@ -25,6 +25,10 @@ if COND_MPD
 MPD_SRC=mpd.c mpd.h
 endif
 
+if COND_RAOP_VERIFICATION
+RAOP_VERIFICATION_SRC=outputs/raop_verification.c outputs/raop_verification.h
+endif
+
 if COND_ALSA
 ALSA_SRC=outputs/alsa.c
 endif
@@ -33,24 +37,30 @@ if COND_PULSEAUDIO
 PULSEAUDIO_SRC=outputs/pulse.c
 endif
 
+if COND_AVAHI
+MDNS_SRC=mdns_avahi.c
+else
+MDNS_SRC=mdns_dnssd.c
+endif
+
 GPERF_FILES = \
 	daap_query.gperf \
 	rsp_query.gperf \
 	dacp_prop.gperf \
 	dmap_fields.gperf
 
-GPERF_PRODUCTS = \
-	daap_query_hash.c \
-	rsp_query_hash.c \
-	dacp_prop_hash.c \
-	dmap_fields_hash.c
+GPERF_SRC = $(GPERF_FILES:.gperf=_hash.h)
 
 ANTLR_GRAMMARS = \
 	RSP.g RSP2SQL.g \
 	DAAP.g DAAP2SQL.g \
 	SMARTPL.g SMARTPL2SQL.g 
 
-ANTLR_SOURCES = \
+ANTLR_TOKENS = $(ANTLR_GRAMMARS:.g=.tokens)
+
+ANTLR_DEPS = $(ANTLR_GRAMMARS:%.g=$(srcdir)/%.u)
+
+ANTLR_SRC = \
 	RSPLexer.c RSPLexer.h RSPParser.c RSPParser.h \
 	RSP2SQL.c RSP2SQL.h \
 	DAAPLexer.c DAAPLexer.h DAAPParser.c DAAPParser.h \
@@ -58,24 +68,21 @@ ANTLR_SOURCES = \
 	SMARTPLLexer.c SMARTPLLexer.h SMARTPLParser.c SMARTPLParser.h  \
 	SMARTPL2SQL.c SMARTPL2SQL.h
 
-ANTLR_PRODUCTS =
-
-forked_daapd_CPPFLAGS = -D_GNU_SOURCE \
-	-DDATADIR="\"$(pkgdatadir)\"" -DCONFDIR="\"$(sysconfdir)\"" \
-	-DSTATEDIR="\"$(localstatedir)\"" -DPKGLIBDIR="\"$(pkglibdir)\""
-
-forked_daapd_CFLAGS = \
-	@ZLIB_CFLAGS@ @AVAHI_CFLAGS@ @SQLITE3_CFLAGS@ @LIBAV_CFLAGS@ \
-	@CONFUSE_CFLAGS@ @MINIXML_CFLAGS@ @LIBPLIST_CFLAGS@ @SPOTIFY_CFLAGS@ \
-	@LIBGCRYPT_CFLAGS@ @GPG_ERROR_CFLAGS@ @ALSA_CFLAGS@ @LIBPULSE_CFLAGS@ \
-	@LIBCURL_CFLAGS@ @LIBPROTOBUF_C_CFLAGS@ @GNUTLS_CFLAGS@ @JSON_C_CFLAGS@
-
-forked_daapd_LDADD = -lrt \
-	@ZLIB_LIBS@ @AVAHI_LIBS@ @SQLITE3_LIBS@ @LIBAV_LIBS@ \
-	@CONFUSE_LIBS@ @LIBEVENT_LIBS@ @LIBUNISTRING@ \
-	@MINIXML_LIBS@ @ANTLR3C_LIBS@ @LIBPLIST_LIBS@ @SPOTIFY_LIBS@ \
-	@LIBGCRYPT_LIBS@ @GPG_ERROR_LIBS@ @ALSA_LIBS@ @LIBPULSE_LIBS@ \
-	@LIBCURL_LIBS@ @LIBPROTOBUF_C_LIBS@ @GNUTLS_LIBS@ @JSON_C_LIBS@
+AM_CPPFLAGS += \
+	$(FORKED_CPPFLAGS) \
+	$(FORKED_OPTS_CPPFLAGS) \
+	$(COMMON_CPPFLAGS) \
+	\
+	-D_GNU_SOURCE \
+	-DDATADIR=\"$(pkgdatadir)\" \
+	-DCONFDIR=\"$(sysconfdir)\" \
+	-DSTATEDIR=\"$(localstatedir)\" \
+	-DPKGLIBDIR=\"$(pkglibdir)\"
+
+forked_daapd_LDADD = \
+	$(FORKED_LIBS) \
+	$(FORKED_OPTS_LIBS) \
+	$(COMMON_LIBS)
 
 forked_daapd_SOURCES = main.c \
 	db.c db.h \
@@ -84,10 +91,11 @@ forked_daapd_SOURCES = main.c \
 	logger.c logger.h \
 	conffile.c conffile.h \
 	cache.c cache.h \
-	filescanner.c filescanner.h \
-	filescanner_ffmpeg.c filescanner_playlist.c \
-	filescanner_smartpl.c $(ITUNES_SRC) \
-	mdns_avahi.c mdns.h \
+	library/filescanner.c library/filescanner.h \
+	library/filescanner_ffmpeg.c library/filescanner_playlist.c \
+	library/filescanner_smartpl.c $(ITUNES_SRC) \
+	library.c library.h \
+	$(MDNS_SRC) mdns.h \
 	remote_pairing.c remote_pairing.h \
 	avio_evbuffer.c avio_evbuffer.h \
 	httpd.c httpd.h \
@@ -98,75 +106,59 @@ forked_daapd_SOURCES = main.c \
 	http.c http.h \
 	dmap_common.c dmap_common.h \
 	transcode.c transcode.h \
-	pipe.c pipe.h \
 	artwork.c artwork.h \
 	misc.c misc.h \
 	rng.c rng.h \
 	rsp_query.c rsp_query.h \
 	daap_query.c daap_query.h \
 	player.c player.h \
-	queue.c queue.h \
 	worker.c worker.h \
+	input.h input.c \
+	inputs/file_http.c inputs/pipe.c \
 	outputs.h outputs.c \
-	outputs/raop.c outputs/streaming.c outputs/dummy.c outputs/fifo.c \
+	outputs/raop.c $(RAOP_VERIFICATION_SRC) \
+	outputs/streaming.c outputs/dummy.c outputs/fifo.c \
 	$(ALSA_SRC) $(PULSEAUDIO_SRC) $(CHROMECAST_SRC) \
-	evrtsp/rtsp.c evrtp/evrtsp.h evrtsp/rtsp-internal.h evrtsp/log.h \
+	evrtsp/rtsp.c evrtsp/evrtsp.h evrtsp/rtsp-internal.h evrtsp/log.h \
 	$(SPOTIFY_SRC) \
 	$(LASTFM_SRC) \
 	$(MPD_SRC) \
 	listener.c listener.h \
-	commands.c commands.h
-
-nodist_forked_daapd_SOURCES = \
-	$(ANTLR_SOURCES)
+	commands.c commands.h \
+	ffmpeg-compat.h mxml-compat.h \
+	$(GPERF_SRC) \
+	$(ANTLR_SRC)
 
+# built by maintainers, and distributed. Clean with maintainer-clean
 BUILT_SOURCES = \
-	$(GPERF_PRODUCTS)
+	$(GPERF_SRC) \
+	$(ANTLR_SRC) \
+	$(ANTLR_TOKENS) \
+	$(ANTLR_DEPS)
 
 EXTRA_DIST = \
-	$(ANTLR_GRAMMARS)
-
-CLEANFILES = \
-	$(GPERF_PRODUCTS)
-
+	$(GPERF_FILES) \
+	$(ANTLR_GRAMMARS) \
+	$(ANTLR_TOKENS) \
+	$(ANTLR_DEPS)
 
 # gperf construction rules
-%_hash.c: %.gperf
-	if $(GPERF) $< > $@.tmp; then \
-		mv $@.tmp $@; \
-	elif $(GPERF) --version >/dev/null 2>&1; then \
-		rm $@.tmp; \
-		exit 1; \
-	else \
-		rm $@.tmp; \
-		touch $@; \
-	fi
-
-# Support for building the parsers when ANTLR3 is available
-if COND_ANTLR
-SUFFIXES = .g .u
+%_hash.h: %.gperf
+	$(AM_V_GEN)$(GPERF) --output-file=$@ $<
 
+# silent rules for antlr
+antlr_verbose = $(antlr_verbose_ at AM_V@)
+antlr_verbose_ = $(antlr_verbose_ at AM_DEFAULT_V@)
+antlr_verbose_0 = @echo "  GEN     " $< "products";
+
+# ANTLR grammar products
 %.tokens %.c %Lexer.c %Parser.c %Lexer.h %Parser.h %.h: %.g
-	$(ANTLR) -Xconversiontimeout 30000 $(ANTLR_OPTIONS) $<
+	$(antlr_verbose)$(ANTLR) -Xconversiontimeout 30000 $(ANTLR_OPTIONS) -fo . $<
 
+# ANTLR dependency files (bypass circular dependency of .g on .tokens)
 %.u: %.g
-	$(ANTLR) -depend $< > $@
-	@echo -n "ANTLR_PRODUCTS += " > $@.tmp
-	@grep : $@ | cut -d : -f 1 | tr -d ' ' | { while read f; do test "$$f" != "$<" && echo -n "$$f "; done } >> $@.tmp
-	@cat $@.tmp >> $@
-	@rm $@.tmp
-
-BUILT_SOURCES += $(ANTLR_SOURCES)
-
-CLEANFILES += \
-	$(ANTLR_PRODUCTS) \
-	$(ANTLR_GRAMMARS:.g=.u)
-
-else !COND_ANTLR
-DISTCLEANFILES = \
-	$(ANTLR_PRODUCTS) \
-	$(ANTLR_GRAMMARS:.g=.u)
-
-endif
+	$(AM_V_GEN)$(ANTLR) -depend -fo . $< > $@
+	$(AM_V_at)$(SED) -n -e '/^.*\.g[ ]*:\(.*\)/ { s//\1/;h;d; }' -e '/\.tokens.*:/ { p;d; }' -e '/:/ { G;s/\n/ /;p; }' $@ > $@-t
+	$(AM_V_at)mv $@-t $@
 
--include $(ANTLR_GRAMMARS:.g=.u)
+-include $(ANTLR_DEPS)
diff --git a/src/RSP2SQL.g b/src/RSP2SQL.g
index 807e8e8..78c6759 100644
--- a/src/RSP2SQL.g
+++ b/src/RSP2SQL.g
@@ -50,7 +50,7 @@ options {
 	};
 
 	/* gperf static hash, rsp_query.gperf */
-	#include "rsp_query_hash.c"
+	#include "rsp_query_hash.h"
 }
 
 query	returns [ pANTLR3_STRING result ]
@@ -112,7 +112,7 @@ expr	returns [ pANTLR3_STRING result, int valid ]
 		}
 	|	^(NOT c = strcrit)
 		{
-			if (!$c.valid)
+			if (!$c.valid || !$c.result)
 			{
 				$valid = 0;
 			}
@@ -131,7 +131,7 @@ expr	returns [ pANTLR3_STRING result, int valid ]
 		}
 	|	^(NOT i = intcrit)
 		{
-			if (!$i.valid)
+			if (!$i.valid || !$i.result)
 			{
 				$valid = 0;
 			}
diff --git a/src/artwork.c b/src/artwork.c
index 525a839..ed8471d 100644
--- a/src/artwork.c
+++ b/src/artwork.c
@@ -40,7 +40,6 @@
 #include "logger.h"
 #include "conffile.h"
 #include "cache.h"
-#include "player.h"
 #include "http.h"
 
 #include "avio_evbuffer.h"
@@ -145,6 +144,7 @@ static int source_item_embedded_get(struct artwork_ctx *ctx);
 static int source_item_own_get(struct artwork_ctx *ctx);
 static int source_item_stream_get(struct artwork_ctx *ctx);
 static int source_item_spotify_get(struct artwork_ctx *ctx);
+static int source_item_ownpl_get(struct artwork_ctx *ctx);
 
 /* List of sources that can provide artwork for a group (i.e. usually an album
  * identified by a persistentid). The source handlers will be called in the
@@ -207,6 +207,12 @@ static struct artwork_source artwork_item_source[] =
       .cache = ON_SUCCESS,
     },
     {
+      .name = "playlist own",
+      .handler = source_item_ownpl_get,
+      .data_kinds = (1 << DATA_KIND_HTTP),
+      .cache = ON_SUCCESS | ON_FAILURE,
+    },
+    {
       .name = NULL,
       .handler = NULL,
       .data_kinds = 0,
@@ -450,7 +456,7 @@ artwork_rescale(struct evbuffer *evbuf, AVFormatContext *src_ctx, int s, int out
   dst->codec_id = dst_fmt->video_codec;
   dst->codec_type = AVMEDIA_TYPE_VIDEO;
 
-  dst->pix_fmt = avcodec_find_best_pix_fmt_of_list((enum AVPixelFormat *)img_encoder->pix_fmts, src->pix_fmt, 1, NULL);
+  dst->pix_fmt = avcodec_default_get_format(dst, img_encoder->pix_fmts);
   if (dst->pix_fmt < 0)
     {
       DPRINTF(E_LOG, L_ART, "Could not determine best pixel format\n");
@@ -498,11 +504,14 @@ artwork_rescale(struct evbuffer *evbuf, AVFormatContext *src_ctx, int s, int out
       goto out_free_frames;
     }
 
-#ifdef HAVE_LIBAV_IMAGE_FILL_ARRAYS
+#if HAVE_DECL_AV_IMAGE_FILL_ARRAYS
   av_image_fill_arrays(o_frame->data, o_frame->linesize, buf, dst->pix_fmt, src->width, src->height, 1);
 #else
   avpicture_fill((AVPicture *)o_frame, buf, dst->pix_fmt, src->width, src->height);
 #endif
+  o_frame->height = dst->height;
+  o_frame->width = dst->width;
+  o_frame->format = dst->pix_fmt;
 
   swsctx = sws_getContext(src->width, src->height, src->pix_fmt,
 			  dst->width, dst->height, dst->pix_fmt,
@@ -1094,6 +1103,7 @@ static int
 source_item_stream_get(struct artwork_ctx *ctx)
 {
   struct http_client_ctx client;
+  struct db_queue_item *queue_item;
   struct keyval *kv;
   const char *content_type;
   char *url;
@@ -1105,9 +1115,15 @@ source_item_stream_get(struct artwork_ctx *ctx)
 
   ret = ART_E_NONE;
 
-  url = player_get_icy_artwork_url(ctx->id);
-  if (!url)
-    return ART_E_NONE;
+  queue_item = db_queue_fetch_byfileid(ctx->id);
+  if (!queue_item || !queue_item->artwork_url)
+    {
+      free_queue_item(queue_item, 0);
+      return ART_E_NONE;
+    }
+
+  url = strdup(queue_item->artwork_url);
+  free_queue_item(queue_item, 0);
 
   len = strlen(url);
   if ((len < 14) || (len > PATH_MAX)) // Can't be shorter than http://a/1.jpg
@@ -1129,8 +1145,8 @@ source_item_stream_get(struct artwork_ctx *ctx)
 
   memset(&client, 0, sizeof(struct http_client_ctx));
   client.url = url;
-  client.headers = kv;
-  client.body = ctx->evbuf;
+  client.input_headers = kv;
+  client.input_body = ctx->evbuf;
 
   if (http_client_request(&client) < 0)
     goto out_kv;
@@ -1285,6 +1301,60 @@ source_item_spotify_get(struct artwork_ctx *ctx)
 }
 #endif
 
+/* First looks of the mfi->path is in any playlist, and if so looks in the dir
+ * of the playlist file (m3u et al) to see if there is any artwork. So if the
+ * playlist is /foo/bar.m3u it will look for /foo/bar.png and /foo/bar.jpg.
+ */
+static int
+source_item_ownpl_get(struct artwork_ctx *ctx)
+{
+  struct query_params qp;
+  struct db_playlist_info dbpli;
+  char filter[PATH_MAX + 64];
+  char *mfi_path;
+  int format;
+  int ret;
+
+  ret = snprintf(filter, sizeof(filter), "(filepath = '%s')", ctx->dbmfi->path);
+  if ((ret < 0) || (ret >= sizeof(filter)))
+    {
+      DPRINTF(E_LOG, L_ART, "Artwork path exceeds PATH_MAX (%s)\n", ctx->dbmfi->path);
+      return ART_E_ERROR;
+    }
+
+  memset(&qp, 0, sizeof(struct query_params));
+  qp.type = Q_FIND_PL;
+  qp.filter = filter;
+
+  ret = db_query_start(&qp);
+  if (ret < 0)
+    {
+      DPRINTF(E_LOG, L_ART, "Could not start ownpl query\n");
+      return ART_E_ERROR;
+    }
+
+  mfi_path = ctx->dbmfi->path;
+
+  format = ART_E_NONE;
+  while (((ret = db_query_fetch_pl(&qp, &dbpli, 0)) == 0) && (dbpli.id) && (format == ART_E_NONE))
+    {
+      if (!dbpli.path)
+	continue;
+
+      ctx->dbmfi->path = dbpli.path;
+      format = source_item_own_get(ctx);
+    }
+
+  ctx->dbmfi->path = mfi_path;
+
+  if ((ret < 0) || (format < 0))
+    format = ART_E_ERROR;
+
+  db_query_end(&qp);
+
+  return format;
+}
+
 
 /* --------------------------- SOURCE PROCESSING --------------------------- */
 
diff --git a/src/cache.c b/src/cache.c
index a42ce01..1aeb909 100644
--- a/src/cache.c
+++ b/src/cache.c
@@ -42,6 +42,7 @@
 #include "httpd_daap.h"
 #include "db.h"
 #include "cache.h"
+#include "listener.h"
 #include "commands.h"
 
 
@@ -73,8 +74,8 @@ static pthread_t tid_cache;
 
 // Event base, pipes and events
 struct event_base *evbase_cache;
-static struct event *g_cacheev;
 static struct commands_base *cmdbase;
+static struct event *cache_daap_updateev;
 
 static int g_initialized;
 
@@ -91,8 +92,6 @@ struct stash
   uint8_t *data;
 } g_stash;
 
-// After being triggered wait 60 seconds before rebuilding cache
-static struct timeval g_wait = { 60, 0 };
 static int g_suspended;
 
 // The user may configure a threshold (in msec), and queries slower than
@@ -608,6 +607,7 @@ cache_daap_query_add(void *arg, int *retval)
 #define Q_TMPL "INSERT OR REPLACE INTO queries (user_agent, query, msec, timestamp) VALUES ('%q', '%q', %d, %" PRIi64 ");"
 #define Q_CLEANUP "DELETE FROM queries WHERE id NOT IN (SELECT id FROM queries ORDER BY timestamp DESC LIMIT 20);"
   struct cache_arg *cmdarg;
+  struct timeval delay = { 60, 0 };
   char *query;
   char *errmsg;
   int ret;
@@ -663,7 +663,9 @@ cache_daap_query_add(void *arg, int *retval)
       return COMMAND_END;
     }
 
-  cache_daap_trigger();
+  // Will set of cache regeneration after waiting a bit (so there is less risk
+  // of disturbing the user)
+  evtimer_add(cache_daap_updateev, &delay);
 
   *retval = 0;
   return COMMAND_END;
@@ -790,7 +792,13 @@ cache_daap_update_cb(int fd, short what, void *arg)
   char *query;
   int ret;
 
-  DPRINTF(E_INFO, L_CACHE, "Timeout reached, time to update DAAP cache\n");
+  if (g_suspended)
+    {
+      DPRINTF(E_DBG, L_CACHE, "Got a request to update DAAP cache while suspended\n");
+      return;
+    }
+
+  DPRINTF(E_LOG, L_CACHE, "Beginning DAAP cache update\n");
 
   ret = sqlite3_exec(g_db_hdl, "DELETE FROM replies;", NULL, NULL, &errmsg);
   if (ret != SQLITE_OK)
@@ -845,63 +853,28 @@ cache_daap_update_cb(int fd, short what, void *arg)
 
   sqlite3_finalize(stmt);
 
-  DPRINTF(E_INFO, L_CACHE, "DAAP cache updated\n");
+  DPRINTF(E_LOG, L_CACHE, "DAAP cache updated\n");
 }
 
-/* This function will just set a timer, which when it times out will trigger
- * the actual cache update. The purpose is to avoid avoid cache updates when
- * the database is busy, eg during a library scan.
+/* Sets off an update by activating the event. The delay is because we are low
+ * priority compared to other listeners of database updates.
  */
 static enum command_state
-cache_daap_update_timer(void *arg, int *ret)
+cache_daap_update(void *arg, int *retval)
 {
-  if (!g_cacheev)
-    {
-      *ret = -1;
-      return COMMAND_END;
-    }
-
-  evtimer_add(g_cacheev, &g_wait);
-
-  *ret = 0;
+  struct timeval delay = { 10, 0 };
 
+  *retval = event_add(cache_daap_updateev, &delay);
   return COMMAND_END;
 }
 
-static enum command_state
-cache_daap_suspend_timer(void *arg, int *ret)
+/* Callback from filescanner thread */
+static void
+cache_daap_listener_cb(enum listener_event_type type)
 {
-  if (!g_cacheev)
-    {
-      *ret = -1;
-      return COMMAND_END;
-    }
-
-  g_suspended = evtimer_pending(g_cacheev, NULL);
-  if (g_suspended)
-    evtimer_del(g_cacheev);
-
-  *ret = 0;
-
-  return COMMAND_END;
+  commands_exec_async(cmdbase, cache_daap_update, NULL);
 }
 
-static enum command_state
-cache_daap_resume_timer(void *arg, int *ret)
-{
-  if (!g_cacheev)
-    {
-      *ret = -1;
-      return COMMAND_END;
-    }
-
-  if (g_suspended)
-    evtimer_add(g_cacheev, &g_wait);
-
-  *ret = 0;
-
-  return COMMAND_END;
-}
 
 /*
  * Updates cached timestamps to current time for all cache entries for the given path, if the file was not modfied
@@ -1329,30 +1302,15 @@ cache(void *arg)
  */
 
 void
-cache_daap_trigger(void)
-{
-  if (!g_initialized)
-    return;
-
-  commands_exec_async(cmdbase, cache_daap_update_timer, NULL);
-}
-
-void
 cache_daap_suspend(void)
 {
-  if (!g_initialized)
-    return;
-
-  commands_exec_async(cmdbase, cache_daap_suspend_timer, NULL);
+  g_suspended = 1;
 }
 
 void
 cache_daap_resume(void)
 {
-  if (!g_initialized)
-    return;
-
-  commands_exec_async(cmdbase, cache_daap_resume_timer, NULL);
+  g_suspended = 0;
 }
 
 int
@@ -1377,15 +1335,13 @@ cache_daap_add(const char *query, const char *ua, int msec)
   if (!g_initialized)
     return;
 
-  cmdarg = (struct cache_arg *)malloc(sizeof(struct cache_arg));
+  cmdarg = calloc(1, sizeof(struct cache_arg));
   if (!cmdarg)
     {
       DPRINTF(E_LOG, L_CACHE, "Could not allocate cache_arg\n");
       return;
     }
 
-  memset(cmdarg, 0, sizeof(struct cache_arg));
-
   cmdarg->query = strdup(query);
   cmdarg->ua = strdup(ua);
   cmdarg->msec = msec;
@@ -1415,22 +1371,20 @@ cache_daap_threshold(void)
  * @return 0 if successful, -1 if an error occurred
  */
 void
-cache_artwork_ping(char *path, time_t mtime, int del)
+cache_artwork_ping(const char *path, time_t mtime, int del)
 {
   struct cache_arg *cmdarg;
 
   if (!g_initialized)
     return;
 
-  cmdarg = (struct cache_arg *)malloc(sizeof(struct cache_arg));
+  cmdarg = calloc(1, sizeof(struct cache_arg));
   if (!cmdarg)
     {
       DPRINTF(E_LOG, L_CACHE, "Could not allocate cache_arg\n");
       return;
     }
 
-  memset(cmdarg, 0, sizeof(struct cache_arg));
-
   cmdarg->path = strdup(path);
   cmdarg->mtime = mtime;
   cmdarg->del = del;
@@ -1629,8 +1583,8 @@ cache_init(void)
       goto evbase_fail;
     }
 
-  g_cacheev = evtimer_new(evbase_cache, cache_daap_update_cb, NULL);
-  if (!g_cacheev)
+  cache_daap_updateev = evtimer_new(evbase_cache, cache_daap_update_cb, NULL);
+  if (!cache_daap_updateev)
     {
       DPRINTF(E_LOG, L_CACHE, "Could not create cache event\n");
       goto evnew_fail;
@@ -1638,6 +1592,13 @@ cache_init(void)
 
   cmdbase = commands_base_new(evbase_cache, NULL);
 
+  ret = listener_add(cache_daap_listener_cb, LISTENER_DATABASE);
+  if (ret < 0)
+    {
+      DPRINTF(E_LOG, L_CACHE, "Could not create listener event\n");
+      goto listener_fail;
+    }
+
   DPRINTF(E_INFO, L_CACHE, "cache thread init\n");
 
   ret = pthread_create(&tid_cache, NULL, cache, NULL);
@@ -1657,6 +1618,8 @@ cache_init(void)
   return 0;
   
  thread_fail:
+  listener_remove(cache_daap_listener_cb);
+ listener_fail:
   commands_base_free(cmdbase);
  evnew_fail:
   event_base_free(evbase_cache);
@@ -1675,6 +1638,9 @@ cache_deinit(void)
     return;
 
   g_initialized = 0;
+
+  listener_remove(cache_daap_listener_cb);
+
   commands_base_destroy(cmdbase);
 
   ret = pthread_join(tid_cache, NULL);
diff --git a/src/cache.h b/src/cache.h
index 16b4235..69acf4d 100644
--- a/src/cache.h
+++ b/src/cache.h
@@ -7,9 +7,6 @@
 /* ---------------------------- DAAP cache API  --------------------------- */
 
 void
-cache_daap_trigger(void);
-
-void
 cache_daap_suspend(void);
 
 void
@@ -31,7 +28,7 @@ cache_daap_threshold(void);
 #define CACHE_ARTWORK_INDIVIDUAL 1
 
 void
-cache_artwork_ping(char *path, time_t mtime, int del);
+cache_artwork_ping(const char *path, time_t mtime, int del);
 
 int
 cache_artwork_delete_by_path(char *path);
diff --git a/src/commands.c b/src/commands.c
index 8636afb..b80fcad 100644
--- a/src/commands.c
+++ b/src/commands.c
@@ -20,14 +20,13 @@
 
 #include <errno.h>
 #include <fcntl.h>
-#include <pthread.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
 
 #include "logger.h"
-
+#include "misc.h"
 
 struct command
 {
@@ -79,7 +78,7 @@ command_cb_sync(struct commands_base *cmdbase, struct command *cmd)
 {
   enum command_state cmdstate;
 
-  pthread_mutex_lock(&cmd->lck);
+  CHECK_ERR(L_MAIN, pthread_mutex_lock(&cmd->lck));
 
   cmdstate = cmd->func(cmd->arg, &cmd->ret);
   if (cmdstate == COMMAND_PENDING)
@@ -92,15 +91,16 @@ command_cb_sync(struct commands_base *cmdbase, struct command *cmd)
     {
       // Command execution finished, execute the bottom half function
       if (cmd->ret == 0 && cmd->func_bh)
-      {
-	cmdstate = cmd->func_bh(cmd->arg, &cmd->ret);
-      }
+	cmd->func_bh(cmd->arg, &cmd->ret);
+
+      event_add(cmdbase->command_event, NULL);
 
       // Signal the calling thread that the command execution finished
-      pthread_cond_signal(&cmd->cond);
-      pthread_mutex_unlock(&cmd->lck);
+      CHECK_ERR(L_MAIN, pthread_cond_signal(&cmd->cond));
+      CHECK_ERR(L_MAIN, pthread_mutex_unlock(&cmd->lck));
 
-      event_add(cmdbase->command_event, NULL);
+      // Note if cmd->func was cmdloop_exit then cmdbase may be invalid now,
+      // because commands_base_destroy() may have freed it
     }
 }
 
@@ -159,6 +159,7 @@ send_command(struct commands_base *cmdbase, struct command *cmd)
   ret = write(cmdbase->command_pipe[1], &cmd, sizeof(cmd));
   if (ret != sizeof(cmd))
     {
+      DPRINTF(E_LOG, L_MAIN, "Bad write to command pipe (write incomplete)\n");
       return -1;
     }
 
@@ -184,11 +185,11 @@ commands_base_new(struct event_base *evbase, command_exit_cb exit_cb)
       return NULL;
     }
 
-# if defined(__linux__)
+#ifdef HAVE_PIPE2
   ret = pipe2(cmdbase->command_pipe, O_CLOEXEC);
-# else
+#else
   ret = pipe(cmdbase->command_pipe);
-# endif
+#endif
   if (ret < 0)
     {
       DPRINTF(E_LOG, L_MAIN, "Could not create command pipe: %s\n", strerror(errno));
@@ -228,6 +229,7 @@ commands_base_new(struct event_base *evbase, command_exit_cb exit_cb)
 int
 commands_base_free(struct commands_base *cmdbase)
 {
+  event_free(cmdbase->command_event);
   close(cmdbase->command_pipe[0]);
   close(cmdbase->command_pipe[1]);
   free(cmdbase);
@@ -267,31 +269,32 @@ commands_exec_returnvalue(struct commands_base *cmdbase)
 void
 commands_exec_end(struct commands_base *cmdbase, int retvalue)
 {
-  if (cmdbase->current_cmd == NULL)
+  struct command *current_cmd = cmdbase->current_cmd;
+
+  if (!current_cmd)
     return;
 
   // A pending event finished, decrease the number of pending events and update the return value
-  cmdbase->current_cmd->pending--;
-  cmdbase->current_cmd->ret = retvalue;
+  current_cmd->pending--;
+  current_cmd->ret = retvalue;
 
-  DPRINTF(E_DBG, L_MAIN, "Command has %d pending events\n", cmdbase->current_cmd->pending);
+  DPRINTF(E_DBG, L_MAIN, "Command has %d pending events\n", current_cmd->pending);
 
   // If there are still pending events return
-  if (cmdbase->current_cmd->pending > 0)
+  if (current_cmd->pending > 0)
     return;
 
   // All pending events have finished, execute the bottom half and signal the caller that the command execution finished
-  if (cmdbase->current_cmd->func_bh)
-    {
-      cmdbase->current_cmd->func_bh(cmdbase->current_cmd->arg, &cmdbase->current_cmd->ret);
-    }
-  pthread_cond_signal(&cmdbase->current_cmd->cond);
-  pthread_mutex_unlock(&cmdbase->current_cmd->lck);
+  if (current_cmd->func_bh)
+    current_cmd->func_bh(current_cmd->arg, &current_cmd->ret);
 
   cmdbase->current_cmd = NULL;
 
   /* Process commands again */
   event_add(cmdbase->command_event, NULL);
+
+  CHECK_ERR(L_MAIN, pthread_cond_signal(&current_cmd->cond));
+  CHECK_ERR(L_MAIN, pthread_mutex_unlock(&current_cmd->lck));
 }
 
 /*
@@ -319,18 +322,25 @@ commands_exec_sync(struct commands_base *cmdbase, command_function func, command
   cmd.arg = arg;
   cmd.nonblock = 0;
 
-  pthread_mutex_lock(&cmd.lck);
+  CHECK_ERR(L_MAIN, mutex_init(&cmd.lck));
+  CHECK_ERR(L_MAIN, pthread_cond_init(&cmd.cond, NULL));
+
+  CHECK_ERR(L_MAIN, pthread_mutex_lock(&cmd.lck));
 
   ret = send_command(cmdbase, &cmd);
   if (ret < 0)
     {
       DPRINTF(E_LOG, L_MAIN, "Error sending command\n");
-      pthread_mutex_unlock(&cmd.lck);
-      return -1;
+      cmd.ret = -1;
+    }
+  else
+    {
+      CHECK_ERR(L_MAIN, pthread_cond_wait(&cmd.cond, &cmd.lck));
     }
+  CHECK_ERR(L_MAIN, pthread_mutex_unlock(&cmd.lck));
 
-  pthread_cond_wait(&cmd.cond, &cmd.lck);
-  pthread_mutex_unlock(&cmd.lck);
+  CHECK_ERR(L_MAIN, pthread_cond_destroy(&cmd.cond));
+  CHECK_ERR(L_MAIN, pthread_mutex_destroy(&cmd.lck));
 
   return cmd.ret;
 }
@@ -360,7 +370,10 @@ commands_exec_async(struct commands_base *cmdbase, command_function func, void *
 
   ret = send_command(cmdbase, cmd);
   if (ret < 0)
-    return -1;
+    {
+      free(cmd);
+      return -1;
+    }
 
   return 0;
 }
diff --git a/src/conffile.c b/src/conffile.c
index 8d01de0..7cc8872 100644
--- a/src/conffile.c
+++ b/src/conffile.c
@@ -56,6 +56,11 @@ static cfg_opt_t sec_general[] =
     CFG_STR("cache_path", STATEDIR "/cache/" PACKAGE "/cache.db", CFGF_NONE),
     CFG_INT("cache_daap_threshold", 1000, CFGF_NONE),
     CFG_BOOL("speaker_autoselect", cfg_true, CFGF_NONE),
+#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
+    CFG_BOOL("high_resolution_clock", cfg_false, CFGF_NONE),
+#else
+    CFG_BOOL("high_resolution_clock", cfg_true, CFGF_NONE),
+#endif
     CFG_STR("allow_origin", "*", CFGF_NONE),
     CFG_END()
   };
@@ -82,13 +87,14 @@ static cfg_opt_t sec_library[] =
     CFG_STR("name_radio", "Radio", CFGF_NONE),
     CFG_STR_LIST("artwork_basenames", "{artwork,cover,Folder}", CFGF_NONE),
     CFG_BOOL("artwork_individual", cfg_false, CFGF_NONE),
-    CFG_STR_LIST("filetypes_ignore", "{.db,.ini,.db-journal,.pdf}", CFGF_NONE),
+    CFG_STR_LIST("filetypes_ignore", "{.db,.ini,.db-journal,.pdf,.metadata}", CFGF_NONE),
     CFG_STR_LIST("filepath_ignore", NULL, CFGF_NONE),
     CFG_BOOL("filescan_disable", cfg_false, CFGF_NONE),
     CFG_BOOL("itunes_overrides", cfg_false, CFGF_NONE),
     CFG_BOOL("itunes_smartpl", cfg_false, CFGF_NONE),
     CFG_STR_LIST("no_decode", NULL, CFGF_NONE),
     CFG_STR_LIST("force_decode", NULL, CFGF_NONE),
+    CFG_BOOL("pipe_autostart", cfg_true, CFGF_NONE),
     CFG_END()
   };
 
@@ -108,6 +114,7 @@ static cfg_opt_t sec_audio[] =
 static cfg_opt_t sec_airplay[] =
   {
     CFG_INT("max_volume", 11, CFGF_NONE),
+    CFG_BOOL("exclude", cfg_false, CFGF_NONE),
     CFG_STR("password", NULL, CFGF_NONE),
     CFG_END()
   };
diff --git a/src/db.c b/src/db.c
index 21e468d..2ea77ab 100644
--- a/src/db.c
+++ b/src/db.c
@@ -35,17 +35,18 @@
 #include <sys/mman.h>
 #include <limits.h>
 
-#include <pthread.h>
-
 #include <sqlite3.h>
 
 #include "conffile.h"
 #include "logger.h"
 #include "cache.h"
+#include "listener.h"
+#include "library.h"
 #include "misc.h"
 #include "db.h"
 #include "db_init.h"
 #include "db_upgrade.h"
+#include "rng.h"
 
 
 #define STR(x) ((x) ? (x) : "")
@@ -74,6 +75,12 @@ struct col_type_map {
   short type;
 };
 
+struct query_clause {
+  char *where;
+  const char *order;
+  char *index;
+};
+
 /* This list must be kept in sync with
  * - the order of the columns in the files table
  * - the type and name of the fields in struct media_file_info
@@ -297,8 +304,13 @@ static const char *sort_clause[] =
     "ORDER BY f.disc ASC",
     "ORDER BY f.track ASC",
     "ORDER BY f.virtual_path ASC",
+    "ORDER BY pos ASC",
+    "ORDER BY shuffle_pos ASC",
   };
 
+/* Shuffle RNG state */
+struct rng_ctx shuffle_rng;
+
 static char *db_path;
 static __thread sqlite3 *hdl;
 
@@ -316,6 +328,9 @@ db_pl_fetch_byid(int id);
 static enum group_type
 db_group_type_bypersistentid(int64_t persistentid);
 
+static int
+db_query_run(char *query, int free, int cache_update);
+
 
 char *
 db_escape_string(const char *str)
@@ -341,14 +356,12 @@ db_escape_string(const char *str)
 void
 free_pi(struct pairing_info *pi, int content_only)
 {
-  if (pi->remote_id)
-    free(pi->remote_id);
-
-  if (pi->name)
-    free(pi->name);
+  if (!pi)
+    return;
 
-  if (pi->guid)
-    free(pi->guid);
+  free(pi->remote_id);
+  free(pi->name);
+  free(pi->guid);
 
   if (!content_only)
     free(pi);
@@ -359,77 +372,33 @@ free_pi(struct pairing_info *pi, int content_only)
 void
 free_mfi(struct media_file_info *mfi, int content_only)
 {
-  if (mfi->path)
-    free(mfi->path);
-
-  if (mfi->fname)
-    free(mfi->fname);
-
-  if (mfi->title)
-    free(mfi->title);
-
-  if (mfi->artist)
-    free(mfi->artist);
-
-  if (mfi->album)
-    free(mfi->album);
-
-  if (mfi->genre)
-    free(mfi->genre);
-
-  if (mfi->comment)
-    free(mfi->comment);
-
-  if (mfi->type)
-    free(mfi->type);
-
-  if (mfi->composer)
-    free(mfi->composer);
-
-  if (mfi->orchestra)
-    free(mfi->orchestra);
-
-  if (mfi->conductor)
-    free(mfi->conductor);
-
-  if (mfi->grouping)
-    free(mfi->grouping);
-
-  if (mfi->description)
-    free(mfi->description);
-
-  if (mfi->codectype)
-    free(mfi->codectype);
-
-  if (mfi->album_artist)
-    free(mfi->album_artist);
-
-  if (mfi->tv_series_name)
-    free(mfi->tv_series_name);
-
-  if (mfi->tv_episode_num_str)
-    free(mfi->tv_episode_num_str);
-
-  if (mfi->tv_network_name)
-    free(mfi->tv_network_name);
-
-  if (mfi->title_sort)
-    free(mfi->title_sort);
-
-  if (mfi->artist_sort)
-    free(mfi->artist_sort);
-
-  if (mfi->album_sort)
-    free(mfi->album_sort);
-
-  if (mfi->composer_sort)
-    free(mfi->composer_sort);
-
-  if (mfi->album_artist_sort)
-    free(mfi->album_artist_sort);
+  if (!mfi)
+    return;
 
-  if (mfi->virtual_path)
-    free(mfi->virtual_path);
+  free(mfi->path);
+  free(mfi->fname);
+  free(mfi->title);
+  free(mfi->artist);
+  free(mfi->album);
+  free(mfi->genre);
+  free(mfi->comment);
+  free(mfi->type);
+  free(mfi->composer);
+  free(mfi->orchestra);
+  free(mfi->conductor);
+  free(mfi->grouping);
+  free(mfi->description);
+  free(mfi->codectype);
+  free(mfi->album_artist);
+  free(mfi->tv_series_name);
+  free(mfi->tv_episode_num_str);
+  free(mfi->tv_network_name);
+  free(mfi->title_sort);
+  free(mfi->artist_sort);
+  free(mfi->album_sort);
+  free(mfi->composer_sort);
+  free(mfi->album_artist_sort);
+  free(mfi->virtual_path);
 
   if (!content_only)
     free(mfi);
@@ -474,17 +443,13 @@ unicode_fixup_mfi(struct media_file_info *mfi)
 void
 free_pli(struct playlist_info *pli, int content_only)
 {
-  if (pli->title)
-    free(pli->title);
-
-  if (pli->query)
-    free(pli->query);
-
-  if (pli->path)
-    free(pli->path);
+  if (!pli)
+    return;
 
-  if (pli->virtual_path)
-    free(pli->virtual_path);
+  free(pli->title);
+  free(pli->query);
+  free(pli->path);
+  free(pli->virtual_path);
 
   if (!content_only)
     free(pli);
@@ -495,8 +460,10 @@ free_pli(struct playlist_info *pli, int content_only)
 void
 free_di(struct directory_info *di, int content_only)
 {
-  if (di->virtual_path)
-    free(di->virtual_path);
+  if (!di)
+    return;
+
+  free(di->virtual_path);
 
   if (!content_only)
     free(di);
@@ -516,12 +483,12 @@ unlock_notify_cb(void **args, int nargs)
     {
       u = (struct db_unlock *)args[i];
 
-      pthread_mutex_lock(&u->lck);
+      CHECK_ERR(L_DB, pthread_mutex_lock(&u->lck));
 
       u->proceed = 1;
-      pthread_cond_signal(&u->cond);
+      CHECK_ERR(L_DB, pthread_cond_signal(&u->cond));
 
-      pthread_mutex_unlock(&u->lck);
+      CHECK_ERR(L_DB, pthread_mutex_unlock(&u->lck));
     }
 }
 
@@ -532,25 +499,25 @@ db_wait_unlock(void)
   int ret;
 
   u.proceed = 0;
-  pthread_mutex_init(&u.lck, NULL);
-  pthread_cond_init(&u.cond, NULL);
+  CHECK_ERR(L_DB, mutex_init(&u.lck));
+  CHECK_ERR(L_DB, pthread_cond_init(&u.cond, NULL));
 
   ret = sqlite3_unlock_notify(hdl, unlock_notify_cb, &u);
   if (ret == SQLITE_OK)
     {
-      pthread_mutex_lock(&u.lck);
+      CHECK_ERR(L_DB, pthread_mutex_lock(&u.lck));
 
       if (!u.proceed)
 	{
 	  DPRINTF(E_INFO, L_DB, "Waiting for database unlock\n");
-	  pthread_cond_wait(&u.cond, &u.lck);
+	  CHECK_ERR(L_DB, pthread_cond_wait(&u.cond, &u.lck));
 	}
 
-      pthread_mutex_unlock(&u.lck);
+      CHECK_ERR(L_DB, pthread_mutex_unlock(&u.lck));
     }
 
-  pthread_cond_destroy(&u.cond);
-  pthread_mutex_destroy(&u.lck);
+  CHECK_ERR(L_DB, pthread_cond_destroy(&u.cond));
+  CHECK_ERR(L_DB, pthread_mutex_destroy(&u.lck));
 
   return ret;
 }
@@ -713,61 +680,60 @@ db_hook_post_scan(void)
 void
 db_purge_cruft(time_t ref)
 {
-  char *errmsg;
+#define Q_TMPL "DELETE FROM directories WHERE id >= %d AND db_timestamp < %" PRIi64 ";"
   int i;
   int ret;
-  char *queries[4] = { NULL, NULL, NULL, NULL };
-  char *queries_tmpl[4] =
+  char *query;
+  char *queries_tmpl[3] =
     {
       "DELETE FROM playlistitems WHERE playlistid IN (SELECT id FROM playlists p WHERE p.type <> %d AND p.db_timestamp < %" PRIi64 ");",
       "DELETE FROM playlists WHERE type <> %d AND db_timestamp < %" PRIi64 ";",
       "DELETE FROM files WHERE -1 <> %d AND db_timestamp < %" PRIi64 ";",
-      "DELETE FROM directories WHERE id > 4 AND -1 <> %d AND db_timestamp < %" PRIi64 ";"
     };
 
-  if (sizeof(queries) != sizeof(queries_tmpl))
-    {
-      DPRINTF(E_LOG, L_DB, "db_purge_cruft(): queries out of sync with queries_tmpl\n");
-      return;
-    }
+  db_transaction_begin();
 
   for (i = 0; i < (sizeof(queries_tmpl) / sizeof(queries_tmpl[0])); i++)
     {
-      queries[i] = sqlite3_mprintf(queries_tmpl[i], PL_SPECIAL, (int64_t)ref);
-      if (!queries[i])
+      query = sqlite3_mprintf(queries_tmpl[i], PL_SPECIAL, (int64_t)ref);
+      if (!query)
 	{
 	  DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
-	  goto purge_fail;
+	  db_transaction_end();
+	  return;
 	}
-    }
-
-  for (i = 0; i < (sizeof(queries) / sizeof(queries[0])); i++)
-    {
-      DPRINTF(E_DBG, L_DB, "Running purge query '%s'\n", queries[i]);
 
-      ret = db_exec(queries[i], &errmsg);
-      if (ret != SQLITE_OK)
-	{
-	  DPRINTF(E_LOG, L_DB, "Purge query %d error: %s\n", i, errmsg);
+      DPRINTF(E_DBG, L_DB, "Running purge query '%s'\n", query);
 
-	  sqlite3_free(errmsg);
-	}
-      else
+      ret = db_query_run(query, 1, 0);
+      if (ret == 0)
 	DPRINTF(E_DBG, L_DB, "Purged %d rows\n", sqlite3_changes(hdl));
     }
 
- purge_fail:
-  for (i = 0; i < (sizeof(queries) / sizeof(queries[0])); i++)
+  query = sqlite3_mprintf(Q_TMPL, DIR_MAX, (int64_t)ref);
+  if (!query)
     {
-      sqlite3_free(queries[i]);
+      DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
+      db_transaction_end();
+      return;
     }
 
+  DPRINTF(E_DBG, L_DB, "Running purge query '%s'\n", query);
+
+  ret = db_query_run(query, 1, 1);
+  if (ret == 0)
+    DPRINTF(E_DBG, L_DB, "Purged %d rows\n", sqlite3_changes(hdl));
+
+  db_transaction_end();
+
+#undef Q_TMPL
 }
 
 void
 db_purge_all(void)
 {
-#define Q_TMPL "DELETE FROM playlists WHERE type <> %d;"
+#define Q_TMPL_PL "DELETE FROM playlists WHERE type <> %d;"
+#define Q_TMPL_DIR "DELETE FROM directories WHERE id >= %d;"
   char *queries[4] =
     {
       "DELETE FROM inotify;",
@@ -795,7 +761,8 @@ db_purge_all(void)
 	DPRINTF(E_DBG, L_DB, "Purged %d rows\n", sqlite3_changes(hdl));
     }
 
-  query = sqlite3_mprintf(Q_TMPL, PL_SPECIAL);
+  // Purge playlists
+  query = sqlite3_mprintf(Q_TMPL_PL, PL_SPECIAL);
   if (!query)
     {
       DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
@@ -815,11 +782,35 @@ db_purge_all(void)
     DPRINTF(E_DBG, L_DB, "Purged %d rows\n", sqlite3_changes(hdl));
 
   sqlite3_free(query);
-#undef Q_TMPL
+
+  // Purge directories
+  query = sqlite3_mprintf(Q_TMPL_DIR, DIR_MAX);
+  if (!query)
+    {
+      DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
+      return;
+    }
+
+  DPRINTF(E_DBG, L_DB, "Running purge query '%s'\n", query);
+
+  ret = db_exec(query, &errmsg);
+  if (ret != SQLITE_OK)
+    {
+      DPRINTF(E_LOG, L_DB, "Purge query '%s' error: %s\n", query, errmsg);
+
+      sqlite3_free(errmsg);
+    }
+  else
+    DPRINTF(E_DBG, L_DB, "Purged %d rows\n", sqlite3_changes(hdl));
+
+  sqlite3_free(query);
+
+#undef Q_TMPL_PL
+#undef Q_TMPL_DIR
 }
 
 static int
-db_get_count(char *query)
+db_get_one_int(const char *query)
 {
   sqlite3_stmt *stmt;
   int ret;
@@ -892,461 +883,334 @@ db_transaction_end(void)
     }
 }
 
+void
+db_transaction_rollback(void)
+{
+  char *query = "ROLLBACK TRANSACTION;";
+  char *errmsg;
+  int ret;
+
+  DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query);
+
+  ret = db_exec(query, &errmsg);
+  if (ret != SQLITE_OK)
+    {
+      DPRINTF(E_LOG, L_DB, "SQL error running '%s': %s\n", query, errmsg);
+
+      sqlite3_free(errmsg);
+    }
+}
+
+static void
+db_free_query_clause(struct query_clause *qc)
+{
+  if (!qc)
+    return;
+
+  sqlite3_free(qc->where);
+  sqlite3_free(qc->index);
+  free(qc);
+}
 
-/* Queries */
-static int
-db_build_query_index_clause(char **i, struct query_params *qp)
+static struct query_clause *
+db_build_query_clause(struct query_params *qp)
 {
-  char *idx;
+  struct query_clause *qc;
+
+  qc = calloc(1, sizeof(struct query_clause));
+  if (!qc)
+    goto error;
 
-  *i = NULL;
+  if (qp->filter)
+    qc->where = sqlite3_mprintf("WHERE f.disabled = 0 AND %s", qp->filter);
+  else
+    qc->where = sqlite3_mprintf("WHERE f.disabled = 0");
+
+  if (qp->sort)
+    qc->order = sort_clause[qp->sort];
+  else
+    qc->order = "";
 
   switch (qp->idx_type)
     {
       case I_FIRST:
-	idx = sqlite3_mprintf("LIMIT %d", qp->limit);
+	qc->index = sqlite3_mprintf("LIMIT %d", qp->limit);
 	break;
 
       case I_LAST:
-	idx = sqlite3_mprintf("LIMIT -1 OFFSET %d", qp->results - qp->limit);
+	qc->index = sqlite3_mprintf("LIMIT -1 OFFSET %d", qp->results - qp->limit);
 	break;
 
       case I_SUB:
-	idx = sqlite3_mprintf("LIMIT %d OFFSET %d", qp->limit, qp->offset);
+	qc->index = sqlite3_mprintf("LIMIT %d OFFSET %d", qp->limit, qp->offset);
 	break;
 
       case I_NONE:
-	return 0;
-
-      default:
-	DPRINTF(E_LOG, L_DB, "Unknown index type\n");
-	return -1;
+	qc->index = sqlite3_mprintf("");
+	break;
     }
 
-  if (!idx)
-    {
-      DPRINTF(E_LOG, L_DB, "Could not build index string; out of memory");
-      return -1;
-    }
+  if (!qc->where || !qc->index)
+    goto error;
 
-  *i = idx;
+  return qc;
 
-  return 0;
+ error:
+  DPRINTF(E_LOG, L_DB, "Error building query clause\n");
+  db_free_query_clause(qc);
+  return NULL;
 }
 
-static int
-db_build_query_items(struct query_params *qp, char **q)
+static char *
+db_build_query_check(struct query_params *qp, char *count, char *query)
 {
-  char *query;
-  char *count;
-  char *idx;
-  const char *sort;
-  int ret;
-
-  if (qp->filter)
-    count = sqlite3_mprintf("SELECT COUNT(*) FROM files f WHERE f.disabled = 0 AND %s;", qp->filter);
-  else
-    count = sqlite3_mprintf("SELECT COUNT(*) FROM files f WHERE f.disabled = 0;");
-
-  if (!count)
+  if (!count || !query)
     {
-      DPRINTF(E_LOG, L_DB, "Out of memory for count query string\n");
-
-      return -1;
+      DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
+      goto failed;
     }
 
-  qp->results = db_get_count(count);
-  sqlite3_free(count);
-
+  qp->results = db_get_one_int(count);
   if (qp->results < 0)
-    return -1;
-
-  /* Get index clause */
-  ret = db_build_query_index_clause(&idx, qp);
-  if (ret < 0)
-    return -1;
-
-  sort = sort_clause[qp->sort];
-
-  if (idx && qp->filter)
-    query = sqlite3_mprintf("SELECT f.* FROM files f WHERE f.disabled = 0 AND %s %s %s;", qp->filter, sort, idx);
-  else if (idx)
-    query = sqlite3_mprintf("SELECT f.* FROM files f WHERE f.disabled = 0 %s %s;", sort, idx);
-  else if (qp->filter)
-    query = sqlite3_mprintf("SELECT f.* FROM files f WHERE f.disabled = 0 AND %s %s;", qp->filter, sort);
-  else
-    query = sqlite3_mprintf("SELECT f.* FROM files f WHERE f.disabled = 0 %s;", sort);
-
-  if (idx)
-    sqlite3_free(idx);
+    goto failed;
 
-  if (!query)
-    {
-      DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
-      return -1;
-    }
+  sqlite3_free(count);
 
-  *q = query;
+  return query;
 
-  return 0;
+ failed:
+  sqlite3_free(count);
+  sqlite3_free(query);
+  return NULL;
 }
 
-static int
-db_build_query_pls(struct query_params *qp, char **q)
+static char *
+db_build_query_items(struct query_params *qp)
 {
+  struct query_clause *qc;
+  char *count;
   char *query;
-  char *idx;
-  const char *sort;
-  int ret;
 
-  qp->results = db_get_count("SELECT COUNT(*) FROM playlists p WHERE p.disabled = 0;");
-  if (qp->results < 0)
-    return -1;
+  qc = db_build_query_clause(qp);
+  if (!qc)
+    return NULL;
 
-  /* Get index clause */
-  ret = db_build_query_index_clause(&idx, qp);
-  if (ret < 0)
-    return -1;
+  count = sqlite3_mprintf("SELECT COUNT(*) FROM files f %s;", qc->where);
+  query = sqlite3_mprintf("SELECT f.* FROM files f %s %s %s;", qc->where, qc->order, qc->index);
 
-  sort = sort_clause[qp->sort];
+  db_free_query_clause(qc);
 
-  if (idx && qp->filter)
-    query = sqlite3_mprintf("SELECT f.* FROM playlists f WHERE f.disabled = 0 AND %s %s %s;", qp->filter, sort, idx);
-  else if (idx)
-    query = sqlite3_mprintf("SELECT f.* FROM playlists f WHERE f.disabled = 0 %s %s;", sort, idx);
-  else if (qp->filter)
-    query = sqlite3_mprintf("SELECT f.* FROM playlists f WHERE f.disabled = 0 AND %s %s;", qp->filter, sort);
-  else
-    query = sqlite3_mprintf("SELECT f.* FROM playlists f WHERE f.disabled = 0 %s;", sort);
+  return db_build_query_check(qp, count, query);
+}
 
-  if (idx)
-    sqlite3_free(idx);
+static char *
+db_build_query_pls(struct query_params *qp)
+{
+  struct query_clause *qc;
+  char *count;
+  char *query;
 
-  if (!query)
-    {
-      DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
-      return -1;
-    }
+  qc = db_build_query_clause(qp);
+  if (!qc)
+    return NULL;
 
-  *q = query;
+  count = sqlite3_mprintf("SELECT COUNT(*) FROM playlists f %s;", qc->where);
+  query = sqlite3_mprintf("SELECT f.* FROM playlists f %s %s %s;", qc->where, qc->order, qc->index);
 
-  return 0;
+  db_free_query_clause(qc);
+
+  return db_build_query_check(qp, count, query);
 }
 
-static int
-db_build_query_plitems_plain(struct query_params *qp, char **q)
+static char *
+db_build_query_find_pls(struct query_params *qp)
 {
-  char *query;
+  struct query_clause *qc;
   char *count;
-  char *idx;
-  int ret;
-
-  if (qp->filter)
-    count = sqlite3_mprintf("SELECT COUNT(*) FROM files f JOIN playlistitems pi ON f.path = pi.filepath"
-			    " WHERE pi.playlistid = %d AND f.disabled = 0 AND %s;", qp->id, qp->filter);
-  else
-    count = sqlite3_mprintf("SELECT COUNT(*) FROM files f JOIN playlistitems pi ON f.path = pi.filepath"
-			    " WHERE pi.playlistid = %d AND f.disabled = 0;", qp->id);
+  char *query;
 
-  if (!count)
+  if (!qp->filter)
     {
-      DPRINTF(E_LOG, L_DB, "Out of memory for count query string\n");
-
-      return -1;
+      DPRINTF(E_LOG, L_DB, "Bug! Playlist find called without search criteria\n");
+      return NULL;
     }
 
-  qp->results = db_get_count(count);
-  sqlite3_free(count);
-
-  if (qp->results < 0)
-    return -1;
-
-  /* Get index clause */
-  ret = db_build_query_index_clause(&idx, qp);
-  if (ret < 0)
-    return -1;
-
-  if (idx && qp->filter)
-    query = sqlite3_mprintf("SELECT f.* FROM files f JOIN playlistitems pi ON f.path = pi.filepath"
-			    " WHERE pi.playlistid = %d AND f.disabled = 0 AND %s ORDER BY pi.id ASC %s;",
-			    qp->id, qp->filter, idx);
-  else if (idx)
-    query = sqlite3_mprintf("SELECT f.* FROM files f JOIN playlistitems pi ON f.path = pi.filepath"
-			    " WHERE pi.playlistid = %d AND f.disabled = 0 ORDER BY pi.id ASC %s;",
-			    qp->id, idx);
-  else if (qp->filter)
-    query = sqlite3_mprintf("SELECT f.* FROM files f JOIN playlistitems pi ON f.path = pi.filepath"
-			    " WHERE pi.playlistid = %d AND f.disabled = 0 AND %s ORDER BY pi.id ASC;",
-			    qp->id, qp->filter);
-  else
-    query = sqlite3_mprintf("SELECT f.* FROM files f JOIN playlistitems pi ON f.path = pi.filepath"
-			    " WHERE pi.playlistid = %d AND f.disabled = 0 ORDER BY pi.id ASC;",
-			    qp->id);
-
-  if (idx)
-    sqlite3_free(idx);
+  qc = db_build_query_clause(qp);
+  if (!qc)
+    return NULL;
 
-  if (!query)
-    {
-      DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
-      return -1;
-    }
+  // Use qp->filter because qc->where has a f.disabled which is not a column in playlistitems
+  count = sqlite3_mprintf("SELECT COUNT(*) FROM playlists f WHERE f.id IN (SELECT playlistid FROM playlistitems WHERE %s);", qp->filter);
+  query = sqlite3_mprintf("SELECT f.* FROM playlists f WHERE f.id IN (SELECT playlistid FROM playlistitems WHERE %s) %s %s;", qp->filter, qc->order, qc->index);
 
-  *q = query;
+  db_free_query_clause(qc);
 
-  return 0;
+  return db_build_query_check(qp, count, query);
 }
 
-static int
-db_build_query_plitems_smart(struct query_params *qp, char *smartpl_query, char **q)
+static char *
+db_build_query_plitems_plain(struct query_params *qp)
 {
-  char *query;
+  struct query_clause *qc;
   char *count;
-  char *filter;
-  char *idx;
-  const char *sort;
-  int ret;
-
-  if (qp->filter)
-    filter = qp->filter;
-  else
-    filter = "1 = 1";
-
-  count = sqlite3_mprintf("SELECT COUNT(*) FROM files f WHERE f.disabled = 0 AND %s AND %s;", filter, smartpl_query);
-  if (!count)
-    {
-      DPRINTF(E_LOG, L_DB, "Out of memory for count query string\n");
-      return -1;
-    }
-
-  qp->results = db_get_count(count);
+  char *query;
 
-  sqlite3_free(count);
+  qc = db_build_query_clause(qp);
+  if (!qc)
+    return NULL;
 
-  if (qp->results < 0)
-    return -1;
+  count = sqlite3_mprintf("SELECT COUNT(*) FROM files f JOIN playlistitems pi ON f.path = pi.filepath %s AND pi.playlistid = %d;", qc->where, qp->id);
+  query = sqlite3_mprintf("SELECT f.* FROM files f JOIN playlistitems pi ON f.path = pi.filepath %s AND pi.playlistid = %d ORDER BY pi.id ASC %s;", qc->where, qp->id, qc->index);
 
-  /* Get index clause */
-  ret = db_build_query_index_clause(&idx, qp);
-  if (ret < 0)
-    return -1;
+  db_free_query_clause(qc);
 
-  sort = sort_clause[qp->sort];
+  return db_build_query_check(qp, count, query);
+}
 
-  if (idx)
-    query = sqlite3_mprintf("SELECT f.* FROM files f WHERE f.disabled = 0 AND %s AND %s %s %s;", smartpl_query, filter, sort, idx);
-  else
-    query = sqlite3_mprintf("SELECT f.* FROM files f WHERE f.disabled = 0 AND %s AND %s %s;", smartpl_query, filter, sort);
+static char *
+db_build_query_plitems_smart(struct query_params *qp, char *smartpl_query)
+{
+  struct query_clause *qc;
+  char *count;
+  char *query;
 
-  if (idx)
-    sqlite3_free(idx);
+  qc = db_build_query_clause(qp);
+  if (!qc)
+    return NULL;
 
-  if (!query)
-    {
-      DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
-      return -1;
-    }
+  count = sqlite3_mprintf("SELECT COUNT(*) FROM files f %s AND %s;", qc->where, smartpl_query);
+  query = sqlite3_mprintf("SELECT f.* FROM files f %s AND %s %s %s;", qc->where, smartpl_query, qc->order, qc->index);
 
-  *q = query;
+  db_free_query_clause(qc);
 
-  return 0;
+  return db_build_query_check(qp, count, query);
 }
 
-static int
-db_build_query_plitems(struct query_params *qp, char **q)
+static char *
+db_build_query_plitems(struct query_params *qp)
 {
   struct playlist_info *pli;
-  int ret;
+  char *query;
 
   if (qp->id <= 0)
-    {
-      DPRINTF(E_LOG, L_DB, "No playlist id specified in playlist items query\n");
-      return -1;
-    }
+  {
+    DPRINTF(E_LOG, L_DB, "No playlist id specified in playlist items query\n");
+    return NULL;
+  }
 
   pli = db_pl_fetch_byid(qp->id);
   if (!pli)
-    return -1;
+    return NULL;
 
   switch (pli->type)
     {
       case PL_SPECIAL:
       case PL_SMART:
-	ret = db_build_query_plitems_smart(qp, pli->query, q);
+	query = db_build_query_plitems_smart(qp, pli->query);
 	break;
 
       case PL_PLAIN:
       case PL_FOLDER:
-	ret = db_build_query_plitems_plain(qp, q);
+	query = db_build_query_plitems_plain(qp);
 	break;
 
       default:
 	DPRINTF(E_LOG, L_DB, "Unknown playlist type %d in playlist items query\n", pli->type);
-	ret = -1;
+	query = NULL;
 	break;
     }
 
   free_pli(pli, 0);
 
-  return ret;
+  return query;
 }
 
-static int
-db_build_query_group_albums(struct query_params *qp, char **q)
+static char *
+db_build_query_group_albums(struct query_params *qp)
 {
+  struct query_clause *qc;
+  char *count;
   char *query;
-  char *idx;
-  const char *sort;
-  int ret;
-
-  qp->results = db_get_count("SELECT COUNT(DISTINCT f.songalbumid) FROM files f WHERE f.disabled = 0;");
-  if (qp->results < 0)
-    return -1;
-
-  /* Get index clause */
-  ret = db_build_query_index_clause(&idx, qp);
-  if (ret < 0)
-    return -1;
-
-  sort = sort_clause[qp->sort];
-
-  if (idx && qp->filter)
-    query = sqlite3_mprintf("SELECT g.id, g.persistentid, f.album, f.album_sort, COUNT(f.id), 1, f.album_artist, f.songartistid, SUM(f.song_length) FROM files f JOIN groups g ON f.songalbumid = g.persistentid WHERE f.disabled = 0 AND %s GROUP BY f.songalbumid %s %s;", qp->filter, sort, idx);
-  else if (idx)
-    query = sqlite3_mprintf("SELECT g.id, g.persistentid, f.album, f.album_sort, COUNT(f.id), 1, f.album_artist, f.songartistid, SUM(f.song_length) FROM files f JOIN groups g ON f.songalbumid = g.persistentid WHERE f.disabled = 0 GROUP BY f.songalbumid %s %s;", sort, idx);
-  else if (qp->filter)
-    query = sqlite3_mprintf("SELECT g.id, g.persistentid, f.album, f.album_sort, COUNT(f.id), 1, f.album_artist, f.songartistid, SUM(f.song_length) FROM files f JOIN groups g ON f.songalbumid = g.persistentid WHERE f.disabled = 0 AND %s GROUP BY f.songalbumid %s;", qp->filter, sort);
-  else
-    query = sqlite3_mprintf("SELECT g.id, g.persistentid, f.album, f.album_sort, COUNT(f.id), 1, f.album_artist, f.songartistid, SUM(f.song_length) FROM files f JOIN groups g ON f.songalbumid = g.persistentid WHERE f.disabled = 0 GROUP BY f.songalbumid %s;", sort);
 
-  if (idx)
-    sqlite3_free(idx);
+  qc = db_build_query_clause(qp);
+  if (!qc)
+    return NULL;
 
-  if (!query)
-    {
-      DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
-      return -1;
-    }
+  count = sqlite3_mprintf("SELECT COUNT(DISTINCT f.songalbumid) FROM files f WHERE f.disabled = 0;");
+  query = sqlite3_mprintf("SELECT g.id, g.persistentid, f.album, f.album_sort, COUNT(f.id), 1, f.album_artist, f.songartistid, SUM(f.song_length) FROM files f JOIN groups g ON f.songalbumid = g.persistentid %s GROUP BY f.songalbumid %s %s;", qc->where, qc->order, qc->index);
 
-  *q = query;
+  db_free_query_clause(qc);
 
-  return 0;
+  return db_build_query_check(qp, count, query);
 }
 
-static int
-db_build_query_group_artists(struct query_params *qp, char **q)
+static char *
+db_build_query_group_artists(struct query_params *qp)
 {
+  struct query_clause *qc;
+  char *count;
   char *query;
-  char *idx;
-  const char *sort;
-  int ret;
-
-  qp->results = db_get_count("SELECT COUNT(DISTINCT f.songartistid) FROM files f WHERE f.disabled = 0;");
-  if (qp->results < 0)
-    return -1;
-
-  /* Get index clause */
-  ret = db_build_query_index_clause(&idx, qp);
-  if (ret < 0)
-    return -1;
-
-  sort = sort_clause[qp->sort];
-
-  if (idx && qp->filter)
-    query = sqlite3_mprintf("SELECT g.id, g.persistentid, f.album_artist, f.album_artist_sort, COUNT(f.id), COUNT(DISTINCT f.songalbumid), f.album_artist, f.songartistid, SUM(f.song_length) FROM files f JOIN groups g ON f.songartistid = g.persistentid WHERE f.disabled = 0 AND %s GROUP BY f.songartistid %s %s;", qp->filter, sort, idx);
-  else if (idx)
-    query = sqlite3_mprintf("SELECT g.id, g.persistentid, f.album_artist, f.album_artist_sort, COUNT(f.id), COUNT(DISTINCT f.songalbumid), f.album_artist, f.songartistid, SUM(f.song_length) FROM files f JOIN groups g ON f.songartistid = g.persistentid WHERE f.disabled = 0 GROUP BY f.songartistid %s %s;", sort, idx);
-  else if (qp->filter)
-    query = sqlite3_mprintf("SELECT g.id, g.persistentid, f.album_artist, f.album_artist_sort, COUNT(f.id), COUNT(DISTINCT f.songalbumid), f.album_artist, f.songartistid, SUM(f.song_length) FROM files f JOIN groups g ON f.songartistid = g.persistentid WHERE f.disabled = 0 AND %s GROUP BY f.songartistid %s;", qp->filter, sort);
-  else
-    query = sqlite3_mprintf("SELECT g.id, g.persistentid, f.album_artist, f.album_artist_sort, COUNT(f.id), COUNT(DISTINCT f.songalbumid), f.album_artist, f.songartistid, SUM(f.song_length) FROM files f JOIN groups g ON f.songartistid = g.persistentid WHERE f.disabled = 0 GROUP BY f.songartistid %s;", sort);
 
-  if (idx)
-    sqlite3_free(idx);
+  qc = db_build_query_clause(qp);
+  if (!qc)
+    return NULL;
 
-  if (!query)
-    {
-      DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
-      return -1;
-    }
+  count = sqlite3_mprintf("SELECT COUNT(DISTINCT f.songartistid) FROM files f %s;", qc->where);
+  query = sqlite3_mprintf("SELECT g.id, g.persistentid, f.album_artist, f.album_artist_sort, COUNT(f.id), COUNT(DISTINCT f.songalbumid), f.album_artist, f.songartistid, SUM(f.song_length) FROM files f JOIN groups g ON f.songartistid = g.persistentid %s GROUP BY f.songartistid %s %s;", qc->where, qc->order, qc->index);
 
-  *q = query;
+  db_free_query_clause(qc);
 
-  return 0;
+  return db_build_query_check(qp, count, query);
 }
 
-static int
-db_build_query_group_items(struct query_params *qp, char **q)
+static char *
+db_build_query_group_items(struct query_params *qp)
 {
-  char *query;
-  char *count;
   enum group_type gt;
+  struct query_clause *qc;
+  char *count;
+  char *query;
+
+  qc = db_build_query_clause(qp);
+  if (!qc)
+    return NULL;
 
   gt = db_group_type_bypersistentid(qp->persistentid);
 
   switch (gt)
     {
       case G_ALBUMS:
-	count = sqlite3_mprintf("SELECT COUNT(*) FROM files f"
-				" WHERE f.songalbumid = %" PRIi64 " AND f.disabled = 0;", qp->persistentid);
+	count = sqlite3_mprintf("SELECT COUNT(*) FROM files f %s AND f.songalbumid = %" PRIi64 ";", qc->where, qp->persistentid);
+	query = sqlite3_mprintf("SELECT f.* FROM files f %s AND f.songalbumid = %" PRIi64 " %s %s;", qc->where, qp->persistentid, qc->order, qc->index);
 	break;
 
       case G_ARTISTS:
-	count = sqlite3_mprintf("SELECT COUNT(*) FROM files f"
-				" WHERE f.songartistid = %" PRIi64 " AND f.disabled = 0;", qp->persistentid);
+	count = sqlite3_mprintf("SELECT COUNT(*) FROM files f %s AND f.songartistid = %" PRIi64 ";", qc->where, qp->persistentid);
+	query = sqlite3_mprintf("SELECT f.* FROM files f %s AND f.songartistid = %" PRIi64 " %s %s;", qc->where, qp->persistentid, qc->order, qc->index);
 	break;
 
       default:
 	DPRINTF(E_LOG, L_DB, "Unsupported group type %d for group id %" PRIi64 "\n", gt, qp->persistentid);
-	return -1;
-    }
-
-  if (!count)
-    {
-      DPRINTF(E_LOG, L_DB, "Out of memory for count query string\n");
-
-      return -1;
-    }
-
-  qp->results = db_get_count(count);
-  sqlite3_free(count);
-
-  if (qp->results < 0)
-    return -1;
-
-  switch (gt)
-    {
-      case G_ALBUMS:
-	query = sqlite3_mprintf("SELECT f.* FROM files f"
-				" WHERE f.songalbumid = %" PRIi64 " AND f.disabled = 0;", qp->persistentid);
-	break;
-
-      case G_ARTISTS:
-	query = sqlite3_mprintf("SELECT f.* FROM files f"
-				" WHERE f.songartistid = %" PRIi64 " AND f.disabled = 0;", qp->persistentid);
-	break;
-
-      default:
-	return -1;
-    }
-
-  if (!query)
-    {
-      DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
-      return -1;
+        db_free_query_clause(qc);
+	return NULL;
     }
 
-  *q = query;
+  db_free_query_clause(qc);
 
-  return 0;
+  return db_build_query_check(qp, count, query);
 }
 
-static int
-db_build_query_group_dirs(struct query_params *qp, char **q)
+static char *
+db_build_query_group_dirs(struct query_params *qp)
 {
-  char *query;
-  char *count;
   enum group_type gt;
+  struct query_clause *qc;
+  char *count;
+  char *query;
+
+  qc = db_build_query_clause(qp);
+  if (!qc)
+    return NULL;
 
   gt = db_group_type_bypersistentid(qp->persistentid);
 
@@ -1354,147 +1218,67 @@ db_build_query_group_dirs(struct query_params *qp, char **q)
     {
       case G_ALBUMS:
 	count = sqlite3_mprintf("SELECT COUNT(DISTINCT(SUBSTR(f.path, 1, LENGTH(f.path) - LENGTH(f.fname) - 1)))"
-				" FROM files f"
-				" WHERE f.songalbumid = %" PRIi64 " AND f.disabled = 0;", qp->persistentid);
-	break;
-
-      case G_ARTISTS:
-	count = sqlite3_mprintf("SELECT COUNT(DISTINCT(SUBSTR(f.path, 1, LENGTH(f.path) - LENGTH(f.fname) - 1)))"
-				" FROM files f"
-				" WHERE f.songartistid = %" PRIi64 " AND f.disabled = 0;", qp->persistentid);
-	break;
-
-      default:
-	DPRINTF(E_LOG, L_DB, "Unsupported group type %d for group id %" PRIi64 "\n", gt, qp->persistentid);
-	return -1;
-    }
-
-  if (!count)
-    {
-      DPRINTF(E_LOG, L_DB, "Out of memory for count query string\n");
-
-      return -1;
-    }
-
-  qp->results = db_get_count(count);
-  sqlite3_free(count);
-
-  if (qp->results < 0)
-    return -1;
-
-  switch (gt)
-    {
-      case G_ALBUMS:
+				" FROM files f %s AND f.songalbumid = %" PRIi64 ";", qc->where, qp->persistentid);
 	query = sqlite3_mprintf("SELECT DISTINCT(SUBSTR(f.path, 1, LENGTH(f.path) - LENGTH(f.fname) - 1))"
-				" FROM files f"
-				" WHERE f.songalbumid = %" PRIi64 " AND f.disabled = 0;", qp->persistentid);
+				" FROM files f %s AND f.songalbumid = %" PRIi64 " %s %s;", qc->where, qp->persistentid, qc->order, qc->index);
 	break;
 
       case G_ARTISTS:
+	count = sqlite3_mprintf("SELECT COUNT(DISTINCT(SUBSTR(f.path, 1, LENGTH(f.path) - LENGTH(f.fname) - 1)))"
+				" FROM files f %s AND f.songartistid = %" PRIi64 ";", qc->where, qp->persistentid);
 	query = sqlite3_mprintf("SELECT DISTINCT(SUBSTR(f.path, 1, LENGTH(f.path) - LENGTH(f.fname) - 1))"
-				" FROM files f"
-				" WHERE f.songartistid = %" PRIi64 " AND f.disabled = 0;", qp->persistentid);
+				" FROM files f %s AND f.songartistid = %" PRIi64 " %s %s;", qc->where, qp->persistentid, qc->order, qc->index);
 	break;
 
       default:
-	return -1;
-    }
-
-  if (!query)
-    {
-      DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
-      return -1;
+	DPRINTF(E_LOG, L_DB, "Unsupported group type %d for group id %" PRIi64 "\n", gt, qp->persistentid);
+        db_free_query_clause(qc);
+	return NULL;
     }
 
-  *q = query;
+  db_free_query_clause(qc);
 
-  return 0;
+  return db_build_query_check(qp, count, query);
 }
 
-static int
-db_build_query_browse(struct query_params *qp, const char *field, const char *group_field, char **q)
+static char *
+db_build_query_browse(struct query_params *qp, const char *field, const char *group_field)
 {
-  char *query;
+  struct query_clause *qc;
   char *count;
-  char *idx;
-  const char *sort;
-  int ret;
-
-  if (qp->filter)
-    count = sqlite3_mprintf("SELECT COUNT(DISTINCT f.%s) FROM files f WHERE f.disabled = 0 AND f.%s != '' AND %s;",
-			    field, field, qp->filter);
-  else
-    count = sqlite3_mprintf("SELECT COUNT(DISTINCT f.%s) FROM files f WHERE f.disabled = 0 AND f.%s != '';",
-			    field, field);
-
-  if (!count)
-    {
-      DPRINTF(E_LOG, L_DB, "Out of memory for count query string\n");
-
-      return -1;
-    }
-
-  qp->results = db_get_count(count);
-  sqlite3_free(count);
-
-  if (qp->results < 0)
-    return -1;
-
-  /* Get index clause */
-  ret = db_build_query_index_clause(&idx, qp);
-  if (ret < 0)
-    return -1;
-
-  sort = sort_clause[qp->sort];
-
-  if (idx && qp->filter)
-    query = sqlite3_mprintf("SELECT f.%s, f.%s FROM files f WHERE f.disabled = 0 AND f.%s != ''"
-                            " AND %s GROUP BY f.%s %s %s;", field, group_field, field, qp->filter, group_field, sort, idx);
-  else if (idx)
-    query = sqlite3_mprintf("SELECT f.%s, f.%s FROM files f WHERE f.disabled = 0 AND f.%s != ''"
-                            " GROUP BY f.%s %s %s;", field, group_field, field, group_field, sort, idx);
-  else if (qp->filter)
-    query = sqlite3_mprintf("SELECT f.%s, f.%s FROM files f WHERE f.disabled = 0 AND f.%s != ''"
-                            " AND %s GROUP BY f.%s %s;", field, group_field, field, qp->filter, group_field, sort);
-  else
-    query = sqlite3_mprintf("SELECT f.%s, f.%s FROM files f WHERE f.disabled = 0 AND f.%s != ''"
-                            " GROUP BY f.%s %s", field, group_field, field, group_field, sort);
+  char *query;
 
-  if (idx)
-    sqlite3_free(idx);
+  qc = db_build_query_clause(qp);
+  if (!qc)
+    return NULL;
 
-  if (!query)
-    {
-      DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
-      return -1;
-    }
+  count = sqlite3_mprintf("SELECT COUNT(DISTINCT f.%s) FROM files f %s AND f.%s != '';", field, qc->where, field);
+  query = sqlite3_mprintf("SELECT f.%s, f.%s FROM files f %s AND f.%s != '' GROUP BY f.%s %s %s;", field, group_field, qc->where, field, group_field, qc->order, qc->index);
 
-  *q = query;
+  db_free_query_clause(qc);
 
-  return 0;
+  return db_build_query_check(qp, count, query);
 }
 
-static int
-db_build_query_count_items(struct query_params *qp, char **q)
+static char *
+db_build_query_count_items(struct query_params *qp)
 {
+  struct query_clause *qc;
   char *query;
 
-  qp->results = 1;
+  qc = db_build_query_clause(qp);
+  if (!qc)
+    return NULL;
 
-  if (qp->filter)
-    query = sqlite3_mprintf("SELECT COUNT(*), SUM(song_length) FROM files f WHERE f.disabled = 0 AND %s;", qp->filter);
-  else
-    query = sqlite3_mprintf("SELECT COUNT(*), SUM(song_length) FROM files f WHERE f.disabled = 0;");
+  qp->results = 1;
 
+  query = sqlite3_mprintf("SELECT COUNT(*), SUM(song_length) FROM files f %s;", qc->where);
   if (!query)
-    {
-      DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
-      return -1;
-    }
+    DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
 
-  *q = query;
+  db_free_query_clause(qc);
 
-  return 0;
+  return query;
 }
 
 int
@@ -1508,71 +1292,75 @@ db_query_start(struct query_params *qp)
   switch (qp->type)
     {
       case Q_ITEMS:
-	ret = db_build_query_items(qp, &query);
+	query = db_build_query_items(qp);
 	break;
 
       case Q_PL:
-	ret = db_build_query_pls(qp, &query);
+	query = db_build_query_pls(qp);
+	break;
+
+      case Q_FIND_PL:
+	query = db_build_query_find_pls(qp);
 	break;
 
       case Q_PLITEMS:
-	ret = db_build_query_plitems(qp, &query);
+	query = db_build_query_plitems(qp);
 	break;
 
       case Q_GROUP_ALBUMS:
-	ret = db_build_query_group_albums(qp, &query);
+	query = db_build_query_group_albums(qp);
 	break;
 
       case Q_GROUP_ARTISTS:
-	ret = db_build_query_group_artists(qp, &query);
+	query = db_build_query_group_artists(qp);
 	break;
 
       case Q_GROUP_ITEMS:
-	ret = db_build_query_group_items(qp, &query);
+	query = db_build_query_group_items(qp);
 	break;
 
       case Q_GROUP_DIRS:
-	ret = db_build_query_group_dirs(qp, &query);
+	query = db_build_query_group_dirs(qp);
 	break;
 
       case Q_BROWSE_ALBUMS:
-	ret = db_build_query_browse(qp, "album", "album_sort", &query);
+	query = db_build_query_browse(qp, "album", "album_sort");
 	break;
 
       case Q_BROWSE_ARTISTS:
-	ret = db_build_query_browse(qp, "album_artist", "album_artist_sort", &query);
+	query = db_build_query_browse(qp, "album_artist", "album_artist_sort");
 	break;
 
       case Q_BROWSE_GENRES:
-	ret = db_build_query_browse(qp, "genre", "genre", &query);
+	query = db_build_query_browse(qp, "genre", "genre");
 	break;
 
       case Q_BROWSE_COMPOSERS:
-	ret = db_build_query_browse(qp, "composer", "composer_sort", &query);
+	query = db_build_query_browse(qp, "composer", "composer_sort");
 	break;
 
       case Q_BROWSE_YEARS:
-	ret = db_build_query_browse(qp, "year", "year", &query);
+	query = db_build_query_browse(qp, "year", "year");
 	break;
 
       case Q_BROWSE_DISCS:
-	ret = db_build_query_browse(qp, "disc", "disc", &query);
+	query = db_build_query_browse(qp, "disc", "disc");
 	break;
 
       case Q_BROWSE_TRACKS:
-	ret = db_build_query_browse(qp, "track", "track", &query);
+	query = db_build_query_browse(qp, "track", "track");
 	break;
 
       case Q_BROWSE_VPATH:
-	ret = db_build_query_browse(qp, "virtual_path", "virtual_path", &query);
+	query = db_build_query_browse(qp, "virtual_path", "virtual_path");
 	break;
 
       case Q_BROWSE_PATH:
-	ret = db_build_query_browse(qp, "path", "path", &query);
+	query = db_build_query_browse(qp, "path", "path");
 	break;
 
       case Q_COUNT_ITEMS:
-	ret = db_build_query_count_items(qp, &query);
+	query = db_build_query_count_items(qp);
 	break;
 
       default:
@@ -1580,7 +1368,7 @@ db_query_start(struct query_params *qp)
 	return -1;
     }
 
-  if (ret < 0)
+  if (!query)
     return -1;
 
   DPRINTF(E_DBG, L_DB, "Starting query '%s'\n", query);
@@ -1611,8 +1399,15 @@ db_query_end(struct query_params *qp)
   qp->stmt = NULL;
 }
 
+/*
+ * Utility function for running write queries (INSERT, UPDATE, DELETE). If you
+ * set free to non-zero, the function will free the query. If you set
+ * library_update to non-zero it means that the update was not just of some
+ * internal value (like a timestamp), but of something that requires clients
+ * to update their cache of the library (and of course also of our own cache).
+ */
 static int
-db_query_run(char *query, int free, int cache_update)
+db_query_run(char *query, int free, int library_update)
 {
   char *errmsg;
   int ret;
@@ -1638,10 +1433,10 @@ db_query_run(char *query, int free, int cache_update)
   if (free)
     sqlite3_free(query);
 
-  if (cache_update)
-    cache_daap_trigger();
-  else
-    cache_daap_resume();
+  cache_daap_resume();
+
+  if (library_update)
+    library_update_trigger();
 
   return ((ret != SQLITE_OK) ? -1 : 0);
 }
@@ -1719,7 +1514,7 @@ db_query_fetch_pl(struct query_params *qp, struct db_playlist_info *dbpli, int w
       return -1;
     }
 
-  if (qp->type != Q_PL)
+  if ((qp->type != Q_PL) && (qp->type != Q_FIND_PL))
     {
       DPRINTF(E_LOG, L_DB, "Not a playlist query!\n");
       return -1;
@@ -1882,7 +1677,7 @@ db_query_fetch_count(struct query_params *qp, struct filecount_info *fci)
     }
 
   fci->count = sqlite3_column_int(qp->stmt, 0);
-  fci->length = sqlite3_column_int(qp->stmt, 1);
+  fci->length = sqlite3_column_int64(qp->stmt, 1);
 
   return 0;
 }
@@ -1967,19 +1762,19 @@ db_query_fetch_string_sort(struct query_params *qp, char **string, char **sortst
 int
 db_files_get_count(void)
 {
-  return db_get_count("SELECT COUNT(*) FROM files f WHERE f.disabled = 0;");
+  return db_get_one_int("SELECT COUNT(*) FROM files f WHERE f.disabled = 0;");
 }
 
 int
 db_files_get_artist_count(void)
 {
-  return db_get_count("SELECT COUNT(DISTINCT songartistid) FROM files f WHERE f.disabled = 0;");
+  return db_get_one_int("SELECT COUNT(DISTINCT songartistid) FROM files f WHERE f.disabled = 0;");
 }
 
 int
 db_files_get_album_count(void)
 {
-  return db_get_count("SELECT COUNT(DISTINCT songalbumid) FROM files f WHERE f.disabled = 0;");
+  return db_get_one_int("SELECT COUNT(DISTINCT songalbumid) FROM files f WHERE f.disabled = 0;");
 }
 
 int
@@ -1997,7 +1792,7 @@ db_files_get_count_bymatch(char *path)
       return -1;
     }
 
-  count = db_get_count(query);
+  count = db_get_one_int(query);
   sqlite3_free(query);
 
   return count;
@@ -2005,18 +1800,6 @@ db_files_get_count_bymatch(char *path)
 }
 
 void
-db_files_update_songartistid(void)
-{
-  db_query_run("UPDATE files SET songartistid = daap_songalbumid(LOWER(album_artist), '');", 0, 1);
-}
-
-void
-db_files_update_songalbumid(void)
-{
-  db_query_run("UPDATE files SET songalbumid = daap_songalbumid(LOWER(album_artist), LOWER(album));", 0, 1);
-}
-
-void
 db_file_inc_playcount(int id)
 {
 #define Q_TMPL "UPDATE files SET play_count = play_count + 1, time_played = %" PRIi64 ", seek = 0 WHERE id = %d;"
@@ -2042,7 +1825,7 @@ db_file_ping(int id)
 
   query = sqlite3_mprintf(Q_TMPL, (int64_t)time(NULL), id);
 
-  db_query_run(query, 1, 1);
+  db_query_run(query, 1, 0);
 #undef Q_TMPL
 }
 
@@ -2058,7 +1841,7 @@ db_file_ping_bymatch(char *path, int isdir)
   else
     query = sqlite3_mprintf(Q_TMPL_NODIR, (int64_t)time(NULL), path);
 
-  db_query_run(query, 1, 1);
+  db_query_run(query, 1, 0);
 #undef Q_TMPL_DIR
 #undef Q_TMPL_NODIR
 }
@@ -2212,31 +1995,6 @@ db_file_id_bymatch(char *path)
 #undef Q_TMPL
 }
 
-//TODO [cleanup] unused function(?)
-int
-db_file_id_byfilebase(char *filename, char *base)
-{
-#define Q_TMPL "SELECT f.id FROM files f WHERE f.path LIKE '%q/%%/%q';"
-  char *query;
-  int ret;
-
-  query = sqlite3_mprintf(Q_TMPL, base, filename);
-  if (!query)
-    {
-      DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
-
-      return 0;
-    }
-
-  ret = db_file_id_byquery(query);
-
-  sqlite3_free(query);
-
-  return ret;
-
-#undef Q_TMPL
-}
-
 int
 db_file_id_byfile(char *filename)
 {
@@ -2310,13 +2068,14 @@ db_file_id_by_virtualpath_match(char *path)
 }
 
 void
-db_file_stamp_bypath(char *path, time_t *stamp, int *id)
+db_file_stamp_bypath(const char *path, time_t *stamp, int *id)
 {
 #define Q_TMPL "SELECT f.id, f.db_timestamp FROM files f WHERE f.path = '%q';"
   char *query;
   sqlite3_stmt *stmt;
   int ret;
 
+  *id = 0;
   *stamp = 0;
 
   query = sqlite3_mprintf(Q_TMPL, path);
@@ -2605,7 +2364,7 @@ db_file_add(struct media_file_info *mfi)
 
   sqlite3_free(query);
 
-  cache_daap_trigger();
+  library_update_trigger();
 
   return 0;
 
@@ -2684,7 +2443,7 @@ db_file_update(struct media_file_info *mfi)
 
   sqlite3_free(query);
 
-  cache_daap_trigger();
+  library_update_trigger();
 
   return 0;
 
@@ -2692,28 +2451,7 @@ db_file_update(struct media_file_info *mfi)
 }
 
 void
-db_file_update_icy(int id, char *artist, char *album)
-{
-#define Q_TMPL "UPDATE files SET artist = TRIM(%Q), album = TRIM(%Q) WHERE id = %d;"
-  char *query;
-
-  if (id == 0)
-    return;
-
-  query = sqlite3_mprintf(Q_TMPL, artist, album, id);
-  if (!query)
-    {
-      DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
-
-      return;
-    }
-
-  db_query_run(query, 1, 0);
-#undef Q_TMPL
-}
-
-void
-db_file_save_seek(int id, uint32_t seek)
+db_file_seek_update(int id, uint32_t seek)
 {
 #define Q_TMPL "UPDATE files SET seek = %d WHERE id = %d;"
   char *query;
@@ -2824,7 +2562,7 @@ db_file_update_directoryid(char *path, int dir_id)
 int
 db_pl_get_count(void)
 {
-  return db_get_count("SELECT COUNT(*) FROM playlists p WHERE p.disabled = 0;");
+  return db_get_one_int("SELECT COUNT(*) FROM playlists p WHERE p.disabled = 0;");
 }
 
 static int
@@ -2848,7 +2586,7 @@ db_pl_count_items(int id, int streams_only)
       return 0;
     }
 
-  ret = db_get_count(query);
+  ret = db_get_one_int(query);
 
   sqlite3_free(query);
 
@@ -2873,7 +2611,7 @@ db_smartpl_count_items(const char *smartpl_query)
       return 0;
     }
 
-  ret = db_get_count(query);
+  ret = db_get_one_int(query);
 
   sqlite3_free(query);
 
@@ -3093,7 +2831,7 @@ db_pl_fetch_byquery(char *query)
 }
 
 struct playlist_info *
-db_pl_fetch_bypath(char *path)
+db_pl_fetch_bypath(const char *path)
 {
 #define Q_TMPL "SELECT p.* FROM playlists p WHERE p.path = '%q';"
   struct playlist_info *pli;
@@ -3206,7 +2944,7 @@ db_pl_add(struct playlist_info *pli, int *id)
       return -1;
     }
 
-  ret = db_get_count(query);
+  ret = db_get_one_int(query);
 
   sqlite3_free(query);
 
@@ -3257,7 +2995,7 @@ db_pl_add(struct playlist_info *pli, int *id)
 }
 
 int
-db_pl_add_item_bypath(int plid, char *path)
+db_pl_add_item_bypath(int plid, const char *path)
 {
 #define Q_TMPL "INSERT INTO playlistitems (playlistid, filepath) VALUES (%d, '%q');"
   char *query;
@@ -3405,10 +3143,40 @@ db_pl_enable_bycookie(uint32_t cookie, char *path)
 
 
 /* Groups */
+
+// Remove album and artist entries in the groups table that are not longer referenced from the files table
 int
-db_groups_clear(void)
+db_groups_cleanup()
 {
-  return db_query_run("DELETE FROM groups;", 0, 1);
+#define Q_TMPL_ALBUM "DELETE FROM groups WHERE type = 1 AND NOT persistentid IN (SELECT songalbumid from files WHERE disabled = 0);"
+#define Q_TMPL_ARTIST "DELETE FROM groups WHERE type = 2 AND NOT persistentid IN (SELECT songartistid from files WHERE disabled = 0);"
+  int ret;
+
+  db_transaction_begin();
+
+  ret = db_query_run(Q_TMPL_ALBUM, 0, 1);
+  if (ret < 0)
+    {
+      db_transaction_rollback();
+      return -1;
+    }
+
+  DPRINTF(E_DBG, L_DB, "Removed album group-entries: %d\n", sqlite3_changes(hdl));
+
+  ret = db_query_run(Q_TMPL_ARTIST, 0, 1);
+  if (ret < 0)
+    {
+      db_transaction_rollback();
+      return -1;
+    }
+
+  DPRINTF(E_DBG, L_DB, "Removed artist group-entries: %d\n", sqlite3_changes(hdl));
+  db_transaction_end();
+
+  return 0;
+
+#undef Q_TMPL_ALBUM
+#undef Q_TMPL_ARTIST
 }
 
 static enum group_type
@@ -3744,14 +3512,14 @@ db_directory_addorupdate(char *virtual_path, int disabled, int parent_id)
 }
 
 void
-db_directory_ping_bymatch(char *path)
+db_directory_ping_bymatch(char *virtual_path)
 {
-#define Q_TMPL_DIR "UPDATE directories SET db_timestamp = %" PRIi64 " WHERE virtual_path = '/file:%q' OR virtual_path LIKE '/file:%q/%%';"
+#define Q_TMPL_DIR "UPDATE directories SET db_timestamp = %" PRIi64 " WHERE virtual_path = '%q' OR virtual_path LIKE '%q/%%';"
   char *query;
 
-  query = sqlite3_mprintf(Q_TMPL_DIR, (int64_t)time(NULL), path, path);
+  query = sqlite3_mprintf(Q_TMPL_DIR, (int64_t)time(NULL), virtual_path, virtual_path);
 
-  db_query_run(query, 1, 1);
+  db_query_run(query, 1, 0);
 #undef Q_TMPL_DIR
 }
 
@@ -3784,7 +3552,7 @@ db_directory_enable_bycookie(uint32_t cookie, char *path)
 
   query = sqlite3_mprintf(Q_TMPL, path, (int64_t)cookie);
 
-  ret = db_query_run(query, 1, 0);
+  ret = db_query_run(query, 1, 1);
 
   return ((ret < 0) ? -1 : sqlite3_changes(hdl));
 #undef Q_TMPL
@@ -3799,7 +3567,7 @@ db_directory_enable_bypath(char *path)
 
   query = sqlite3_mprintf(Q_TMPL, path);
 
-  ret = db_query_run(query, 1, 0);
+  ret = db_query_run(query, 1, 1);
 
   return ((ret < 0) ? -1 : sqlite3_changes(hdl));
 #undef Q_TMPL
@@ -3894,238 +3662,1770 @@ db_pairing_fetch_byguid(struct pairing_info *pi)
 void
 db_spotify_purge(void)
 {
-  char *queries[5] =
+#define Q_TMPL "UPDATE directories SET disabled = %" PRIi64 " WHERE virtual_path = '/spotify:';"
+  char *queries[4] =
     {
       "DELETE FROM files WHERE path LIKE 'spotify:%%';",
       "DELETE FROM playlistitems WHERE filepath LIKE 'spotify:%%';",
       "DELETE FROM playlists WHERE path LIKE 'spotify:%%';",
       "DELETE FROM directories WHERE virtual_path LIKE '/spotify:/%%';",
-      "UPDATE directories SET disabled = 4294967296 WHERE virtual_path = '/spotify:';",
     };
+  char *query;
   int i;
   int ret;
 
-  for (i = 0; i < (sizeof(queries) / sizeof(queries[0])); i++)
+  for (i = 0; i < (sizeof(queries) / sizeof(queries[0])); i++)
+    {
+      ret = db_query_run(queries[i], 0, 1);
+
+      if (ret == 0)
+	DPRINTF(E_DBG, L_DB, "Processed %d rows\n", sqlite3_changes(hdl));
+    }
+
+  // Disable the spotify directory by setting 'disabled' to INOTIFY_FAKE_COOKIE value
+  query = sqlite3_mprintf(Q_TMPL, INOTIFY_FAKE_COOKIE);
+  if (!query)
+    {
+      DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
+      return;
+    }
+  ret = db_query_run(query, 1, 1);
+
+  if (ret == 0)
+    DPRINTF(E_DBG, L_DB, "Disabled spotify directory\n");
+
+#undef Q_TMPL
+}
+
+/* Spotify */
+void
+db_spotify_pl_delete(int id)
+{
+  char *queries_tmpl[2] =
+    {
+      "DELETE FROM playlists WHERE id = %d;",
+      "DELETE FROM playlistitems WHERE playlistid = %d;",
+    };
+  char *query;
+  int i;
+  int ret;
+
+  for (i = 0; i < (sizeof(queries_tmpl) / sizeof(queries_tmpl[0])); i++)
+    {
+      query = sqlite3_mprintf(queries_tmpl[i], id);
+
+      ret = db_query_run(query, 1, 1);
+
+      if (ret == 0)
+	DPRINTF(E_DBG, L_DB, "Deleted %d rows\n", sqlite3_changes(hdl));
+    }
+}
+
+/* Spotify */
+void
+db_spotify_files_delete(void)
+{
+#define Q_TMPL "DELETE FROM files WHERE path LIKE 'spotify:%%' AND NOT path IN (SELECT filepath FROM playlistitems);"
+  char *query;
+  int ret;
+
+  query = sqlite3_mprintf(Q_TMPL);
+
+  ret = db_query_run(query, 1, 1);
+
+  if (ret == 0)
+    DPRINTF(E_DBG, L_DB, "Deleted %d rows\n", sqlite3_changes(hdl));
+#undef Q_TMPL
+}
+#endif
+
+/* Admin */
+int
+db_admin_set(const char *key, const char *value)
+{
+#define Q_TMPL "INSERT OR REPLACE INTO admin (key, value) VALUES ('%q', '%q');"
+  char *query;
+
+  query = sqlite3_mprintf(Q_TMPL, key, value);
+
+  return db_query_run(query, 1, 0);
+#undef Q_TMPL
+}
+
+char *
+db_admin_get(const char *key)
+{
+#define Q_TMPL "SELECT value FROM admin a WHERE a.key = '%q';"
+  char *query;
+  sqlite3_stmt *stmt;
+  char *res;
+  int ret;
+
+  query = sqlite3_mprintf(Q_TMPL, key);
+  if (!query)
+    {
+      DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
+
+      return NULL;
+    }
+
+  DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query);
+
+  ret = db_blocking_prepare_v2(query, strlen(query) + 1, &stmt, NULL);
+  if (ret != SQLITE_OK)
+    {
+      DPRINTF(E_WARN, L_DB, "Could not prepare statement: %s\n", sqlite3_errmsg(hdl));
+
+      sqlite3_free(query);
+      return NULL;
+    }
+
+  ret = db_blocking_step(stmt);
+  if (ret != SQLITE_ROW)
+    {
+      if (ret == SQLITE_DONE)
+	DPRINTF(E_DBG, L_DB, "No results\n");
+      else
+	DPRINTF(E_WARN, L_DB, "Could not step: %s\n", sqlite3_errmsg(hdl));	
+
+      sqlite3_finalize(stmt);
+      sqlite3_free(query);
+      return NULL;
+    }
+
+  res = (char *)sqlite3_column_text(stmt, 0);
+  if (res)
+    res = strdup(res);
+
+#ifdef DB_PROFILE
+  while (db_blocking_step(stmt) == SQLITE_ROW)
+    ; /* EMPTY */
+#endif
+
+  sqlite3_finalize(stmt);
+  sqlite3_free(query);
+
+  return res;
+
+#undef Q_TMPL
+}
+
+int
+db_admin_delete(const char *key)
+{
+#define Q_TMPL "DELETE FROM admin where key='%q';"
+  char *query;
+
+  query = sqlite3_mprintf(Q_TMPL, key);
+
+  return db_query_run(query, 1, 0);
+#undef Q_TMPL
+}
+
+/* Speakers */
+int
+db_speaker_save(struct output_device *device)
+{
+#define Q_TMPL "INSERT OR REPLACE INTO speakers (id, selected, volume, name, auth_key) VALUES (%" PRIi64 ", %d, %d, %Q, %Q);"
+  char *query;
+
+  query = sqlite3_mprintf(Q_TMPL, device->id, device->selected, device->volume, device->name, device->auth_key);
+
+  return db_query_run(query, 1, 0);
+#undef Q_TMPL
+}
+
+int
+db_speaker_get(struct output_device *device, uint64_t id)
+{
+#define Q_TMPL "SELECT s.selected, s.volume, s.name, s.auth_key FROM speakers s WHERE s.id = %" PRIi64 ";"
+  sqlite3_stmt *stmt;
+  char *query;
+  int ret;
+
+  query = sqlite3_mprintf(Q_TMPL, id);
+  if (!query)
+    {
+      DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
+      return -1;
+    }
+
+  DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query);
+
+  ret = db_blocking_prepare_v2(query, -1, &stmt, NULL);
+  if (ret != SQLITE_OK)
+    {
+      DPRINTF(E_LOG, L_DB, "Could not prepare statement: %s\n", sqlite3_errmsg(hdl));
+      ret = -1;
+      goto out;
+    }
+
+  ret = db_blocking_step(stmt);
+  if (ret != SQLITE_ROW)
+    {
+      if (ret != SQLITE_DONE)
+	DPRINTF(E_LOG, L_DB, "Could not step: %s\n", sqlite3_errmsg(hdl));
+      sqlite3_finalize(stmt);
+      ret = -1;
+      goto out;
+    }
+
+  device->id = id;
+  device->selected = sqlite3_column_int(stmt, 0);
+  device->volume = sqlite3_column_int(stmt, 1);
+
+  free(device->name);
+  device->name = safe_strdup((char *)sqlite3_column_text(stmt, 2));
+
+  free(device->auth_key);
+  device->auth_key = safe_strdup((char *)sqlite3_column_text(stmt, 3));
+
+#ifdef DB_PROFILE
+  while (db_blocking_step(stmt) == SQLITE_ROW)
+    ; /* EMPTY */
+#endif
+
+  sqlite3_finalize(stmt);
+
+  ret = 0;
+
+ out:
+  sqlite3_free(query);
+  return ret;
+
+#undef Q_TMPL
+}
+
+void
+db_speaker_clear_all(void)
+{
+  db_query_run("UPDATE speakers SET selected = 0;", 0, 0);
+}
+
+/* Queue */
+
+void
+free_queue_item(struct db_queue_item *queue_item, int content_only)
+{
+  if (!queue_item)
+    return;
+
+  free(queue_item->path);
+  free(queue_item->virtual_path);
+  free(queue_item->title);
+  free(queue_item->artist);
+  free(queue_item->album_artist);
+  free(queue_item->album);
+  free(queue_item->genre);
+  free(queue_item->artist_sort);
+  free(queue_item->album_sort);
+  free(queue_item->album_artist_sort);
+  free(queue_item->artwork_url);
+
+  if (!content_only)
+    free(queue_item);
+  else
+    memset(queue_item, 0, sizeof(struct db_queue_item));
+}
+
+/*
+ * Returns the queue version from the admin table
+ *
+ * @return queue version
+ */
+int
+db_queue_get_version()
+{
+  char *version;
+  int32_t queue_version;
+  int ret;
+
+  queue_version = 0;
+  version = db_admin_get("queue_version");
+  if (version)
+    {
+      ret = safe_atoi32(version, &queue_version);
+      free(version);
+      if (ret < 0)
+	{
+	  DPRINTF(E_LOG, L_DB, "Could not get playlist version\n");
+	  return -1;
+	}
+    }
+
+  return queue_version;
+}
+
+/*
+ * Increments the version of the queue in the admin table and notifies listener of LISTENER_QUEUE
+ * about the change.
+ *
+ * This function must be called after successfully modifying the queue table in order to send
+ * notification messages to the clients (e. g. dacp or mpd clients).
+ */
+static void
+queue_inc_version_and_notify()
+{
+  int queue_version;
+  char version[10];
+  int ret;
+
+  db_transaction_begin();
+
+  queue_version = db_queue_get_version();
+  if (queue_version < 0)
+    queue_version = 0;
+
+  queue_version++;
+  ret = snprintf(version, sizeof(version), "%d", queue_version);
+  if (ret >= sizeof(version))
+    {
+      DPRINTF(E_LOG, L_DB, "Error incrementing queue version. Could not convert version to string: %d\n", queue_version);
+      db_transaction_rollback();
+      return;
+    }
+
+  ret = db_admin_set("queue_version", version);
+  if (ret < 0)
+    {
+      DPRINTF(E_LOG, L_DB, "Error incrementing queue version. Could not update version in admin table: %d\n", queue_version);
+      db_transaction_rollback();
+      return;
+    }
+
+  db_transaction_end();
+
+  listener_notify(LISTENER_QUEUE);
+}
+
+static int
+queue_add_file(struct db_media_file_info *dbmfi, int pos, int shuffle_pos)
+{
+#define Q_TMPL "INSERT INTO queue "							\
+		    "(id, file_id, song_length, data_kind, media_kind, "		\
+		    "pos, shuffle_pos, path, virtual_path, title, "			\
+		    "artist, album_artist, album, genre, songalbumid, "			\
+		    "time_modified, artist_sort, album_sort, album_artist_sort, year, "	\
+		    "track, disc)" 							\
+		"VALUES"                                           			\
+		    "(NULL, %s, %s, %s, %s, "						\
+		    "%d, %d, %Q, %Q, %Q, "						\
+		    "%Q, %Q, %Q, %Q, %s, "						\
+		    "%s, %Q, %Q, %Q, %s, "						\
+		    "%s, %s);"
+
+  char *query;
+  int ret;
+
+  query = sqlite3_mprintf(Q_TMPL,
+			  dbmfi->id, dbmfi->song_length, dbmfi->data_kind, dbmfi->media_kind,
+			  pos, pos, dbmfi->path, dbmfi->virtual_path, dbmfi->title,
+			  dbmfi->artist, dbmfi->album_artist, dbmfi->album, dbmfi->genre, dbmfi->songalbumid,
+			  dbmfi->time_modified, dbmfi->artist_sort, dbmfi->album_sort, dbmfi->album_artist_sort, dbmfi->year,
+			  dbmfi->track, dbmfi->disc);
+  ret = db_query_run(query, 1, 0);
+
+  return ret;
+
+#undef Q_TMPL
+}
+
+int
+db_queue_update_item(struct db_queue_item *qi)
+{
+#define Q_TMPL "UPDATE queue SET "							\
+		    "file_id = %d, song_length = %d, data_kind = %d, media_kind = %d, "	\
+		    "pos = %d, shuffle_pos = %d, path = '%q', virtual_path = %Q, "	\
+		    "title = %Q, artist = %Q, album_artist = %Q, album = %Q, "		\
+		    "genre = %Q, songalbumid = %" PRIi64 ", time_modified = %d, "	\
+		    "artist_sort = %Q, album_sort = %Q, album_artist_sort = %Q, "	\
+		    "year = %d, track = %d, disc = %d, artwork_url = %Q "		\
+		"WHERE id = %d;"
+  char *query;
+  int ret;
+
+  query = sqlite3_mprintf(Q_TMPL,
+			  qi->file_id, qi->song_length, qi->data_kind, qi->media_kind,
+			  qi->pos, qi->shuffle_pos, qi->path, qi->virtual_path,
+			  qi->title, qi->artist, qi->album_artist, qi->album,
+			  qi->genre, qi->songalbumid, qi->time_modified,
+			  qi->artist_sort, qi->album_sort, qi->album_artist_sort,
+			  qi->year, qi->track, qi->disc, qi->artwork_url,
+			  qi->id);
+
+  ret = db_query_run(query, 1, 0);
+
+  return ret;
+#undef Q_TMPL
+}
+
+/*
+ * Adds the files matching the given query to the queue after the item with the given item id
+ *
+ * The files table is queried with the given parameters and all found files are added after the
+ * item with the given item id to the "normal" queue. They are appended to end of the shuffled queue
+ * (assuming that the shuffled queue will get reshuffled after adding new items).
+ *
+ * The function returns -1 on failure (e. g. error reading from database) and if the given item id
+ * does not exist. It wraps all database access in a transaction and performs a rollback if an error
+ * occurs, leaving the queue in a consistent state.
+ *
+ * @param qp Query parameters for the files table
+ * @param item_id Files are added after item with this id
+ * @return 0 on success, -1 on failure
+ */
+int
+db_queue_add_by_queryafteritemid(struct query_params *qp, uint32_t item_id)
+{
+  struct db_media_file_info dbmfi;
+  char *query;
+  int shuffle_pos;
+  int pos;
+  int ret;
+
+  db_transaction_begin();
+
+  // Position of the first new item
+  pos = db_queue_get_pos(item_id, 0);
+  if (pos < 0)
+    {
+      DPRINTF(E_LOG, L_DB, "Could not fetch queue item for item-id %d\n", item_id);
+      db_transaction_rollback();
+      return -1;
+    }
+  pos++;
+
+  // Shuffle position of the first new item
+  shuffle_pos = db_queue_get_count();
+  if (shuffle_pos < 0)
+    {
+      DPRINTF(E_LOG, L_DB, "Could not get count from queue\n");
+      db_transaction_rollback();
+      return -1;
+    }
+
+  // Start query for new items from files table
+  ret = db_query_start(qp);
+  if (ret < 0)
+    {
+      DPRINTF(E_LOG, L_DB, "Could not start query\n");
+      db_transaction_rollback();
+      return -1;
+    }
+
+  DPRINTF(E_DBG, L_DB, "Player queue query returned %d items\n", qp->results);
+
+  // Update pos for all items after the item with item_id
+  query = sqlite3_mprintf("UPDATE queue SET pos = pos + %d WHERE pos > %d;", qp->results, (pos - 1));
+  ret = db_query_run(query, 1, 0);
+  if (ret < 0)
+    {
+      db_transaction_rollback();
+      return -1;
+    }
+
+  // Iterate over new items from files table and insert into queue
+  while (((ret = db_query_fetch_file(qp, &dbmfi)) == 0) && (dbmfi.id))
+    {
+      ret = queue_add_file(&dbmfi, pos, shuffle_pos);
+
+      if (ret < 0)
+	{
+	  DPRINTF(E_LOG, L_DB, "Failed to add song with id %s (%s) to queue\n", dbmfi.id, dbmfi.title);
+	  break;
+	}
+
+      DPRINTF(E_DBG, L_DB, "Added song with id %s (%s) to queue\n", dbmfi.id, dbmfi.title);
+      shuffle_pos++;
+      pos++;
+    }
+
+  db_query_end(qp);
+
+  if (ret < 0)
+    {
+      DPRINTF(E_LOG, L_DB, "Error fetching results\n");
+      db_transaction_rollback();
+      return -1;
+    }
+
+  db_transaction_end();
+
+  queue_inc_version_and_notify();
+
+  return 0;
+}
+
+/*
+ * Adds the files matching the given query to the queue
+ *
+ * The files table is queried with the given parameters and all found files are added to the end of the
+ * "normal" queue and the shuffled queue.
+ *
+ * The function returns -1 on failure (e. g. error reading from database). It wraps all database access
+ * in a transaction and performs a rollback if an error occurs, leaving the queue in a consistent state.
+ *
+ * @param qp Query parameters for the files table
+ * @param reshuffle If 1 queue will be reshuffled after adding new items
+ * @param item_id The base item id, all items after this will be reshuffled
+ * @return Item id of the last inserted item on success, -1 on failure
+ */
+int
+db_queue_add_by_query(struct query_params *qp, char reshuffle, uint32_t item_id)
+{
+  struct db_media_file_info dbmfi;
+  int pos;
+  int ret;
+
+  db_transaction_begin();
+
+  pos = db_queue_get_count();
+  if (pos < 0)
+    {
+      DPRINTF(E_LOG, L_DB, "Could not get count from queue\n");
+      db_transaction_rollback();
+      return -1;
+    }
+
+  ret = db_query_start(qp);
+  if (ret < 0)
+    {
+      DPRINTF(E_LOG, L_DB, "Could not start query\n");
+      db_transaction_rollback();
+      return -1;
+    }
+
+  DPRINTF(E_DBG, L_DB, "Player queue query returned %d items\n", qp->results);
+
+  if (qp->results == 0)
+    {
+      db_query_end(qp);
+      db_transaction_end();
+      return 0;
+    }
+
+  while (((ret = db_query_fetch_file(qp, &dbmfi)) == 0) && (dbmfi.id))
+    {
+      ret = queue_add_file(&dbmfi, pos, pos);
+
+      if (ret < 0)
+	{
+	  DPRINTF(E_DBG, L_DB, "Failed to add song id %s (%s)\n", dbmfi.id, dbmfi.title);
+	  break;
+	}
+
+      DPRINTF(E_DBG, L_DB, "Added song id %s (%s) to queue\n", dbmfi.id, dbmfi.title);
+      pos++;
+    }
+
+  db_query_end(qp);
+
+  if (ret < 0)
+    {
+      DPRINTF(E_LOG, L_DB, "Error fetching results\n");
+      db_transaction_rollback();
+      return -1;
+    }
+
+  ret = (int) sqlite3_last_insert_rowid(hdl);
+
+  db_transaction_end();
+
+  // Reshuffle after adding new items
+  if (reshuffle)
+    {
+      db_queue_reshuffle(item_id);
+    }
+  else
+    {
+      queue_inc_version_and_notify();
+    }
+
+  return ret;
+}
+
+/*
+ * Adds the items of the stored playlist with the given id to the end of the queue
+ *
+ * @param plid Id of the stored playlist
+ * @param reshuffle If 1 queue will be reshuffled after adding new items
+ * @param item_id The base item id, all items after this will be reshuffled
+ * @return 0 on success, -1 on failure
+ */
+int
+db_queue_add_by_playlistid(int plid, char reshuffle, uint32_t item_id)
+{
+  struct query_params qp;
+  int ret;
+
+  memset(&qp, 0, sizeof(struct query_params));
+
+  qp.id = plid;
+  qp.type = Q_PLITEMS;
+
+  ret = db_queue_add_by_query(&qp, reshuffle, item_id);
+
+  return ret;
+}
+
+/*
+ * Adds the file with the given id to the queue
+ *
+ * @param id Id of the file
+ * @param reshuffle If 1 queue will be reshuffled after adding new items
+ * @param item_id The base item id, all items after this will be reshuffled
+ * @return 0 on success, -1 on failure
+ */
+int
+db_queue_add_by_fileid(int id, char reshuffle, uint32_t item_id)
+{
+  struct query_params qp;
+  char buf[124];
+  int ret;
+
+  memset(&qp, 0, sizeof(struct query_params));
+
+  qp.type = Q_ITEMS;
+  snprintf(buf, sizeof(buf), "f.id = %" PRIu32, id);
+  qp.filter = buf;
+
+  ret = db_queue_add_by_query(&qp, reshuffle, item_id);
+
+  return ret;
+}
+
+int
+db_queue_add_item(struct db_queue_item *queue_item, char reshuffle, uint32_t item_id)
+{
+#define Q_TMPL "INSERT INTO queue "							\
+		    "(id, file_id, song_length, data_kind, media_kind, "		\
+		    "pos, shuffle_pos, path, virtual_path, title, "			\
+		    "artist, album_artist, album, genre, songalbumid, "			\
+		    "time_modified, artist_sort, album_sort, album_artist_sort, year, "	\
+		    "track, disc)" 							\
+		"VALUES"                                           			\
+		    "(NULL, %d, %d, %d, %d, "						\
+		    "%d, %d, %Q, %Q, %Q, "						\
+		    "%Q, %Q, %Q, %Q, %d, "						\
+		    "%d, %Q, %Q, %Q, %d, "						\
+		    "%d, %d);"
+
+  char *query;
+  int pos;
+  int ret;
+
+  db_transaction_begin();
+
+  pos = db_queue_get_count();
+  if (pos < 0)
+    {
+      DPRINTF(E_LOG, L_DB, "Could not get count from queue\n");
+      db_transaction_rollback();
+      return -1;
+    }
+
+  query = sqlite3_mprintf(Q_TMPL,
+			  queue_item->file_id, queue_item->song_length, queue_item->data_kind, queue_item->media_kind,
+			  pos, pos, queue_item->path, queue_item->virtual_path, queue_item->title,
+			  queue_item->artist, queue_item->album_artist, queue_item->album, queue_item->genre, queue_item->songalbumid,
+			  queue_item->time_modified, queue_item->artist_sort, queue_item->album_sort, queue_item->album_artist_sort, queue_item->year,
+			  queue_item->track, queue_item->disc);
+  ret = db_query_run(query, 1, 0);
+  if (ret < 0)
+    {
+      DPRINTF(E_LOG, L_DB, "Error adding queue item\n");
+      db_transaction_rollback();
+      return -1;
+    }
+
+  ret = (int) sqlite3_last_insert_rowid(hdl);
+
+  db_transaction_end();
+
+  // Reshuffle after adding new items
+  if (reshuffle)
+    {
+      db_queue_reshuffle(item_id);
+    }
+  else
+    {
+      queue_inc_version_and_notify();
+    }
+  return ret;
+
+#undef Q_TMPL
+}
+
+static int
+queue_enum_start(struct query_params *qp)
+{
+#define Q_TMPL "SELECT * FROM queue WHERE %s %s;"
+  char *query;
+  const char *orderby;
+  int ret;
+
+  qp->stmt = NULL;
+
+  if (qp->sort)
+    orderby = sort_clause[qp->sort];
+  else
+    orderby = sort_clause[S_POS];
+
+  if (qp->filter)
+     query = sqlite3_mprintf(Q_TMPL, qp->filter, orderby);
+  else
+    query = sqlite3_mprintf(Q_TMPL, "1=1", orderby);
+
+  if (!query)
+    {
+      DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
+
+      return -1;
+    }
+
+  DPRINTF(E_DBG, L_DB, "Starting enum '%s'\n", query);
+
+  ret = db_blocking_prepare_v2(query, -1, &qp->stmt, NULL);
+  if (ret != SQLITE_OK)
+    {
+      DPRINTF(E_LOG, L_DB, "Could not prepare statement: %s\n", sqlite3_errmsg(hdl));
+
+      sqlite3_free(query);
+      return -1;
+    }
+
+  sqlite3_free(query);
+
+  return 0;
+
+#undef Q_TMPL
+}
+
+static inline char *
+strdup_if(char *str, int cond)
+{
+  if (str == NULL)
+    return NULL;
+
+  if (cond)
+    return strdup(str);
+
+  return str;
+}
+
+static int
+queue_enum_fetch(struct query_params *qp, struct db_queue_item *queue_item, int keep_item)
+{
+  int ret;
+
+  memset(queue_item, 0, sizeof(struct db_queue_item));
+
+  if (!qp->stmt)
+    {
+      DPRINTF(E_LOG, L_DB, "Queue enum not started!\n");
+      return -1;
+    }
+
+  ret = db_blocking_step(qp->stmt);
+  if (ret == SQLITE_DONE)
+    {
+      DPRINTF(E_DBG, L_DB, "End of queue enum results\n");
+      return 0;
+    }
+  else if (ret != SQLITE_ROW)
+    {
+      DPRINTF(E_LOG, L_DB, "Could not step: %s\n", sqlite3_errmsg(hdl));
+      return -1;
+    }
+
+  queue_item->id = (uint32_t)sqlite3_column_int(qp->stmt, 0);
+  queue_item->file_id = (uint32_t)sqlite3_column_int(qp->stmt, 1);
+  queue_item->pos = (uint32_t)sqlite3_column_int(qp->stmt, 2);
+  queue_item->shuffle_pos = (uint32_t)sqlite3_column_int(qp->stmt, 3);
+  queue_item->data_kind = sqlite3_column_int(qp->stmt, 4);
+  queue_item->media_kind = sqlite3_column_int(qp->stmt, 5);
+  queue_item->song_length = (uint32_t)sqlite3_column_int(qp->stmt, 6);
+  queue_item->path = strdup_if((char *)sqlite3_column_text(qp->stmt, 7), keep_item);
+  queue_item->virtual_path = strdup_if((char *)sqlite3_column_text(qp->stmt, 8), keep_item);
+  queue_item->title = strdup_if((char *)sqlite3_column_text(qp->stmt, 9), keep_item);
+  queue_item->artist = strdup_if((char *)sqlite3_column_text(qp->stmt, 10), keep_item);
+  queue_item->album_artist = strdup_if((char *)sqlite3_column_text(qp->stmt, 11), keep_item);
+  queue_item->album = strdup_if((char *)sqlite3_column_text(qp->stmt, 12), keep_item);
+  queue_item->genre = strdup_if((char *)sqlite3_column_text(qp->stmt, 13), keep_item);
+  queue_item->songalbumid = sqlite3_column_int64(qp->stmt, 14);
+  queue_item->time_modified = sqlite3_column_int(qp->stmt, 15);
+  queue_item->artist_sort = strdup_if((char *)sqlite3_column_text(qp->stmt, 16), keep_item);
+  queue_item->album_sort = strdup_if((char *)sqlite3_column_text(qp->stmt, 17), keep_item);
+  queue_item->album_artist_sort = strdup_if((char *)sqlite3_column_text(qp->stmt, 18), keep_item);
+  queue_item->year = sqlite3_column_int(qp->stmt, 19);
+  queue_item->track = sqlite3_column_int(qp->stmt, 20);
+  queue_item->disc = sqlite3_column_int(qp->stmt, 21);
+  queue_item->artwork_url = strdup_if((char *)sqlite3_column_text(qp->stmt, 22), keep_item);
+
+  return 0;
+}
+
+int
+db_queue_enum_start(struct query_params *qp)
+{
+  int ret;
+
+  db_transaction_begin();
+
+  ret = queue_enum_start(qp);
+
+  if (ret < 0)
+    db_transaction_rollback();
+
+  return ret;
+}
+
+void
+db_queue_enum_end(struct query_params *qp)
+{
+  db_query_end(qp);
+  db_transaction_end();
+}
+
+int
+db_queue_enum_fetch(struct query_params *qp, struct db_queue_item *queue_item)
+{
+  return queue_enum_fetch(qp, queue_item, 0);
+}
+
+int
+db_queue_get_pos(uint32_t item_id, char shuffle)
+{
+#define Q_TMPL "SELECT pos FROM queue WHERE id = %d;"
+#define Q_TMPL_SHUFFLE "SELECT shuffle_pos FROM queue WHERE id = %d;"
+
+  char *query;
+  int pos;
+
+  if (shuffle)
+    query = sqlite3_mprintf(Q_TMPL_SHUFFLE, item_id);
+  else
+    query = sqlite3_mprintf(Q_TMPL, item_id);
+
+  pos = db_get_one_int(query);
+
+  sqlite3_free(query);
+
+  return pos;
+
+#undef Q_TMPL
+#undef Q_TMPL_SHUFFLE
+}
+
+int
+db_queue_get_pos_byfileid(uint32_t file_id, char shuffle)
+{
+#define Q_TMPL "SELECT pos FROM queue WHERE file_id = %d LIMIT 1;"
+#define Q_TMPL_SHUFFLE "SELECT shuffle_pos FROM queue WHERE file_id = %d LIMIT 1;"
+
+  char *query;
+  int pos;
+
+  if (shuffle)
+    query = sqlite3_mprintf(Q_TMPL_SHUFFLE, file_id);
+  else
+    query = sqlite3_mprintf(Q_TMPL, file_id);
+
+  pos = db_get_one_int(query);
+
+  sqlite3_free(query);
+
+  return pos;
+
+#undef Q_TMPL
+#undef Q_TMPL_SHUFFLE
+}
+
+static int
+queue_fetch_byitemid(uint32_t item_id, struct db_queue_item *queue_item, int with_metadata)
+{
+  struct query_params qp;
+  int ret;
+
+  memset(&qp, 0, sizeof(struct query_params));
+  qp.filter = sqlite3_mprintf("id = %d", item_id);
+
+  ret = queue_enum_start(&qp);
+  if (ret < 0)
+    {
+      sqlite3_free(qp.filter);
+      return -1;
+    }
+
+  ret = queue_enum_fetch(&qp, queue_item, with_metadata);
+  db_query_end(&qp);
+  sqlite3_free(qp.filter);
+  return ret;
+}
+
+struct db_queue_item *
+db_queue_fetch_byitemid(uint32_t item_id)
+{
+  struct db_queue_item *queue_item;
+  int ret;
+
+  queue_item = calloc(1, sizeof(struct db_queue_item));
+  if (!queue_item)
+    {
+      DPRINTF(E_LOG, L_DB, "Out of memory for queue_item\n");
+      return NULL;
+    }
+
+  db_transaction_begin();
+  ret = queue_fetch_byitemid(item_id, queue_item, 1);
+  db_transaction_end();
+
+  if (ret < 0)
+    {
+      free_queue_item(queue_item, 0);
+      DPRINTF(E_LOG, L_DB, "Error fetching queue item by item id\n");
+      return NULL;
+    }
+  else if (queue_item->id == 0)
+    {
+      // No item found
+      free_queue_item(queue_item, 0);
+      return NULL;
+    }
+
+  return queue_item;
+}
+
+struct db_queue_item *
+db_queue_fetch_byfileid(uint32_t file_id)
+{
+  struct db_queue_item *queue_item;
+  struct query_params qp;
+  int ret;
+
+  memset(&qp, 0, sizeof(struct query_params));
+  queue_item = calloc(1, sizeof(struct db_queue_item));
+  if (!queue_item)
+    {
+      DPRINTF(E_LOG, L_DB, "Out of memory for queue_item\n");
+      return NULL;
+    }
+
+  db_transaction_begin();
+
+  qp.filter = sqlite3_mprintf("file_id = %d", file_id);
+
+  ret = queue_enum_start(&qp);
+  if (ret < 0)
+    {
+      sqlite3_free(qp.filter);
+      db_transaction_end();
+      free_queue_item(queue_item, 0);
+      DPRINTF(E_LOG, L_DB, "Error fetching queue item by file id\n");
+      return NULL;
+    }
+
+  ret = queue_enum_fetch(&qp, queue_item, 1);
+  db_query_end(&qp);
+  sqlite3_free(qp.filter);
+  db_transaction_end();
+
+  if (ret < 0)
+    {
+      free_queue_item(queue_item, 0);
+      DPRINTF(E_LOG, L_DB, "Error fetching queue item by file id\n");
+      return NULL;
+    }
+  else if (queue_item->id == 0)
+    {
+      // No item found
+      free_queue_item(queue_item, 0);
+      return NULL;
+    }
+
+  return queue_item;
+}
+
+static int
+queue_fetch_bypos(uint32_t pos, char shuffle, struct db_queue_item *queue_item, int with_metadata)
+{
+  struct query_params qp;
+  int ret;
+
+  memset(&qp, 0, sizeof(struct query_params));
+  if (shuffle)
+    qp.filter = sqlite3_mprintf("shuffle_pos = %d", pos);
+  else
+    qp.filter = sqlite3_mprintf("pos = %d", pos);
+
+  ret = queue_enum_start(&qp);
+  if (ret < 0)
+    {
+      sqlite3_free(qp.filter);
+      return -1;
+    }
+
+  ret = queue_enum_fetch(&qp, queue_item, with_metadata);
+  db_query_end(&qp);
+  sqlite3_free(qp.filter);
+  return ret;
+}
+
+struct db_queue_item *
+db_queue_fetch_bypos(uint32_t pos, char shuffle)
+{
+  struct db_queue_item *queue_item;
+  int ret;
+
+  queue_item = calloc(1, sizeof(struct db_queue_item));
+  if (!queue_item)
+    {
+      DPRINTF(E_LOG, L_MAIN, "Out of memory for queue_item\n");
+      return NULL;
+    }
+
+  db_transaction_begin();
+  ret = queue_fetch_bypos(pos, shuffle, queue_item, 1);
+  db_transaction_end();
+
+  if (ret < 0)
+    {
+      free_queue_item(queue_item, 0);
+      DPRINTF(E_LOG, L_DB, "Error fetching queue item by pos id\n");
+      return NULL;
+    }
+  else if (queue_item->id == 0)
+    {
+      // No item found
+      free_queue_item(queue_item, 0);
+      return NULL;
+    }
+
+  return queue_item;
+}
+
+static int
+queue_fetch_byposrelativetoitem(int pos, uint32_t item_id, char shuffle, struct db_queue_item *queue_item, int with_metadata)
+{
+  int pos_absolute;
+  int ret;
+
+  DPRINTF(E_DBG, L_DB, "Fetch by pos: pos (%d) relative to item with id (%d)\n", pos, item_id);
+
+  pos_absolute = db_queue_get_pos(item_id, shuffle);
+  if (pos_absolute < 0)
+    {
+      return -1;
+    }
+
+  DPRINTF(E_DBG, L_DB, "Fetch by pos: item (%d) has absolute pos %d\n", item_id, pos_absolute);
+
+  pos_absolute += pos;
+
+  ret = queue_fetch_bypos(pos_absolute, shuffle, queue_item, with_metadata);
+
+  if (ret < 0)
+    DPRINTF(E_LOG, L_DB, "Error fetching item by pos: pos (%d) relative to item with id (%d)\n", pos, item_id);
+  else
+    DPRINTF(E_DBG, L_DB, "Fetch by pos: fetched item (id=%d, pos=%d, file-id=%d)\n", queue_item->id, queue_item->pos, queue_item->file_id);
+
+  return ret;
+}
+
+struct db_queue_item *
+db_queue_fetch_byposrelativetoitem(int pos, uint32_t item_id, char shuffle)
+{
+  struct db_queue_item *queue_item;
+  int ret;
+
+  DPRINTF(E_DBG, L_DB, "Fetch by pos: pos (%d) relative to item with id (%d)\n", pos, item_id);
+
+  queue_item = calloc(1, sizeof(struct db_queue_item));
+  if (!queue_item)
+    {
+      DPRINTF(E_LOG, L_MAIN, "Out of memory for queue_item\n");
+      return NULL;
+    }
+
+  db_transaction_begin();
+
+  ret = queue_fetch_byposrelativetoitem(pos, item_id, shuffle, queue_item, 1);
+
+  db_transaction_end();
+
+  if (ret < 0)
+    {
+      free_queue_item(queue_item, 0);
+      DPRINTF(E_LOG, L_DB, "Error fetching queue item by pos relative to item id\n");
+      return NULL;
+    }
+  else if (queue_item->id == 0)
+    {
+      // No item found
+      free_queue_item(queue_item, 0);
+      return NULL;
+    }
+
+  DPRINTF(E_DBG, L_DB, "Fetch by pos: fetched item (id=%d, pos=%d, file-id=%d)\n", queue_item->id, queue_item->pos, queue_item->file_id);
+
+  return queue_item;
+}
+
+struct db_queue_item *
+db_queue_fetch_next(uint32_t item_id, char shuffle)
+{
+  return db_queue_fetch_byposrelativetoitem(1, item_id, shuffle);
+}
+
+struct db_queue_item *
+db_queue_fetch_prev(uint32_t item_id, char shuffle)
+{
+  return db_queue_fetch_byposrelativetoitem(-1, item_id, shuffle);
+}
+
+static int
+queue_fix_pos(enum sort_type sort)
+{
+#define Q_TMPL "UPDATE queue SET %q = %d WHERE id = %d;"
+
+  struct query_params qp;
+  struct db_queue_item queue_item;
+  char *query;
+  int pos;
+  int ret;
+
+  memset(&qp, 0, sizeof(struct query_params));
+  qp.sort = sort;
+
+  ret = queue_enum_start(&qp);
+  if (ret < 0)
+    {
+      return -1;
+    }
+
+  pos = 0;
+  while ((ret = queue_enum_fetch(&qp, &queue_item, 0)) == 0 && (queue_item.id > 0))
+    {
+      if (queue_item.pos != pos)
+        {
+	  if (sort == S_SHUFFLE_POS)
+	    query = sqlite3_mprintf(Q_TMPL, "shuffle_pos", pos, queue_item.id);
+	  else
+	    query = sqlite3_mprintf(Q_TMPL, "pos", pos, queue_item.id);
+
+	  ret = db_query_run(query, 1, 0);
+	  if (ret < 0)
+	    {
+	      DPRINTF(E_LOG, L_DB, "Failed to update item with item-id: %d\n", queue_item.id);
+	      break;
+	    }
+	}
+
+      pos++;
+    }
+
+  db_query_end(&qp);
+  return ret;
+
+#undef Q_TMPL
+}
+
+/*
+ * Remove files that are disabled or non existant in the library and repair ordering of
+ * the queue (shuffle and normal)
+ */
+int
+db_queue_cleanup()
+{
+#define Q_TMPL "DELETE FROM queue WHERE NOT file_id IN (SELECT id from files WHERE disabled = 0);"
+
+  int deleted;
+  int ret;
+
+  db_transaction_begin();
+
+  ret = db_query_run(Q_TMPL, 0, 0);
+  if (ret < 0)
+    {
+      db_transaction_rollback();
+      return -1;
+    }
+
+  deleted = sqlite3_changes(hdl);
+  if (deleted <= 0)
+    {
+      // Nothing to do
+      db_transaction_end();
+      return 0;
+    }
+
+  // Update position of normal queue
+  ret = queue_fix_pos(S_POS);
+  if (ret < 0)
+    {
+      db_transaction_rollback();
+      return -1;
+    }
+
+  // Update position of shuffle queue
+  ret = queue_fix_pos(S_SHUFFLE_POS);
+  if (ret < 0)
+    {
+      db_transaction_rollback();
+      return -1;
+    }
+
+  db_transaction_end();
+  queue_inc_version_and_notify();
+
+  return 0;
+
+#undef Q_TMPL
+}
+
+/*
+ * Removes all items from the queue except the item give by 'keep_item_id' (if 'keep_item_id' > 0).
+ *
+ * @param keep_item_id item-id (e. g. the now playing item) to be left in the queue
+ */
+int
+db_queue_clear(uint32_t keep_item_id)
+{
+  char *query;
+  int ret;
+
+  query = sqlite3_mprintf("DELETE FROM queue where id <> %d;", keep_item_id);
+
+  db_transaction_begin();
+  ret = db_query_run(query, 1, 0);
+
+  if (ret < 0)
+    {
+      db_transaction_rollback();
+      return ret;
+    }
+
+  if (keep_item_id)
+    {
+      query = sqlite3_mprintf("UPDATE queue SET pos = 0 AND shuffle_pos = 0 where id = %d;", keep_item_id);
+      ret = db_query_run(query, 1, 0);
+    }
+
+  if (ret < 0)
+    {
+      db_transaction_rollback();
+      return ret;
+    }
+
+  db_transaction_end();
+  queue_inc_version_and_notify();
+  return ret;
+}
+
+static int
+queue_delete_item(struct db_queue_item *queue_item)
+{
+  char *query;
+  int ret;
+
+  // Remove item with the given item_id
+  query = sqlite3_mprintf("DELETE FROM queue where id = %d;", queue_item->id);
+  ret = db_query_run(query, 1, 0);
+  if (ret < 0)
+    {
+      return -1;
+    }
+
+  // Update pos for all items after the item with given item_id
+  query = sqlite3_mprintf("UPDATE queue SET pos = pos - 1 WHERE pos > %d;", queue_item->pos);
+  ret = db_query_run(query, 1, 0);
+  if (ret < 0)
+    {
+      return -1;
+    }
+
+  // Update shuffle_pos for all items after the item with given item_id
+  query = sqlite3_mprintf("UPDATE queue SET shuffle_pos = shuffle_pos - 1 WHERE shuffle_pos > %d;", queue_item->shuffle_pos);
+  ret = db_query_run(query, 1, 0);
+  if (ret < 0)
+    {
+      return -1;
+    }
+
+  return 0;
+}
+
+int
+db_queue_delete_byitemid(uint32_t item_id)
+{
+  struct db_queue_item queue_item;
+  int ret;
+
+  db_transaction_begin();
+
+  ret = queue_fetch_byitemid(item_id, &queue_item, 0);
+
+  if (ret < 0)
+    {
+      db_transaction_rollback();
+      return -1;
+    }
+
+  if (queue_item.id == 0)
+    {
+      db_transaction_end();
+      return 0;
+    }
+
+  ret = queue_delete_item(&queue_item);
+
+  if (ret < 0)
+    {
+      db_transaction_rollback();
+    }
+  else
+    {
+      db_transaction_end();
+      queue_inc_version_and_notify();
+    }
+
+  return ret;
+}
+
+int
+db_queue_delete_bypos(uint32_t pos, int count)
+{
+  char *query;
+  int to_pos;
+  int ret;
+
+  db_transaction_begin();
+
+  // Remove item with the given item_id
+  to_pos = pos + count;
+  query = sqlite3_mprintf("DELETE FROM queue where pos >= %d AND pos < %d;", pos, to_pos);
+  ret = db_query_run(query, 1, 0);
+  if (ret < 0)
+    {
+      db_transaction_rollback();
+      return -1;
+    }
+
+  ret = queue_fix_pos(S_POS);
+  if (ret < 0)
+    {
+      db_transaction_rollback();
+      return -1;
+    }
+
+  ret = queue_fix_pos(S_SHUFFLE_POS);
+  if (ret < 0)
+    {
+      db_transaction_rollback();
+      return -1;
+    }
+
+  db_transaction_end();
+  queue_inc_version_and_notify();
+
+  return ret;
+}
+
+int
+db_queue_delete_byposrelativetoitem(uint32_t pos, uint32_t item_id, char shuffle)
+{
+  struct db_queue_item queue_item;
+  int ret;
+
+  db_transaction_begin();
+
+  ret = queue_fetch_byposrelativetoitem(pos, item_id, shuffle, &queue_item, 0);
+  if (ret < 0)
+    {
+      db_transaction_rollback();
+      return -1;
+    }
+  else if (queue_item.id == 0)
+    {
+      // No item found
+      db_transaction_end();
+      return 0;
+    }
+
+  ret = queue_delete_item(&queue_item);
+
+  if (ret < 0)
+    {
+      db_transaction_rollback();
+    }
+  else
+    {
+      db_transaction_end();
+      queue_inc_version_and_notify();
+    }
+
+  return ret;
+}
+
+/*
+ * Moves the queue item with the given id to the given position (zero-based).
+ *
+ * @param item_id Queue item id
+ * @param pos_to target position in the queue (zero-based)
+ * @þaram shuffle If 1 move item in the shuffle queue
+ * @return 0 on success, -1 on failure
+ */
+int
+db_queue_move_byitemid(uint32_t item_id, int pos_to, char shuffle)
+{
+  char *query;
+  int pos_from;
+  int ret;
+
+  db_transaction_begin();
+
+  // Find item with the given item_id
+  pos_from = db_queue_get_pos(item_id, shuffle);
+  if (pos_from < 0)
     {
-      ret = db_query_run(queries[i], 0, 1);
-
-      if (ret == 0)
-	DPRINTF(E_DBG, L_DB, "Purged %d rows\n", sqlite3_changes(hdl));
+      db_transaction_rollback();
+      return -1;
     }
-}
 
-/* Spotify */
-void
-db_spotify_pl_delete(int id)
-{
-  char *queries_tmpl[2] =
+  // Update pos for all items after the item with given item_id
+  if (shuffle)
+    query = sqlite3_mprintf("UPDATE queue SET shuffle_pos = shuffle_pos - 1 WHERE shuffle_pos > %d;", pos_from);
+  else
+    query = sqlite3_mprintf("UPDATE queue SET pos = pos - 1 WHERE pos > %d;", pos_from);
+
+  ret = db_query_run(query, 1, 0);
+  if (ret < 0)
     {
-      "DELETE FROM playlists WHERE id = %d;",
-      "DELETE FROM playlistitems WHERE playlistid = %d;",
-    };
-  char *query;
-  int i;
-  int ret;
+      db_transaction_rollback();
+      return -1;
+    }
 
-  for (i = 0; i < (sizeof(queries_tmpl) / sizeof(queries_tmpl[0])); i++)
+  // Update pos for all items from the given pos_to
+  if (shuffle)
+    query = sqlite3_mprintf("UPDATE queue SET shuffle_pos = shuffle_pos + 1 WHERE shuffle_pos >= %d;", pos_to);
+  else
+    query = sqlite3_mprintf("UPDATE queue SET pos = pos + 1 WHERE pos >= %d;", pos_to);
+
+  ret = db_query_run(query, 1, 0);
+  if (ret < 0)
     {
-      query = sqlite3_mprintf(queries_tmpl[i], id);
+      db_transaction_rollback();
+      return -1;
+    }
 
-      ret = db_query_run(query, 1, 1);
+  // Update item with the given item_id
+  if (shuffle)
+    query = sqlite3_mprintf("UPDATE queue SET shuffle_pos = %d where id = %d;", pos_to, item_id);
+  else
+    query = sqlite3_mprintf("UPDATE queue SET pos = %d where id = %d;", pos_to, item_id);
 
-      if (ret == 0)
-	DPRINTF(E_DBG, L_DB, "Deleted %d rows\n", sqlite3_changes(hdl));
+  ret = db_query_run(query, 1, 0);
+  if (ret < 0)
+    {
+      db_transaction_rollback();
+      return -1;
     }
+
+  db_transaction_end();
+  queue_inc_version_and_notify();
+
+  return 0;
 }
 
-/* Spotify */
-void
-db_spotify_files_delete()
+/*
+ * Moves the queue item at the given position to the given position (zero-based).
+ *
+ * @param pos_from Position of the queue item to move
+ * @param pos_to target position in the queue (zero-based)
+ * @return 0 on success, -1 on failure
+ */
+int
+db_queue_move_bypos(int pos_from, int pos_to)
 {
-#define Q_TMPL "DELETE FROM files WHERE path LIKE 'spotify:%%' AND NOT path IN (SELECT filepath FROM playlistitems);"
+  struct db_queue_item queue_item;
   char *query;
   int ret;
 
-  query = sqlite3_mprintf(Q_TMPL);
+  db_transaction_begin();
 
-  ret = db_query_run(query, 1, 1);
+  // Find item to move
+  ret = queue_fetch_bypos(pos_from, 0, &queue_item, 0);
+  if (ret < 0)
+    {
+      db_transaction_rollback();
+      return -1;
+    }
 
-  if (ret == 0)
-    DPRINTF(E_DBG, L_DB, "Deleted %d rows\n", sqlite3_changes(hdl));
-#undef Q_TMPL
-}
-#endif
+  if (queue_item.id == 0)
+    {
+      db_transaction_end();
+      return 0;
+    }
 
-/* Admin */
-int
-db_admin_add(const char *key, const char *value)
-{
-#define Q_TMPL "INSERT OR REPLACE INTO admin (key, value) VALUES ('%q', '%q');"
-  char *query;
+  // Update pos for all items after the item with given position
+  query = sqlite3_mprintf("UPDATE queue SET pos = pos - 1 WHERE pos > %d;", queue_item.pos);
+  ret = db_query_run(query, 1, 0);
+  if (ret < 0)
+    {
+      db_transaction_rollback();
+      return -1;
+    }
 
-  query = sqlite3_mprintf(Q_TMPL, key, value);
+  // Update pos for all items from the given pos_to
+  query = sqlite3_mprintf("UPDATE queue SET pos = pos + 1 WHERE pos >= %d;", pos_to);
+  ret = db_query_run(query, 1, 0);
+  if (ret < 0)
+    {
+      db_transaction_rollback();
+      return -1;
+    }
 
-  return db_query_run(query, 1, 0);
-#undef Q_TMPL
+  // Update item with the given item_id
+  query = sqlite3_mprintf("UPDATE queue SET pos = %d where id = %d;", pos_to, queue_item.id);
+  ret = db_query_run(query, 1, 0);
+  if (ret < 0)
+    {
+      db_transaction_rollback();
+      return -1;
+    }
+
+  db_transaction_end();
+  queue_inc_version_and_notify();
+
+  return 0;
 }
 
-char *
-db_admin_get(const char *key)
+/*
+ * Moves the queue item at the given position to the given target position. The positions
+ * are relavtive to the given base item (item id).
+ *
+ * @param from_pos Relative position of the queue item to the base item
+ * @param to_offset Target position relative to the base item
+ * @param item_id The base item id (normaly the now playing item)
+ * @return 0 on success, -1 on failure
+ */
+int
+db_queue_move_byposrelativetoitem(uint32_t from_pos, uint32_t to_offset, uint32_t item_id, char shuffle)
 {
-#define Q_TMPL "SELECT value FROM admin a WHERE a.key = '%q';"
+  struct db_queue_item queue_item;
   char *query;
-  sqlite3_stmt *stmt;
-  char *res;
+  int pos_move_from;
+  int pos_move_to;
   int ret;
 
-  query = sqlite3_mprintf(Q_TMPL, key);
-  if (!query)
-    {
-      DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
-
-      return NULL;
-    }
+  db_transaction_begin();
 
-  DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query);
+  DPRINTF(E_DBG, L_DB, "Move by pos: from %d offset %d relative to item (%d)\n", from_pos, to_offset, item_id);
 
-  ret = db_blocking_prepare_v2(query, strlen(query) + 1, &stmt, NULL);
-  if (ret != SQLITE_OK)
+  // Find item with the given item_id
+  ret = queue_fetch_byitemid(item_id, &queue_item, 0);
+  if (ret < 0)
     {
-      DPRINTF(E_WARN, L_DB, "Could not prepare statement: %s\n", sqlite3_errmsg(hdl));
-
-      sqlite3_free(query);
-      return NULL;
+      db_transaction_rollback();
+      return -1;
     }
 
-  ret = db_blocking_step(stmt);
-  if (ret != SQLITE_ROW)
-    {
-      if (ret == SQLITE_DONE)
-	DPRINTF(E_DBG, L_DB, "No results\n");
-      else
-	DPRINTF(E_WARN, L_DB, "Could not step: %s\n", sqlite3_errmsg(hdl));	
+  DPRINTF(E_DBG, L_DB, "Move by pos: base item (id=%d, pos=%d, file-id=%d)\n", queue_item.id, queue_item.pos, queue_item.file_id);
 
-      sqlite3_finalize(stmt);
-      sqlite3_free(query);
-      return NULL;
+  if (queue_item.id == 0)
+    {
+      db_transaction_end();
+      return 0;
     }
 
-  res = (char *)sqlite3_column_text(stmt, 0);
-  if (res)
-    res = strdup(res);
+  // Calculate the position of the item to move
+  if (shuffle)
+    pos_move_from = queue_item.shuffle_pos + from_pos;
+  else
+    pos_move_from = queue_item.pos + from_pos;
 
-#ifdef DB_PROFILE
-  while (db_blocking_step(stmt) == SQLITE_ROW)
-    ; /* EMPTY */
-#endif
+  // Calculate the position where to move the item to
+  if (shuffle)
+    pos_move_to = queue_item.shuffle_pos + to_offset;
+  else
+    pos_move_to = queue_item.pos + to_offset;
 
-  sqlite3_finalize(stmt);
-  sqlite3_free(query);
+  if (pos_move_to < pos_move_from)
+    {
+      /*
+       * Moving an item to a previous position seems to send an offset incremented by one
+       */
+      pos_move_to++;
+    }
 
-  return res;
+  DPRINTF(E_DBG, L_DB, "Move by pos: absolute pos: move from %d to %d\n", pos_move_from, pos_move_to);
 
-#undef Q_TMPL
-}
+  // Find item to move
+  ret = queue_fetch_bypos(pos_move_from, shuffle, &queue_item, 0);
+  if (ret < 0)
+    {
+      db_transaction_rollback();
+      return -1;
+    }
 
-int
-db_admin_update(const char *key, const char *value)
-{
-#define Q_TMPL "UPDATE admin SET value='%q' WHERE key='%q';"
-  char *query;
+  DPRINTF(E_DBG, L_DB, "Move by pos: move item (id=%d, pos=%d, file-id=%d)\n", queue_item.id, queue_item.pos, queue_item.file_id);
 
-  query = sqlite3_mprintf(Q_TMPL, value, key);
+  if (queue_item.id == 0)
+    {
+      db_transaction_end();
+      return 0;
+    }
 
-  return db_query_run(query, 1, 0);
-#undef Q_TMPL
-}
+  // Update pos for all items after the item with given position
+  if (shuffle)
+    query = sqlite3_mprintf("UPDATE queue SET shuffle_pos = shuffle_pos - 1 WHERE shuffle_pos > %d;", queue_item.shuffle_pos);
+  else
+    query = sqlite3_mprintf("UPDATE queue SET pos = pos - 1 WHERE pos > %d;", queue_item.pos);
 
-int
-db_admin_delete(const char *key)
-{
-#define Q_TMPL "DELETE FROM admin where key='%q';"
-  char *query;
+  ret = db_query_run(query, 1, 0);
+  if (ret < 0)
+    {
+      db_transaction_rollback();
+      return -1;
+    }
 
-  query = sqlite3_mprintf(Q_TMPL, key);
+  // Update pos for all items from the given pos_to
+  if (shuffle)
+    query = sqlite3_mprintf("UPDATE queue SET shuffle_pos = shuffle_pos + 1 WHERE shuffle_pos >= %d;", pos_move_to);
+  else
+    query = sqlite3_mprintf("UPDATE queue SET pos = pos + 1 WHERE pos >= %d;", pos_move_to);
 
-  return db_query_run(query, 1, 0);
-#undef Q_TMPL
-}
+  ret = db_query_run(query, 1, 0);
+  if (ret < 0)
+    {
+      db_transaction_rollback();
+      return -1;
+    }
 
-/* Speakers */
-int
-db_speaker_save(uint64_t id, int selected, int volume, const char *name)
-{
-#define Q_TMPL "INSERT OR REPLACE INTO speakers (id, selected, volume, name) VALUES (%" PRIi64 ", %d, %d, '%q');"
-  char *query;
+  // Update item with the given item_id
+  if (shuffle)
+    query = sqlite3_mprintf("UPDATE queue SET shuffle_pos = %d where id = %d;", pos_move_to, queue_item.id);
+  else
+    query = sqlite3_mprintf("UPDATE queue SET pos = %d where id = %d;", pos_move_to, queue_item.id);
+
+  ret = db_query_run(query, 1, 0);
+  if (ret < 0)
+    {
+      db_transaction_rollback();
+      return -1;
+    }
 
-  query = sqlite3_mprintf(Q_TMPL, id, selected, volume, name);
+  db_transaction_end();
+  queue_inc_version_and_notify();
 
-  return db_query_run(query, 1, 0);
-#undef Q_TMPL
+  return 0;
 }
 
+/*
+ * Reshuffles the shuffle queue
+ *
+ * If the given item_id is 0, the whole shuffle queue is reshuffled, otherwise the
+ * queue is reshuffled after the item with the given id (excluding this item).
+ *
+ * @param item_id The base item, after this item the queue is reshuffled
+ * @return 0 on success, -1 on failure
+ */
 int
-db_speaker_get(uint64_t id, int *selected, int *volume)
+db_queue_reshuffle(uint32_t item_id)
 {
-#define Q_TMPL "SELECT s.selected, s.volume FROM speakers s WHERE s.id = %" PRIi64 ";"
-  sqlite3_stmt *stmt;
   char *query;
+  int pos;
+  int count;
+  struct db_queue_item queue_item;
+  int *shuffle_pos;
+  int len;
+  int i;
+  struct query_params qp;
   int ret;
 
-  query = sqlite3_mprintf(Q_TMPL, id);
-  if (!query)
-    {
-      DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
+  db_transaction_begin();
 
+  DPRINTF(E_DBG, L_DB, "Reshuffle queue after item with item-id: %d\n", item_id);
+
+  // Reset the shuffled order
+  ret = db_query_run("UPDATE queue SET shuffle_pos = pos;", 0, 0);
+  if (ret < 0)
+    {
+      db_transaction_rollback();
       return -1;
     }
 
-  DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query);
-
-  ret = db_blocking_prepare_v2(query, -1, &stmt, NULL);
-  if (ret != SQLITE_OK)
+  pos = 0;
+  if (item_id > 0)
     {
-      DPRINTF(E_LOG, L_DB, "Could not prepare statement: %s\n", sqlite3_errmsg(hdl));
+      pos = db_queue_get_pos(item_id, 0);
+      if (pos < 0)
+	{
+	  db_transaction_rollback();
+	  return -1;
+	}
 
-      ret = -1;
-      goto out;
+      pos++; // Do not reshuffle the base item
     }
 
-  ret = db_blocking_step(stmt);
-  if (ret != SQLITE_ROW)
+  count = db_queue_get_count();
+
+  len = count - pos;
+
+  DPRINTF(E_DBG, L_DB, "Reshuffle %d items off %d total items, starting from pos %d\n", len, count, pos);
+
+  shuffle_pos = malloc(len * sizeof(int));
+  for (i = 0; i < len; i++)
     {
-      if (ret != SQLITE_DONE)
-	DPRINTF(E_LOG, L_DB, "Could not step: %s\n", sqlite3_errmsg(hdl));
+      shuffle_pos[i] = i + pos;
+    }
 
-      sqlite3_finalize(stmt);
+  shuffle_int(&shuffle_rng, shuffle_pos, len);
 
-      ret = -1;
-      goto out;
+  memset(&qp, 0, sizeof(struct query_params));
+  qp.filter = sqlite3_mprintf("pos >= %d", pos);
+
+  ret = queue_enum_start(&qp);
+  if (ret < 0)
+    {
+      sqlite3_free(qp.filter);
+      db_transaction_rollback();
+      return -1;
     }
 
-  *selected = sqlite3_column_int(stmt, 0);
-  *volume = sqlite3_column_int(stmt, 1);
+  i = 0;
+  while ((ret = queue_enum_fetch(&qp, &queue_item, 0)) == 0 && (queue_item.id > 0) && (i < len))
+    {
+      query = sqlite3_mprintf("UPDATE queue SET shuffle_pos = %d where id = %d;", shuffle_pos[i], queue_item.id);
+      ret = db_query_run(query, 1, 0);
+      if (ret < 0)
+	{
+	  DPRINTF(E_LOG, L_DB, "Failed to delete item with item-id: %d\n", queue_item.id);
+	  break;
+	}
 
-#ifdef DB_PROFILE
-  while (db_blocking_step(stmt) == SQLITE_ROW)
-    ; /* EMPTY */
-#endif
+      i++;
+    }
 
-  sqlite3_finalize(stmt);
+  db_query_end(&qp);
+  sqlite3_free(qp.filter);
 
-  ret = 0;
+  if (ret < 0)
+    {
+      db_transaction_rollback();
+      return -1;
+    }
 
- out:
-  sqlite3_free(query);
-  return ret;
+  db_transaction_end();
+  queue_inc_version_and_notify();
 
-#undef Q_TMPL
+  return 0;
 }
 
-void
-db_speaker_clear_all(void)
+int
+db_queue_get_count()
 {
-  db_query_run("UPDATE speakers SET selected = 0;", 0, 0);
+  return db_get_one_int("SELECT COUNT(*) FROM queue;");
 }
 
 
@@ -4385,7 +5685,7 @@ db_watch_cookie_known(uint32_t cookie)
       return 0;
     }
 
-  ret = db_get_count(query);
+  ret = db_get_one_int(query);
 
   sqlite3_free(query);
 
@@ -5002,6 +6302,8 @@ db_init(void)
 
   DPRINTF(E_LOG, L_DB, "Database OK with %d active files and %d active playlists\n", files, pls);
 
+  rng_init(&shuffle_rng);
+
   return 0;
 }
 
diff --git a/src/db.h b/src/db.h
index be4fc77..0d7510d 100644
--- a/src/db.h
+++ b/src/db.h
@@ -8,6 +8,7 @@
 
 #include <sqlite3.h>
 
+#include "outputs.h"
 
 enum index_type {
   I_NONE,
@@ -28,6 +29,8 @@ enum sort_type {
   S_DISC,
   S_TRACK,
   S_VPATH,
+  S_POS,
+  S_SHUFFLE_POS,
 };
 
 #define Q_F_BROWSE (1 << 15)
@@ -35,21 +38,22 @@ enum sort_type {
 enum query_type {
   Q_ITEMS            = 1,
   Q_PL               = 2,
-  Q_PLITEMS          = 3,
-  Q_BROWSE_ARTISTS   = Q_F_BROWSE | 4,
-  Q_BROWSE_ALBUMS    = Q_F_BROWSE | 5,
-  Q_BROWSE_GENRES    = Q_F_BROWSE | 6,
-  Q_BROWSE_COMPOSERS = Q_F_BROWSE | 7,
-  Q_GROUP_ALBUMS     = 8,
-  Q_GROUP_ARTISTS    = 9,
-  Q_GROUP_ITEMS      = 10,
-  Q_GROUP_DIRS       = Q_F_BROWSE | 11,
-  Q_BROWSE_YEARS     = Q_F_BROWSE | 12,
-  Q_COUNT_ITEMS      = 13,
-  Q_BROWSE_DISCS     = Q_F_BROWSE | 14,
-  Q_BROWSE_TRACKS    = Q_F_BROWSE | 15,
-  Q_BROWSE_VPATH     = Q_F_BROWSE | 16,
-  Q_BROWSE_PATH      = Q_F_BROWSE | 17,
+  Q_FIND_PL          = 3,
+  Q_PLITEMS          = 4,
+  Q_GROUP_ALBUMS     = 5,
+  Q_GROUP_ARTISTS    = 6,
+  Q_GROUP_ITEMS      = 7,
+  Q_COUNT_ITEMS      = 8,
+  Q_BROWSE_ARTISTS   = Q_F_BROWSE | 1,
+  Q_BROWSE_ALBUMS    = Q_F_BROWSE | 2,
+  Q_BROWSE_GENRES    = Q_F_BROWSE | 3,
+  Q_BROWSE_COMPOSERS = Q_F_BROWSE | 4,
+  Q_GROUP_DIRS       = Q_F_BROWSE | 5,
+  Q_BROWSE_YEARS     = Q_F_BROWSE | 6,
+  Q_BROWSE_DISCS     = Q_F_BROWSE | 7,
+  Q_BROWSE_TRACKS    = Q_F_BROWSE | 8,
+  Q_BROWSE_VPATH     = Q_F_BROWSE | 9,
+  Q_BROWSE_PATH      = Q_F_BROWSE | 10,
 };
 
 #define ARTWORK_UNKNOWN   0
@@ -189,7 +193,7 @@ struct media_file_info {
 
 #define mfi_offsetof(field) offsetof(struct media_file_info, field)
 
-/* PL_SPECIAL value must be in sync with type value in Q_PL* in db.c */
+/* PL_SPECIAL value must be in sync with type value in Q_PL* in db_init.c */
 enum pl_type {
   PL_SPECIAL = 0,
   PL_FOLDER = 1,
@@ -347,15 +351,16 @@ struct watch_enum {
 
 struct filecount_info {
   uint32_t count;
-  uint32_t length;
+  uint64_t length;
 };
 
-/* Directory ids must be in sync with the ids in Q_DIR* in db.c */
+/* Directory ids must be in sync with the ids in Q_DIR* in db_init.c */
 enum directory_ids {
   DIR_ROOT = 1,
   DIR_FILE = 2,
   DIR_HTTP = 3,
   DIR_SPOTIFY = 4,
+  DIR_MAX
 };
 
 struct directory_info {
@@ -373,6 +378,51 @@ struct directory_enum {
   sqlite3_stmt *stmt;
 };
 
+struct db_queue_item
+{
+  /* A unique id for this queue item. If the same item appears multiple
+     times in the queue each corresponding queue item has its own id. */
+  uint32_t id;
+
+  /* Id of the file/item in the files database */
+  uint32_t file_id;
+
+  /* Length of the item in ms */
+  uint32_t song_length;
+
+  /* Data type of the item */
+  enum data_kind data_kind;
+  /* Media type of the item */
+  enum media_kind media_kind;
+
+  uint32_t seek;
+
+  uint32_t pos;
+  uint32_t shuffle_pos;
+
+  char *path;
+  char *virtual_path;
+
+  char *title;
+  char *artist;
+  char *album_artist;
+  char *album;
+  char *genre;
+
+  int64_t songalbumid;
+  uint32_t time_modified;
+
+  char *artist_sort;
+  char *album_sort;
+  char *album_artist_sort;
+
+  uint32_t year;
+  uint32_t track;
+  uint32_t disc;
+
+  char *artwork_url;
+};
+
 char *
 db_escape_string(const char *str);
 
@@ -391,6 +441,9 @@ free_pli(struct playlist_info *pli, int content_only);
 void
 free_di(struct directory_info *di, int content_only);
 
+void
+free_queue_item(struct db_queue_item *queue_item, int content_only);
+
 /* Maintenance and DB hygiene */
 void
 db_hook_post_scan(void);
@@ -408,6 +461,9 @@ db_transaction_begin(void);
 void
 db_transaction_end(void);
 
+void
+db_transaction_rollback(void);
+
 /* Queries */
 int
 db_query_start(struct query_params *qp);
@@ -447,12 +503,6 @@ int
 db_files_get_count_bymatch(char *path);
 
 void
-db_files_update_songartistid(void);
-
-void
-db_files_update_songalbumid(void);
-
-void
 db_file_inc_playcount(int id);
 
 void
@@ -471,9 +521,6 @@ int
 db_file_id_bymatch(char *path);
 
 int
-db_file_id_byfilebase(char *filename, char *base);
-
-int
 db_file_id_byfile(char *filename);
 
 int
@@ -483,7 +530,7 @@ int
 db_file_id_by_virtualpath_match(char *path);
 
 void
-db_file_stamp_bypath(char *path, time_t *stamp, int *id);
+db_file_stamp_bypath(const char *path, time_t *stamp, int *id);
 
 struct media_file_info *
 db_file_fetch_byid(int id);
@@ -498,10 +545,7 @@ int
 db_file_update(struct media_file_info *mfi);
 
 void
-db_file_update_icy(int id, char *artist, char *album);
-
-void
-db_file_save_seek(int id, uint32_t seek);
+db_file_seek_update(int id, uint32_t seek);
 
 void
 db_file_delete_bypath(char *path);
@@ -529,7 +573,7 @@ void
 db_pl_ping_bymatch(char *path, int isdir);
 
 struct playlist_info *
-db_pl_fetch_bypath(char *path);
+db_pl_fetch_bypath(const char *path);
 
 struct playlist_info *
 db_pl_fetch_byvirtualpath(char *virtual_path);
@@ -541,7 +585,7 @@ int
 db_pl_add(struct playlist_info *pli, int *id);
 
 int
-db_pl_add_item_bypath(int plid, char *path);
+db_pl_add_item_bypath(int plid, const char *path);
 
 int
 db_pl_add_item_byid(int plid, int fileid);
@@ -569,7 +613,7 @@ db_pl_enable_bycookie(uint32_t cookie, char *path);
 
 /* Groups */
 int
-db_groups_clear(void);
+db_groups_cleanup();
 
 int
 db_group_persistentid_byid(int id, int64_t *persistentid);
@@ -592,7 +636,7 @@ int
 db_directory_addorupdate(char *virtual_path, int disabled, int parent_id);
 
 void
-db_directory_ping_bymatch(char *path);
+db_directory_ping_bymatch(char *virtual_path);
 
 void
 db_directory_disable_bymatch(char *path, char *strip, uint32_t cookie);
@@ -619,32 +663,114 @@ void
 db_spotify_pl_delete(int id);
 
 void
-db_spotify_files_delete();
+db_spotify_files_delete(void);
 #endif
 
 /* Admin */
 int
-db_admin_add(const char *key, const char *value);
+db_admin_set(const char *key, const char *value);
 
 char *
 db_admin_get(const char *key);
 
 int
-db_admin_update(const char *key, const char *value);
-
-int
 db_admin_delete(const char *key);
 
-/* Speakers */
+/* Speakers/outputs */
 int
-db_speaker_save(uint64_t id, int selected, int volume, const char *name);
+db_speaker_save(struct output_device *device);
 
 int
-db_speaker_get(uint64_t id, int *selected, int *volume);
+db_speaker_get(struct output_device *device, uint64_t id);
 
 void
 db_speaker_clear_all(void);
 
+/* Queue */
+int
+db_queue_get_version();
+
+int
+db_queue_update_item(struct db_queue_item *queue_item);
+
+int
+db_queue_add_by_queryafteritemid(struct query_params *qp, uint32_t item_id);
+
+int
+db_queue_add_by_query(struct query_params *qp, char reshuffle, uint32_t item_id);
+
+int
+db_queue_add_by_playlistid(int plid, char reshuffle, uint32_t item_id);
+
+int
+db_queue_add_by_fileid(int id, char reshuffle, uint32_t item_id);
+
+int
+db_queue_add_item(struct db_queue_item *queue_item, char reshuffle, uint32_t item_id);
+
+int
+db_queue_enum_start(struct query_params *qp);
+
+void
+db_queue_enum_end(struct query_params *qp);
+
+int
+db_queue_enum_fetch(struct query_params *qp, struct db_queue_item *queue_item);
+
+struct db_queue_item *
+db_queue_fetch_byitemid(uint32_t item_id);
+
+struct db_queue_item *
+db_queue_fetch_byfileid(uint32_t file_id);
+
+struct db_queue_item *
+db_queue_fetch_bypos(uint32_t pos, char shuffle);
+
+struct db_queue_item *
+db_queue_fetch_byposrelativetoitem(int pos, uint32_t item_id, char shuffle);
+
+struct db_queue_item *
+db_queue_fetch_next(uint32_t item_id, char shuffle);
+
+struct db_queue_item *
+db_queue_fetch_prev(uint32_t item_id, char shuffle);
+
+int
+db_queue_cleanup();
+
+int
+db_queue_clear(uint32_t keep_item_id);
+
+int
+db_queue_delete_byitemid(uint32_t item_id);
+
+int
+db_queue_delete_bypos(uint32_t pos, int count);
+
+int
+db_queue_delete_byposrelativetoitem(uint32_t pos, uint32_t item_id, char shuffle);
+
+int
+db_queue_move_byitemid(uint32_t item_id, int pos_to, char shuffle);
+
+int
+db_queue_move_bypos(int pos_from, int pos_to);
+
+int
+db_queue_move_byposrelativetoitem(uint32_t from_pos, uint32_t to_offset, uint32_t item_id, char shuffle);
+
+int
+db_queue_reshuffle(uint32_t item_id);
+
+int
+db_queue_get_count();
+
+int
+db_queue_get_pos(uint32_t item_id, char shuffle);
+
+int
+db_queue_get_pos_byfileid(uint32_t file_id, char shuffle);
+
 /* Inotify */
 int
 db_watch_clear(void);
diff --git a/src/db_init.c b/src/db_init.c
index 6eb5749..0620162 100644
--- a/src/db_init.c
+++ b/src/db_init.c
@@ -1,3 +1,4 @@
+
 /*
  * Copyright (C) 2009-2011 Julien BLACHE <jb at jblache.org>
  * Copyright (C) 2010 Kai Elwert <elwertk at googlemail.com>
@@ -25,10 +26,10 @@
 #include "logger.h"
 
 
-#define T_ADMIN					\
-  "CREATE TABLE IF NOT EXISTS admin("		\
-  "   key   VARCHAR(32) NOT NULL,"		\
-  "   value VARCHAR(32) NOT NULL"		\
+#define T_ADMIN						\
+  "CREATE TABLE IF NOT EXISTS admin("			\
+  "   key   VARCHAR(32) PRIMARY KEY NOT NULL,"		\
+  "   value VARCHAR(255) NOT NULL"			\
   ");"
 
 #define T_FILES						\
@@ -139,7 +140,8 @@
   "   id             INTEGER PRIMARY KEY NOT NULL,"	\
   "   selected       INTEGER NOT NULL,"			\
   "   volume         INTEGER NOT NULL,"			\
-  "   name           VARCHAR(255) DEFAULT NULL"         \
+  "   name           VARCHAR(255) DEFAULT NULL,"        \
+  "   auth_key       VARCHAR(2048) DEFAULT NULL"        \
   ");"
 
 #define T_INOTIFY					\
@@ -158,6 +160,33 @@
   "   parent_id           INTEGER DEFAULT 0"			\
   ");"
 
+#define T_QUEUE								\
+  "CREATE TABLE IF NOT EXISTS queue ("					\
+  "   id                  INTEGER PRIMARY KEY NOT NULL,"		\
+  "   file_id             INTEGER NOT NULL,"				\
+  "   pos                 INTEGER NOT NULL,"				\
+  "   shuffle_pos         INTEGER NOT NULL,"				\
+  "   data_kind           INTEGER NOT NULL,"				\
+  "   media_kind          INTEGER NOT NULL,"				\
+  "   song_length         INTEGER NOT NULL,"				\
+  "   path                VARCHAR(4096) NOT NULL,"			\
+  "   virtual_path        VARCHAR(4096) NOT NULL,"			\
+  "   title               VARCHAR(1024) DEFAULT NULL COLLATE DAAP,"	\
+  "   artist              VARCHAR(1024) DEFAULT NULL COLLATE DAAP,"	\
+  "   album_artist        VARCHAR(1024) NOT NULL COLLATE DAAP,"		\
+  "   album               VARCHAR(1024) NOT NULL COLLATE DAAP,"		\
+  "   genre               VARCHAR(255) DEFAULT NULL COLLATE DAAP,"	\
+  "   songalbumid         INTEGER NOT NULL,"				\
+  "   time_modified       INTEGER DEFAULT 0,"				\
+  "   artist_sort         VARCHAR(1024) DEFAULT NULL COLLATE DAAP,"	\
+  "   album_sort          VARCHAR(1024) DEFAULT NULL COLLATE DAAP,"	\
+  "   album_artist_sort   VARCHAR(1024) DEFAULT NULL COLLATE DAAP,"	\
+  "   year                INTEGER DEFAULT 0,"				\
+  "   track               INTEGER DEFAULT 0,"				\
+  "   disc                INTEGER DEFAULT 0,"				\
+  "   artwork_url         VARCHAR(4096) DEFAULT NULL"			\
+  ");"
+
 #define TRG_GROUPS_INSERT_FILES						\
   "CREATE TRIGGER update_groups_new_file AFTER INSERT ON files FOR EACH ROW" \
   " BEGIN"								\
@@ -216,6 +245,9 @@
   "INSERT INTO directories (id, virtual_path, db_timestamp, disabled, parent_id)" \
   " VALUES (4, '/spotify:', 0, 4294967296, 1);"
 
+#define Q_QUEUE_VERSION			\
+  "INSERT INTO admin (key, value) VALUES ('queue_version', '0');"
+
 #define Q_SCVER_MAJOR					\
   "INSERT INTO admin (key, value) VALUES ('schema_version_major', '%d');"
 #define Q_SCVER_MINOR					\
@@ -237,6 +269,7 @@ static const struct db_init_query db_init_table_queries[] =
     { T_SPEAKERS,  "create table speakers" },
     { T_INOTIFY,   "create table inotify" },
     { T_DIRECTORIES, "create table directories" },
+    { T_QUEUE,     "create table queue" },
 
     { TRG_GROUPS_INSERT_FILES,    "create trigger update_groups_new_file" },
     { TRG_GROUPS_UPDATE_FILES,    "create trigger update_groups_update_file" },
@@ -251,6 +284,7 @@ static const struct db_init_query db_init_table_queries[] =
     { Q_DIR2,      "create default base directory '/file:'" },
     { Q_DIR3,      "create default base directory '/http:'" },
     { Q_DIR4,      "create default base directory '/spotify:'" },
+    { Q_QUEUE_VERSION, "initialize queue version" },
   };
 
 
@@ -327,6 +361,12 @@ static const struct db_init_query db_init_table_queries[] =
 #define I_DIR_PARENT				\
   "CREATE INDEX IF NOT EXISTS idx_dir_parentid ON directories(parent_id);"
 
+#define I_QUEUE_POS				\
+  "CREATE INDEX IF NOT EXISTS idx_queue_pos ON queue(pos);"
+
+#define I_QUEUE_SHUFFLEPOS				\
+  "CREATE INDEX IF NOT EXISTS idx_queue_shufflepos ON queue(shuffle_pos);"
+
 static const struct db_init_query db_init_index_queries[] =
   {
     { I_RESCAN,    "create rescan index" },
@@ -357,6 +397,9 @@ static const struct db_init_query db_init_index_queries[] =
 
     { I_DIR_VPATH,   "create directories disabled_virtualpath index" },
     { I_DIR_PARENT,  "create directories parentid index" },
+
+    { I_QUEUE_POS,  "create queue pos index" },
+    { I_QUEUE_SHUFFLEPOS,  "create queue shuffle pos index" },
   };
 
 int
diff --git a/src/db_init.h b/src/db_init.h
index befa677..bff24b8 100644
--- a/src/db_init.h
+++ b/src/db_init.h
@@ -26,7 +26,7 @@
  * is a major upgrade. In other words minor version upgrades permit downgrading
  * forked-daapd after the database was upgraded. */
 #define SCHEMA_VERSION_MAJOR 19
-#define SCHEMA_VERSION_MINOR 00
+#define SCHEMA_VERSION_MINOR 04
 
 int
 db_init_indices(sqlite3 *hdl);
diff --git a/src/db_upgrade.c b/src/db_upgrade.c
index f3b3b2f..0a20960 100644
--- a/src/db_upgrade.c
+++ b/src/db_upgrade.c
@@ -377,7 +377,7 @@ db_upgrade_v11(sqlite3 *hdl)
   else if (count < 0)
     return -1;
 
-  spkids = (uint64_t *)malloc(count * sizeof(uint64_t));
+  spkids = calloc(count, sizeof(uint64_t));
   if (!spkids)
     {
       DPRINTF(E_LOG, L_DB, "Out of memory for speaker IDs\n");
@@ -1073,7 +1073,6 @@ db_upgrade_v16(sqlite3 *hdl)
 	}
     }
 
-  sqlite3_free(errmsg);
   sqlite3_finalize(stmt);
 
   return 0;
@@ -1442,6 +1441,129 @@ db_upgrade_v19(sqlite3 *hdl)
   return 0;
 }
 
+/* Upgrade from schema v19.00 to v19.01 */
+/* Create new table queue for persistent playqueue
+ */
+
+#define U_V1901_CREATE_TABLE_QUEUE					\
+  "CREATE TABLE IF NOT EXISTS queue ("					\
+  "   id                  INTEGER PRIMARY KEY NOT NULL,"		\
+  "   file_id             INTEGER NOT NULL,"				\
+  "   pos                 INTEGER NOT NULL,"				\
+  "   shuffle_pos         INTEGER NOT NULL,"				\
+  "   data_kind           INTEGER NOT NULL,"				\
+  "   media_kind          INTEGER NOT NULL,"				\
+  "   song_length         INTEGER NOT NULL,"				\
+  "   path                VARCHAR(4096) NOT NULL,"			\
+  "   virtual_path        VARCHAR(4096) NOT NULL,"			\
+  "   title               VARCHAR(1024) DEFAULT NULL COLLATE DAAP,"	\
+  "   artist              VARCHAR(1024) DEFAULT NULL COLLATE DAAP,"	\
+  "   album_artist        VARCHAR(1024) NOT NULL COLLATE DAAP,"		\
+  "   album               VARCHAR(1024) NOT NULL COLLATE DAAP,"		\
+  "   genre               VARCHAR(255) DEFAULT NULL COLLATE DAAP,"	\
+  "   songalbumid         INTEGER NOT NULL,"				\
+  "   time_modified       INTEGER DEFAULT 0,"				\
+  "   artist_sort         VARCHAR(1024) DEFAULT NULL COLLATE DAAP,"	\
+  "   album_sort          VARCHAR(1024) DEFAULT NULL COLLATE DAAP,"	\
+  "   album_artist_sort   VARCHAR(1024) DEFAULT NULL COLLATE DAAP,"	\
+  "   year                INTEGER DEFAULT 0,"				\
+  "   track               INTEGER DEFAULT 0,"				\
+  "   disc                INTEGER DEFAULT 0"				\
+  ");"
+
+#define U_V1901_QUEUE_VERSION			\
+  "INSERT INTO admin (key, value) VALUES ('queue_version', '0');"
+
+#define U_V1901_SCVER_MAJOR			\
+  "UPDATE admin SET value = '19' WHERE key = 'schema_version_major';"
+#define U_V1901_SCVER_MINOR			\
+  "UPDATE admin SET value = '01' WHERE key = 'schema_version_minor';"
+
+static const struct db_upgrade_query db_upgrade_v1901_queries[] =
+  {
+    { U_V1901_CREATE_TABLE_QUEUE,    "create table directories" },
+    { U_V1901_QUEUE_VERSION,         "insert queue version" },
+
+    { U_V1901_SCVER_MAJOR,    "set schema_version_major to 19" },
+    { U_V1901_SCVER_MINOR,    "set schema_version_minor to 01" },
+  };
+
+/* Upgrade from schema v19.01 to v19.02 */
+/* Set key column as primary key in the admin table
+ */
+
+#define U_V1902_CREATE_TABLE_ADMINTMP 			\
+  "CREATE TEMPORARY TABLE IF NOT EXISTS admin_tmp("	\
+  "   key   VARCHAR(32) NOT NULL,"		\
+  "   value VARCHAR(32) NOT NULL"			\
+  ");"
+#define U_V1902_INSERT_ADMINTMP \
+  "INSERT INTO admin_tmp SELECT * FROM admin;"
+#define U_V1902_DROP_TABLE_ADMIN \
+  "DROP TABLE admin;"
+#define U_V1902_CREATE_TABLE_ADMIN 			\
+  "CREATE TABLE IF NOT EXISTS admin("			\
+  "   key   VARCHAR(32) PRIMARY KEY NOT NULL,"		\
+  "   value VARCHAR(32) NOT NULL"			\
+  ");"
+#define U_V1902_INSERT_ADMIN \
+  "INSERT OR IGNORE INTO admin SELECT * FROM admin_tmp;"
+#define U_V1902_DROP_TABLE_ADMINTMP \
+  "DROP TABLE admin_tmp;"
+
+#define U_V1902_SCVER_MAJOR			\
+  "UPDATE admin SET value = '19' WHERE key = 'schema_version_major';"
+#define U_V1902_SCVER_MINOR			\
+  "UPDATE admin SET value = '02' WHERE key = 'schema_version_minor';"
+
+static const struct db_upgrade_query db_upgrade_v1902_queries[] =
+  {
+    { U_V1902_CREATE_TABLE_ADMINTMP,    "create temporary table admin_tmp" },
+    { U_V1902_INSERT_ADMINTMP,          "insert admin_tmp" },
+    { U_V1902_DROP_TABLE_ADMIN,         "drop table admin" },
+    { U_V1902_CREATE_TABLE_ADMIN,       "create table admin" },
+    { U_V1902_INSERT_ADMIN,             "insert admin" },
+    { U_V1902_DROP_TABLE_ADMINTMP,      "drop table admin_tmp" },
+
+    { U_V1902_SCVER_MAJOR,    "set schema_version_major to 19" },
+    { U_V1902_SCVER_MINOR,    "set schema_version_minor to 02" },
+  };
+
+
+#define U_V1903_ALTER_QUEUE_ADD_ARTWORKURL \
+  "ALTER TABLE queue ADD COLUMN artwork_url VARCHAR(4096) DEFAULT NULL;"
+
+#define U_V1903_SCVER_MAJOR \
+  "UPDATE admin SET value = '19' WHERE key = 'schema_version_major';"
+#define U_V1903_SCVER_MINOR \
+  "UPDATE admin SET value = '03' WHERE key = 'schema_version_minor';"
+
+static const struct db_upgrade_query db_upgrade_v1903_queries[] =
+  {
+    { U_V1903_ALTER_QUEUE_ADD_ARTWORKURL,    "alter table queue add column artwork_url" },
+
+    { U_V1903_SCVER_MAJOR,    "set schema_version_major to 19" },
+    { U_V1903_SCVER_MINOR,    "set schema_version_minor to 03" },
+  };
+
+
+#define U_V1904_ALTER_SPEAKERS_ADD_AUTHKEY \
+  "ALTER TABLE speakers ADD COLUMN auth_key VARCHAR(2048) DEFAULT NULL;"
+
+#define U_V1904_SCVER_MAJOR \
+  "UPDATE admin SET value = '19' WHERE key = 'schema_version_major';"
+#define U_V1904_SCVER_MINOR \
+  "UPDATE admin SET value = '04' WHERE key = 'schema_version_minor';"
+
+static const struct db_upgrade_query db_upgrade_v1904_queries[] =
+  {
+    { U_V1904_ALTER_SPEAKERS_ADD_AUTHKEY,    "alter table speakers add column auth_key" },
+
+    { U_V1904_SCVER_MAJOR,    "set schema_version_major to 19" },
+    { U_V1904_SCVER_MINOR,    "set schema_version_minor to 04" },
+  };
+
+
 int
 db_upgrade(sqlite3 *hdl, int db_ver)
 {
@@ -1552,6 +1674,34 @@ db_upgrade(sqlite3 *hdl, int db_ver)
       if (ret < 0)
 	return -1;
 
+      /* FALLTHROUGH */
+
+    case 1900:
+      ret = db_generic_upgrade(hdl, db_upgrade_v1901_queries, sizeof(db_upgrade_v1901_queries) / sizeof(db_upgrade_v1901_queries[0]));
+      if (ret < 0)
+	return -1;
+
+      /* FALLTHROUGH */
+
+    case 1901:
+      ret = db_generic_upgrade(hdl, db_upgrade_v1902_queries, sizeof(db_upgrade_v1902_queries) / sizeof(db_upgrade_v1902_queries[0]));
+      if (ret < 0)
+	return -1;
+
+      /* FALLTHROUGH */
+
+    case 1902:
+      ret = db_generic_upgrade(hdl, db_upgrade_v1903_queries, sizeof(db_upgrade_v1903_queries) / sizeof(db_upgrade_v1903_queries[0]));
+      if (ret < 0)
+	return -1;
+
+      /* FALLTHROUGH */
+
+    case 1903:
+      ret = db_generic_upgrade(hdl, db_upgrade_v1904_queries, sizeof(db_upgrade_v1904_queries) / sizeof(db_upgrade_v1904_queries[0]));
+      if (ret < 0)
+	return -1;
+
       break;
 
     default:
diff --git a/src/dmap_common.c b/src/dmap_common.c
index 495a7bf..250a2d1 100644
--- a/src/dmap_common.c
+++ b/src/dmap_common.c
@@ -31,7 +31,7 @@
 
 
 /* gperf static hash, dmap_fields.gperf */
-#include "dmap_fields_hash.c"
+#include "dmap_fields_hash.h"
 
 
 const struct dmap_field *
@@ -559,3 +559,78 @@ dmap_encode_file_metadata(struct evbuffer *songlist, struct evbuffer *song, stru
 
   return 0;
 }
+
+int
+dmap_encode_queue_metadata(struct evbuffer *songlist, struct evbuffer *song, struct db_queue_item *queue_item)
+{
+  int32_t val;
+  int want_mikd;
+  int want_asdk;
+  int want_ased;
+  int ret;
+
+  dmap_add_int(song, "miid", queue_item->file_id);
+  dmap_add_string(song, "minm", queue_item->title);
+  dmap_add_long(song, "mper", queue_item->file_id);
+  dmap_add_int(song, "mcti", queue_item->file_id);
+  dmap_add_string(song, "asal", queue_item->album);
+  dmap_add_long(song, "asai", queue_item->songalbumid);
+  dmap_add_string(song, "asaa", queue_item->album_artist);
+  dmap_add_string(song, "asar", queue_item->artist);
+  dmap_add_int(song, "asdm", queue_item->time_modified);
+  dmap_add_short(song, "asdn", queue_item->disc);
+  dmap_add_string(song, "asgn", queue_item->genre);
+  dmap_add_int(song, "astm", queue_item->song_length);
+  dmap_add_short(song, "astn", queue_item->track);
+  dmap_add_short(song, "asyr", queue_item->year);
+  dmap_add_int(song, "aeMK", queue_item->media_kind);
+  dmap_add_char(song, "aeMk", queue_item->media_kind);
+
+  dmap_add_string(song, "asfm", "wav");
+  dmap_add_short(song, "asbr", 1411);
+  dmap_add_string(song, "asdt", "wav audio file");
+
+  want_mikd = 1;/* Will be prepended to the list *//* item kind */
+  want_asdk = 1;/* Will be prepended to the list *//* data kind */
+  want_ased = 1;/* Extradata not in media_file_info but flag for reply */
+
+  /* Required for artwork in iTunes, set songartworkcount (asac) = 1 */
+  if (want_ased)
+    {
+      dmap_add_short(song, "ased", 1);
+      dmap_add_short(song, "asac", 1);
+    }
+
+  val = 0;
+  if (want_mikd)
+    val += 9;
+  if (want_asdk)
+    val += 9;
+
+  dmap_add_container(songlist, "mlit", evbuffer_get_length(song) + val);
+
+  /* Prepend mikd & asdk if needed */
+  if (want_mikd)
+    {
+      /* dmap.itemkind must come first */
+      val = 2; /* music by default */
+      dmap_add_char(songlist, "mikd", val);
+    }
+  if (want_asdk)
+    {
+      ret = queue_item->data_kind;
+      if (ret < 0)
+	val = 0;
+      dmap_add_char(songlist, "asdk", val);
+    }
+
+  ret = evbuffer_add_buffer(songlist, song);
+  if (ret < 0)
+    {
+      DPRINTF(E_LOG, L_DAAP, "Could not add song to song list\n");
+
+      return -1;
+    }
+
+  return 0;
+}
diff --git a/src/dmap_common.h b/src/dmap_common.h
index 620ab37..93a2bdc 100644
--- a/src/dmap_common.h
+++ b/src/dmap_common.h
@@ -2,6 +2,7 @@
 #ifndef __DMAP_HELPERS_H__
 #define __DMAP_HELPERS_H__
 
+#include "config.h"
 #include <event2/buffer.h>
 #include <event2/http.h>
 
@@ -46,7 +47,7 @@ dmap_get_fields_table(int *nfields);
 
 /* From dmap_fields.gperf - keep in sync, don't alter */
 const struct dmap_field *
-dmap_find_field (register const char *str, register unsigned int len);
+dmap_find_field (register const char *str, register GPERF_LEN_TYPE len);
 
 
 void
@@ -84,4 +85,7 @@ dmap_send_error(struct evhttp_request *req, const char *container, const char *e
 int
 dmap_encode_file_metadata(struct evbuffer *songlist, struct evbuffer *song, struct db_media_file_info *dbmfi, const struct dmap_field **meta, int nmeta, int sort_tags, int force_wav);
 
+int
+dmap_encode_queue_metadata(struct evbuffer *songlist, struct evbuffer *song, struct db_queue_item *queue_item);
+
 #endif /* !__DMAP_HELPERS_H__ */
diff --git a/src/evrtsp/evrtsp.h b/src/evrtsp/evrtsp.h
index cb42c29..7ee1f5c 100644
--- a/src/evrtsp/evrtsp.h
+++ b/src/evrtsp/evrtsp.h
@@ -46,6 +46,7 @@ extern "C" {
 /* Response codes */
 #define RTSP_OK			200
 #define RTSP_UNAUTHORIZED       401
+#define RTSP_FORBIDDEN          403
 
 struct evrtsp_connection;
 
@@ -62,6 +63,7 @@ enum evrtsp_cmd_type {
   EVRTSP_REQ_SET_PARAMETER,
   EVRTSP_REQ_FLUSH,
   EVRTSP_REQ_TEARDOWN,
+  EVRTSP_REQ_POST,
 };
 
 enum evrtsp_request_kind { EVRTSP_REQUEST, EVRTSP_RESPONSE };
@@ -142,10 +144,10 @@ void evrtsp_connection_set_base(struct evrtsp_connection *evcon,
 void evrtsp_connection_get_peer(struct evrtsp_connection *evcon,
     char **address, u_short *port);
 
-/** Get the local address and port associated with this connection. */
+/** Get the local address, port and family associated with this connection. */
 void
 evrtsp_connection_get_local_address(struct evrtsp_connection *evcon,
-    char **address, u_short *port);
+    char **address, u_short *port, int *family);
 
 /** The connection gets ownership of the request */
 int evrtsp_make_request(struct evrtsp_connection *evcon,
diff --git a/src/evrtsp/rtsp.c b/src/evrtsp/rtsp.c
index 77c8bca..d579b09 100644
--- a/src/evrtsp/rtsp.c
+++ b/src/evrtsp/rtsp.c
@@ -260,6 +260,10 @@ evrtsp_method(enum evrtsp_cmd_type type)
 	  method = "TEARDOWN";
 	  break;
 
+	case EVRTSP_REQ_POST:
+	  method = "POST";
+	  break;
+
 	default:
 	  method = NULL;
 	  break;
@@ -286,7 +290,7 @@ void
 evrtsp_write_buffer(struct evrtsp_connection *evcon,
     void (*cb)(struct evrtsp_connection *, void *), void *arg)
 {
-	event_debug(("%s: preparing to write buffer\n", __func__));
+	event_debug(("%s: preparing to write buffer", __func__));
 
 	/* Set call back */
 	evcon->cb = cb;
@@ -457,19 +461,20 @@ evrtsp_write(int fd, short what, void *arg)
 	int n;
 
 	if (what == EV_TIMEOUT) {
+		event_warn("%s: write timeout", __func__);
 		evrtsp_connection_fail(evcon, EVCON_RTSP_TIMEOUT);
 		return;
 	}
 
 	n = evbuffer_write(evcon->output_buffer, fd);
 	if (n == -1) {
-		event_debug(("%s: evbuffer_write", __func__));
+		event_warn("%s: evbuffer_write", __func__);
 		evrtsp_connection_fail(evcon, EVCON_RTSP_EOF);
 		return;
 	}
 
 	if (n == 0) {
-		event_debug(("%s: write nothing", __func__));
+		event_warn("%s: write nothing", __func__);
 		evrtsp_connection_fail(evcon, EVCON_RTSP_EOF);
 		return;
 	}
@@ -531,6 +536,7 @@ evrtsp_read_trailer(struct evrtsp_connection *evcon, struct evrtsp_request *req)
 
 	switch (evrtsp_parse_headers(req, buf)) {
 	case DATA_CORRUPTED:
+		event_warn("%s: invalid header", __func__);
 		evrtsp_connection_fail(evcon, EVCON_RTSP_INVALID_HEADER);
 		break;
 	case ALL_DATA_READ:
@@ -583,15 +589,16 @@ evrtsp_read(int fd, short what, void *arg)
 	int n;
 
 	if (what == EV_TIMEOUT) {
+		event_warn("%s: read timeout", __func__);
 		evrtsp_connection_fail(evcon, EVCON_RTSP_TIMEOUT);
 		return;
 	}
 	n = evbuffer_read(buf, fd, -1);
-	event_debug(("%s: got %d on %d\n", __func__, n, fd));
+	event_debug(("%s: got %d on %d", __func__, n, fd));
 	
 	if (n == -1) {
 		if (errno != EINTR && errno != EAGAIN) {
-			event_debug(("%s: evbuffer_read", __func__));
+			event_warn("%s: evbuffer_read", __func__);
 			evrtsp_connection_fail(evcon, EVCON_RTSP_EOF);
 		} else {
 			evrtsp_add_event(&evcon->ev, evcon->timeout,
@@ -760,7 +767,9 @@ static void
 evrtsp_connection_stop_detectclose(struct evrtsp_connection *evcon)
 {
 	evcon->flags &= ~EVRTSP_CON_CLOSEDETECT;
-	event_del(&evcon->close_ev);
+
+	if (event_initialized(&evcon->close_ev))
+		event_del(&evcon->close_ev);
 }
 
 /*
@@ -775,28 +784,28 @@ evrtsp_connectioncb(int fd, short what, void *arg)
   socklen_t errsz = sizeof(error);
 
   if (what == EV_TIMEOUT) {
-    event_debug(("%s: connection timeout for \"%s:%d\" on %d",
-		 __func__, evcon->address, evcon->port, evcon->fd));
+    event_warnx("%s: connection timeout for \"%s:%d\" on %d",
+		 __func__, evcon->address, evcon->port, evcon->fd);
     goto cleanup;
   }
 
   /* Check if the connection completed */
   if (getsockopt(evcon->fd, SOL_SOCKET, SO_ERROR, (void*)&error,
 		 &errsz) == -1) {
-    event_debug(("%s: getsockopt for \"%s:%d\" on %d",
-		 __func__, evcon->address, evcon->port, evcon->fd));
+    event_warnx("%s: getsockopt for \"%s:%d\" on %d",
+		 __func__, evcon->address, evcon->port, evcon->fd);
     goto cleanup;
   }
 
   if (error) {
-    event_debug(("%s: connect failed for \"%s:%d\" on %d: %s",
+    event_warnx("%s: connect failed for \"%s:%d\" on %d: %s",
 		 __func__, evcon->address, evcon->port, evcon->fd,
-		 strerror(error)));
+		 strerror(error));
     goto cleanup;
   }
 
   /* We are connected to the server now */
-  event_debug(("%s: connected to \"%s:%d\" on %d\n",
+  event_debug(("%s: connected to \"%s:%d\" on %d",
 	       __func__, evcon->address, evcon->port, evcon->fd));
 
   evcon->state = EVCON_IDLE;
@@ -856,15 +865,15 @@ evrtsp_parse_response_line(struct evrtsp_request *req, char *line)
 		req->major = 1;
 		req->minor = 1;
 	} else {
-		event_debug(("%s: bad protocol \"%s\"",
-			__func__, protocol));
+		event_warnx("%s: bad protocol \"%s\"",
+			__func__, protocol);
 		return (-1);
 	}
 
 	req->response_code = atoi(number);
 	if (!evrtsp_valid_response_code(req->response_code)) {
-		event_debug(("%s: bad response code \"%s\"",
-			__func__, number));
+		event_warnx("%s: bad response code \"%s\"",
+			__func__, number);
 		return (-1);
 	}
 
@@ -948,16 +957,16 @@ int
 evrtsp_add_header(struct evkeyvalq *headers,
     const char *key, const char *value)
 {
-	event_debug(("%s: key: %s val: %s\n", __func__, key, value));
+	event_debug(("%s: key: %s val: %s", __func__, key, value));
 
 	if (strchr(key, '\r') != NULL || strchr(key, '\n') != NULL) {
 		/* drop illegal headers */
-		event_debug(("%s: dropping illegal header key\n", __func__));
+		event_warn("%s: dropping illegal header key", __func__);
 		return (-1);
 	}
 	
 	if (!evrtsp_header_is_valid_value(value)) {
-		event_debug(("%s: dropping illegal header value\n", __func__));
+		event_warn("%s: dropping illegal header value", __func__);
 		return (-1);
 	}
 
@@ -1105,17 +1114,16 @@ evrtsp_get_body_length(struct evrtsp_request *req)
 	  /* If there is no Content-Length: header, a value of 0 is assumed, per spec. */
 		req->ntoread = 0;
 	} else {
-		char *endp;
-		ev_int64_t ntoread = evutil_strtoll(content_length, &endp, 10);
-		if (*content_length == '\0' || *endp != '\0' || ntoread < 0) {
-			event_debug(("%s: illegal content length: %s",
-				__func__, content_length));
+		ev_int64_t ntoread = evutil_strtoll(content_length, NULL, 10);
+		if (*content_length == '\0' || ntoread < 0) {
+			event_warnx("%s: illegal content length: %s",
+				__func__, content_length);
 			return (-1);
 		}
 		req->ntoread = ntoread;
 	}
 		
-	event_debug(("%s: bytes to read: %lld (in buffer %ld)\n",
+	event_debug(("%s: bytes to read: %lld (in buffer %zu)",
 		__func__, req->ntoread,
 		evbuffer_get_length(req->evcon->input_buffer)));
 
@@ -1127,8 +1135,9 @@ evrtsp_get_body(struct evrtsp_connection *evcon, struct evrtsp_request *req)
 {
 	evcon->state = EVCON_READING_BODY;
 	if (evrtsp_get_body_length(req) == -1) {
-	  evrtsp_connection_fail(evcon, EVCON_RTSP_INVALID_HEADER);
-	  return;
+		event_warn("%s: invalid body", __func__);
+		evrtsp_connection_fail(evcon, EVCON_RTSP_INVALID_HEADER);
+		return;
 	}
 
 	evrtsp_read_body(evcon, req);
@@ -1143,8 +1152,8 @@ evrtsp_read_firstline(struct evrtsp_connection *evcon,
 	res = evrtsp_parse_firstline(req, evcon->input_buffer);
 	if (res == DATA_CORRUPTED) {
 		/* Error while reading, terminate */
-		event_debug(("%s: bad header lines on %d\n",
-			__func__, evcon->fd));
+		event_warnx("%s: bad header lines on %d",
+			__func__, evcon->fd);
 		evrtsp_connection_fail(evcon, EVCON_RTSP_INVALID_HEADER);
 		return;
 	} else if (res == MORE_DATA_EXPECTED) {
@@ -1167,7 +1176,7 @@ evrtsp_read_header(struct evrtsp_connection *evcon, struct evrtsp_request *req)
 	res = evrtsp_parse_headers(req, evcon->input_buffer);
 	if (res == DATA_CORRUPTED) {
 		/* Error while reading, terminate */
-		event_debug(("%s: bad header lines on %d\n", __func__, fd));
+		event_warnx("%s: bad header lines on %d", __func__, fd);
 		evrtsp_connection_fail(evcon, EVCON_RTSP_INVALID_HEADER);
 		return;
 	} else if (res == MORE_DATA_EXPECTED) {
@@ -1180,7 +1189,7 @@ evrtsp_read_header(struct evrtsp_connection *evcon, struct evrtsp_request *req)
 	/* Done reading headers, do the real work */
 	switch (req->kind) {
 	case EVRTSP_RESPONSE:
-	  event_debug(("%s: start of read body on %d\n",
+	  event_debug(("%s: start of read body on %d",
 		       __func__, fd));
 	  evrtsp_get_body(evcon, req);
 	  break;
@@ -1233,7 +1242,7 @@ evrtsp_connection_new(const char *address, unsigned short port)
 	if (intf)
 	  *intf = '%';
 
-	event_debug(("Attempting connection to %s:%d\n", address, port));
+	event_debug(("Attempting connection to %s:%d", address, port));
 
 	if ((evcon = calloc(1, sizeof(struct evrtsp_connection))) == NULL) {
 		free(addr);
@@ -1297,7 +1306,7 @@ evrtsp_connection_set_closecb(struct evrtsp_connection *evcon,
 
 void
 evrtsp_connection_get_local_address(struct evrtsp_connection *evcon,
-    char **address, u_short *port)
+    char **address, u_short *port, int *family)
 {
   union {
     struct sockaddr_storage ss;
@@ -1324,7 +1333,9 @@ evrtsp_connection_get_local_address(struct evrtsp_connection *evcon,
   if (!*address)
     return;
 
-  switch (addr.ss.ss_family)
+  *family = addr.ss.ss_family;
+
+  switch (*family)
     {
       case AF_INET:
 	*port = ntohs(addr.sin.sin_port);
@@ -1364,8 +1375,8 @@ evrtsp_connection_connect(struct evrtsp_connection *evcon)
 	evcon->fd = bind_socket(evcon->family,
 		evcon->bind_address, evcon->bind_port, 0 /*reuse*/);
 	if (evcon->fd == -1) {
-		event_debug(("%s: failed to bind to \"%s\"",
-			__func__, evcon->bind_address));
+		event_warnx("%s: failed to bind to \"%s\"",
+			__func__, evcon->bind_address);
 		return (-1);
 	}
 
@@ -1394,6 +1405,8 @@ evrtsp_make_request(struct evrtsp_connection *evcon,
     struct evrtsp_request *req,
     enum evrtsp_cmd_type type, const char *uri)
 {
+event_debug(("%s: TEST", __func__));
+
 	/* We are making a request */
 	req->kind = EVRTSP_REQUEST;
 	req->type = type;
@@ -1563,7 +1576,7 @@ evrtsp_request_free(struct evrtsp_request *req)
 const char *
 evrtsp_request_uri(struct evrtsp_request *req) {
 	if (req->uri == NULL)
-		event_debug(("%s: request %p has no uri\n", __func__, req));
+		event_warn("%s: request has no uri", __func__);
 	return (req->uri);
 }
 
@@ -1754,8 +1767,8 @@ socket_connect(int fd, const char *address, unsigned short port)
 	int res = -1;
 
 	if (ai == NULL) {
-		event_debug(("%s: make_addrinfo: \"%s:%d\"",
-			__func__, address, port));
+		event_warnx("%s: make_addrinfo: \"%s:%d\"",
+			__func__, address, port);
 		return (-1);
 	}
 
diff --git a/src/ffmpeg-compat.h b/src/ffmpeg-compat.h
index 604de12..88d4868 100644
--- a/src/ffmpeg-compat.h
+++ b/src/ffmpeg-compat.h
@@ -13,25 +13,25 @@
 # define avcodec_find_best_pix_fmt_of_list(a, b, c, d) avcodec_find_best_pix_fmt2((enum AVPixelFormat *)(a), (b), (c), (d))
 #endif
 
-#ifndef HAVE_LIBAV_FRAME_ALLOC
+#if !HAVE_DECL_AV_FRAME_ALLOC
 # define av_frame_alloc() avcodec_alloc_frame()
 # define av_frame_free(x) avcodec_free_frame((x))
 #endif
 
-#ifndef HAVE_LIBAV_BEST_EFFORT_TIMESTAMP
+#if !HAVE_DECL_AV_FRAME_GET_BEST_EFFORT_TIMESTAMP
 # define av_frame_get_best_effort_timestamp(x) (x)->pts
 #endif
 
-#ifndef HAVE_LIBAV_IMAGE_GET_BUFFER_SIZE
+#if !HAVE_DECL_AV_IMAGE_GET_BUFFER_SIZE
 # define av_image_get_buffer_size(a, b, c, d) avpicture_get_size((a), (b), (c))
 #endif
 
-#ifndef HAVE_LIBAV_PACKET_UNREF
+#if !HAVE_DECL_AV_PACKET_UNREF
 # define av_packet_unref(a) av_free_packet((a))
 #endif
 
-#ifndef HAVE_LIBAV_PACKET_RESCALE_TS
-static void
+#if !HAVE_DECL_AV_PACKET_RESCALE_TS
+__attribute__((unused)) static void
 av_packet_rescale_ts(AVPacket *pkt, AVRational src_tb, AVRational dst_tb)
 {
     if (pkt->pts != AV_NOPTS_VALUE)
@@ -45,10 +45,10 @@ av_packet_rescale_ts(AVPacket *pkt, AVRational src_tb, AVRational dst_tb)
 }
 #endif
 
-#ifndef HAVE_LIBAV_ALLOC_OUTPUT_CONTEXT2
+#if !HAVE_DECL_AVFORMAT_ALLOC_OUTPUT_CONTEXT2
 # include <libavutil/opt.h>
 
-static int
+__attribute__((unused)) static int
 avformat_alloc_output_context2(AVFormatContext **avctx, AVOutputFormat *oformat, const char *format, const char *filename)
 {
     AVFormatContext *s = avformat_alloc_context();
diff --git a/src/filescanner.h b/src/filescanner.h
deleted file mode 100644
index 915f99b..0000000
--- a/src/filescanner.h
+++ /dev/null
@@ -1,51 +0,0 @@
-
-#ifndef __FILESCANNER_H__
-#define __FILESCANNER_H__
-
-#include "db.h"
-
-#define F_SCAN_TYPE_FILE         (1 << 0)
-#define F_SCAN_TYPE_PODCAST      (1 << 1)
-#define F_SCAN_TYPE_AUDIOBOOK    (1 << 2)
-#define F_SCAN_TYPE_COMPILATION  (1 << 3)
-#define F_SCAN_TYPE_URL          (1 << 4)
-#define F_SCAN_TYPE_SPOTIFY      (1 << 5)
-#define F_SCAN_TYPE_PIPE         (1 << 6)
-
-int
-filescanner_init(void);
-
-void
-filescanner_deinit(void);
-
-void
-filescanner_process_media(char *path, time_t mtime, off_t size, int type, struct media_file_info *external_mfi, int dir_id);
-
-/* Actual scanners */
-int
-scan_metadata_ffmpeg(char *file, struct media_file_info *mfi);
-
-int
-scan_metadata_icy(char *url, struct media_file_info *mfi);
-
-void
-scan_playlist(char *file, time_t mtime, int dir_id);
-
-void
-scan_smartpl(char *file, time_t mtime, int dir_id);
-
-#ifdef ITUNES
-void
-scan_itunes_itml(char *file);
-#endif
-
-void
-filescanner_trigger_initscan(void);
-
-void
-filescanner_trigger_fullrescan(void);
-
-int
-filescanner_scanning(void);
-
-#endif /* !__FILESCANNER_H__ */
diff --git a/src/http.c b/src/http.c
index 26c478c..e2ffc29 100644
--- a/src/http.c
+++ b/src/http.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 Espen Jürgensen <espenjurgensen at gmail.com>
+ * Copyright (C) 2016 Espen Jürgensen <espenjurgensen at gmail.com>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -36,6 +36,10 @@
 
 #include <event2/event.h>
 
+#ifdef HAVE_LIBCURL
+#include <curl/curl.h>
+#endif
+
 #include "http.h"
 #include "logger.h"
 #include "misc.h"
@@ -128,10 +132,10 @@ request_cb(struct evhttp_request *req, void *arg)
 
   ctx->ret = 0;
 
-  if (ctx->headers)
-    headers_save(ctx->headers, evhttp_request_get_input_headers(req));
-  if (ctx->body)
-    evbuffer_add_buffer(ctx->body, evhttp_request_get_input_buffer(req));
+  if (ctx->input_headers)
+    headers_save(ctx->input_headers, evhttp_request_get_input_headers(req));
+  if (ctx->input_body)
+    evbuffer_add_buffer(ctx->input_body, evhttp_request_get_input_buffer(req));
       
   event_base_loopbreak(ctx->evbase);
 
@@ -159,34 +163,38 @@ request_header_cb(struct evhttp_request *req, void *arg)
 
   ctx = (struct http_client_ctx *)arg;
 
-  if (!ctx->headers)
+  if (!ctx->input_headers)
     {
       DPRINTF(E_LOG, L_HTTP, "BUG: Header callback invoked but caller did not say where to save the headers\n");
       return -1;
     }
 
-  headers_save(ctx->headers, evhttp_request_get_input_headers(req));
+  headers_save(ctx->input_headers, evhttp_request_get_input_headers(req));
 
   return -1;
 }
 #endif
 
-int
-http_client_request(struct http_client_ctx *ctx)
+static int
+http_client_request_impl(struct http_client_ctx *ctx)
 {
   struct evhttp_connection *evcon;
   struct evhttp_request *req;
   struct evkeyvalq *headers;
-  char hostname[PATH_MAX];
-  char path[PATH_MAX];
-  char s[PATH_MAX];
+  struct evbuffer *output_buffer;
+  struct onekeyval *okv;
+  enum evhttp_cmd_type method;
+  char host[512];
+  char host_port[1024];
+  char path[2048];
+  char tmp[128];
   int port;
   int ret;
 
   ctx->ret = -1;
 
-  av_url_split(NULL, 0, NULL, 0, hostname, sizeof(hostname), &port, path, sizeof(path), ctx->url);
-  if (strlen(hostname) == 0)
+  av_url_split(NULL, 0, NULL, 0, host, sizeof(host), &port, path, sizeof(path), ctx->url);
+  if (strlen(host) == 0)
     {
       DPRINTF(E_LOG, L_HTTP, "Error extracting hostname from URL: %s\n", ctx->url);
 
@@ -194,9 +202,9 @@ http_client_request(struct http_client_ctx *ctx)
     }
 
   if (port <= 0)
-    snprintf(s, PATH_MAX, "%s", hostname);
+    snprintf(host_port, sizeof(host_port), "%s", host);
   else
-    snprintf(s, PATH_MAX, "%s:%d", hostname, port);
+    snprintf(host_port, sizeof(host_port), "%s:%d", host, port);
 
   if (port <= 0)
     port = 80;
@@ -215,10 +223,10 @@ http_client_request(struct http_client_ctx *ctx)
       return ctx->ret;
     }
 
-  evcon = evhttp_connection_base_new(ctx->evbase, NULL, hostname, (unsigned short)port);
+  evcon = evhttp_connection_base_new(ctx->evbase, NULL, host, (unsigned short)port);
   if (!evcon)
     {
-      DPRINTF(E_LOG, L_HTTP, "Could not create connection to %s\n", hostname);
+      DPRINTF(E_LOG, L_HTTP, "Could not create connection to %s\n", host_port);
 
       event_base_free(ctx->evbase);
       return ctx->ret;
@@ -230,7 +238,7 @@ http_client_request(struct http_client_ctx *ctx)
   req = evhttp_request_new(request_cb, ctx);
   if (!req)
     {
-      DPRINTF(E_LOG, L_HTTP, "Could not create request to %s\n", hostname);
+      DPRINTF(E_LOG, L_HTTP, "Could not create request to %s\n", host_port);
 
       evhttp_connection_free(evcon);
       event_base_free(ctx->evbase);
@@ -243,18 +251,38 @@ http_client_request(struct http_client_ctx *ctx)
 #endif
 
   headers = evhttp_request_get_output_headers(req);
-  evhttp_add_header(headers, "Host", s);
-  evhttp_add_header(headers, "Content-Length", "0");
+  evhttp_add_header(headers, "Host", host_port);
   evhttp_add_header(headers, "User-Agent", "forked-daapd/" VERSION);
   evhttp_add_header(headers, "Icy-MetaData", "1");
 
+  if (ctx->output_headers)
+    {
+      for (okv = ctx->output_headers->head; okv; okv = okv->next)
+	evhttp_add_header(headers, okv->name, okv->value);
+    }
+
+  if (ctx->output_body)
+    {
+      output_buffer = evhttp_request_get_output_buffer(req);
+      evbuffer_add(output_buffer, ctx->output_body, strlen(ctx->output_body));
+      evbuffer_add_printf(output_buffer, "\n");
+      snprintf(tmp, sizeof(tmp), "%zu", evbuffer_get_length(output_buffer));
+      evhttp_add_header(headers, "Content-Length", tmp);
+      method = EVHTTP_REQ_POST;
+    }
+  else
+    {
+      evhttp_add_header(headers, "Content-Length", "0");
+      method = EVHTTP_REQ_GET;
+    }
+
   /* Make request */
-  DPRINTF(E_INFO, L_HTTP, "Making request for http://%s%s\n", s, path);
+  DPRINTF(E_INFO, L_HTTP, "Making %s request for http://%s%s\n", ((method==EVHTTP_REQ_GET) ? "GET" : "POST"), host_port, path);
 
-  ret = evhttp_make_request(evcon, req, EVHTTP_REQ_GET, path);
+  ret = evhttp_make_request(evcon, req, method, path);
   if (ret < 0)
     {
-      DPRINTF(E_LOG, L_HTTP, "Error making request for http://%s%s\n", s, path);
+      DPRINTF(E_LOG, L_HTTP, "Error making request for http://%s%s\n", host_port, path);
 
       evhttp_connection_free(evcon);
       event_base_free(ctx->evbase);
@@ -269,6 +297,144 @@ http_client_request(struct http_client_ctx *ctx)
   return ctx->ret;
 }
 
+#ifdef HAVE_LIBCURL
+static size_t
+curl_request_cb(char *ptr, size_t size, size_t nmemb, void *userdata)
+{
+  size_t realsize;
+  struct http_client_ctx *ctx;
+  int ret;
+
+  realsize = size * nmemb;
+  ctx = (struct http_client_ctx *)userdata;
+
+  if (!ctx->input_body)
+    return realsize;
+
+  ret = evbuffer_add(ctx->input_body, ptr, realsize);
+  if (ret < 0)
+    {
+      DPRINTF(E_LOG, L_HTTP, "Error adding reply from %s to input buffer\n", ctx->url);
+      return 0;
+    }
+
+  return realsize;
+}
+
+static int
+https_client_request_impl(struct http_client_ctx *ctx)
+{
+  CURL *curl;
+  CURLcode res;
+  struct curl_slist *headers;
+  struct onekeyval *okv;
+  char header[1024];
+
+  curl = curl_easy_init();
+  if (!curl)
+    {
+      DPRINTF(E_LOG, L_HTTP, "Error: Could not get curl handle\n");
+      return -1;
+    }
+
+  curl_easy_setopt(curl, CURLOPT_URL, ctx->url);
+  curl_easy_setopt(curl, CURLOPT_USERAGENT, "forked-daapd/" VERSION);
+
+  if (ctx->output_headers)
+    {
+      headers = NULL;
+      for (okv = ctx->output_headers->head; okv; okv = okv->next)
+	{
+	  snprintf(header, sizeof(header), "%s: %s", okv->name, okv->value);
+	  headers = curl_slist_append(headers, header);
+        }
+
+      curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
+    }
+
+  if (ctx->output_body)
+    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, ctx->output_body);
+
+  curl_easy_setopt(curl, CURLOPT_TIMEOUT, HTTP_CLIENT_TIMEOUT);
+
+  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_request_cb);
+  curl_easy_setopt(curl, CURLOPT_WRITEDATA, ctx);
+
+  /* Make request */
+  DPRINTF(E_INFO, L_HTTP, "Making request for %s\n", ctx->url);
+
+  res = curl_easy_perform(curl);
+  if (res != CURLE_OK)
+    {
+      DPRINTF(E_LOG, L_HTTP, "Request to %s failed: %s\n", ctx->url, curl_easy_strerror(res));
+      curl_easy_cleanup(curl);
+      return -1;
+    }
+
+  curl_easy_cleanup(curl);
+
+  return 0;
+}
+#endif /* HAVE_LIBCURL */
+
+int
+http_client_request(struct http_client_ctx *ctx)
+{
+  if (strncmp(ctx->url, "http:", strlen("http:")) == 0)
+    return http_client_request_impl(ctx);
+
+#ifdef HAVE_LIBCURL
+  if (strncmp(ctx->url, "https:", strlen("https:")) == 0)
+    return https_client_request_impl(ctx);
+#endif
+
+  DPRINTF(E_LOG, L_HTTP, "Request for %s is not supported (not built with libcurl?)\n", ctx->url);
+  return -1;
+}
+
+char *
+http_form_urlencode(struct keyval *kv)
+{
+  struct evbuffer *evbuf;
+  struct onekeyval *okv;
+  char *body;
+  char *k;
+  char *v;
+
+  evbuf = evbuffer_new();
+
+  for (okv = kv->head; okv; okv = okv->next)
+    {
+      k = evhttp_encode_uri(okv->name);
+      if (!k)
+        continue;
+
+      v = evhttp_encode_uri(okv->value);
+      if (!v)
+	{
+	  free(k);
+	  continue;
+	}
+
+      evbuffer_add_printf(evbuf, "%s=%s", k, v);
+      if (okv->next)
+	evbuffer_add_printf(evbuf, "&");
+
+      free(k);
+      free(v);
+    }
+
+  evbuffer_add(evbuf, "\n", 1);
+
+  body = evbuffer_readln(evbuf, NULL, EVBUFFER_EOL_ANY);
+
+  evbuffer_free(evbuf);
+
+  DPRINTF(E_DBG, L_HTTP, "Parameters in request are: %s\n", body);
+
+  return body;
+}
+
 int
 http_stream_setup(char **stream, const char *url)
 {
@@ -282,7 +448,7 @@ http_stream_setup(char **stream, const char *url)
   *stream = NULL;
 
   ext = strrchr(url, '.');
-  if (strcasecmp(ext, ".m3u") != 0)
+  if (!ext || (strcasecmp(ext, ".m3u") != 0))
     {
       *stream = strdup(url);
       return 0;
@@ -296,7 +462,7 @@ http_stream_setup(char **stream, const char *url)
     return -1;
 
   ctx.url = url;
-  ctx.body = evbuf;
+  ctx.input_body = evbuf;
 
   ret = http_client_request(&ctx);
   if (ret < 0)
@@ -308,13 +474,13 @@ http_stream_setup(char **stream, const char *url)
     }
 
   // Pad with CRLF because evbuffer_readln() might not read the last line otherwise
-  evbuffer_add(ctx.body, "\r\n", 2);
+  evbuffer_add(ctx.input_body, "\r\n", 2);
 
   /* Read the playlist until the first stream link is found, but give up if
    * nothing is found in the first 10 lines
    */
   n = 0;
-  while ((line = evbuffer_readln(ctx.body, NULL, EVBUFFER_EOL_ANY)) && (n < 10))
+  while ((line = evbuffer_readln(ctx.input_body, NULL, EVBUFFER_EOL_ANY)) && (n < 10))
     {
       n++;
       if (strncasecmp(line, "http://", strlen("http://")) == 0)
@@ -328,7 +494,7 @@ http_stream_setup(char **stream, const char *url)
       free(line);
     }
 
-  evbuffer_free(ctx.body);
+  evbuffer_free(ctx.input_body);
 
   if (n != -1)
     {
@@ -529,9 +695,9 @@ http_icy_metadata_get(AVFormatContext *fmtctx, int packet_only)
 
   memset(&ctx, 0, sizeof(struct http_client_ctx));
   ctx.url = fmtctx->filename;
-  ctx.headers = kv;
+  ctx.input_headers = kv;
   ctx.headers_only = 1;
-  ctx.body = NULL;
+  ctx.input_body = NULL;
 
   ret = http_client_request(&ctx);
   if (ret < 0)
@@ -548,17 +714,17 @@ http_icy_metadata_get(AVFormatContext *fmtctx, int packet_only)
   memset(metadata, 0, sizeof(struct http_icy_metadata));
 
   got_header = 0;
-  if ( (value = keyval_get(ctx.headers, "icy-name")) )
+  if ( (value = keyval_get(ctx.input_headers, "icy-name")) )
     {
       metadata->name = strdup(value);
       got_header = 1;
     }
-  if ( (value = keyval_get(ctx.headers, "icy-description")) )
+  if ( (value = keyval_get(ctx.input_headers, "icy-description")) )
     {
       metadata->description = strdup(value);
       got_header = 1;
     }
-  if ( (value = keyval_get(ctx.headers, "icy-genre")) )
+  if ( (value = keyval_get(ctx.input_headers, "icy-genre")) )
     {
       metadata->genre = strdup(value);
       got_header = 1;
diff --git a/src/http.h b/src/http.h
index 62ba912..6ab2485 100644
--- a/src/http.h
+++ b/src/http.h
@@ -10,13 +10,18 @@
 
 struct http_client_ctx
 {
+  /* Destination URL, header and body of outgoing request body. If output_body
+   * is set, the request will be POST, otherwise it will be GET
+   */
   const char *url;
+  struct keyval *output_headers;
+  char *output_body;
 
   /* A keyval/evbuf to store response headers and body.
    * Can be set to NULL to ignore that part of the response.
    */
-  struct keyval *headers;
-  struct evbuffer *body;
+  struct keyval *input_headers;
+  struct evbuffer *input_body;
 
   /* Cut the connection after the headers have been received
    * Used for getting Shoutcast/ICY headers for old versions of libav/ffmpeg
@@ -47,15 +52,27 @@ struct http_icy_metadata
 };
 
 
-/* Generic HTTP client. No support for https.
+/* Make a http(s) request. We use libcurl to make https requests. We could use
+ * libevent and avoid the dependency, but for SSL, libevent needs to be v2.1
+ * or better, which is still a bit too new to be in the major distros.
  *
  * @param ctx HTTP request params, see above
- * @return 0 if successful, -1 if an error occurred
+ * @return 0 if successful, -1 if an error occurred (e.g. no libcurl)
  */
 int
 http_client_request(struct http_client_ctx *ctx);
 
 
+/* Converts the keyval dictionary to a application/x-www-form-urlencoded string.
+ * The values will be uri_encoded. Example output: "key1=foo%20bar&key2=123".
+ *
+ * @param kv is the struct containing the parameters
+ * @return encoded string if succesful, NULL if an error occurred
+ */
+char *
+http_form_urlencode(struct keyval *kv);
+
+
 /* Returns a newly allocated string with the first stream in the m3u given in
  * url. If url is not a m3u, the string will be a copy of url.
  *
diff --git a/src/httpd.c b/src/httpd.c
index 800c5e1..af45ecc 100644
--- a/src/httpd.c
+++ b/src/httpd.c
@@ -39,11 +39,11 @@
 #include <stdint.h>
 #include <inttypes.h>
 
-#if defined(HAVE_SYS_EVENTFD_H) && defined(HAVE_EVENTFD)
-# define USE_EVENTFD
+#ifdef HAVE_EVENTFD
 # include <sys/eventfd.h>
 #endif
 #include <event2/event.h>
+#include <event2/keyvalq_struct.h>
 #ifdef HAVE_LIBEVENT2_OLD
 # include <event2/bufferevent.h>
 # include <event2/bufferevent_struct.h>
@@ -64,6 +64,9 @@
 #ifdef LASTFM
 # include "lastfm.h"
 #endif
+#ifdef HAVE_SPOTIFY_H
+# include "spotify.h"
+#endif
 
 /*
  * HTTP client quirks by User-Agent, from mt-daapd
@@ -129,7 +132,7 @@ static const struct content_type_map ext2ctype[] =
 
 struct event_base *evbase_httpd;
 
-#ifdef USE_EVENTFD
+#ifdef HAVE_EVENTFD
 static int exit_efd;
 #else
 static int exit_pipe[2];
@@ -139,7 +142,8 @@ static struct event *exitev;
 static struct evhttp *evhttpd;
 static pthread_t tid_httpd;
 
-static char *allow_origin;
+static const char *allow_origin;
+static int httpd_port;
 
 #ifdef HAVE_LIBEVENT2_OLD
 struct stream_ctx *g_st;
@@ -199,6 +203,63 @@ scrobble_cb(void *arg)
 #endif
 
 static void
+oauth_interface(struct evhttp_request *req, const char *uri)
+{
+  struct evbuffer *evbuf;
+  struct evkeyvalq query;
+  const char *req_uri;
+  const char *ptr;
+  char __attribute__((unused)) redirect_uri[256];
+  int ret;
+
+  req_uri = evhttp_request_get_uri(req);
+
+  evbuf = evbuffer_new();
+  if (!evbuf)
+    {
+      DPRINTF(E_LOG, L_HTTPD, "Could not alloc evbuf for oauth\n");
+      return;
+    }
+
+  evbuffer_add_printf(evbuf, "<H1>forked-daapd oauth</H1>\n\n");
+
+  memset(&query, 0, sizeof(struct evkeyvalq));
+
+  ptr = strchr(req_uri, '?');
+  if (ptr)
+    {
+      ret = evhttp_parse_query_str(ptr + 1, &query);
+      if (ret < 0)
+	{
+	  evbuffer_add_printf(evbuf, "OAuth error: Could not parse parameters in callback (%s)\n", req_uri);
+
+	  httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0);
+	  evbuffer_free(evbuf);
+	  return;
+	}
+    }
+
+#ifdef HAVE_SPOTIFY_H
+  snprintf(redirect_uri, sizeof(redirect_uri), "http://forked-daapd.local:%d/oauth/spotify", httpd_port);
+
+  if (strncmp(uri, "/oauth/spotify", strlen("/oauth/spotify")) == 0)
+    spotify_oauth_callback(evbuf, &query, redirect_uri);
+  else
+    spotify_oauth_interface(evbuf, redirect_uri);
+#else
+  evbuffer_add_printf(evbuf, "<p>This version was built without modules requiring OAuth support</p>\n");
+#endif
+
+  evbuffer_add_printf(evbuf, "<p><i>(sorry about this ugly interface)</i></p>\n");
+
+  evhttp_clear_headers(&query);
+
+  httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0);
+
+  evbuffer_free(evbuf);
+}
+
+static void
 stream_end_register(struct stream_ctx *st)
 {
   if (!st->marked
@@ -491,7 +552,7 @@ httpd_stream_file(struct evhttp_request *req, int id)
 
       stream_cb = stream_chunk_xcode_cb;
 
-      st->xcode = transcode_setup(mfi, XCODE_PCM16_HEADER, &st->size);
+      st->xcode = transcode_setup(mfi->data_kind, mfi->path, mfi->song_length, XCODE_PCM16_HEADER, &st->size);
       if (!st->xcode)
 	{
 	  DPRINTF(E_WARN, L_HTTPD, "Transcoding setup failed, aborting streaming\n");
@@ -875,11 +936,11 @@ static void
 serve_file(struct evhttp_request *req, char *uri)
 {
   const char *host;
+  const char *passwd;
   char *ext;
   char path[PATH_MAX];
   char *deref;
   char *ctype;
-  char *passwd;
   struct evbuffer *evbuf;
   struct evkeyvalq *headers;
   struct stat sb;
@@ -912,6 +973,12 @@ serve_file(struct evhttp_request *req, char *uri)
 	}
     }
 
+  if (strncmp(uri, "/oauth", strlen("/oauth")) == 0)
+    {
+      oauth_interface(req, uri);
+      return;
+    }
+
   ret = snprintf(path, sizeof(path), "%s%s", WEBFACE_ROOT, uri + 1); /* skip starting '/' */
   if ((ret < 0) || (ret >= sizeof(path)))
     {
@@ -1244,15 +1311,14 @@ httpd_fixup_uri(struct evhttp_request *req)
 static const char *http_reply_401 = "<html><head><title>401 Unauthorized</title></head><body>Authorization required</body></html>";
 
 int
-httpd_basic_auth(struct evhttp_request *req, char *user, char *passwd, char *realm)
+httpd_basic_auth(struct evhttp_request *req, const char *user, const char *passwd, const char *realm)
 {
   struct evbuffer *evbuf;
   struct evkeyvalq *headers;
-  char *header;
+  char header[256];
   const char *auth;
   char *authuser;
   char *authpwd;
-  int len;
   int ret;
 
   headers = evhttp_request_get_input_headers(req);
@@ -1317,16 +1383,8 @@ httpd_basic_auth(struct evhttp_request *req, char *user, char *passwd, char *rea
   return 0;
 
  need_auth:
-  len = strlen(realm) + strlen("Basic realm=") + 3;
-  header = (char *)malloc(len);
-  if (!header)
-    {
-      httpd_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
-      return -1;
-    }
-
-  ret = snprintf(header, len, "Basic realm=\"%s\"", realm);
-  if ((ret < 0) || (ret >= len))
+  ret = snprintf(header, sizeof(header), "Basic realm=\"%s\"", realm);
+  if ((ret < 0) || (ret >= sizeof(header)))
     {
       httpd_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
       return -1;
@@ -1346,7 +1404,6 @@ httpd_basic_auth(struct evhttp_request *req, char *user, char *passwd, char *rea
 
   httpd_send_reply(req, 401, "Unauthorized", evbuf, HTTPD_SEND_NO_GZIP);
 
-  free(header);
   evbuffer_free(evbuf);
 
   return -1;
@@ -1357,7 +1414,6 @@ int
 httpd_init(void)
 {
   int v6enabled;
-  unsigned short port;
   int ret;
 
   httpd_exit = 0;
@@ -1396,7 +1452,7 @@ httpd_init(void)
 
   streaming_init();
 
-#ifdef USE_EVENTFD
+#ifdef HAVE_EVENTFD
   exit_efd = eventfd(0, EFD_CLOEXEC);
   if (exit_efd < 0)
     {
@@ -1420,7 +1476,7 @@ httpd_init(void)
     }
 
   exitev = event_new(evbase_httpd, exit_pipe[0], EV_READ, exit_cb, NULL);
-#endif /* USE_EVENTFD */
+#endif /* HAVE_EVENTFD */
   if (!exitev)
     {
       DPRINTF(E_FATAL, L_HTTPD, "Could not create exit event\n");
@@ -1438,7 +1494,7 @@ httpd_init(void)
     }
 
   v6enabled = cfg_getbool(cfg_getsec(cfg, "general"), "ipv6");
-  port = cfg_getint(cfg_getsec(cfg, "library"), "port");
+  httpd_port = cfg_getint(cfg_getsec(cfg, "library"), "port");
 
   // For CORS headers
   allow_origin = cfg_getstr(cfg_getsec(cfg, "general"), "allow_origin");
@@ -1452,20 +1508,20 @@ httpd_init(void)
 
   if (v6enabled)
     {
-      ret = evhttp_bind_socket(evhttpd, "::", port);
+      ret = evhttp_bind_socket(evhttpd, "::", httpd_port);
       if (ret < 0)
 	{
-	  DPRINTF(E_LOG, L_HTTPD, "Could not bind to port %d with IPv6, falling back to IPv4\n", port);
+	  DPRINTF(E_LOG, L_HTTPD, "Could not bind to port %d with IPv6, falling back to IPv4\n", httpd_port);
 	  v6enabled = 0;
 	}
     }
 
   if (!v6enabled)
     {
-      ret = evhttp_bind_socket(evhttpd, "0.0.0.0", port);
+      ret = evhttp_bind_socket(evhttpd, "0.0.0.0", httpd_port);
       if (ret < 0)
 	{
-	  DPRINTF(E_FATAL, L_HTTPD, "Could not bind to port %d (forked-daapd already running?)\n", port);
+	  DPRINTF(E_FATAL, L_HTTPD, "Could not bind to port %d (forked-daapd already running?)\n", httpd_port);
 	  goto bind_fail;
 	}
     }
@@ -1492,7 +1548,7 @@ httpd_init(void)
  bind_fail:
   evhttp_free(evhttpd);
  event_fail:
-#ifdef USE_EVENTFD
+#ifdef HAVE_EVENTFD
   close(exit_efd);
 #else
   close(exit_pipe[0]);
@@ -1517,7 +1573,7 @@ httpd_deinit(void)
 {
   int ret;
 
-#ifdef USE_EVENTFD
+#ifdef HAVE_EVENTFD
   ret = eventfd_write(exit_efd, 1);
   if (ret < 0)
     {
@@ -1550,7 +1606,7 @@ httpd_deinit(void)
   dacp_deinit();
   daap_deinit();
 
-#ifdef USE_EVENTFD
+#ifdef HAVE_EVENTFD
   close(exit_efd);
 #else
   close(exit_pipe[0]);
diff --git a/src/httpd.h b/src/httpd.h
index 7cd0f8f..bec6efc 100644
--- a/src/httpd.h
+++ b/src/httpd.h
@@ -56,7 +56,7 @@ char *
 httpd_fixup_uri(struct evhttp_request *req);
 
 int
-httpd_basic_auth(struct evhttp_request *req, char *user, char *passwd, char *realm);
+httpd_basic_auth(struct evhttp_request *req, const char *user, const char *passwd, const char *realm);
 
 int
 httpd_init(void);
diff --git a/src/httpd_daap.c b/src/httpd_daap.c
index 22d69f4..aef2fe0 100644
--- a/src/httpd_daap.c
+++ b/src/httpd_daap.c
@@ -666,15 +666,10 @@ get_query_params(struct evkeyvalq *query, int *sort_headers, struct query_params
     {
       *sort_headers = 0;
       param = evhttp_find_header(query, "include-sort-headers");
-      if (param)
+      if (param && (strcmp(param, "1") == 0))
 	{
-	  if (strcmp(param, "1") == 0)
-	    {
-	      *sort_headers = 1;
-	      DPRINTF(E_DBG, L_DAAP, "Sort headers requested\n");
-	    }
-	  else
-	    DPRINTF(E_DBG, L_DAAP, "Unknown include-sort-headers param: %s\n", param);
+	  *sort_headers = 1;
+	  DPRINTF(E_SPAM, L_DAAP, "Sort headers requested\n");
 	}
     }
 
@@ -1661,7 +1656,8 @@ daap_reply_playlists(struct evhttp_request *req, struct evbuffer *evbuf, char **
   memset(&qp, 0, sizeof(struct query_params));
   get_query_params(query, NULL, &qp);
   qp.type = Q_PL;
-  qp.sort = S_PLAYLIST;
+  if (qp.sort == S_NONE)
+    qp.sort = S_PLAYLIST;
 
   ret = db_query_start(&qp);
   if (ret < 0)
@@ -1876,15 +1872,22 @@ daap_reply_groups(struct evhttp_request *req, struct evbuffer *evbuf, char **uri
   param = evhttp_find_header(query, "group-type");
   if (strcmp(param, "artists") == 0)
     {
+      // Request from Remote may have the form:
+      //  groups?meta=dmap.xxx,dma...&type=music&group-type=artists&sort=album&include-sort-headers=1&query=('...')&session-id=...
+      // Note: Since grouping by artist and sorting by album is crazy we override
       tag = "agar";
       qp.type = Q_GROUP_ARTISTS;
       qp.sort = S_ARTIST;
     }
   else
     {
+      // Request from Remote may have the form:
+      //  groups?meta=dmap.xxx,dma...&type=music&group-type=albums&sort=artist&include-sort-headers=0&query=('...'))&session-id=...
+      // Sort may also be 'album'
       tag = "agal";
       qp.type = Q_GROUP_ALBUMS;
-      qp.sort = S_ALBUM;
+      if (qp.sort == S_NONE)
+	qp.sort = S_ALBUM;
     }
 
   ret = evbuffer_expand(evbuf, 61);
diff --git a/src/httpd_dacp.c b/src/httpd_dacp.c
index 4d7b81a..5b97327 100644
--- a/src/httpd_dacp.c
+++ b/src/httpd_dacp.c
@@ -32,8 +32,7 @@
 #include <stdint.h>
 #include <inttypes.h>
 
-#if defined(HAVE_SYS_EVENTFD_H) && defined(HAVE_EVENTFD)
-# define USE_EVENTFD
+#ifdef HAVE_EVENTFD
 # include <sys/eventfd.h>
 #endif
 
@@ -51,7 +50,6 @@
 #include "db.h"
 #include "daap_query.h"
 #include "player.h"
-#include "queue.h"
 #include "listener.h"
 
 /* httpd event base, from httpd.c */
@@ -76,7 +74,7 @@ struct dacp_update_request {
   struct dacp_update_request *next;
 };
 
-typedef void (*dacp_propget)(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi);
+typedef void (*dacp_propget)(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item);
 typedef void (*dacp_propset)(const char *value, struct evkeyvalq *query);
 
 struct dacp_prop_map {
@@ -88,40 +86,40 @@ struct dacp_prop_map {
 
 /* Forward - properties getters */
 static void
-dacp_propget_volume(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi);
+dacp_propget_volume(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item);
 static void
-dacp_propget_volumecontrollable(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi);
+dacp_propget_volumecontrollable(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item);
 static void
-dacp_propget_playerstate(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi);
+dacp_propget_playerstate(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item);
 static void
-dacp_propget_shufflestate(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi);
+dacp_propget_shufflestate(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item);
 static void
-dacp_propget_availableshufflestates(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi);
+dacp_propget_availableshufflestates(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item);
 static void
-dacp_propget_repeatstate(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi);
+dacp_propget_repeatstate(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item);
 static void
-dacp_propget_availablerepeatstates(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi);
+dacp_propget_availablerepeatstates(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item);
 static void
-dacp_propget_nowplaying(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi);
+dacp_propget_nowplaying(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item);
 static void
-dacp_propget_playingtime(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi);
+dacp_propget_playingtime(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item);
 
 static void
-dacp_propget_fullscreenenabled(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi);
+dacp_propget_fullscreenenabled(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item);
 static void
-dacp_propget_fullscreen(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi);
+dacp_propget_fullscreen(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item);
 static void
-dacp_propget_visualizerenabled(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi);
+dacp_propget_visualizerenabled(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item);
 static void
-dacp_propget_visualizer(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi);
+dacp_propget_visualizer(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item);
 static void
-dacp_propget_itms_songid(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi);
+dacp_propget_itms_songid(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item);
 static void
-dacp_propget_haschapterdata(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi);
+dacp_propget_haschapterdata(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item);
 static void
-dacp_propget_mediakind(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi);
+dacp_propget_mediakind(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item);
 static void
-dacp_propget_extendedmediakind(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi);
+dacp_propget_extendedmediakind(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item);
 
 /* Forward - properties setters */
 static void
@@ -137,11 +135,11 @@ dacp_propset_userrating(const char *value, struct evkeyvalq *query);
 
 
 /* gperf static hash, dacp_prop.gperf */
-#include "dacp_prop_hash.c"
+#include "dacp_prop_hash.h"
 
 
 /* Play status update */
-#ifdef USE_EVENTFD
+#ifdef HAVE_EVENTFD
 static int update_efd;
 #else
 static int update_pipe[2];
@@ -165,16 +163,25 @@ static struct media_file_info dummy_mfi =
   .album = "(unknown album)",
   .genre = "(unknown genre)",
 };
+static struct db_queue_item dummy_queue_item =
+{
+  .file_id = 9999999,
+  .title = "(unknown title)",
+  .artist = "(unknown artist)",
+  .album = "(unknown album)",
+  .genre = "(unknown genre)",
+};
 
 
 /* DACP helpers */
 static void
-dacp_nowplaying(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi)
+dacp_nowplaying(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item)
 {
   uint32_t id;
   int64_t songalbumid;
+  int pos_pl;
 
-  if ((status->status == PLAY_STOPPED) || !mfi)
+  if ((status->status == PLAY_STOPPED) || !queue_item)
     return;
 
   /* Send bogus id's if playing internet radio, because clients like
@@ -183,44 +190,46 @@ dacp_nowplaying(struct evbuffer *evbuf, struct player_status *status, struct med
    * FIXME: Giving the client invalid ids on purpose is hardly ideal, but the
    * clients don't seem to use these ids for anything other than rating.
    */
-  if (mfi->data_kind == DATA_KIND_HTTP)
+  if (queue_item->data_kind == DATA_KIND_HTTP)
     {
-      id = djb_hash(mfi->album, strlen(mfi->album));
+      id = djb_hash(queue_item->album, strlen(queue_item->album));
       songalbumid = (int64_t)id;
     }
   else
     {
       id = status->id;
-      songalbumid = mfi->songalbumid;
+      songalbumid = queue_item->songalbumid;
     }
 
+  pos_pl = db_queue_get_pos(status->item_id, 0);
+
   dmap_add_container(evbuf, "canp", 16);
   dmap_add_raw_uint32(evbuf, 1); /* Database */
   dmap_add_raw_uint32(evbuf, status->plid);
-  dmap_add_raw_uint32(evbuf, status->pos_pl);
+  dmap_add_raw_uint32(evbuf, pos_pl);
   dmap_add_raw_uint32(evbuf, id);
 
-  dmap_add_string(evbuf, "cann", mfi->title);
-  dmap_add_string(evbuf, "cana", mfi->artist);
-  dmap_add_string(evbuf, "canl", mfi->album);
-  dmap_add_string(evbuf, "cang", mfi->genre);
+  dmap_add_string(evbuf, "cann", queue_item->title);
+  dmap_add_string(evbuf, "cana", queue_item->artist);
+  dmap_add_string(evbuf, "canl", queue_item->album);
+  dmap_add_string(evbuf, "cang", queue_item->genre);
   dmap_add_long(evbuf, "asai", songalbumid);
 
   dmap_add_int(evbuf, "cmmk", 1);
 }
 
 static void
-dacp_playingtime(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi)
+dacp_playingtime(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item)
 {
-  if ((status->status == PLAY_STOPPED) || !mfi)
+  if ((status->status == PLAY_STOPPED) || !queue_item)
     return;
 
-  if (mfi->song_length)
-    dmap_add_int(evbuf, "cant", mfi->song_length - status->pos_ms); /* Remaining time in ms */
+  if (queue_item->song_length)
+    dmap_add_int(evbuf, "cant", queue_item->song_length - status->pos_ms); /* Remaining time in ms */
   else
     dmap_add_int(evbuf, "cant", 0); /* Unknown remaining time */
 
-  dmap_add_int(evbuf, "cast", mfi->song_length); /* Song length in ms */
+  dmap_add_int(evbuf, "cast", queue_item->song_length); /* Song length in ms */
 }
 
 
@@ -229,7 +238,7 @@ static int
 make_playstatusupdate(struct evbuffer *evbuf)
 {
   struct player_status status;
-  struct media_file_info *mfi;
+  struct db_queue_item *queue_item = NULL;
   struct evbuffer *psu;
   int ret;
 
@@ -245,16 +254,14 @@ make_playstatusupdate(struct evbuffer *evbuf)
 
   if (status.status != PLAY_STOPPED)
     {
-      mfi = db_file_fetch_byid(status.id);
-      if (!mfi)
+      queue_item = db_queue_fetch_byitemid(status.item_id);
+      if (!queue_item)
 	{
-	  DPRINTF(E_LOG, L_DACP, "Could not fetch file id %d\n", status.id);
+	  DPRINTF(E_LOG, L_DACP, "Could not fetch item id %d (file id %d)\n", status.item_id, status.id);
 
-	  mfi = &dummy_mfi;
+	  queue_item = &dummy_queue_item;
 	}
     }
-  else
-    mfi = NULL;
 
   dmap_add_int(psu, "mstt", 200);         /* 12 */
 
@@ -271,19 +278,19 @@ make_playstatusupdate(struct evbuffer *evbuf)
   dmap_add_char(psu, "cafe", 0);              /*  9 */ /* dacp.fullscreenenabled */
   dmap_add_char(psu, "cave", 0);              /*  9 */ /* dacp.visualizerenabled */
 
-  if (mfi)
+  if (queue_item)
     {
-      dacp_nowplaying(psu, &status, mfi);
+      dacp_nowplaying(psu, &status, queue_item);
 
       dmap_add_int(psu, "casa", 1);           /* 12 */ /* unknown */
-      dmap_add_int(psu, "astm", mfi->song_length);
+      dmap_add_int(psu, "astm", queue_item->song_length);
       dmap_add_char(psu, "casc", 1);         /* Maybe an indication of extra data? */
       dmap_add_char(psu, "caks", 6);         /* Unknown */
 
-      dacp_playingtime(psu, &status, mfi);
+      dacp_playingtime(psu, &status, queue_item);
 
-      if (mfi != &dummy_mfi)
-	free_mfi(mfi, 0);
+      if (queue_item != &dummy_queue_item)
+	free_queue_item(queue_item, 0);
     }
 
   dmap_add_char(psu, "casu", 1);              /*  9 */ /* unknown */
@@ -314,7 +321,7 @@ playstatusupdate_cb(int fd, short what, void *arg)
   size_t len;
   int ret;
 
-#ifdef USE_EVENTFD
+#ifdef HAVE_EVENTFD
   eventfd_t count;
 
   ret = eventfd_read(update_efd, &count);
@@ -398,7 +405,7 @@ dacp_playstatus_update_handler(enum listener_event_type type)
   if (type != LISTENER_PLAYER)
     return;
 
-#ifdef USE_EVENTFD
+#ifdef HAVE_EVENTFD
   ret = eventfd_write(update_efd, 1);
   if (ret < 0)
     DPRINTF(E_LOG, L_DACP, "Could not send status update event: %s\n", strerror(errno));
@@ -448,103 +455,103 @@ update_fail_cb(struct evhttp_connection *evcon, void *arg)
 
 /* Properties getters */
 static void
-dacp_propget_volume(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi)
+dacp_propget_volume(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item)
 {
   dmap_add_int(evbuf, "cmvo", status->volume);
 }
 
 static void
-dacp_propget_volumecontrollable(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi)
+dacp_propget_volumecontrollable(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item)
 {
   dmap_add_char(evbuf, "cavc", 1);
 }
 
 static void
-dacp_propget_playerstate(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi)
+dacp_propget_playerstate(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item)
 {
   dmap_add_char(evbuf, "caps", status->status);
 }
 
 static void
-dacp_propget_shufflestate(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi)
+dacp_propget_shufflestate(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item)
 {
   dmap_add_char(evbuf, "cash", status->shuffle);
 }
 
 static void
-dacp_propget_availableshufflestates(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi)
+dacp_propget_availableshufflestates(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item)
 {
   dmap_add_int(evbuf, "caas", 2);
 }
 
 static void
-dacp_propget_repeatstate(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi)
+dacp_propget_repeatstate(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item)
 {
   dmap_add_char(evbuf, "carp", status->repeat);
 }
 
 static void
-dacp_propget_availablerepeatstates(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi)
+dacp_propget_availablerepeatstates(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item)
 {
   dmap_add_int(evbuf, "caar", 6);
 }
 
 static void
-dacp_propget_nowplaying(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi)
+dacp_propget_nowplaying(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item)
 {
-  dacp_nowplaying(evbuf, status, mfi);
+  dacp_nowplaying(evbuf, status, queue_item);
 }
 
 static void
-dacp_propget_playingtime(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi)
+dacp_propget_playingtime(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item)
 {
-  dacp_playingtime(evbuf, status, mfi);
+  dacp_playingtime(evbuf, status, queue_item);
 }
 
 static void
-dacp_propget_fullscreenenabled(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi)
+dacp_propget_fullscreenenabled(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item)
 {
 	// TODO
 }
 
 static void
-dacp_propget_fullscreen(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi)
+dacp_propget_fullscreen(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item)
 {
 	// TODO
 }
 
 static void
-dacp_propget_visualizerenabled(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi)
+dacp_propget_visualizerenabled(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item)
 {
 	// TODO
 }
 
 static void
-dacp_propget_visualizer(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi)
+dacp_propget_visualizer(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item)
 {
 	// TODO
 }
 
 static void
-dacp_propget_itms_songid(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi)
+dacp_propget_itms_songid(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item)
 {
 	// TODO
 }
 
 static void
-dacp_propget_haschapterdata(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi)
+dacp_propget_haschapterdata(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item)
 {
 	// TODO
 }
 
 static void
-dacp_propget_mediakind(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi)
+dacp_propget_mediakind(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item)
 {
 	// TODO
 }
 
 static void
-dacp_propget_extendedmediakind(struct evbuffer *evbuf, struct player_status *status, struct media_file_info *mfi)
+dacp_propget_extendedmediakind(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item)
 {
 	// TODO
 }
@@ -614,7 +621,7 @@ seek_timer_cb(int fd, short what, void *arg)
       return;
     }
 
-  ret = player_playback_start(NULL);
+  ret = player_playback_start();
   if (ret < 0)
     DPRINTF(E_LOG, L_DACP, "Player returned an error for start after seek\n");
 }
@@ -785,6 +792,7 @@ find_first_song_id(const char *query)
   int id;
   int ret;
 
+  id = 0;
   memset(&qp, 0, sizeof(struct query_params));
 
   /* We only want the id of the first song */
@@ -844,20 +852,19 @@ find_first_song_id(const char *query)
 
 
 static int
-dacp_queueitem_make(struct queue_item **head, const char *query, const char *queuefilter, const char *sort, int quirk)
+dacp_queueitem_add(const char *query, const char *queuefilter, const char *sort, int quirk, int mode)
 {
   struct media_file_info *mfi;
   struct query_params qp;
-  struct queue_item *items;
   int64_t albumid;
   int64_t artistid;
   int plid;
   int id;
-  int idx;
   int ret;
   int len;
   char *s;
   char buf[1024];
+  struct player_status status;
 
   if (query)
     {
@@ -964,36 +971,37 @@ dacp_queueitem_make(struct queue_item **head, const char *query, const char *que
 	qp.sort = S_ARTIST;
     }
 
-  items = queueitem_make_byquery(&qp);
+  player_get_status(&status);
+
+  if (mode == 3)
+    ret = db_queue_add_by_queryafteritemid(&qp, status.item_id);
+  else
+    ret = db_queue_add_by_query(&qp, status.shuffle, status.item_id);
 
   if (qp.filter)
     free(qp.filter);
 
-  if (items)
-    *head = items;
-  else
+  if (ret < 0)
     return -1;
 
-  // Get the position (0-based) of the first item
-  idx = queueitem_pos(items, id);
+  if (status.shuffle && mode != 1)
+    return 0;
 
-  return idx;
+  return id;
 }
 
 static void
 dacp_reply_cue_play(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query)
 {
   struct player_status status;
-  struct queue_item *items;
   const char *sort;
   const char *cuequery;
   const char *param;
-  uint32_t id;
   uint32_t item_id;
   uint32_t pos;
   int clear;
+  struct db_queue_item *queue_item = NULL;
   struct player_history *history;
-  int hist;
   int ret;
 
   /* /cue?command=play&query=...&sort=...&index=N */
@@ -1008,16 +1016,18 @@ dacp_reply_cue_play(struct evhttp_request *req, struct evbuffer *evbuf, char **u
 	{
 	  player_playback_stop();
 
-	  player_queue_clear();
+	  db_queue_clear(0);
 	}
     }
 
+  player_get_status(&status);
+
   cuequery = evhttp_find_header(query, "query");
   if (cuequery)
     {
       sort = evhttp_find_header(query, "sort");
 
-      ret = dacp_queueitem_make(&items, cuequery, NULL, sort, 0);
+      ret = dacp_queueitem_add(cuequery, NULL, sort, 0, 0);
       if (ret < 0)
 	{
 	  DPRINTF(E_LOG, L_DACP, "Could not build song queue\n");
@@ -1025,22 +1035,16 @@ dacp_reply_cue_play(struct evhttp_request *req, struct evbuffer *evbuf, char **u
 	  dmap_send_error(req, "cacr", "Could not build song queue");
 	  return;
 	}
-
-      player_queue_add(items, NULL);
     }
-  else
+  else if (status.status != PLAY_STOPPED)
     {
-      player_get_status(&status);
-
-      if (status.status != PLAY_STOPPED)
-	player_playback_stop();
+      player_playback_stop();
     }
 
   param = evhttp_find_header(query, "dacp.shufflestate");
   if (param)
     dacp_propset_shufflestate(param, NULL);
 
-  id = 0;
   item_id = 0;
   pos = 0;
   param = evhttp_find_header(query, "index");
@@ -1052,7 +1056,6 @@ dacp_reply_cue_play(struct evhttp_request *req, struct evbuffer *evbuf, char **u
     }
 
   /* If selection was from Up Next queue or history queue (command will be playnow), then index is relative */
-  hist = 0;
   if ((param = evhttp_find_header(query, "command")) && (strcmp(param, "playnow") == 0))
     {
       /* If mode parameter is -1 or 1, the index is relative to the history queue, otherwise to the Up Next queue */
@@ -1060,12 +1063,20 @@ dacp_reply_cue_play(struct evhttp_request *req, struct evbuffer *evbuf, char **u
       if (param && ((strcmp(param, "-1") == 0) || (strcmp(param, "1") == 0)))
 	{
 	  /* Play from history queue */
-	  hist = 1;
 	  history = player_history_get();
 	  if (history->count > pos)
 	    {
 	      pos = (history->start_index + history->count - pos - 1) % MAX_HISTORY_COUNT;
 	      item_id = history->item_id[pos];
+
+	      queue_item = db_queue_fetch_byitemid(item_id);
+	      if (!queue_item)
+		{
+		  DPRINTF(E_LOG, L_DACP, "Could not start playback from history\n");
+
+		  dmap_send_error(req, "cacr", "Playback failed to start");
+		  return;
+		}
 	    }
 	  else
 	    {
@@ -1078,19 +1089,33 @@ dacp_reply_cue_play(struct evhttp_request *req, struct evbuffer *evbuf, char **u
       else
 	{
 	  /* Play from Up Next queue */
-	  pos += status.pos_pl;
-
 	  if (status.status == PLAY_STOPPED && pos > 0)
 	    pos--;
+
+	  queue_item = db_queue_fetch_byposrelativetoitem(pos, status.item_id, status.shuffle);
+	  if (!queue_item)
+	    {
+	      DPRINTF(E_LOG, L_DACP, "Could not fetch item from queue: pos=%d, now playing=%d\n", pos, status.item_id);
+
+	      dmap_send_error(req, "cacr", "Playback failed to start");
+	      return;
+	    }
 	}
     }
-
-  /* If playing from history queue, the pos holds the id of the item to play */
-  if (hist)
-    ret = player_playback_start_byitemid(item_id, &id);
   else
-    ret = player_playback_start_bypos(pos, &id);
+    {
+      queue_item = db_queue_fetch_bypos(pos, status.shuffle);
+      if (!queue_item)
+	{
+	  DPRINTF(E_LOG, L_DACP, "Could not fetch item from queue: pos=%d\n", pos);
+
+	  dmap_send_error(req, "cacr", "Playback failed to start");
+	  return;
+	}
+    }
 
+  ret = player_playback_start_byitem(queue_item);
+  free_queue_item(queue_item, 0);
   if (ret < 0)
     {
       DPRINTF(E_LOG, L_DACP, "Could not start playback\n");
@@ -1099,9 +1124,11 @@ dacp_reply_cue_play(struct evhttp_request *req, struct evbuffer *evbuf, char **u
       return;
     }
 
+  player_get_status(&status);
+
   dmap_add_container(evbuf, "cacr", 24); /* 8 + len */
   dmap_add_int(evbuf, "mstt", 200);      /* 12 */
-  dmap_add_int(evbuf, "miid", id);       /* 12 */
+  dmap_add_int(evbuf, "miid", status.id);/* 12 */
 
   httpd_send_reply(req, HTTP_OK, "OK", evbuf, 0);
 }
@@ -1113,7 +1140,7 @@ dacp_reply_cue_clear(struct evhttp_request *req, struct evbuffer *evbuf, char **
 
   player_playback_stop();
 
-  player_queue_clear();
+  db_queue_clear(0);
 
   dmap_add_container(evbuf, "cacr", 24); /* 8 + len */
   dmap_add_int(evbuf, "mstt", 200);      /* 12 */
@@ -1158,13 +1185,12 @@ static void
 dacp_reply_playspec(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query)
 {
   struct player_status status;
-  struct queue_item *items;
   struct daap_session *s;
   const char *param;
   const char *shuffle;
   uint32_t plid;
   uint32_t id;
-  int pos;
+  struct db_queue_item *queue_item = NULL;
   int ret;
 
   /* /ctrl-int/1/playspec?database-spec='dmap.persistentid:0x1'&container-spec='dmap.persistentid:0x5'&container-item-spec='dmap.containeritemid:0x9'
@@ -1240,40 +1266,41 @@ dacp_reply_playspec(struct evhttp_request *req, struct evbuffer *evbuf, char **u
 
   DPRINTF(E_DBG, L_DACP, "Playspec request for playlist %d, start song id %d%s\n", plid, id, (shuffle) ? ", shuffle" : "");
 
-  items = NULL;
+  player_get_status(&status);
+
+  if (status.status != PLAY_STOPPED)
+    player_playback_stop();
+
+  db_queue_clear(0);
+
   if (plid > 0)
-    items = queueitem_make_byplid(plid);
+    ret = db_queue_add_by_playlistid(plid, status.shuffle, status.item_id);
   else if (id > 0)
-    items = queueitem_make_byid(id);
+    ret = db_queue_add_by_fileid(id, status.shuffle, status.item_id);
 
-  if (!items)
+  if (ret < 0)
     {
       DPRINTF(E_LOG, L_DACP, "Could not build song queue from playlist %d\n", plid);
 
       goto out_fail;
     }
 
-  pos = queueitem_pos(items, id);
-  if (pos < 0)
-    {
-      DPRINTF(E_DBG, L_DACP, "No item with %d found in queue\n", id);
-      pos = 0;
-    }
-  DPRINTF(E_DBG, L_DACP, "Playspec start song index is %d\n", pos);
-
-  player_get_status(&status);
-
-  if (status.status != PLAY_STOPPED)
-    player_playback_stop();
-
-  player_queue_clear();
-  player_queue_add(items, NULL);
   player_queue_plid(plid);
 
   if (shuffle)
     dacp_propset_shufflestate(shuffle, NULL);
 
-  ret = player_playback_start_bypos(pos, NULL);
+  if (id > 0)
+    queue_item = db_queue_fetch_byfileid(id);
+
+  if (queue_item)
+    {
+      ret = player_playback_start_byitem(queue_item);
+      free_queue_item(queue_item, 0);
+    }
+  else
+    ret = player_playback_start();
+
   if (ret < 0)
     {
       DPRINTF(E_LOG, L_DACP, "Could not start playback\n");
@@ -1323,7 +1350,7 @@ dacp_reply_playpause(struct evhttp_request *req, struct evbuffer *evbuf, char **
     }
   else
     {
-      ret = player_playback_start(NULL);
+      ret = player_playback_start();
       if (ret < 0)
 	{
 	  DPRINTF(E_LOG, L_DACP, "Player returned an error for start after pause\n");
@@ -1356,7 +1383,7 @@ dacp_reply_nextitem(struct evhttp_request *req, struct evbuffer *evbuf, char **u
       return;
     }
 
-  ret = player_playback_start(NULL);
+  ret = player_playback_start();
   if (ret < 0)
     {
       DPRINTF(E_LOG, L_DACP, "Player returned an error for start after nextitem\n");
@@ -1388,7 +1415,7 @@ dacp_reply_previtem(struct evhttp_request *req, struct evbuffer *evbuf, char **u
       return;
     }
 
-  ret = player_playback_start(NULL);
+  ret = player_playback_start();
   if (ret < 0)
     {
       DPRINTF(E_LOG, L_DACP, "Player returned an error for start after previtem\n");
@@ -1499,6 +1526,44 @@ playqueuecontents_add_source(struct evbuffer *songlist, uint32_t source_id, int
   return 0;
 }
 
+static int
+playqueuecontents_add_queue_item(struct evbuffer *songlist, struct db_queue_item *queue_item, int pos_in_queue, uint32_t plid)
+{
+  struct evbuffer *song;
+  int ret;
+
+  song = evbuffer_new();
+  if (!song)
+    {
+      DPRINTF(E_LOG, L_DACP, "Could not allocate song evbuffer for playqueue-contents\n");
+      return -1;
+    }
+
+  dmap_add_container(song, "ceQs", 16);
+  dmap_add_raw_uint32(song, 1); /* Database */
+  dmap_add_raw_uint32(song, plid);
+  dmap_add_raw_uint32(song, 0); /* Should perhaps be playlist index? */
+  dmap_add_raw_uint32(song, queue_item->file_id);
+  dmap_add_string(song, "ceQn", queue_item->title);
+  dmap_add_string(song, "ceQr", queue_item->artist);
+  dmap_add_string(song, "ceQa", queue_item->album);
+  dmap_add_string(song, "ceQg", queue_item->genre);
+  dmap_add_long(song, "asai", queue_item->songalbumid);
+  dmap_add_int(song, "cmmk", queue_item->media_kind);
+  dmap_add_int(song, "casa", 1); /* Unknown  */
+  dmap_add_int(song, "astm", queue_item->song_length);
+  dmap_add_char(song, "casc", 1); /* Maybe an indication of extra data? */
+  dmap_add_char(song, "caks", 6); /* Unknown */
+  dmap_add_int(song, "ceQI", pos_in_queue);
+
+  dmap_add_container(songlist, "mlit", evbuffer_get_length(song));
+
+  ret = evbuffer_add_buffer(songlist, song);
+  evbuffer_free(song);
+
+  return ret;
+}
+
 static void
 dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf, char **uri,
     struct evkeyvalq *query)
@@ -1508,17 +1573,15 @@ dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf,
   struct evbuffer *playlists;
   struct player_status status;
   struct player_history *history;
-  struct queue *queue;
-  struct queue_item *item;
   const char *param;
   size_t songlist_length;
   size_t playlist_length;
   int span;
   int count;
-  int i;
-  int n;
   int ret;
   int start_index;
+  struct query_params query_params;
+  struct db_queue_item queue_item;
 
   /* /ctrl-int/1/playqueue-contents?span=50&session-id=... */
 
@@ -1537,8 +1600,7 @@ dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf,
 	DPRINTF(E_LOG, L_DACP, "Invalid span value in playqueue-contents request\n");
     }
 
-  i = 0;
-  n = 0; // count of songs in songlist
+  count = 0; // count of songs in songlist
   songlist = evbuffer_new();
   if (!songlist)
     {
@@ -1548,6 +1610,8 @@ dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf,
       return;
     }
 
+  player_get_status(&status);
+
   /*
    * If the span parameter is negativ make song list for Previously Played,
    * otherwise make song list for Up Next and begin with first song after playlist position.
@@ -1563,9 +1627,9 @@ dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf,
 	{
 	  start_index = (history->start_index + history->count - abs(span)) % MAX_HISTORY_COUNT;
 	}
-      for (n = 0; n < history->count && n < abs(span); n++)
+      for (count = 0; count < history->count && count < abs(span); count++)
 	{
-	  ret = playqueuecontents_add_source(songlist, history->id[(start_index + n) % MAX_HISTORY_COUNT], (n + 1), status.plid);
+	  ret = playqueuecontents_add_source(songlist, history->id[(start_index + count) % MAX_HISTORY_COUNT], (count + 1), status.plid);
 	  if (ret < 0)
 	    {
 	      DPRINTF(E_LOG, L_DACP, "Could not add song to songlist for playqueue-contents\n");
@@ -1577,17 +1641,19 @@ dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf,
     }
   else
     {
-      player_get_status(&status);
+      memset(&query_params, 0, sizeof(struct query_params));
+      if (status.shuffle)
+	query_params.sort = S_SHUFFLE_POS;
+      ret = db_queue_enum_start(&query_params);
 
-      queue = player_queue_get_bypos(abs(span));
-      if (queue)
+      count = 0; //FIXME [queue] Check count value
+      while ((ret = db_queue_enum_fetch(&query_params, &queue_item)) == 0 && queue_item.id > 0)
 	{
-	  i = 0;
-	  count = queue_count(queue);
-	  for (n = 0; (n < count) && (n < abs(span)); n++)
+	  if (status.item_id == 0 || status.item_id == queue_item.id)
+	    count = 1;
+	  else if (count > 0)
 	    {
-	      item = queue_get_byindex(queue, n, 0);
-	      ret = playqueuecontents_add_source(songlist, queueitem_id(item), (n + i + 1), status.plid);
+	      ret = playqueuecontents_add_queue_item(songlist, &queue_item, count, status.plid);
 	      if (ret < 0)
 		{
 		  DPRINTF(E_LOG, L_DACP, "Could not add song to songlist for playqueue-contents\n");
@@ -1595,9 +1661,13 @@ dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf,
 		  dmap_send_error(req, "ceQR", "Out of memory");
 		  return;
 		}
+
+	      count++;
 	    }
-	  queue_free(queue);
 	}
+
+      db_queue_enum_end(&query_params);
+      sqlite3_free(query_params.filter);
     }
 
   /* Playlists are hist, curr and main. */
@@ -1628,7 +1698,7 @@ dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf,
       dmap_add_container(playlists, "mlit", 69);
       dmap_add_string(playlists, "ceQk", "main");            /* 12 */
       dmap_add_int(playlists, "ceQi", 1);                    /* 12 */
-      dmap_add_int(playlists, "ceQm", n);                    /* 12 */
+      dmap_add_int(playlists, "ceQm", count);                /* 12 */
       dmap_add_string(playlists, "ceQl", "Up Next");         /* 15 = 8 + 7 */
       dmap_add_string(playlists, "ceQh", "from Music");      /* 18 = 8 + 10 */
 
@@ -1640,10 +1710,10 @@ dacp_reply_playqueuecontents(struct evhttp_request *req, struct evbuffer *evbuf,
   /* Final construction of reply */
   playlist_length = evbuffer_get_length(playlists);
   dmap_add_container(evbuf, "ceQR", 79 + playlist_length + songlist_length); /* size of entire container */
-  dmap_add_int(evbuf, "mstt", 200);                                                     /* 12, dmap.status */
-  dmap_add_int(evbuf, "mtco", abs(span));                                               /* 12 */
-  dmap_add_int(evbuf, "mrco", n);                                                       /* 12 */
-  dmap_add_char(evbuf, "ceQu", 0);                                                      /*  9 */
+  dmap_add_int(evbuf, "mstt", 200);                                          /* 12, dmap.status */
+  dmap_add_int(evbuf, "mtco", abs(span));                                    /* 12 */
+  dmap_add_int(evbuf, "mrco", count);                                        /* 12 */
+  dmap_add_char(evbuf, "ceQu", 0);                                           /*  9 */
   dmap_add_container(evbuf, "mlcl", 8 + playlist_length + songlist_length);  /*  8 */
   dmap_add_container(evbuf, "ceQS", playlist_length);                        /*  8 */
   ret = evbuffer_add_buffer(evbuf, playlists);
@@ -1680,6 +1750,7 @@ static void
 dacp_reply_playqueueedit_clear(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query)
 {
   const char *param;
+  struct player_status status;
 
   param = evhttp_find_header(query, "mode");
 
@@ -1691,7 +1762,10 @@ dacp_reply_playqueueedit_clear(struct evhttp_request *req, struct evbuffer *evbu
   if (strcmp(param,"0x68697374") == 0)
     player_queue_clear_history();
   else
-    player_queue_clear();
+    {
+      player_get_status(&status);
+      db_queue_clear(status.item_id);
+    }
 
   dmap_add_container(evbuf, "cacr", 24); /* 8 + len */
   dmap_add_int(evbuf, "mstt", 200);      /* 12 */
@@ -1712,18 +1786,18 @@ dacp_reply_playqueueedit_add(struct evhttp_request *req, struct evbuffer *evbuf,
   //?command=add&query='dmap.itemid:2'&query-modifier=containers&sort=name&mode=2&session-id=100
   // -> mode 2: stop playblack, clear playqueue, add shuffled songs from playlist=itemid to playqueue
 
-  struct queue_item *items;
   const char *editquery;
   const char *queuefilter;
   const char *querymodifier;
   const char *sort;
   const char *param;
   char modifiedquery[32];
-  uint32_t idx;
   int mode;
   int plid;
   int ret;
   int quirkyquery;
+  struct db_queue_item *queue_item;
+  struct player_status status;
 
   mode = 1;
 
@@ -1743,80 +1817,87 @@ dacp_reply_playqueueedit_add(struct evhttp_request *req, struct evbuffer *evbuf,
   if ((mode == 1) || (mode == 2))
     {
       player_playback_stop();
-      player_queue_clear();
+      db_queue_clear(0);
     }
 
+  if (mode == 2)
+    player_shuffle_set(1);
+
   editquery = evhttp_find_header(query, "query");
-  if (editquery)
+  if (!editquery)
     {
-      sort = evhttp_find_header(query, "sort");
+      DPRINTF(E_LOG, L_DACP, "Could not add song queue, DACP query missing\n");
 
-      // if sort param is missing and an album or artist is added to the queue, set sort to "album"
-      if (!sort && (strstr(editquery, "daap.songalbumid:") || strstr(editquery, "daap.songartistid:")))
-      {
-	sort = "album";
-      }
+      dmap_send_error(req, "cacr", "Invalid request");
+      return;
+    }
 
-      // only use queryfilter if mode is not equal 0 (add to up next), 3 (play next) or 5 (add to up next)
-      queuefilter = (mode == 0 || mode == 3 || mode == 5) ? NULL : evhttp_find_header(query, "queuefilter");
+  sort = evhttp_find_header(query, "sort");
 
-      querymodifier = evhttp_find_header(query, "query-modifier");
-      if (!querymodifier || (strcmp(querymodifier, "containers") != 0))
-	{
-	  quirkyquery = (mode == 1) && strstr(editquery, "dmap.itemid:") && ((!queuefilter) || strstr(queuefilter, "(null)"));
-	  ret = dacp_queueitem_make(&items, editquery, queuefilter, sort, quirkyquery);
-	}
-      else
-	{
-	  // Modify the query: Take the id from the editquery and use it as a queuefilter playlist id
-	  ret = safe_atoi32(strchr(editquery, ':') + 1, &plid);
-	  if (ret < 0)
-	    {
-	      DPRINTF(E_LOG, L_DACP, "Invalid playlist id in request: %s\n", editquery);
+  // if sort param is missing and an album or artist is added to the queue, set sort to "album"
+  if (!sort && (strstr(editquery, "daap.songalbumid:") || strstr(editquery, "daap.songartistid:")))
+    {
+      sort = "album";
+    }
 
-	      dmap_send_error(req, "cacr", "Invalid request");
-	      return;
-	    }
-	  
-	  snprintf(modifiedquery, sizeof(modifiedquery), "playlist:%d", plid);
-	  ret = dacp_queueitem_make(&items, NULL, modifiedquery, sort, 0);
-	}
+  // only use queryfilter if mode is not equal 0 (add to up next), 3 (play next) or 5 (add to up next)
+  queuefilter = (mode == 0 || mode == 3 || mode == 5) ? NULL : evhttp_find_header(query, "queuefilter");
 
+  querymodifier = evhttp_find_header(query, "query-modifier");
+  if (!querymodifier || (strcmp(querymodifier, "containers") != 0))
+    {
+      quirkyquery = (mode == 1) && strstr(editquery, "dmap.itemid:") && ((!queuefilter) || strstr(queuefilter, "(null)"));
+      ret = dacp_queueitem_add(editquery, queuefilter, sort, quirkyquery, mode);
+    }
+  else
+    {
+      // Modify the query: Take the id from the editquery and use it as a queuefilter playlist id
+      ret = safe_atoi32(strchr(editquery, ':') + 1, &plid);
       if (ret < 0)
-	{
-	  DPRINTF(E_LOG, L_DACP, "Could not build song queue\n");
+        {
+	  DPRINTF(E_LOG, L_DACP, "Invalid playlist id in request: %s\n", editquery);
 
 	  dmap_send_error(req, "cacr", "Invalid request");
 	  return;
 	}
 
-      idx = ret;
-
-      if (mode == 3)
-      {
-        player_queue_add_next(items);
-      }
-      else
-      {
-        player_queue_add(items, NULL);
-      }
+      snprintf(modifiedquery, sizeof(modifiedquery), "playlist:%d", plid);
+      ret = dacp_queueitem_add(NULL, modifiedquery, sort, 0, mode);
     }
-  else
+
+  if (ret < 0)
     {
-      DPRINTF(E_LOG, L_DACP, "Could not add song queue, DACP query missing\n");
+      DPRINTF(E_LOG, L_DACP, "Could not build song queue\n");
 
       dmap_send_error(req, "cacr", "Invalid request");
       return;
     }
 
-  if (mode == 2)
+  if (ret > 0)
+    queue_item = db_queue_fetch_byfileid(ret);
+  else
+    queue_item = NULL;
+
+  if (queue_item)
     {
-      player_shuffle_set(1);
-      idx = 0;
+      player_get_status(&status);
+
+      if (status.shuffle)
+	{
+	  DPRINTF(E_DBG, L_DACP, "Start shuffle queue with item %d\n", queue_item->id);
+	  db_queue_move_byitemid(queue_item->id, 0, status.shuffle);
+	}
+
+      DPRINTF(E_DBG, L_DACP, "Song queue built, starting playback at index %d\n", queue_item->pos);
+      ret = player_playback_start_byitem(queue_item);
+      free_queue_item(queue_item, 0);
+    }
+  else
+    {
+      DPRINTF(E_DBG, L_DACP, "Song queue built, starting playback\n");
+      ret = player_playback_start();
     }
 
-  DPRINTF(E_DBG, L_DACP, "Song queue built, playback starting at index %" PRIu32 "\n", idx);
-  ret = player_playback_start_bypos(idx, NULL);
   if (ret < 0)
     {
       DPRINTF(E_LOG, L_DACP, "Could not start playback\n");
@@ -1840,6 +1921,7 @@ dacp_reply_playqueueedit_move(struct evhttp_request *req, struct evbuffer *evbuf
    * The 'edit-param.move-pair' param contains the index of the song in the playqueue to be moved (index 3 in the example)
    * and the index of the song after which it should be inserted (index 0 in the exampe, the now playing song).
    */
+  struct player_status status;
   int ret;
 
   const char *param;
@@ -1867,7 +1949,8 @@ dacp_reply_playqueueedit_move(struct evhttp_request *req, struct evbuffer *evbuf
       return;
     }
 
-    player_queue_move_bypos(src, dst);
+    player_get_status(&status);
+    db_queue_move_byposrelativetoitem(src, dst, status.item_id, status.shuffle);
   }
 
   /* 204 No Content is the canonical reply */
@@ -1882,6 +1965,7 @@ dacp_reply_playqueueedit_remove(struct evhttp_request *req, struct evbuffer *evb
    * Exampe request (removes song at position 1 in the playqueue):
    * ?command=remove&items=1&session-id=100
    */
+  struct player_status status;
   int ret;
 
   const char *param;
@@ -1899,7 +1983,9 @@ dacp_reply_playqueueedit_remove(struct evhttp_request *req, struct evbuffer *evb
       return;
     }
 
-    player_queue_remove_bypos(item_index);
+    player_get_status(&status);
+
+    db_queue_delete_byposrelativetoitem(item_index, status.item_id, status.shuffle);
   }
 
   /* 204 No Content is the canonical reply */
@@ -2150,7 +2236,7 @@ dacp_reply_getproperty(struct evhttp_request *req, struct evbuffer *evbuf, char
   struct player_status status;
   struct daap_session *s;
   const struct dacp_prop_map *dpm;
-  struct media_file_info *mfi;
+  struct db_queue_item *queue_item = NULL;
   struct evbuffer *proplist;
   const char *param;
   char *ptr;
@@ -2194,17 +2280,15 @@ dacp_reply_getproperty(struct evhttp_request *req, struct evbuffer *evbuf, char
 
   if (status.status != PLAY_STOPPED)
     {
-      mfi = db_file_fetch_byid(status.id);
-      if (!mfi)
+      queue_item = db_queue_fetch_byitemid(status.item_id);
+      if (!queue_item)
 	{
-	  DPRINTF(E_LOG, L_DACP, "Could not fetch file id %d\n", status.id);
+	  DPRINTF(E_LOG, L_DACP, "Could not fetch queue_item for item-id %d\n", status.item_id);
 
 	  dmap_send_error(req, "cmgt", "Server error");
 	  goto out_free_proplist;
 	}
     }
-  else
-    mfi = NULL;
 
   prop = strtok_r(propstr, ",", &ptr);
   while (prop)
@@ -2213,7 +2297,7 @@ dacp_reply_getproperty(struct evhttp_request *req, struct evbuffer *evbuf, char
       if (dpm)
 	{
 	  if (dpm->propget)
-	    dpm->propget(proplist, &status, mfi);
+	    dpm->propget(proplist, &status, queue_item);
 	  else
 	    DPRINTF(E_WARN, L_DACP, "No getter method for DACP property %s\n", prop);
 	}
@@ -2225,8 +2309,8 @@ dacp_reply_getproperty(struct evhttp_request *req, struct evbuffer *evbuf, char
 
   free(propstr);
 
-  if (mfi)
-    free_mfi(mfi, 0);
+  if (queue_item)
+    free_queue_item(queue_item, 0);
 
   len = evbuffer_get_length(proplist);
   dmap_add_container(evbuf, "cmgt", 12 + len);
@@ -2664,7 +2748,7 @@ dacp_init(void)
   current_rev = 2;
   update_requests = NULL;
 
-#ifdef USE_EVENTFD
+#ifdef HAVE_EVENTFD
   update_efd = eventfd(0, EFD_CLOEXEC);
   if (update_efd < 0)
     {
@@ -2684,7 +2768,7 @@ dacp_init(void)
 
       return -1;
     }
-#endif /* USE_EVENTFD */
+#endif /* HAVE_EVENTFD */
 
   for (i = 0; dacp_handlers[i].handler; i++)
     {
@@ -2698,7 +2782,7 @@ dacp_init(void)
         }
     }
 
-#ifdef USE_EVENTFD
+#ifdef HAVE_EVENTFD
   updateev = event_new(evbase_httpd, update_efd, EV_READ, playstatusupdate_cb, NULL);
 #else
   updateev = event_new(evbase_httpd, update_pipe[0], EV_READ, playstatusupdate_cb, NULL);
@@ -2724,7 +2808,7 @@ dacp_init(void)
   return 0;
 
  regexp_fail:
-#ifdef USE_EVENTFD
+#ifdef HAVE_EVENTFD
   close(update_efd);
 #else
   close(update_pipe[0]);
@@ -2763,7 +2847,7 @@ dacp_deinit(void)
 
   event_free(updateev);
 
-#ifdef USE_EVENTFD
+#ifdef HAVE_EVENTFD
   close(update_efd);
 #else
   close(update_pipe[0]);
diff --git a/src/httpd_streaming.c b/src/httpd_streaming.c
index fd8354f..b9340fc 100644
--- a/src/httpd_streaming.c
+++ b/src/httpd_streaming.c
@@ -97,6 +97,13 @@ streaming_fail_cb(struct evhttp_connection *evcon, void *arg)
       prev = session;
     }
 
+  if (!session)
+    {
+      DPRINTF(E_LOG, L_STREAMING, "Bug! Got a failure callback for an unknown stream\n");
+      free(this);
+      return;
+    }
+
   if (!prev)
     streaming_sessions = session->next;
   else
@@ -197,7 +204,12 @@ streaming_write(uint8_t *buf, uint64_t rtptime)
 
   ret = write(streaming_pipe[1], buf, STREAMING_RAWBUF_SIZE);
   if (ret < 0)
-    DPRINTF(E_LOG, L_STREAMING, "Error writing to streaming pipe: %s\n", strerror(errno));
+    {
+      if (errno == EAGAIN)
+	DPRINTF(E_WARN, L_STREAMING, "Streaming pipe full, skipping write\n");
+      else
+	DPRINTF(E_LOG, L_STREAMING, "Error writing to streaming pipe: %s\n", strerror(errno));
+    }
 }
 
 int
diff --git a/src/input.c b/src/input.c
new file mode 100644
index 0000000..863b470
--- /dev/null
+++ b/src/input.c
@@ -0,0 +1,602 @@
+/*
+ * Copyright (C) 2017 Espen Jürgensen <espenjurgensen at gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <stdint.h>
+#include <inttypes.h>
+
+#include <event2/event.h>
+#include <event2/buffer.h>
+#include <pthread.h>
+#ifdef HAVE_PTHREAD_NP_H
+# include <pthread_np.h>
+#endif
+
+#include "misc.h"
+#include "logger.h"
+#include "input.h"
+
+// Disallow further writes to the buffer when its size is larger than this threshold
+#define INPUT_BUFFER_THRESHOLD STOB(88200)
+// How long (in sec) to wait for player read before looping in playback thread
+#define INPUT_LOOP_TIMEOUT 1
+
+#define DEBUG 1 //TODO disable
+
+extern struct input_definition input_file;
+extern struct input_definition input_http;
+extern struct input_definition input_pipe;
+#ifdef HAVE_SPOTIFY_H
+extern struct input_definition input_spotify;
+#endif
+
+// Must be in sync with enum input_types
+static struct input_definition *inputs[] = {
+    &input_file,
+    &input_http,
+    &input_pipe,
+#ifdef HAVE_SPOTIFY_H
+    &input_spotify,
+#endif
+    NULL
+};
+
+struct input_buffer
+{
+  // Raw pcm stream data
+  struct evbuffer *evbuf;
+
+  // If non-zero, remaining length of buffer until EOF
+  size_t eof;
+  // If non-zero, remaining length of buffer until read error occurred
+  size_t error;
+  // If non-zero, remaining length of buffer until (possible) new metadata
+  size_t metadata;
+
+  // Optional callback to player if buffer is full
+  input_cb full_cb;
+
+  // Locks for sharing the buffer between input and player thread
+  pthread_mutex_t mutex;
+  pthread_cond_t cond;
+};
+
+/* --- Globals --- */
+// Input thread
+static pthread_t tid_input;
+
+// Input buffer
+static struct input_buffer input_buffer;
+
+// Timeout waiting in playback loop
+static struct timespec input_loop_timeout = { INPUT_LOOP_TIMEOUT, 0 };
+
+#ifdef DEBUG
+static size_t debug_elapsed;
+#endif
+
+
+/* ------------------------------ MISC HELPERS ---------------------------- */
+
+static short
+flags_set(size_t len)
+{
+  short flags = 0;
+
+  if (input_buffer.error)
+    {
+      if (len >= input_buffer.error)
+	{
+	  flags |= INPUT_FLAG_ERROR;
+	  input_buffer.error = 0;
+	}
+      else
+	input_buffer.error -= len;
+    }
+
+  if (input_buffer.eof)
+    {
+      if (len >= input_buffer.eof)
+	{
+	  flags |= INPUT_FLAG_EOF;
+	  input_buffer.eof = 0;
+	}
+      else
+	input_buffer.eof -= len;
+    }
+
+  if (input_buffer.metadata)
+    {
+      if (len >= input_buffer.metadata)
+	{
+	  flags |= INPUT_FLAG_METADATA;
+	  input_buffer.metadata = 0;
+	}
+      else
+	input_buffer.metadata -= len;
+    }
+
+  return flags;
+}
+
+static int
+map_data_kind(int data_kind)
+{
+  switch (data_kind)
+    {
+      case DATA_KIND_FILE:
+	return INPUT_TYPE_FILE;
+
+      case DATA_KIND_HTTP:
+	return INPUT_TYPE_HTTP;
+
+      case DATA_KIND_PIPE:
+	return INPUT_TYPE_PIPE;
+
+#ifdef HAVE_SPOTIFY_H
+      case DATA_KIND_SPOTIFY:
+	return INPUT_TYPE_SPOTIFY;
+#endif
+
+      default:
+	return -1;
+    }
+}
+
+static int
+source_check_and_map(struct player_source *ps, const char *action, char check_setup)
+{
+  int type;
+
+#ifdef DEBUG
+  DPRINTF(E_DBG, L_PLAYER, "Action is %s\n", action);
+#endif
+
+  if (!ps)
+    {
+      DPRINTF(E_LOG, L_PLAYER, "Stream %s called with invalid player source\n", action);
+      return -1;
+    }
+
+  if (check_setup && !ps->setup_done)
+    {
+      DPRINTF(E_LOG, L_PLAYER, "Given player source not setup, %s not possible\n", action);
+      return -1;
+    }
+
+  type = map_data_kind(ps->data_kind);
+  if (type < 0)
+    {
+      DPRINTF(E_LOG, L_PLAYER, "Unsupported input type, %s not possible\n", action);
+      return -1;
+    }
+
+  return type;
+}
+
+/* ----------------------------- PLAYBACK LOOP ---------------------------- */
+/*                               Thread: input                              */
+
+// TODO Thread safety of ps?
+static void *
+playback(void *arg)
+{
+  struct player_source *ps = arg;
+  int type;
+  int ret;
+
+  type = source_check_and_map(ps, "start", 1);
+  if ((type < 0) || (inputs[type]->disabled))
+    goto thread_exit;
+
+  // Loops until input_loop_break is set or no more input, e.g. EOF
+  ret = inputs[type]->start(ps);
+  if (ret < 0)
+    input_write(NULL, INPUT_FLAG_ERROR);
+
+#ifdef DEBUG
+  DPRINTF(E_DBG, L_PLAYER, "Playback loop stopped (break is %d, ret %d)\n", input_loop_break, ret);
+#endif
+
+ thread_exit:
+  pthread_exit(NULL);
+}
+
+void
+input_wait(void)
+{
+  struct timespec ts;
+
+  pthread_mutex_lock(&input_buffer.mutex);
+
+  ts = timespec_reltoabs(input_loop_timeout);
+  pthread_cond_timedwait(&input_buffer.cond, &input_buffer.mutex, &ts);
+
+  pthread_mutex_unlock(&input_buffer.mutex);
+}
+
+// Called by input modules from within the playback loop
+int
+input_write(struct evbuffer *evbuf, short flags)
+{
+  struct timespec ts;
+  int ret;
+
+  pthread_mutex_lock(&input_buffer.mutex);
+
+  while ( (!input_loop_break) && (evbuffer_get_length(input_buffer.evbuf) > INPUT_BUFFER_THRESHOLD) && evbuf )
+    {
+      if (input_buffer.full_cb)
+	{
+	  input_buffer.full_cb();
+	  input_buffer.full_cb = NULL;
+	}
+
+      if (flags & INPUT_FLAG_NONBLOCK)
+	{
+	  pthread_mutex_unlock(&input_buffer.mutex);
+	  return EAGAIN;
+	}
+
+      ts = timespec_reltoabs(input_loop_timeout);
+      pthread_cond_timedwait(&input_buffer.cond, &input_buffer.mutex, &ts);
+    }
+
+  if (input_loop_break)
+    {
+      pthread_mutex_unlock(&input_buffer.mutex);
+      return 0;
+    }
+
+  if (evbuf)
+    ret = evbuffer_add_buffer(input_buffer.evbuf, evbuf);
+  else
+    ret = 0;
+
+  if (ret < 0)
+    DPRINTF(E_LOG, L_PLAYER, "Error adding stream data to input buffer\n");
+
+  if (!input_buffer.error && (flags & INPUT_FLAG_ERROR))
+    input_buffer.error = evbuffer_get_length(input_buffer.evbuf);
+  if (!input_buffer.eof && (flags & INPUT_FLAG_EOF))
+    input_buffer.eof = evbuffer_get_length(input_buffer.evbuf);
+  if (!input_buffer.metadata && (flags & INPUT_FLAG_METADATA))
+    input_buffer.metadata = evbuffer_get_length(input_buffer.evbuf);
+
+  pthread_mutex_unlock(&input_buffer.mutex);
+
+  return ret;
+}
+
+
+/* -------------------- Interface towards player thread ------------------- */
+/*                               Thread: player                             */
+
+int
+input_read(void *data, size_t size, short *flags)
+{
+  int len;
+
+  *flags = 0;
+
+  if (!tid_input)
+    {
+      DPRINTF(E_LOG, L_PLAYER, "Bug! Read called, but playback not running\n");
+      return -1;
+    }
+
+  pthread_mutex_lock(&input_buffer.mutex);
+
+#ifdef DEBUG
+  debug_elapsed += size;
+  if (debug_elapsed > STOB(441000)) // 10 sec
+    {
+      DPRINTF(E_DBG, L_PLAYER, "Input buffer has %zu bytes\n", evbuffer_get_length(input_buffer.evbuf));
+      debug_elapsed = 0;
+    }
+#endif
+
+  len = evbuffer_remove(input_buffer.evbuf, data, size);
+  if (len < 0)
+    {
+      DPRINTF(E_LOG, L_PLAYER, "Error reading stream data from input buffer\n");
+      goto out_unlock;
+    }
+
+  *flags = flags_set(len);
+
+ out_unlock:
+  pthread_cond_signal(&input_buffer.cond);
+  pthread_mutex_unlock(&input_buffer.mutex);
+
+  return len;
+}
+
+void
+input_buffer_full_cb(input_cb cb)
+{
+  pthread_mutex_lock(&input_buffer.mutex);
+  input_buffer.full_cb = cb;
+
+  pthread_mutex_unlock(&input_buffer.mutex);
+}
+
+int
+input_setup(struct player_source *ps)
+{
+  int type;
+
+  type = source_check_and_map(ps, "setup", 0);
+  if ((type < 0) || (inputs[type]->disabled))
+    return -1;
+
+  if (!inputs[type]->setup)
+    return 0;
+
+  return inputs[type]->setup(ps);
+}
+
+int
+input_start(struct player_source *ps)
+{
+  int ret;
+
+  if (tid_input)
+    {
+      DPRINTF(E_WARN, L_PLAYER, "Input start called, but playback already running\n");
+      return 0;
+    }
+
+  input_loop_break = 0;
+
+  ret = pthread_create(&tid_input, NULL, playback, ps);
+  if (ret < 0)
+    {
+      DPRINTF(E_LOG, L_PLAYER, "Could not spawn input thread: %s\n", strerror(errno));
+      return -1;
+    }
+
+#if defined(HAVE_PTHREAD_SETNAME_NP)
+  pthread_setname_np(tid_input, "input");
+#elif defined(HAVE_PTHREAD_SET_NAME_NP)
+  pthread_set_name_np(tid_input, "input");
+#endif
+
+  return 0;
+}
+
+int
+input_pause(struct player_source *ps)
+{
+  short flags;
+  int ret;
+
+#ifdef DEBUG
+  DPRINTF(E_DBG, L_PLAYER, "Pause called, stopping playback loop\n");
+#endif
+
+  if (!tid_input)
+    return -1;
+
+  pthread_mutex_lock(&input_buffer.mutex);
+
+  input_loop_break = 1;
+
+  pthread_cond_signal(&input_buffer.cond);
+  pthread_mutex_unlock(&input_buffer.mutex);
+
+  // TODO What if input thread is hanging waiting for source? Kill thread?
+  ret = pthread_join(tid_input, NULL);
+  if (ret != 0)
+    {
+      DPRINTF(E_LOG, L_PLAYER, "Could not join input thread: %s\n", strerror(errno));
+      return -1;
+    }
+
+  tid_input = 0;
+
+  input_flush(&flags);
+
+  return 0;
+}
+
+int
+input_stop(struct player_source *ps)
+{
+  int type;
+
+  if (tid_input)
+    input_pause(ps);
+
+  if (!ps)
+    return 0;
+
+  type = source_check_and_map(ps, "stop", 1);
+  if ((type < 0) || (inputs[type]->disabled))
+    return -1;
+
+  if (!inputs[type]->stop)
+    return 0;
+
+  return inputs[type]->stop(ps);
+}
+
+int
+input_seek(struct player_source *ps, int seek_ms)
+{
+  int type;
+
+  type = source_check_and_map(ps, "seek", 1);
+  if ((type < 0) || (inputs[type]->disabled))
+    return -1;
+
+  if (!inputs[type]->seek)
+    return 0;
+
+  if (tid_input)
+    input_pause(ps);
+
+  return inputs[type]->seek(ps, seek_ms);
+}
+
+void
+input_flush(short *flags)
+{
+  size_t len;
+
+  pthread_mutex_lock(&input_buffer.mutex);
+
+  len = evbuffer_get_length(input_buffer.evbuf);
+
+  evbuffer_drain(input_buffer.evbuf, len);
+
+  *flags = flags_set(len);
+
+  input_buffer.error = 0;
+  input_buffer.eof = 0;
+  input_buffer.metadata = 0;
+  input_buffer.full_cb = NULL;
+
+  pthread_mutex_unlock(&input_buffer.mutex);
+
+#ifdef DEBUG
+  DPRINTF(E_DBG, L_PLAYER, "Flush with flags %d\n", *flags);
+#endif
+}
+
+int
+input_metadata_get(struct input_metadata *metadata, struct player_source *ps, int startup, uint64_t rtptime)
+{
+  int type;
+
+  if (!metadata || !ps || !ps->stream_start || !ps->output_start)
+    {
+      DPRINTF(E_LOG, L_PLAYER, "Bug! Unhandled case in input_metadata_get()\n");
+      return -1;
+    }
+
+  memset(metadata, 0, sizeof(struct input_metadata));
+
+  metadata->item_id = ps->item_id;
+
+  metadata->startup = startup;
+  metadata->offset = ps->output_start - ps->stream_start;
+  metadata->rtptime = ps->stream_start;
+
+  // Note that the source may overwrite the above progress metadata
+  type = source_check_and_map(ps, "metadata_get", 1);
+  if ((type < 0) || (inputs[type]->disabled))
+    return -1;
+
+  if (!inputs[type]->metadata_get)
+    return 0;
+
+  return inputs[type]->metadata_get(metadata, ps, rtptime);
+}
+
+void
+input_metadata_free(struct input_metadata *metadata, int content_only)
+{
+  free(metadata->artist);
+  free(metadata->title);
+  free(metadata->album);
+  free(metadata->genre);
+  free(metadata->artwork_url);
+
+  if (!content_only)
+    free(metadata);
+  else
+    memset(metadata, 0, sizeof(struct input_metadata));
+}
+
+int
+input_init(void)
+{
+  int no_input;
+  int ret;
+  int i;
+
+  // Prepare input buffer
+  pthread_mutex_init(&input_buffer.mutex, NULL);
+  pthread_cond_init(&input_buffer.cond, NULL);
+
+  input_buffer.evbuf = evbuffer_new();
+  if (!input_buffer.evbuf)
+    {
+      DPRINTF(E_LOG, L_PLAYER, "Out of memory for input buffer\n");
+      return -1;
+    }
+
+  no_input = 1;
+  for (i = 0; inputs[i]; i++)
+    {
+      if (inputs[i]->type != i)
+	{
+	  DPRINTF(E_FATAL, L_PLAYER, "BUG! Input definitions are misaligned with input enum\n");
+	  return -1;
+	}
+
+      if (!inputs[i]->init)
+	{
+	  no_input = 0;
+	  continue;
+	}
+
+      ret = inputs[i]->init();
+      if (ret < 0)
+	inputs[i]->disabled = 1;
+      else
+	no_input = 0;
+    }
+
+  if (no_input)
+    return -1;
+
+  return 0;
+}
+
+void
+input_deinit(void)
+{
+  int i;
+
+  input_stop(NULL);
+
+  for (i = 0; inputs[i]; i++)
+    {
+      if (inputs[i]->disabled)
+	continue;
+
+      if (inputs[i]->deinit)
+        inputs[i]->deinit();
+    }
+
+  pthread_cond_destroy(&input_buffer.cond);
+  pthread_mutex_destroy(&input_buffer.mutex);
+
+  evbuffer_free(input_buffer.evbuf);
+}
+
diff --git a/src/input.h b/src/input.h
new file mode 100644
index 0000000..c988d85
--- /dev/null
+++ b/src/input.h
@@ -0,0 +1,249 @@
+
+#ifndef __INPUT_H__
+#define __INPUT_H__
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+#include <event2/buffer.h>
+#include "transcode.h"
+
+// Must be in sync with inputs[] in input.c
+enum input_types
+{
+  INPUT_TYPE_FILE,
+  INPUT_TYPE_HTTP,
+  INPUT_TYPE_PIPE,
+#ifdef HAVE_SPOTIFY_H
+  INPUT_TYPE_SPOTIFY,
+#endif
+};
+
+enum input_flags
+{
+  // Write to input buffer must not block
+  INPUT_FLAG_NONBLOCK = (1 << 0),
+  // Flags end of file
+  INPUT_FLAG_EOF      = (1 << 1),
+  // Flags error reading file
+  INPUT_FLAG_ERROR    = (1 << 2),
+  // Flags possible new stream metadata
+  INPUT_FLAG_METADATA = (1 << 3),
+};
+
+struct player_source
+{
+  /* Id of the file/item in the files database */
+  uint32_t id;
+
+  /* Item-Id of the file/item in the queue */
+  uint32_t item_id;
+
+  /* Length of the file/item in milliseconds */
+  uint32_t len_ms;
+
+  enum data_kind data_kind;
+  enum media_kind media_kind;
+  char *path;
+
+  /* Start time of the media item as rtp-time
+     The stream-start is the rtp-time the media item did or would have
+     started playing (after seek or pause), therefor the elapsed time of the
+     media item is always:
+     elapsed time = current rtptime - stream-start */
+  uint64_t stream_start;
+
+  /* Output start time of the media item as rtp-time
+     The output start time is the rtp-time of the first audio packet send
+     to the audio outputs.
+     It differs from stream-start especially after a seek, where the first audio
+     packet has the next rtp-time as output start and stream start becomes the
+     rtp-time the media item would have been started playing if the seek did
+     not happen. */
+  uint64_t output_start;
+
+  /* End time of media item as rtp-time
+     The end time is set if the reading (source_read) of the media item reached
+     end of file, until then it is 0. */
+  uint64_t end;
+
+  /* Opaque pointer to data that the input sets up when called with setup(), and
+   * which is cleaned up by the input with stop()
+   */
+  void *input_ctx;
+
+  /* Input has completed setup of the source
+   */
+  int setup_done;
+
+  struct player_source *play_next;
+};
+
+typedef int (*input_cb)(void);
+
+struct input_metadata
+{
+  uint32_t item_id;
+
+  int startup;
+
+  uint64_t rtptime;
+  uint64_t offset;
+
+  // The player will update queue_item with the below
+  uint32_t song_length;
+
+  char *artist;
+  char *title;
+  char *album;
+  char *genre;
+  char *artwork_url;
+};
+
+struct input_definition
+{
+  // Name of the input
+  const char *name;
+
+  // Type of input
+  enum input_types type;
+
+  // Set to 1 if the input initialization failed
+  char disabled;
+
+  // Prepare a playback session
+  int (*setup)(struct player_source *ps);
+
+  // Starts playback loop (must be defined)
+  int (*start)(struct player_source *ps);
+
+  // Cleans up when playback loop has ended
+  int (*stop)(struct player_source *ps);
+
+  // Changes the playback position
+  int (*seek)(struct player_source *ps, int seek_ms);
+
+  // Return metadata
+  int (*metadata_get)(struct input_metadata *metadata, struct player_source *ps, uint64_t rtptime);
+
+  // Initialization function called during startup
+  int (*init)(void);
+
+  // Deinitialization function called at shutdown
+  void (*deinit)(void);
+
+};
+
+/*
+ * Input modules should use this to test if playback should end
+ */
+int input_loop_break;
+
+/*
+ * Transfer stream data to the player's input buffer. The input evbuf will be
+ * drained on succesful write. This is to avoid copying memory. If the player's
+ * input buffer is full the function will block until the write can be made
+ * (unless INPUT_FILE_NONBLOCK is set).
+ *
+ * @in  evbuf    Raw audio data to write
+ * @in  flags    One or more INPUT_FLAG_*
+ * @return       0 on success, EAGAIN if buffer was full (and _NONBLOCK is set),
+ *               -1 on error
+ */
+int
+input_write(struct evbuffer *evbuf, short flags);
+
+/*
+ * Input modules can use this to wait in the playback loop (like input_write()
+ * would have done)
+ */
+void
+input_wait(void);
+
+/*
+ * Move a chunk of stream data from the player's input buffer to an output
+ * buffer. Should only be called by the player thread. Will not block.
+ *
+ * @in  data     Output buffer
+ * @in  size     How much data to move to the output buffer
+ * @out flags    Flags INPUT_FLAG_*
+ * @return       Number of bytes moved, -1 on error
+ */
+int
+input_read(void *data, size_t size, short *flags);
+
+/*
+ * Player can set this to get a callback from the input when the input buffer
+ * is full. The player may use this to resume playback after an underrun.
+ *
+ * @in  cb       The callback
+ */
+void
+input_buffer_full_cb(input_cb cb);
+
+/*
+ * Initializes the given player source for playback
+ */
+int
+input_setup(struct player_source *ps);
+
+/*
+ * Tells the input to start or resume playback, i.e. after calling this function
+ * the input buffer will begin to fill up, and should be read periodically with
+ * input_read(). Before calling this input_setup() must have been called.
+ */
+int
+input_start(struct player_source *ps);
+
+/*
+ * Pauses playback of the given player source (stops playback loop) and flushes
+ * the input buffer
+ */
+int
+input_pause(struct player_source *ps);
+
+/*
+ * Stops playback loop (if running), flushes input buffer and cleans up the
+ * player source
+ */
+int
+input_stop(struct player_source *ps);
+
+/*
+ * Seeks playback position to seek_ms. Returns actual seek position, 0 on
+ * unseekable, -1 on error. May block.
+ */
+int
+input_seek(struct player_source *ps, int seek_ms);
+
+/*
+ * Flush input buffer. Output flags will be the same as input_read().
+ */
+void
+input_flush(short *flags);
+
+/*
+ * Gets metadata from the input, returns 0 if metadata is set, otherwise -1
+ */
+int
+input_metadata_get(struct input_metadata *metadata, struct player_source *ps, int startup, uint64_t rtptime);
+
+/*
+ * Free the entire struct
+ */
+void
+input_metadata_free(struct input_metadata *metadata, int content_only);
+
+/*
+ * Called by player_init (so will run in main thread)
+ */
+int
+input_init(void);
+
+/*
+ * Called by player_deinit (so will run in main thread)
+ */
+void
+input_deinit(void);
+
+#endif /* !__INPUT_H__ */
diff --git a/src/inputs/file_http.c b/src/inputs/file_http.c
new file mode 100644
index 0000000..220c7a9
--- /dev/null
+++ b/src/inputs/file_http.c
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2017 Espen Jurgensen
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdint.h>
+
+#include <event2/buffer.h>
+
+#include "transcode.h"
+#include "http.h"
+#include "misc.h"
+#include "input.h"
+
+static int
+setup(struct player_source *ps)
+{
+  ps->input_ctx = transcode_setup(ps->data_kind, ps->path, ps->len_ms, XCODE_PCM16_NOHEADER, NULL);
+  if (!ps->input_ctx)
+    return -1;
+
+  ps->setup_done = 1;
+
+  return 0;
+}
+
+static int
+setup_http(struct player_source *ps)
+{
+  char *url;
+
+  if (http_stream_setup(&url, ps->path) < 0)
+    return -1;
+
+  free(ps->path);
+  ps->path = url;
+
+  return setup(ps);
+}
+
+static int
+start(struct player_source *ps)
+{
+  struct evbuffer *evbuf;
+  short flags;
+  int ret;
+  int icy_timer;
+
+  evbuf = evbuffer_new();
+
+  ret = -1;
+  flags = 0;
+  while (!input_loop_break && !(flags & INPUT_FLAG_EOF))
+    {
+      // We set "wanted" to 1 because the read size doesn't matter to us
+      // TODO optimize?
+      ret = transcode(evbuf, 1, ps->input_ctx, &icy_timer);
+      if (ret < 0)
+	break;
+
+      flags = ((ret == 0) ? INPUT_FLAG_EOF : 0) |
+               (icy_timer ? INPUT_FLAG_METADATA : 0);
+
+      ret = input_write(evbuf, flags);
+      if (ret < 0)
+	break;
+    }
+
+  evbuffer_free(evbuf);
+
+  return ret;
+}
+
+static int
+stop(struct player_source *ps)
+{
+  transcode_cleanup(ps->input_ctx);
+
+  ps->input_ctx = NULL;
+  ps->setup_done = 0;
+
+  return 0;
+}
+
+static int
+seek(struct player_source *ps, int seek_ms)
+{
+  return transcode_seek(ps->input_ctx, seek_ms);
+}
+
+static int
+metadata_get_http(struct input_metadata *metadata, struct player_source *ps, uint64_t rtptime)
+{
+  struct http_icy_metadata *m;
+  int changed;
+
+  m = transcode_metadata(ps->input_ctx, &changed);
+  if (!m)
+    return -1;
+
+  if (!changed)
+    {
+      http_icy_metadata_free(m, 0);
+      return -1; // TODO Perhaps a problem since this prohibits the player updating metadata
+    }
+
+  if (m->artist)
+    swap_pointers(&metadata->artist, &m->artist);
+  // Note we map title to album, because clients should show stream name as titel
+  if (m->title)
+    swap_pointers(&metadata->album, &m->title);
+  if (m->artwork_url)
+    swap_pointers(&metadata->artwork_url, &m->artwork_url);
+
+  http_icy_metadata_free(m, 0);
+  return 0;
+}
+
+struct input_definition input_file =
+{
+  .name = "file",
+  .type = INPUT_TYPE_FILE,
+  .disabled = 0,
+  .setup = setup,
+  .start = start,
+  .stop = stop,
+  .seek = seek,
+};
+
+struct input_definition input_http =
+{
+  .name = "http",
+  .type = INPUT_TYPE_HTTP,
+  .disabled = 0,
+  .setup = setup_http,
+  .start = start,
+  .stop = stop,
+  .metadata_get = metadata_get_http,
+};
diff --git a/src/inputs/pipe.c b/src/inputs/pipe.c
new file mode 100644
index 0000000..a0b513d
--- /dev/null
+++ b/src/inputs/pipe.c
@@ -0,0 +1,959 @@
+/*
+ * Copyright (C) 2017 Espen Jurgensen
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ *
+ * About pipe.c
+ * --------------
+ * This module will read a PCM16 stream from a named pipe and write it to the
+ * input buffer. The user may start/stop playback from a pipe by selecting it
+ * through a client. If the user has configured pipe_autostart, then pipes in
+ * the library will also be watched for data, and playback will start/stop
+ * automatically.
+ *
+ * The module will also look for pipes with a .metadata suffix, and if found,
+ * the metadata will be parsed and fed to the player. The metadata must be in
+ * the format Shairport uses for this purpose.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <fcntl.h>
+#include <string.h>
+#include <errno.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+
+#include <event2/event.h>
+#include <event2/buffer.h>
+#include <mxml.h>
+
+#include "input.h"
+#include "misc.h"
+#include "logger.h"
+#include "db.h"
+#include "conffile.h"
+#include "listener.h"
+#include "player.h"
+#include "worker.h"
+#include "commands.h"
+#include "mxml-compat.h"
+
+// Maximum number of pipes to watch for data
+#define PIPE_MAX_WATCH 4
+// Max number of bytes to read from a pipe at a time
+#define PIPE_READ_MAX 65536
+// Max number of bytes to buffer from metadata pipes
+#define PIPE_METADATA_BUFLEN_MAX 262144
+
+enum pipetype
+{
+  PIPE_PCM,
+  PIPE_METADATA,
+};
+
+struct pipe
+{
+  int id;               // The mfi id of the pipe
+  int fd;               // File descriptor
+  bool is_autostarted;  // We autostarted the pipe (and we will autostop)
+  char *path;           // Path
+  enum pipetype type;   // PCM (audio) or metadata
+  event_callback_fn cb; // Callback when there is data to read
+  struct event *ev;     // Event for the callback
+
+  struct pipe *next;
+};
+
+union pipe_arg
+{
+  uint32_t id;
+  struct pipe *pipelist;
+};
+
+// The usual thread stuff
+static pthread_t tid_pipe;
+static struct event_base *evbase_pipe;
+static struct commands_base *cmdbase;
+
+// From config - should we watch library pipes for data or only start on request
+static int pipe_autostart;
+// The mfi id of the pipe autostarted by the pipe thread
+static int pipe_autostart_id;
+
+// Global list of pipes we are watching (if watching/autostart is enabled)
+static struct pipe *pipe_watch_list;
+
+// Single pipe that we start watching for metadata after playback starts
+static struct pipe *pipe_metadata;
+// We read metadata into this evbuffer
+static struct evbuffer *pipe_metadata_buf;
+// Parsed metadata goes here
+static struct input_metadata pipe_metadata_parsed;
+// Mutex to share the parsed metadata
+static pthread_mutex_t pipe_metadata_lock;
+// True if there is new metadata to push to the player
+static bool pipe_metadata_is_new;
+
+/* -------------------------------- HELPERS ------------------------------- */
+
+static struct pipe *
+pipe_create(const char *path, int id, enum pipetype type, event_callback_fn cb)
+{
+  struct pipe *pipe;
+
+  pipe = calloc(1, sizeof(struct pipe));
+  pipe->path  = strdup(path);
+  pipe->id    = id;
+  pipe->fd    = -1;
+  pipe->type  = type;
+  pipe->cb    = cb;
+
+  return pipe;
+}
+
+static void
+pipe_free(struct pipe *pipe)
+{
+  free(pipe->path);
+  free(pipe);
+}
+
+static int
+pipe_open(const char *path, bool silent)
+{
+  struct stat sb;
+  int fd;
+
+  DPRINTF(E_DBG, L_PLAYER, "(Re)opening pipe: '%s'\n", path);
+
+  if (lstat(path, &sb) < 0)
+    {
+      if (!silent)
+	DPRINTF(E_LOG, L_PLAYER, "Could not lstat() '%s': %s\n", path, strerror(errno));
+      return -1;
+    }
+
+  if (!S_ISFIFO(sb.st_mode))
+    {
+      DPRINTF(E_LOG, L_PLAYER, "Source type is pipe, but path is not a fifo: %s\n", path);
+      return -1;
+    }
+
+  fd = open(path, O_RDONLY | O_NONBLOCK);
+  if (fd < 0)
+    {
+      DPRINTF(E_LOG, L_PLAYER, "Could not open pipe for reading '%s': %s\n", path, strerror(errno));
+      return -1;
+    }
+
+  return fd;
+}
+
+static void
+pipe_close(int fd)
+{
+  if (fd >= 0)
+    close(fd);
+}
+
+static int
+watch_add(struct pipe *pipe)
+{
+  bool silent;
+
+  silent = (pipe->type == PIPE_METADATA);
+  pipe->fd = pipe_open(pipe->path, silent);
+  if (pipe->fd < 0)
+    return -1;
+
+  pipe->ev = event_new(evbase_pipe, pipe->fd, EV_READ, pipe->cb, pipe);
+  if (!pipe->ev)
+    {
+      DPRINTF(E_LOG, L_PLAYER, "Could not watch pipe for new data '%s'\n", pipe->path);
+      pipe_close(pipe->fd);
+      return -1;
+    }
+
+  event_add(pipe->ev, NULL);
+
+  return 0;
+}
+
+static void
+watch_del(struct pipe *pipe)
+{
+  if (pipe->ev)
+    event_free(pipe->ev);
+
+  pipe_close(pipe->fd);
+
+  pipe->fd = -1;
+}
+
+// If a read on pipe returns 0 it is an EOF, and we must close it and reopen it
+// for renewed watching. The event will be freed and reallocated by this.
+static int
+watch_reset(struct pipe *pipe)
+{
+  watch_del(pipe);
+
+  return watch_add(pipe);
+}
+
+static void
+pipelist_add(struct pipe **list, struct pipe *pipe)
+{
+  pipe->next = *list;
+  *list = pipe;
+}
+
+static void
+pipelist_remove(struct pipe **list, struct pipe *pipe)
+{
+  struct pipe *prev = NULL;
+  struct pipe *p;
+
+  for (p = *list; p; p = p->next)
+    {
+      if (p->id == pipe->id)
+	break;
+
+      prev = p;
+    }
+
+  if (!p)
+    return;
+
+  if (!prev)
+    *list = pipe->next;
+  else
+    prev->next = pipe->next;
+
+  pipe_free(pipe);
+}
+
+static struct pipe *
+pipelist_find(struct pipe *list, int id)
+{
+  struct pipe *p;
+
+  for (p = list; p; p = p->next)
+    {
+      if (id == p->id)
+	return p;
+    }
+
+  return NULL;
+}
+
+// Convert to macro?
+static inline uint32_t
+dmapval(const char s[4])
+{
+  return ((s[0] << 24) | (s[1] << 16) | (s[2] << 8) | (s[3] << 0));
+}
+
+static void
+parse_progress(struct input_metadata *m, char *progress)
+{
+  char *s;
+  char *ptr;
+  uint64_t start;
+  uint64_t pos;
+  uint64_t end;
+
+  if (!(s = strtok_r(progress, "/", &ptr)))
+    return;
+  safe_atou64(s, &start);
+
+  if (!(s = strtok_r(NULL, "/", &ptr)))
+    return;
+  safe_atou64(s, &pos);
+
+  if (!(s = strtok_r(NULL, "/", &ptr)))
+    return;
+  safe_atou64(s, &end);
+
+  if (!start || !pos || !end)
+    return;
+
+  m->rtptime = start; // Not actually used - we have our own rtptime
+  m->offset = (pos > start) ? (pos - start) : 0;
+  m->song_length = (end - start) * 10 / 441; // Convert to ms based on 44100
+}
+
+static void
+parse_volume(const char *volume)
+{
+  char *volume_next;
+  float airplay_volume;
+  int local_volume;
+
+  errno = 0;
+  airplay_volume = strtof(volume, &volume_next);
+
+  if ((errno == ERANGE) || (volume == volume_next))
+    {
+      DPRINTF(E_LOG, L_PLAYER, "Invalid Shairport airplay volume in string (%s): %s\n", volume,
+	      (errno == ERANGE ? strerror(errno) : "First token is not a number."));
+      return;
+    }
+
+  if (strcmp(volume_next, ",0.00,0.00,0.00") != 0)
+    {
+      DPRINTF(E_DBG, L_PLAYER, "Not applying Shairport airplay volume while software volume control is enabled (%s)\n", volume);
+      return;
+    }
+
+  if (((int) airplay_volume) == -144)
+    {
+      DPRINTF(E_DBG, L_PLAYER, "Applying Shairport airplay volume ('mute', value: %.2f)\n", airplay_volume);
+      player_volume_set(0);
+    }
+  else if (airplay_volume >= -30.0 && airplay_volume <= 0.0)
+    {
+      local_volume = (int)(100.0 + (airplay_volume / 30.0 * 100.0));
+
+      DPRINTF(E_DBG, L_PLAYER, "Applying Shairport airplay volume (percent: %d, value: %.2f)\n", local_volume, airplay_volume);
+      player_volume_set(local_volume);
+    }
+  else
+    DPRINTF(E_LOG, L_PLAYER, "Shairport airplay volume out of range (-144.0, [-30.0 - 0.0]): %.2f\n", airplay_volume);
+}
+
+// returns 1 on metadata found, 0 on nothing, -1 on error
+static int
+parse_item(struct input_metadata *m, const char *item)
+{
+  mxml_node_t *xml;
+  mxml_node_t *haystack;
+  mxml_node_t *needle;
+  const char *s;
+  uint32_t type;
+  uint32_t code;
+  char *progress;
+  char *volume;
+  char **data;
+  int ret;
+
+  ret = 0;
+  xml = mxmlNewXML("1.0");
+  if (!xml)
+    return -1;
+
+//  DPRINTF(E_DBG, L_PLAYER, "Parsing %s\n", item);
+
+  haystack = mxmlLoadString(xml, item, MXML_NO_CALLBACK);
+  if (!haystack)
+    {
+      DPRINTF(E_LOG, L_PLAYER, "Could not parse pipe metadata\n");
+      goto out_error;
+    }
+
+  type = 0;
+  if ( (needle = mxmlFindElement(haystack, haystack, "type", NULL, NULL, MXML_DESCEND)) &&
+       (s = mxmlGetText(needle, NULL)) )
+    sscanf(s, "%8x", &type);
+
+  code = 0;
+  if ( (needle = mxmlFindElement(haystack, haystack, "code", NULL, NULL, MXML_DESCEND)) &&
+       (s = mxmlGetText(needle, NULL)) )
+    sscanf(s, "%8x", &code);
+
+  if (!type || !code)
+    {
+      DPRINTF(E_LOG, L_PLAYER, "No type (%d) or code (%d) in pipe metadata, aborting\n", type, code);
+      goto out_error;
+    }
+
+  if (code == dmapval("asal"))
+    data = &m->album;
+  else if (code == dmapval("asar"))
+    data = &m->artist;
+  else if (code == dmapval("minm"))
+    data = &m->title;
+  else if (code == dmapval("asgn"))
+    data = &m->genre;
+  else if (code == dmapval("prgr"))
+    data = &progress;
+  else if (code == dmapval("pvol"))
+    data = &volume;
+  else
+    goto out_nothing;
+
+  if ( (needle = mxmlFindElement(haystack, haystack, "data", NULL, NULL, MXML_DESCEND)) &&
+       (s = mxmlGetText(needle, NULL)) )
+    {
+      pthread_mutex_lock(&pipe_metadata_lock);
+
+      if (data != &progress && data != &volume)
+	free(*data);
+
+      *data = b64_decode(s);
+
+      DPRINTF(E_DBG, L_PLAYER, "Read Shairport metadata (type=%8x, code=%8x): '%s'\n", type, code, *data);
+
+      if (data == &progress)
+	{
+	  parse_progress(m, progress);
+	  free(*data);
+	}
+      else if (data == &volume)
+	{
+	  parse_volume(volume);
+	  free(*data);
+	}
+
+      pthread_mutex_unlock(&pipe_metadata_lock);
+
+      ret = 1;
+    }
+
+ out_nothing:
+  mxmlDelete(xml);
+  return ret;
+
+ out_error:
+  mxmlDelete(xml);
+  return -1;
+}
+
+static char *
+extract_item(struct evbuffer *evbuf)
+{
+  struct evbuffer_ptr evptr;
+  size_t size;
+  char *item;
+
+  evptr = evbuffer_search(evbuf, "</item>", strlen("</item>"), NULL);
+  if (evptr.pos < 0)
+    return NULL;
+
+  size = evptr.pos + strlen("</item>") + 1;
+  item = malloc(size);
+  if (!item)
+    return NULL;
+
+  evbuffer_remove(evbuf, item, size - 1);
+  item[size - 1] = '\0';
+
+  return item;
+}
+
+static int
+pipe_metadata_parse(struct input_metadata *m, struct evbuffer *evbuf)
+{
+  char *item;
+  int found;
+  int ret;
+
+  found = 0;
+  while ((item = extract_item(evbuf)))
+    {
+      ret = parse_item(m, item);
+      free(item);
+      if (ret < 0)
+	return -1;
+      if (ret > 0)
+	found = 1;
+    }
+
+  return found;
+}
+
+
+/* ----------------------------- PIPE WATCHING ---------------------------- */
+/*                                Thread: pipe                              */
+
+// Some data arrived on a pipe we watch - let's autostart playback
+static void
+pipe_read_cb(evutil_socket_t fd, short event, void *arg)
+{
+  struct pipe *pipe = arg;
+  struct player_status status;
+  int ret;
+
+  ret = player_get_status(&status);
+  if (status.id == pipe->id)
+    {
+      DPRINTF(E_DBG, L_PLAYER, "Pipe '%s' already playing\n", pipe->path);
+      return; // We are already playing the pipe
+    }
+  else if ((ret < 0) || (status.status == PLAY_PLAYING))
+    {
+      DPRINTF(E_LOG, L_PLAYER, "Data arrived on pipe '%s' - ignoring, player is busy\n", pipe->path);
+      // FIXME What to do in this situation? Can't re-add the event, since it
+      // will trigger right away, but also not good to stop watching the pipe
+      // like we do right now.
+      return;
+    }
+
+  DPRINTF(E_INFO, L_PLAYER, "Autostarting pipe '%s' (fd %d)\n", pipe->path, fd);
+
+  player_playback_stop();
+
+  ret = player_playback_start_byid(pipe->id);
+  if (ret < 0)
+    {
+      DPRINTF(E_LOG, L_PLAYER, "Autostarting pipe '%s' (fd %d) failed\n", pipe->path, fd);
+      return;
+    }
+
+  pipe_autostart_id = pipe->id;
+}
+
+static enum command_state
+pipe_watch_reset(void *arg, int *retval)
+{
+  union pipe_arg *cmdarg = arg;
+  struct pipe *pipe;
+
+  pipe_autostart_id = 0;
+
+  pipe = pipelist_find(pipe_watch_list, cmdarg->id);
+
+  *retval = watch_reset(pipe);
+
+  return COMMAND_END;
+}
+
+static enum command_state
+pipe_watch_update(void *arg, int *retval)
+{
+  union pipe_arg *cmdarg = arg;
+  struct pipe *pipelist;
+  struct pipe *pipe;
+  struct pipe *next;
+  int count;
+
+  if (cmdarg)
+    pipelist = cmdarg->pipelist;
+  else
+    pipelist = NULL;
+
+  // Removes pipes that are gone from the watchlist
+  for (pipe = pipe_watch_list; pipe; pipe = next)
+    {
+      next = pipe->next;
+
+      if (!pipelist_find(pipelist, pipe->id))
+	{
+	  DPRINTF(E_DBG, L_PLAYER, "Pipe watch deleted: '%s'\n", pipe->path);
+	  watch_del(pipe);
+	  pipelist_remove(&pipe_watch_list, pipe); // Will free pipe
+	}
+    }
+
+  // Looks for new pipes and adds them to the watchlist
+  for (pipe = pipelist, count = 0; pipe; pipe = next, count++)
+    {
+      next = pipe->next;
+
+      if (count > PIPE_MAX_WATCH)
+	{
+	  DPRINTF(E_LOG, L_PLAYER, "Max open pipes reached (%d), will not watch '%s'\n", PIPE_MAX_WATCH, pipe->path);
+	  pipe_free(pipe);
+	  continue;
+	}
+
+      if (!pipelist_find(pipe_watch_list, pipe->id))
+	{
+	  DPRINTF(E_DBG, L_PLAYER, "Pipe watch added: '%s'\n", pipe->path);
+	  watch_add(pipe);
+	  pipelist_add(&pipe_watch_list, pipe); // Changes pipe->next
+	}
+      else
+	{
+	  DPRINTF(E_DBG, L_PLAYER, "Pipe watch exists: '%s'\n", pipe->path);
+	  pipe_free(pipe);
+	}
+    }
+
+  *retval = 0;
+  return COMMAND_END;
+}
+
+static void *
+pipe_thread_run(void *arg)
+{
+  event_base_dispatch(evbase_pipe);
+
+  pthread_exit(NULL);
+}
+
+
+/* -------------------------- METADATA PIPE HANDLING ---------------------- */
+/*                               Thread: worker                             */
+
+static void
+pipe_metadata_watch_del(void *arg)
+{
+  if (!pipe_metadata)
+    return;
+
+  evbuffer_free(pipe_metadata_buf);
+  watch_del(pipe_metadata);
+  pipe_free(pipe_metadata);
+  pipe_metadata = NULL;
+}
+
+// Some metadata arrived on a pipe we watch
+static void
+pipe_metadata_read_cb(evutil_socket_t fd, short event, void *arg)
+{
+  int ret;
+
+  ret = evbuffer_read(pipe_metadata_buf, pipe_metadata->fd, PIPE_READ_MAX);
+  if (ret < 0)
+    {
+      if (errno != EAGAIN)
+	pipe_metadata_watch_del(NULL);
+      return;
+    }
+  else if (ret == 0)
+    {
+      // Reset the pipe
+      ret = watch_reset(pipe_metadata);
+      if (ret < 0)
+	return;
+      goto readd;
+    }
+
+  if (evbuffer_get_length(pipe_metadata_buf) > PIPE_METADATA_BUFLEN_MAX)
+    {
+      DPRINTF(E_LOG, L_PLAYER, "Can't process data from metadata pipe, reading will stop\n");
+      pipe_metadata_watch_del(NULL);
+      return;
+    }
+
+  ret = pipe_metadata_parse(&pipe_metadata_parsed, pipe_metadata_buf);
+  if (ret < 0)
+    {
+      pipe_metadata_watch_del(NULL);
+      return;
+    }
+  else if (ret > 0)
+    {
+      // Trigger notification in playback loop
+      pipe_metadata_is_new = 1;
+    }
+
+ readd:
+  if (pipe_metadata && pipe_metadata->ev)
+    event_add(pipe_metadata->ev, NULL);
+}
+
+static void
+pipe_metadata_watch_add(void *arg)
+{
+  char *base_path = arg;
+  char path[PATH_MAX];
+  int ret;
+
+  ret = snprintf(path, sizeof(path), "%s.metadata", base_path);
+  if ((ret < 0) || (ret > sizeof(path)))
+    return;
+
+  pipe_metadata = pipe_create(path, 0, PIPE_METADATA, pipe_metadata_read_cb);
+  if (!pipe_metadata)
+    return;
+
+  pipe_metadata_buf = evbuffer_new();
+
+  ret = watch_add(pipe_metadata);
+  if (ret < 0)
+    {
+      evbuffer_free(pipe_metadata_buf);
+      pipe_free(pipe_metadata);
+      pipe_metadata = NULL;
+      return;
+    }
+}
+
+
+/* ---------------------- PIPE WATCH THREAD START/STOP -------------------- */
+/*                            Thread: filescanner                           */
+
+static void
+pipe_thread_start(void)
+{
+  CHECK_NULL(L_PLAYER, evbase_pipe = event_base_new());
+  CHECK_NULL(L_PLAYER, cmdbase = commands_base_new(evbase_pipe, NULL));
+  CHECK_ERR(L_PLAYER, pthread_create(&tid_pipe, NULL, pipe_thread_run, NULL));
+
+#if defined(HAVE_PTHREAD_SETNAME_NP)
+  pthread_setname_np(tid_pipe, "pipe");
+#elif defined(HAVE_PTHREAD_SET_NAME_NP)
+  pthread_set_name_np(tid_pipe, "pipe");
+#endif
+}
+
+static void
+pipe_thread_stop(void)
+{
+  if (!tid_pipe)
+    return;
+
+  commands_exec_sync(cmdbase, pipe_watch_update, NULL, NULL);
+
+  commands_base_destroy(cmdbase);
+  pthread_join(tid_pipe, NULL);
+  event_base_free(evbase_pipe);
+  tid_pipe = 0;
+}
+
+// Makes a pipelist with pipe items from the db, returns NULL on no pipes
+static struct pipe *
+pipelist_create(void)
+{
+  struct query_params qp;
+  struct db_media_file_info dbmfi;
+  struct pipe *head;
+  struct pipe *pipe;
+  char filter[32];
+  int id;
+  int ret;
+
+  memset(&qp, 0, sizeof(struct query_params));
+  qp.type = Q_ITEMS;
+  qp.filter = filter;
+
+  snprintf(filter, sizeof(filter), "f.data_kind = %d", DATA_KIND_PIPE);
+
+  ret = db_query_start(&qp);
+  if (ret < 0)
+    return NULL;
+
+  head = NULL;
+  while (((ret = db_query_fetch_file(&qp, &dbmfi)) == 0) && (dbmfi.id))
+    {
+      ret = safe_atoi32(dbmfi.id, &id);
+      if (ret < 0)
+	continue;
+
+      pipe = pipe_create(dbmfi.path, id, PIPE_PCM, pipe_read_cb);
+      pipelist_add(&head, pipe);
+    }
+
+  db_query_end(&qp);
+
+  return head;
+}
+
+// Queries the db to see if any pipes are present in the library. If so, starts
+// the pipe thread to watch the pipes. If no pipes in library, it will shut down
+// the pipe thread.
+static void
+pipe_listener_cb(enum listener_event_type type)
+{
+  union pipe_arg *cmdarg;
+
+  cmdarg = malloc(sizeof(union pipe_arg));
+  if (!cmdarg)
+    return;
+
+  cmdarg->pipelist = pipelist_create();
+  if (!cmdarg->pipelist)
+    {
+      pipe_thread_stop();
+      free(cmdarg);
+      return;
+    }
+
+  if (!tid_pipe)
+    pipe_thread_start();
+
+  commands_exec_async(cmdbase, pipe_watch_update, cmdarg);
+}
+
+
+/* -------------------------- PIPE INPUT INTERFACE ------------------------ */
+/*                            Thread: player/input                          */
+
+static int
+setup(struct player_source *ps)
+{
+  struct pipe *pipe;
+  int fd;
+
+  fd = pipe_open(ps->path, 0);
+  if (fd < 0)
+    return -1;
+
+  CHECK_NULL(L_PLAYER, pipe = pipe_create(ps->path, ps->id, PIPE_PCM, NULL));
+  pipe->fd = fd;
+  pipe->is_autostarted = (ps->id == pipe_autostart_id);
+
+  worker_execute(pipe_metadata_watch_add, ps->path, strlen(ps->path) + 1, 0);
+
+  ps->input_ctx = pipe;
+  ps->setup_done = 1;
+
+  return 0;
+}
+
+static int
+start(struct player_source *ps)
+{
+  struct pipe *pipe = ps->input_ctx;
+  struct evbuffer *evbuf;
+  short flags;
+  int ret;
+
+  evbuf = evbuffer_new();
+  if (!evbuf)
+    {
+      DPRINTF(E_LOG, L_PLAYER, "Out of memory for pipe evbuf\n");
+      return -1;
+    }
+
+  ret = -1;
+  while (!input_loop_break)
+    {
+      ret = evbuffer_read(evbuf, pipe->fd, PIPE_READ_MAX);
+      if ((ret == 0) && (pipe->is_autostarted))
+	{
+	  input_write(evbuf, INPUT_FLAG_EOF); // Autostop
+	  break;
+	}
+      else if ((ret == 0) || ((ret < 0) && (errno == EAGAIN)))
+	{
+	  input_wait();
+	  continue;
+	}
+      else if (ret < 0)
+	{
+	  DPRINTF(E_LOG, L_PLAYER, "Could not read from pipe '%s': %s\n", ps->path, strerror(errno));
+	  break;
+	}
+
+      flags = (pipe_metadata_is_new ? INPUT_FLAG_METADATA : 0);
+      pipe_metadata_is_new = 0;
+
+      ret = input_write(evbuf, flags);
+      if (ret < 0)
+	break;
+    }
+
+  evbuffer_free(evbuf);
+
+  return ret;
+}
+
+static int
+stop(struct player_source *ps)
+{
+  struct pipe *pipe = ps->input_ctx;
+  union pipe_arg *cmdarg;
+
+  DPRINTF(E_DBG, L_PLAYER, "Stopping pipe\n");
+
+  pipe_close(pipe->fd);
+
+  // Reset the pipe and start watching it again for new data. Must be async or
+  // we will deadlock from the stop in pipe_read_cb().
+  if (pipe_autostart && (cmdarg = malloc(sizeof(union pipe_arg))))
+    {
+      cmdarg->id = pipe->id;
+      commands_exec_async(cmdbase, pipe_watch_reset, cmdarg);
+    }
+
+  if (pipe_metadata)
+    worker_execute(pipe_metadata_watch_del, NULL, 0, 0);
+
+  pipe_free(pipe);
+
+  ps->input_ctx = NULL;
+  ps->setup_done = 0;
+
+  return 0;
+}
+
+static int
+metadata_get(struct input_metadata *metadata, struct player_source *ps, uint64_t rtptime)
+{
+  pthread_mutex_lock(&pipe_metadata_lock);
+
+  if (pipe_metadata_parsed.artist)
+    swap_pointers(&metadata->artist, &pipe_metadata_parsed.artist);
+  if (pipe_metadata_parsed.title)
+    swap_pointers(&metadata->title, &pipe_metadata_parsed.title);
+  if (pipe_metadata_parsed.album)
+    swap_pointers(&metadata->album, &pipe_metadata_parsed.album);
+  if (pipe_metadata_parsed.genre)
+    swap_pointers(&metadata->genre, &pipe_metadata_parsed.genre);
+  if (pipe_metadata_parsed.artwork_url)
+    swap_pointers(&metadata->artwork_url, &pipe_metadata_parsed.artwork_url);
+
+  if (pipe_metadata_parsed.song_length)
+    {
+      if (rtptime > ps->stream_start)
+	metadata->rtptime = rtptime - pipe_metadata_parsed.offset;
+      metadata->offset = pipe_metadata_parsed.offset;
+      metadata->song_length = pipe_metadata_parsed.song_length;
+    }
+
+  input_metadata_free(&pipe_metadata_parsed, 1);
+
+  pthread_mutex_unlock(&pipe_metadata_lock);
+
+  return 0;
+}
+
+// Thread: main
+static int
+init(void)
+{
+  CHECK_ERR(L_PLAYER, mutex_init(&pipe_metadata_lock));
+
+  pipe_autostart = cfg_getbool(cfg_getsec(cfg, "library"), "pipe_autostart");
+  if (pipe_autostart)
+    CHECK_ERR(L_PLAYER, listener_add(pipe_listener_cb, LISTENER_DATABASE));
+
+  return 0;
+}
+
+static void
+deinit(void)
+{
+  if (pipe_autostart)
+    {
+      listener_remove(pipe_listener_cb);
+      pipe_thread_stop();
+    }
+
+  CHECK_ERR(L_PLAYER, pthread_mutex_destroy(&pipe_metadata_lock));
+}
+
+struct input_definition input_pipe =
+{
+  .name = "pipe",
+  .type = INPUT_TYPE_PIPE,
+  .disabled = 0,
+  .setup = setup,
+  .start = start,
+  .stop = stop,
+  .metadata_get = metadata_get,
+  .init = init,
+  .deinit = deinit,
+};
diff --git a/src/inputs/spotify.c b/src/inputs/spotify.c
new file mode 100644
index 0000000..f4d5cc8
--- /dev/null
+++ b/src/inputs/spotify.c
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2017 Espen Jurgensen
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdint.h>
+
+#include "input.h"
+#include "spotify.h"
+
+static int
+setup(struct player_source *ps)
+{
+  int ret;
+
+  ret = spotify_playback_setup(ps->path);
+  if (ret < 0)
+    return -1;
+
+  ps->setup_done = 1;
+
+  return 0;
+}
+
+static int
+start(struct player_source *ps)
+{
+  int ret;
+
+  ret = spotify_playback_play();
+  if (ret < 0)
+    return -1;
+
+  while (!input_loop_break)
+    {
+      input_wait();
+    }
+
+  ret = spotify_playback_pause();
+
+  return ret;
+}
+
+static int
+stop(struct player_source *ps)
+{
+  int ret;
+
+  ret = spotify_playback_stop();
+  if (ret < 0)
+    return -1;
+
+  ps->setup_done = 0;
+
+  return 0;
+}
+
+static int
+seek(struct player_source *ps, int seek_ms)
+{
+  int ret;
+
+  ret = spotify_playback_seek(seek_ms);
+  if (ret < 0)
+    return -1;
+
+  return ret;
+}
+
+struct input_definition input_spotify =
+{
+  .name = "Spotify",
+  .type = INPUT_TYPE_SPOTIFY,
+  .disabled = 0,
+  .setup = setup,
+  .start = start,
+  .stop = stop,
+  .seek = seek,
+};
+
diff --git a/src/lastfm.c b/src/lastfm.c
index 7399063..b683851 100644
--- a/src/lastfm.c
+++ b/src/lastfm.c
@@ -33,20 +33,14 @@
 #include <mxml.h>
 #include <event2/buffer.h>
 #include <event2/http.h>
-#include <curl/curl.h>
+
+#include "mxml-compat.h"
 
 #include "db.h"
 #include "lastfm.h"
 #include "logger.h"
 #include "misc.h"
-
-
-struct https_client_ctx
-{
-  const char *url;
-  const char *body;
-  struct evbuffer *data;
-};
+#include "http.h"
 
 // LastFM becomes disabled if we get a scrobble, try initialising session,
 // but can't (probably no session key in db because user does not use LastFM)
@@ -69,153 +63,6 @@ static char *lastfm_session_key = NULL;
 
 /* --------------------------------- HELPERS ------------------------------- */
 
-/* Reads a LastFM credentials file (1st line username, 2nd line password) */
-static int
-credentials_read(char *path, char **username, char **password)
-{
-  FILE *fp;
-  char *u;
-  char *p;
-  char buf[256];
-  int len;
-
-  fp = fopen(path, "rb");
-  if (!fp)
-    {
-      DPRINTF(E_LOG, L_LASTFM, "Could not open lastfm credentials file %s: %s\n", path, strerror(errno));
-      return -1;
-    }
-
-  u = fgets(buf, sizeof(buf), fp);
-  if (!u)
-    {
-      DPRINTF(E_LOG, L_LASTFM, "Empty lastfm credentials file %s\n", path);
-
-      fclose(fp);
-      return -1;
-    }
-
-  len = strlen(u);
-  if (buf[len - 1] != '\n')
-    {
-      DPRINTF(E_LOG, L_LASTFM, "Invalid lastfm credentials file %s: username name too long or missing password\n", path);
-
-      fclose(fp);
-      return -1;
-    }
-
-  while (len)
-    {
-      if ((buf[len - 1] == '\r') || (buf[len - 1] == '\n'))
-	{
-	  buf[len - 1] = '\0';
-	  len--;
-	}
-      else
-	break;
-    }
-
-  if (!len)
-    {
-      DPRINTF(E_LOG, L_LASTFM, "Invalid lastfm credentials file %s: empty line where username expected\n", path);
-
-      fclose(fp);
-      return -1;
-    }
-
-  u = strdup(buf);
-  if (!u)
-    {
-      DPRINTF(E_LOG, L_LASTFM, "Out of memory for username while reading %s\n", path);
-
-      fclose(fp);
-      return -1;
-    }
-
-  p = fgets(buf, sizeof(buf), fp);
-  fclose(fp);
-  if (!p)
-    {
-      DPRINTF(E_LOG, L_LASTFM, "Invalid lastfm credentials file %s: no password\n", path);
-
-      free(u);
-      return -1;
-    }
-
-  len = strlen(p);
-
-  while (len)
-    {
-      if ((buf[len - 1] == '\r') || (buf[len - 1] == '\n'))
-	{
-	  buf[len - 1] = '\0';
-	  len--;
-	}
-      else
-	break;
-    }
-
-  p = strdup(buf);
-  if (!p)
-    {
-      DPRINTF(E_LOG, L_LASTFM, "Out of memory for password while reading %s\n", path);
-
-      free(u);
-      return -1;
-    }
-
-  DPRINTF(E_LOG, L_LASTFM, "lastfm credentials file OK, logging in with username %s\n", u);
-
-  *username = u;
-  *password = p;
-
-  return 0;
-}
-
-/* Converts parameters to a string in application/x-www-form-urlencoded format */
-static int
-body_print(char **body, struct keyval *kv)
-{
-  struct evbuffer *evbuf;
-  struct onekeyval *okv;
-  char *k;
-  char *v;
-
-  evbuf = evbuffer_new();
-
-  for (okv = kv->head; okv; okv = okv->next)
-    {
-      k = evhttp_encode_uri(okv->name);
-      if (!k)
-        continue;
-
-      v = evhttp_encode_uri(okv->value);
-      if (!v)
-	{
-	  free(k);
-	  continue;
-	}
-
-      evbuffer_add(evbuf, k, strlen(k));
-      evbuffer_add(evbuf, "=", 1);
-      evbuffer_add(evbuf, v, strlen(v));
-      if (okv->next)
-	evbuffer_add(evbuf, "&", 1);
-
-      free(k);
-      free(v);
-    }
-
-  evbuffer_add(evbuf, "\n", 1);
-
-  *body = evbuffer_readln(evbuf, NULL, EVBUFFER_EOL_ANY);
-
-  evbuffer_free(evbuf);
-
-  DPRINTF(E_DBG, L_LASTFM, "Parameters in request are: %s\n", *body);
-
-  return 0;
-}
 
 /* Creates an md5 signature of the concatenated parameters and adds it to keyval */
 static int
@@ -267,49 +114,11 @@ param_sign(struct keyval *kv)
   return ret;
 }
 
-/* For compability with mxml 2.6 */
-#ifndef HAVE_MXML_GETOPAQUE
-const char *				/* O - Opaque string or NULL */
-mxmlGetOpaque(mxml_node_t *node)	/* I - Node to get */
-{
-  if (!node)
-    return (NULL);
-
-  if (node->type == MXML_OPAQUE)
-    return (node->value.opaque);
-  else if (node->type == MXML_ELEMENT &&
-           node->child &&
-	   node->child->type == MXML_OPAQUE)
-    return (node->child->value.opaque);
-  else
-    return (NULL);
-}
-#endif
 
 /* --------------------------------- MAIN --------------------------------- */
 
-static size_t
-request_cb(char *ptr, size_t size, size_t nmemb, void *userdata)
-{
-  size_t realsize;
-  struct https_client_ctx *ctx;
-  int ret;
-
-  realsize = size * nmemb;
-  ctx = (struct https_client_ctx *)userdata;
-
-  ret = evbuffer_add(ctx->data, ptr, realsize);
-  if (ret < 0)
-    {
-      DPRINTF(E_LOG, L_LASTFM, "Error adding reply from LastFM to data buffer\n");
-      return 0;
-    }
-
-  return realsize;
-}
-
 static void
-response_proces(struct https_client_ctx *ctx)
+response_proces(struct http_client_ctx *ctx)
 {
   mxml_node_t *tree;
   mxml_node_t *s_node;
@@ -319,9 +128,9 @@ response_proces(struct https_client_ctx *ctx)
   char *sk;
 
   // NULL-terminate the buffer
-  evbuffer_add(ctx->data, "", 1);
+  evbuffer_add(ctx->input_body, "", 1);
 
-  body = (char *)evbuffer_pullup(ctx->data, -1);
+  body = (char *)evbuffer_pullup(ctx->input_body, -1);
   if (!body || (strlen(body) == 0))
     {
       DPRINTF(E_LOG, L_LASTFM, "Empty response\n");
@@ -369,7 +178,7 @@ response_proces(struct https_client_ctx *ctx)
   if (sk)
     {
       DPRINTF(E_LOG, L_LASTFM, "Got session key from LastFM: %s\n", sk);
-      db_admin_add("lastfm_sk", sk);
+      db_admin_set("lastfm_sk", sk);
 
       if (lastfm_session_key)
 	free(lastfm_session_key);
@@ -380,59 +189,10 @@ response_proces(struct https_client_ctx *ctx)
   mxmlDelete(tree);
 }
 
-// We use libcurl to make the request. We could use libevent and avoid the
-// dependency, but for SSL, libevent needs to be v2.1 or better, which is still
-// a bit too new to be in the major distros
-static int
-https_client_request(struct https_client_ctx *ctx)
-{
-  CURL *curl;
-  CURLcode res;
- 
-  curl = curl_easy_init();
-  if (!curl)
-    {
-      DPRINTF(E_LOG, L_LASTFM, "Error: Could not get curl handle\n");
-      return -1;
-    }
-
-  curl_easy_setopt(curl, CURLOPT_USERAGENT, "libcurl-agent/1.0");
-  curl_easy_setopt(curl, CURLOPT_POSTFIELDS, ctx->body);
-  curl_easy_setopt(curl, CURLOPT_URL, ctx->url);
-
-  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, request_cb);
-  curl_easy_setopt(curl, CURLOPT_WRITEDATA, ctx);
-
-  ctx->data = evbuffer_new();
-  if (!ctx->data)
-    {
-      DPRINTF(E_LOG, L_LASTFM, "Could not create evbuffer for LastFM response\n");
-      curl_easy_cleanup(curl);
-      return -1;
-    }
-
-  res = curl_easy_perform(curl);
-  if (res != CURLE_OK)
-    {
-      DPRINTF(E_LOG, L_LASTFM, "Request to %s failed: %s\n", ctx->url, curl_easy_strerror(res));
-      curl_easy_cleanup(curl);
-      return -1;
-    }
-
-  response_proces(ctx);
-
-  evbuffer_free(ctx->data);
- 
-  curl_easy_cleanup(curl);
-
-  return 0;
-}
-
 static int
 request_post(char *method, struct keyval *kv, int auth)
 {
-  struct https_client_ctx ctx;
-  char *body;
+  struct http_client_ctx ctx;
   int ret;
 
   ret = keyval_add(kv, "method", method);
@@ -453,18 +213,27 @@ request_post(char *method, struct keyval *kv, int auth)
       return -1;
     }
 
-  ret = body_print(&body, kv);
+  memset(&ctx, 0, sizeof(struct http_client_ctx));
+
+  ctx.output_body = http_form_urlencode(kv);
   if (ret < 0)
     {
-      DPRINTF(E_LOG, L_LASTFM, "Aborting request, body_print failed\n");
+      DPRINTF(E_LOG, L_LASTFM, "Aborting request, http_form_urlencode failed\n");
       return -1;
     }
 
-  memset(&ctx, 0, sizeof(struct https_client_ctx));
   ctx.url = auth ? auth_url : api_url;
-  ctx.body = body;
+  ctx.input_body = evbuffer_new();
+
+  ret = http_client_request(&ctx);
+  if (ret < 0)
+    goto out_free_ctx;
 
-  ret = https_client_request(&ctx);
+  response_proces(&ctx);
+
+ out_free_ctx:
+  free(ctx.output_body);
+  evbuffer_free(ctx.input_body);
 
   return ret;
 }
@@ -546,54 +315,39 @@ scrobble(int id)
 /* ---------------------------- Our lastfm API  --------------------------- */
 
 /* Thread: filescanner */
-int
-lastfm_login(char *path)
+void
+lastfm_login(char **arglist)
 {
   struct keyval *kv;
-  char *username;
-  char *password;
   int ret;
 
-  DPRINTF(E_DBG, L_LASTFM, "Got LastFM login request\n");
+  DPRINTF(E_LOG, L_LASTFM, "LastFM credentials file OK, logging in with username %s\n", arglist[0]);
 
   // Delete any existing session key
-  if (lastfm_session_key)
-    free(lastfm_session_key);
-
+  free(lastfm_session_key);
   lastfm_session_key = NULL;
 
   db_admin_delete("lastfm_sk");
 
-  // Read the credentials file
-  ret = credentials_read(path, &username, &password);
-  if (ret < 0)
-    return -1;
-
   // Enable LastFM now that we got a login attempt
   lastfm_disabled = 0;
 
   kv = keyval_alloc();
   if (!kv)
-    {
-      free(username);
-      free(password);
-      return -1;
-    }
+    return;
 
   ret = ( (keyval_add(kv, "api_key", lastfm_api_key) == 0) &&
-          (keyval_add(kv, "username", username) == 0) &&
-          (keyval_add(kv, "password", password) == 0) );
-
-  free(username);
-  free(password);
+          (keyval_add(kv, "username", arglist[0]) == 0) &&
+          (keyval_add(kv, "password", arglist[1]) == 0) );
+  if (!ret)
+    goto out_free_kv;
 
   // Send the login request
-  ret = request_post("auth.getMobileSession", kv, 1);
+  request_post("auth.getMobileSession", kv, 1);
 
+ out_free_kv:
   keyval_clear(kv);
   free(kv);
-
-  return ret;
 }
 
 /* Thread: worker */
diff --git a/src/lastfm.h b/src/lastfm.h
index 6a3fa03..7c6d9fe 100644
--- a/src/lastfm.h
+++ b/src/lastfm.h
@@ -2,8 +2,8 @@
 #ifndef __LASTFM_H__
 #define __LASTFM_H__
 
-int
-lastfm_login(char *path);
+void
+lastfm_login(char **arglist);
 
 int
 lastfm_scrobble(int id);
diff --git a/src/library.c b/src/library.c
new file mode 100644
index 0000000..e1dc1fb
--- /dev/null
+++ b/src/library.c
@@ -0,0 +1,844 @@
+/*
+ * Copyright (C) 2015 Christian Meffert <christian.meffert at googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <pthread.h>
+#ifdef HAVE_PTHREAD_NP_H
+# include <pthread_np.h>
+#endif
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unictype.h>
+#include <uninorm.h>
+#include <unistr.h>
+
+#include <event2/event.h>
+
+#include "library.h"
+#include "cache.h"
+#include "commands.h"
+#include "conffile.h"
+#include "db.h"
+#include "logger.h"
+#include "misc.h"
+#include "listener.h"
+#include "player.h"
+
+static struct commands_base *cmdbase;
+static pthread_t tid_library;
+
+struct event_base *evbase_lib;
+
+extern struct library_source filescanner;
+#ifdef HAVE_SPOTIFY_H
+extern struct library_source spotifyscanner;
+#endif
+
+static struct library_source *sources[] = {
+    &filescanner,
+#ifdef HAVE_SPOTIFY_H
+    &spotifyscanner,
+#endif
+    NULL
+};
+
+/* Flag for aborting scan on exit */
+static bool scan_exit;
+
+/* Flag for scan in progress */
+static bool scanning;
+
+// After being told by db that the library was updated through update_trigger(),
+// wait 60 seconds before notifying listeners of LISTENER_DATABASE. This is to
+// avoid bombarding the listeners while there are many db updates, and to make
+// sure they only get a single update (useful for the cache).
+static struct timeval library_update_wait = { 60, 0 };
+static struct event *updateev;
+
+
+static void
+sort_tag_create(char **sort_tag, char *src_tag)
+{
+  const uint8_t *i_ptr;
+  const uint8_t *n_ptr;
+  const uint8_t *number;
+  uint8_t out[1024];
+  uint8_t *o_ptr;
+  int append_number;
+  ucs4_t puc;
+  int numlen;
+  size_t len;
+  int charlen;
+
+  /* Note: include terminating NUL in string length for u8_normalize */
+
+  if (*sort_tag)
+    {
+      DPRINTF(E_DBG, L_LIB, "Existing sort tag will be normalized: %s\n", *sort_tag);
+      o_ptr = u8_normalize(UNINORM_NFD, (uint8_t *)*sort_tag, strlen(*sort_tag) + 1, NULL, &len);
+      free(*sort_tag);
+      *sort_tag = (char *)o_ptr;
+      return;
+    }
+
+  if (!src_tag || ((len = strlen(src_tag)) == 0))
+    {
+      *sort_tag = NULL;
+      return;
+    }
+
+  // Set input pointer past article if present
+  if ((strncasecmp(src_tag, "a ", 2) == 0) && (len > 2))
+    i_ptr = (uint8_t *)(src_tag + 2);
+  else if ((strncasecmp(src_tag, "an ", 3) == 0) && (len > 3))
+    i_ptr = (uint8_t *)(src_tag + 3);
+  else if ((strncasecmp(src_tag, "the ", 4) == 0) && (len > 4))
+    i_ptr = (uint8_t *)(src_tag + 4);
+  else
+    i_ptr = (uint8_t *)src_tag;
+
+  // Poor man's natural sort. Makes sure we sort like this: a1, a2, a10, a11, a21, a111
+  // We do this by padding zeroes to (short) numbers. As an alternative we could have
+  // made a proper natural sort algorithm in sqlext.c, but we don't, since we don't
+  // want any risk of hurting response times
+  memset(&out, 0, sizeof(out));
+  o_ptr = (uint8_t *)&out;
+  number = NULL;
+  append_number = 0;
+
+  do
+    {
+      n_ptr = u8_next(&puc, i_ptr);
+
+      if (uc_is_digit(puc))
+	{
+	  if (!number) // We have encountered the beginning of a number
+	    number = i_ptr;
+	  append_number = (n_ptr == NULL); // If last char in string append number now
+	}
+      else
+	{
+	  if (number)
+	    append_number = 1; // A number has ended so time to append it
+	  else
+	    {
+              charlen = u8_strmblen(i_ptr);
+              if (charlen >= 0)
+	    	o_ptr = u8_stpncpy(o_ptr, i_ptr, charlen); // No numbers in sight, just append char
+	    }
+	}
+
+      // Break if less than 100 bytes remain (prevent buffer overflow)
+      if (sizeof(out) - u8_strlen(out) < 100)
+	break;
+
+      // Break if number is very large (prevent buffer overflow)
+      if (number && (i_ptr - number > 50))
+	break;
+
+      if (append_number)
+	{
+	  numlen = i_ptr - number;
+	  if (numlen < 5) // Max pad width
+	    {
+	      u8_strcpy(o_ptr, (uint8_t *)"00000");
+	      o_ptr += (5 - numlen);
+	    }
+	  o_ptr = u8_stpncpy(o_ptr, number, numlen + u8_strmblen(i_ptr));
+
+	  number = NULL;
+	  append_number = 0;
+	}
+
+      i_ptr = n_ptr;
+    }
+  while (n_ptr);
+
+  *sort_tag = (char *)u8_normalize(UNINORM_NFD, (uint8_t *)&out, u8_strlen(out) + 1, NULL, &len);
+}
+
+static void
+fixup_tags(struct media_file_info *mfi)
+{
+  cfg_t *lib;
+  size_t len;
+  char *tag;
+  char *sep = " - ";
+  char *ca;
+
+  if (mfi->genre && (strlen(mfi->genre) == 0))
+    {
+      free(mfi->genre);
+      mfi->genre = NULL;
+    }
+
+  if (mfi->artist && (strlen(mfi->artist) == 0))
+    {
+      free(mfi->artist);
+      mfi->artist = NULL;
+    }
+
+  if (mfi->title && (strlen(mfi->title) == 0))
+    {
+      free(mfi->title);
+      mfi->title = NULL;
+    }
+
+  /*
+   * Default to mpeg4 video/audio for unknown file types
+   * in an attempt to allow streaming of DRM-afflicted files
+   */
+  if (mfi->codectype && strcmp(mfi->codectype, "unkn") == 0)
+    {
+      if (mfi->has_video)
+	{
+	  strcpy(mfi->codectype, "mp4v");
+	  strcpy(mfi->type, "m4v");
+	}
+      else
+	{
+	  strcpy(mfi->codectype, "mp4a");
+	  strcpy(mfi->type, "m4a");
+	}
+    }
+
+  if (!mfi->artist)
+    {
+      if (mfi->orchestra && mfi->conductor)
+	{
+	  len = strlen(mfi->orchestra) + strlen(sep) + strlen(mfi->conductor);
+	  tag = (char *)malloc(len + 1);
+	  if (tag)
+	    {
+	      sprintf(tag,"%s%s%s", mfi->orchestra, sep, mfi->conductor);
+	      mfi->artist = tag;
+            }
+        }
+      else if (mfi->orchestra)
+	{
+	  mfi->artist = strdup(mfi->orchestra);
+        }
+      else if (mfi->conductor)
+	{
+	  mfi->artist = strdup(mfi->conductor);
+        }
+    }
+
+  /* Handle TV shows, try to present prettier metadata */
+  if (mfi->tv_series_name && strlen(mfi->tv_series_name) != 0)
+    {
+      mfi->media_kind = MEDIA_KIND_TVSHOW;  /* tv show */
+
+      /* Default to artist = series_name */
+      if (mfi->artist && strlen(mfi->artist) == 0)
+	{
+	  free(mfi->artist);
+	  mfi->artist = NULL;
+	}
+
+      if (!mfi->artist)
+	mfi->artist = strdup(mfi->tv_series_name);
+
+      /* Default to album = "<series_name>, Season <season_num>" */
+      if (mfi->album && strlen(mfi->album) == 0)
+	{
+	  free(mfi->album);
+	  mfi->album = NULL;
+	}
+
+      if (!mfi->album)
+	{
+	  len = snprintf(NULL, 0, "%s, Season %d", mfi->tv_series_name, mfi->tv_season_num);
+
+	  mfi->album = (char *)malloc(len + 1);
+	  if (mfi->album)
+	    sprintf(mfi->album, "%s, Season %d", mfi->tv_series_name, mfi->tv_season_num);
+	}
+    }
+
+  /* Check the 4 top-tags are filled */
+  if (!mfi->artist)
+    mfi->artist = strdup("Unknown artist");
+  if (!mfi->album)
+    mfi->album = strdup("Unknown album");
+  if (!mfi->genre)
+    mfi->genre = strdup("Unknown genre");
+  if (!mfi->title)
+    {
+      /* fname is left untouched by unicode_fixup_mfi() for
+       * obvious reasons, so ensure it is proper UTF-8
+       */
+      mfi->title = unicode_fixup_string(mfi->fname, "ascii");
+      if (mfi->title == mfi->fname)
+	mfi->title = strdup(mfi->fname);
+    }
+
+  /* Ensure sort tags are filled, manipulated and normalized */
+  sort_tag_create(&mfi->artist_sort, mfi->artist);
+  sort_tag_create(&mfi->album_sort, mfi->album);
+  sort_tag_create(&mfi->title_sort, mfi->title);
+
+  /* We need to set album_artist according to media type and config */
+  if (mfi->compilation)          /* Compilation */
+    {
+      lib = cfg_getsec(cfg, "library");
+      ca = cfg_getstr(lib, "compilation_artist");
+      if (ca && mfi->album_artist)
+	{
+	  free(mfi->album_artist);
+	  mfi->album_artist = strdup(ca);
+	}
+      else if (ca && !mfi->album_artist)
+	{
+	  mfi->album_artist = strdup(ca);
+	}
+      else if (!ca && !mfi->album_artist)
+	{
+	  mfi->album_artist = strdup("");
+	  mfi->album_artist_sort = strdup("");
+	}
+    }
+  else if (mfi->media_kind == MEDIA_KIND_PODCAST) /* Podcast */
+    {
+      if (mfi->album_artist)
+	free(mfi->album_artist);
+      mfi->album_artist = strdup("");
+      mfi->album_artist_sort = strdup("");
+    }
+  else if (!mfi->album_artist)   /* Regular media without album_artist */
+    {
+      mfi->album_artist = strdup(mfi->artist);
+    }
+
+  if (!mfi->album_artist_sort && (strcmp(mfi->album_artist, mfi->artist) == 0))
+    mfi->album_artist_sort = strdup(mfi->artist_sort);
+  else
+    sort_tag_create(&mfi->album_artist_sort, mfi->album_artist);
+
+  /* Composer is not one of our mandatory tags, so take extra care */
+  if (mfi->composer_sort || mfi->composer)
+    sort_tag_create(&mfi->composer_sort, mfi->composer);
+}
+
+void
+library_add_media(struct media_file_info *mfi)
+{
+  if (!mfi->path || !mfi->fname)
+    {
+      DPRINTF(E_LOG, L_LIB, "Ignoring media file with missing values (path='%s', fname='%s', data_kind='%d')\n",
+	      mfi->path, mfi->fname, mfi->data_kind);
+      return;
+    }
+
+  if (!mfi->directory_id || !mfi->virtual_path)
+    {
+      // Missing informations for virtual_path and directory_id (may) lead to misplaced appearance in mpd clients
+      DPRINTF(E_WARN, L_LIB, "Media file with missing values (path='%s', directory='%d', virtual_path='%s')\n",
+	      mfi->path, mfi->directory_id, mfi->virtual_path);
+    }
+
+  if (!mfi->item_kind)
+    mfi->item_kind = 2; /* music */
+  if (!mfi->media_kind)
+    mfi->media_kind = MEDIA_KIND_MUSIC; /* music */
+
+  unicode_fixup_mfi(mfi);
+
+  fixup_tags(mfi);
+
+  if (mfi->id == 0)
+    db_file_add(mfi);
+  else
+    db_file_update(mfi);
+}
+
+int
+library_scan_media(const char *path, struct media_file_info *mfi)
+{
+  int i;
+  int ret;
+
+  DPRINTF(E_DBG, L_LIB, "Scan metadata for path '%s'\n", path);
+
+  ret = LIBRARY_PATH_INVALID;
+  for (i = 0; sources[i] && ret == LIBRARY_PATH_INVALID; i++)
+    {
+      if (sources[i]->disabled || !sources[i]->scan_metadata)
+        {
+	  DPRINTF(E_DBG, L_LIB, "Library source '%s' is disabled or does not support scan_metadata\n", sources[i]->name);
+	  continue;
+	}
+
+      ret = sources[i]->scan_metadata(path, mfi);
+
+      if (ret == LIBRARY_OK)
+	DPRINTF(E_DBG, L_LIB, "Got metadata for path '%s' from library source '%s'\n", path, sources[i]->name);
+    }
+
+  if (ret == LIBRARY_OK)
+    {
+      if (!mfi->virtual_path)
+	mfi->virtual_path = strdup(mfi->path);
+      if (!mfi->item_kind)
+	mfi->item_kind = 2; /* music */
+      if (!mfi->media_kind)
+	mfi->media_kind = MEDIA_KIND_MUSIC; /* music */
+
+      unicode_fixup_mfi(mfi);
+
+      fixup_tags(mfi);
+    }
+  else
+    {
+      DPRINTF(E_LOG, L_LIB, "Failed to read metadata for path '%s' (ret=%d)\n", path, ret);
+    }
+
+  return ret;
+}
+
+int
+library_add_playlist_info(const char *path, const char *title, const char *virtual_path, enum pl_type type, int parent_pl_id, int dir_id)
+{
+  struct playlist_info *pli;
+  int plid;
+  int ret;
+
+  pli = db_pl_fetch_bypath(path);
+  if (pli)
+    {
+      DPRINTF(E_DBG, L_LIB, "Playlist found ('%s', link %s), updating\n", title, path);
+
+      plid = pli->id;
+
+      pli->type = type;
+      free(pli->title);
+      pli->title = strdup(title);
+      if (pli->virtual_path)
+	free(pli->virtual_path);
+      pli->virtual_path = safe_strdup(virtual_path);
+      pli->directory_id = dir_id;
+
+      ret = db_pl_update(pli);
+      if (ret < 0)
+	{
+	  DPRINTF(E_LOG, L_LIB, "Error updating playlist ('%s', link %s)\n", title, path);
+
+	  free_pli(pli, 0);
+	  return -1;
+	}
+
+      db_pl_clear_items(plid);
+    }
+  else
+    {
+      DPRINTF(E_DBG, L_LIB, "Adding playlist ('%s', link %s)\n", title, path);
+
+      pli = (struct playlist_info *)malloc(sizeof(struct playlist_info));
+      if (!pli)
+	{
+	  DPRINTF(E_LOG, L_LIB, "Out of memory\n");
+
+	  return -1;
+	}
+
+      memset(pli, 0, sizeof(struct playlist_info));
+
+      pli->type = type;
+      pli->title = strdup(title);
+      pli->path = strdup(path);
+      pli->virtual_path = safe_strdup(virtual_path);
+      pli->parent_id = parent_pl_id;
+      pli->directory_id = dir_id;
+
+      ret = db_pl_add(pli, &plid);
+      if ((ret < 0) || (plid < 1))
+	{
+	  DPRINTF(E_LOG, L_LIB, "Error adding playlist ('%s', link %s, ret %d, plid %d)\n", title, path, ret, plid);
+
+	  free_pli(pli, 0);
+	  return -1;
+	}
+    }
+
+  free_pli(pli, 0);
+  return plid;
+}
+
+int
+library_add_queue_item(struct media_file_info *mfi)
+{
+  struct db_queue_item queue_item;
+
+  memset(&queue_item, 0, sizeof(struct db_queue_item));
+
+  if (mfi->id)
+    queue_item.file_id = mfi->id;
+  else
+    queue_item.file_id = 9999999;
+
+  queue_item.title = mfi->title;
+  queue_item.artist = mfi->artist;
+  queue_item.album_artist = mfi->album_artist;
+  queue_item.album = mfi->album;
+  queue_item.genre = mfi->genre;
+  queue_item.artist_sort = mfi->artist_sort;
+  queue_item.album_artist_sort = mfi->album_artist_sort;
+  queue_item.album_sort = mfi->album_sort;
+  queue_item.path = mfi->path;
+  queue_item.virtual_path = mfi->virtual_path;
+  queue_item.data_kind = mfi->data_kind;
+  queue_item.media_kind = mfi->media_kind;
+  queue_item.song_length = mfi->song_length;
+  queue_item.seek = mfi->seek;
+  queue_item.songalbumid = mfi->songalbumid;
+  queue_item.time_modified = mfi->time_modified;
+  queue_item.year = mfi->year;
+  queue_item.track = mfi->track;
+  queue_item.disc = mfi->disc;
+  //queue_item.artwork_url
+
+  return db_queue_add_item(&queue_item, 0, 0);
+}
+
+static void
+purge_cruft(time_t start)
+{
+  DPRINTF(E_DBG, L_LIB, "Purging old library content\n");
+  db_purge_cruft(start);
+  db_groups_cleanup();
+  db_queue_cleanup();
+
+  DPRINTF(E_DBG, L_LIB, "Purging old artwork content\n");
+  cache_artwork_purge_cruft(start);
+}
+
+static enum command_state
+rescan(void *arg, int *ret)
+{
+  time_t starttime;
+  time_t endtime;
+  int i;
+
+  DPRINTF(E_LOG, L_LIB, "Library rescan triggered\n");
+
+  starttime = time(NULL);
+
+  for (i = 0; sources[i]; i++)
+    {
+      if (!sources[i]->disabled && sources[i]->rescan)
+	{
+	  DPRINTF(E_INFO, L_LIB, "Rescan library source '%s'\n", sources[i]->name);
+	  sources[i]->rescan();
+	}
+      else
+	{
+	  DPRINTF(E_INFO, L_LIB, "Library source '%s' is disabled\n", sources[i]->name);
+	}
+    }
+
+  purge_cruft(starttime);
+
+  endtime = time(NULL);
+  DPRINTF(E_LOG, L_LIB, "Library rescan completed in %.f sec\n", difftime(endtime, starttime));
+
+  scanning = false;
+  *ret = 0;
+  return COMMAND_END;
+}
+
+static enum command_state
+fullrescan(void *arg, int *ret)
+{
+  time_t starttime;
+  time_t endtime;
+  int i;
+
+  DPRINTF(E_LOG, L_LIB, "Library full-rescan triggered\n");
+
+  starttime = time(NULL);
+
+  player_playback_stop();
+  db_queue_clear(0);
+  db_purge_all(); // Clears files, playlists, playlistitems, inotify and groups
+
+  for (i = 0; sources[i]; i++)
+    {
+      if (!sources[i]->disabled && sources[i]->fullrescan)
+	{
+	  DPRINTF(E_INFO, L_LIB, "Full-rescan library source '%s'\n", sources[i]->name);
+	  sources[i]->fullrescan();
+	}
+      else
+	{
+	  DPRINTF(E_INFO, L_LIB, "Library source '%s' is disabled\n", sources[i]->name);
+	}
+    }
+
+  endtime = time(NULL);
+  DPRINTF(E_LOG, L_LIB, "Library full-rescan completed in %.f sec\n", difftime(endtime, starttime));
+
+  scanning = false;
+  *ret = 0;
+  return COMMAND_END;
+}
+
+static void
+update_trigger_cb(int fd, short what, void *arg)
+{
+  listener_notify(LISTENER_DATABASE);
+}
+
+static enum command_state
+update_trigger(void *arg, int *retval)
+{
+  evtimer_add(updateev, &library_update_wait);
+
+  *retval = 0;
+  return COMMAND_END;
+}
+
+
+/* --------------------------- LIBRARY INTERFACE -------------------------- */
+
+void
+library_rescan()
+{
+  if (scanning)
+    {
+      DPRINTF(E_INFO, L_LIB, "Scan already running, ignoring request to trigger a new init scan\n");
+      return;
+    }
+
+  scanning = true; // TODO Guard "scanning" with a mutex
+  commands_exec_async(cmdbase, rescan, NULL);
+}
+
+void
+library_fullrescan()
+{
+  if (scanning)
+    {
+      DPRINTF(E_INFO, L_LIB, "Scan already running, ignoring request to trigger a new full rescan\n");
+      return;
+    }
+
+  scanning = true; // TODO Guard "scanning" with a mutex
+  commands_exec_async(cmdbase, fullrescan, NULL);
+}
+
+static void
+initscan()
+{
+  time_t starttime;
+  time_t endtime;
+  bool clear_queue_disabled;
+  int i;
+
+  scanning = true;
+  starttime = time(NULL);
+
+  // Only clear the queue if enabled (default) in config
+  clear_queue_disabled = cfg_getbool(cfg_getsec(cfg, "mpd"), "clear_queue_on_stop_disable");
+  if (!clear_queue_disabled)
+    {
+      db_queue_clear(0);
+    }
+
+  for (i = 0; sources[i]; i++)
+    {
+      if (!sources[i]->disabled && sources[i]->initscan)
+	sources[i]->initscan();
+    }
+
+  if (! (cfg_getbool(cfg_getsec(cfg, "library"), "filescan_disable")))
+    {
+      purge_cruft(starttime);
+
+      DPRINTF(E_DBG, L_LIB, "Running post library scan jobs\n");
+      db_hook_post_scan();
+    }
+
+  endtime = time(NULL);
+  DPRINTF(E_LOG, L_LIB, "Library init scan completed in %.f sec\n", difftime(endtime, starttime));
+
+  scanning = false;
+
+  listener_notify(LISTENER_DATABASE);
+}
+
+/*
+ * @return true if scan is running, otherwise false
+ */
+bool
+library_is_scanning()
+{
+  return scanning;
+}
+
+/*
+ * @param is_scanning true if scan is running, otherwise false
+ */
+void
+library_set_scanning(bool is_scanning)
+{
+  scanning = is_scanning;
+}
+
+/*
+ * @return true if a running scan should be aborted due to imminent shutdown, otherwise false
+ */
+bool
+library_is_exiting()
+{
+  return scan_exit;
+}
+
+void
+library_update_trigger(void)
+{
+  if (scanning)
+    return;
+
+  commands_exec_async(cmdbase, update_trigger, NULL);
+}
+
+/*
+ * Execute the function 'func' with the given argument 'arg' in the library thread.
+ *
+ * The pointer passed as argument is freed in the library thread after func returned.
+ *
+ * @param func The function to be executed
+ * @param arg Argument passed to func
+ * @return 0 if triggering the function execution succeeded, -1 on failure.
+ */
+int
+library_exec_async(command_function func, void *arg)
+{
+  return commands_exec_async(cmdbase, func, arg);
+}
+
+static void *
+library(void *arg)
+{
+  int ret;
+
+#ifdef __linux__
+  struct sched_param param;
+
+  /* Lower the priority of the thread so forked-daapd may still respond
+   * during library scan on low power devices. Param must be 0 for the SCHED_BATCH
+   * policy.
+   */
+  memset(&param, 0, sizeof(struct sched_param));
+  ret = pthread_setschedparam(pthread_self(), SCHED_BATCH, &param);
+  if (ret != 0)
+    {
+      DPRINTF(E_LOG, L_LIB, "Warning: Could not set thread priority to SCHED_BATCH\n");
+    }
+#endif
+
+  ret = db_perthread_init();
+  if (ret < 0)
+    {
+      DPRINTF(E_LOG, L_LIB, "Error: DB init failed\n");
+
+      pthread_exit(NULL);
+    }
+
+  initscan();
+
+  event_base_dispatch(evbase_lib);
+
+  if (!scan_exit)
+    DPRINTF(E_FATAL, L_LIB, "Scan event loop terminated ahead of time!\n");
+
+  db_perthread_deinit();
+
+  pthread_exit(NULL);
+}
+
+/* Thread: main */
+int
+library_init(void)
+{
+  int i;
+  int ret;
+
+  scan_exit = false;
+  scanning = false;
+
+  CHECK_NULL(L_LIB, evbase_lib = event_base_new());
+  CHECK_NULL(L_LIB, updateev = evtimer_new(evbase_lib, update_trigger_cb, NULL));
+
+  for (i = 0; sources[i]; i++)
+    {
+      if (!sources[i]->init)
+	continue;
+
+      ret = sources[i]->init();
+      if (ret < 0)
+	sources[i]->disabled = 1;
+    }
+
+  CHECK_NULL(L_LIB, cmdbase = commands_base_new(evbase_lib, NULL));
+
+  CHECK_ERR(L_LIB, pthread_create(&tid_library, NULL, library, NULL));
+
+#if defined(HAVE_PTHREAD_SETNAME_NP)
+  pthread_setname_np(tid_library, "library");
+#elif defined(HAVE_PTHREAD_SET_NAME_NP)
+  pthread_set_name_np(tid_library, "library");
+#endif
+
+  return 0;
+}
+
+/* Thread: main */
+void
+library_deinit()
+{
+  int i;
+  int ret;
+
+  scan_exit = true;
+  commands_base_destroy(cmdbase);
+
+  ret = pthread_join(tid_library, NULL);
+  if (ret != 0)
+    {
+      DPRINTF(E_FATAL, L_LIB, "Could not join library thread: %s\n", strerror(errno));
+
+      return;
+    }
+
+  for (i = 0; sources[i]; i++)
+    {
+      if (sources[i]->deinit && !sources[i]->disabled)
+      sources[i]->deinit();
+    }
+
+  event_base_free(evbase_lib);
+}
diff --git a/src/library.h b/src/library.h
new file mode 100644
index 0000000..a72c595
--- /dev/null
+++ b/src/library.h
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2015 Christian Meffert <christian.meffert at googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef SRC_LIBRARY_H_
+#define SRC_LIBRARY_H_
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <time.h>
+
+#include "commands.h"
+#include "db.h"
+
+#define LIBRARY_OK 0
+#define LIBRARY_ERROR -1
+#define LIBRARY_PATH_INVALID -2
+
+/*
+ * Definition of a library source
+ *
+ * A library source is responsible for scanning items into the library db.
+ */
+struct library_source
+{
+  char *name;
+  int disabled;
+
+  /*
+   * Initialize library source (called from the main thread)
+   */
+  int (*init)(void);
+
+  /*
+   * Shutdown library source (called from the main thread after
+   * terminating the library thread)
+   */
+  void (*deinit)(void);
+
+  /*
+   * Run initial scan after startup (called from the library thread)
+   */
+  int (*initscan)(void);
+
+  /*
+   * Run rescan (called from the library thread)
+   */
+  int (*rescan)(void);
+
+  /*
+   * Run a full rescan (purge library entries and rescan) (called from the library thread)
+   */
+  int (*fullrescan)(void);
+
+  /*
+   * Scans metadata for the media file with the given path into the given mfi
+   */
+  int (*scan_metadata)(const char *path, struct media_file_info *mfi);
+};
+
+
+void
+library_add_media(struct media_file_info *mfi);
+
+int
+library_add_playlist_info(const char *path, const char *title, const char *virtual_path, enum pl_type type, int parent_pl_id, int dir_id);
+
+int
+library_scan_media(const char *path, struct media_file_info *mfi);
+
+int
+library_add_queue_item(struct media_file_info *mfi);
+
+void
+library_rescan();
+
+void
+library_fullrescan();
+
+bool
+library_is_scanning();
+
+void
+library_set_scanning(bool is_scanning);
+
+bool
+library_is_exiting();
+
+void
+library_update_trigger(void);
+
+int
+library_exec_async(command_function func, void *arg);
+
+int
+library_init();
+
+void
+library_deinit();
+
+
+#endif /* SRC_LIBRARY_H_ */
diff --git a/src/filescanner.c b/src/library/filescanner.c
similarity index 61%
rename from src/filescanner.c
rename to src/library/filescanner.c
index d53e9f8..858b798 100644
--- a/src/filescanner.c
+++ b/src/library/filescanner.c
@@ -54,7 +54,7 @@
 
 #include "logger.h"
 #include "db.h"
-#include "filescanner.h"
+#include "library/filescanner.h"
 #include "conffile.h"
 #include "misc.h"
 #include "remote_pairing.h"
@@ -62,6 +62,7 @@
 #include "cache.h"
 #include "artwork.h"
 #include "commands.h"
+#include "library.h"
 
 #ifdef LASTFM
 # include "lastfm.h"
@@ -76,6 +77,12 @@
 #define F_SCAN_FAST    (1 << 2)
 #define F_SCAN_MOVED   (1 << 3)
 
+#define F_SCAN_TYPE_FILE         (1 << 0)
+#define F_SCAN_TYPE_PODCAST      (1 << 1)
+#define F_SCAN_TYPE_AUDIOBOOK    (1 << 2)
+#define F_SCAN_TYPE_COMPILATION  (1 << 3)
+
+
 enum file_type {
   FILE_UNKNOWN = 0,
   FILE_IGNORE,
@@ -85,6 +92,7 @@ enum file_type {
   FILE_ITUNES,
   FILE_ARTWORK,
   FILE_CTRL_REMOTE,
+  FILE_CTRL_RAOP_VERIFICATION,
   FILE_CTRL_LASTFM,
   FILE_CTRL_SPOTIFY,
   FILE_CTRL_INITSCAN,
@@ -104,23 +112,23 @@ struct stacked_dir {
   struct stacked_dir *next;
 };
 
-static int scan_exit;
 static int inofd;
-static struct event_base *evbase_scan;
 static struct event *inoev;
-static pthread_t tid_scan;
 static struct deferred_pl *playlists;
 static struct stacked_dir *dirstack;
-static struct commands_base *cmdbase;
 
-#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
+/* From library.c */
+extern struct event_base *evbase_lib;
+
+#ifndef __linux__
 struct deferred_file
 {
   struct watch_info wi;
-  struct inotify_event ie;
   char path[PATH_MAX];
 
   struct deferred_file *next;
+  /* variable sized, must be at the end */
+  struct inotify_event ie;
 };
 
 static struct deferred_file *filestack;
@@ -130,9 +138,6 @@ static struct event *deferred_inoev;
 /* Count of files scanned during a bulk scan */
 static int counter;
 
-/* Flag for scan in progress */
-static int scanning;
-
 /* When copying into the lib (eg. if a file is moved to the lib by copying into
  * a Samba network share) inotify might give us IN_CREATE -> n x IN_ATTRIB ->
  * IN_CLOSE_WRITE, but we don't want to do any scanning before the
@@ -150,18 +155,34 @@ static int
 inofd_event_set(void);
 static void
 inofd_event_unset(void);
-static enum command_state
-filescanner_initscan(void *arg, int *retval);
-static enum command_state
-filescanner_fullrescan(void *arg, int *retval);
+static int
+filescanner_initscan();
+static int
+filescanner_rescan();
+static int
+filescanner_fullrescan();
+
+
+const char *
+filename_from_path(const char *path)
+{
+  const char *filename;
+
+  filename = strrchr(path, '/');
+  if ((!filename) || (strlen(filename) == 1))
+    filename = path;
+  else
+    filename++;
 
+  return filename;
+}
 
 static int
 push_dir(struct stacked_dir **s, char *path, int parent_id)
 {
   struct stacked_dir *d;
 
-  d = (struct stacked_dir *)malloc(sizeof(struct stacked_dir));
+  d = malloc(sizeof(struct stacked_dir));
   if (!d)
     {
       DPRINTF(E_LOG, L_SCAN, "Could not stack directory %s; out of memory\n", path);
@@ -172,6 +193,7 @@ push_dir(struct stacked_dir **s, char *path, int parent_id)
   if (!d->path)
     {
       DPRINTF(E_LOG, L_SCAN, "Could not stack directory %s; out of memory for path\n", path);
+      free(d);
       return -1;
     }
 
@@ -297,6 +319,9 @@ file_type_get(const char *path) {
   if (strcasecmp(ext, ".remote") == 0)
     return FILE_CTRL_REMOTE;
 
+  if (strcasecmp(ext, ".verification") == 0)
+    return FILE_CTRL_RAOP_VERIFICATION;
+
   if (strcasecmp(ext, ".lastfm") == 0)
     return FILE_CTRL_LASTFM;
 
@@ -322,416 +347,6 @@ file_type_get(const char *path) {
 }
 
 static void
-sort_tag_create(char **sort_tag, char *src_tag)
-{
-  const uint8_t *i_ptr;
-  const uint8_t *n_ptr;
-  const uint8_t *number;
-  uint8_t out[1024];
-  uint8_t *o_ptr;
-  int append_number;
-  ucs4_t puc;
-  int numlen;
-  size_t len;
-  int charlen;
-
-  /* Note: include terminating NUL in string length for u8_normalize */
-
-  if (*sort_tag)
-    {
-      DPRINTF(E_DBG, L_SCAN, "Existing sort tag will be normalized: %s\n", *sort_tag);
-      o_ptr = u8_normalize(UNINORM_NFD, (uint8_t *)*sort_tag, strlen(*sort_tag) + 1, NULL, &len);
-      free(*sort_tag);
-      *sort_tag = (char *)o_ptr;
-      return;
-    }
-
-  if (!src_tag || ((len = strlen(src_tag)) == 0))
-    {
-      *sort_tag = NULL;
-      return;
-    }
-
-  // Set input pointer past article if present
-  if ((strncasecmp(src_tag, "a ", 2) == 0) && (len > 2))
-    i_ptr = (uint8_t *)(src_tag + 2);
-  else if ((strncasecmp(src_tag, "an ", 3) == 0) && (len > 3))
-    i_ptr = (uint8_t *)(src_tag + 3);
-  else if ((strncasecmp(src_tag, "the ", 4) == 0) && (len > 4))
-    i_ptr = (uint8_t *)(src_tag + 4);
-  else
-    i_ptr = (uint8_t *)src_tag;
-
-  // Poor man's natural sort. Makes sure we sort like this: a1, a2, a10, a11, a21, a111
-  // We do this by padding zeroes to (short) numbers. As an alternative we could have
-  // made a proper natural sort algorithm in sqlext.c, but we don't, since we don't
-  // want any risk of hurting response times
-  memset(&out, 0, sizeof(out));
-  o_ptr = (uint8_t *)&out;
-  number = NULL;
-  append_number = 0;
-
-  do
-    {
-      n_ptr = u8_next(&puc, i_ptr);
-
-      if (uc_is_digit(puc))
-	{
-	  if (!number) // We have encountered the beginning of a number
-	    number = i_ptr;
-	  append_number = (n_ptr == NULL); // If last char in string append number now
-	}
-      else
-	{
-	  if (number)
-	    append_number = 1; // A number has ended so time to append it
-	  else
-	    {
-              charlen = u8_strmblen(i_ptr);
-              if (charlen >= 0)
-	    	o_ptr = u8_stpncpy(o_ptr, i_ptr, charlen); // No numbers in sight, just append char
-	    }
-	}
-
-      // Break if less than 100 bytes remain (prevent buffer overflow)
-      if (sizeof(out) - u8_strlen(out) < 100)
-	break;
-
-      // Break if number is very large (prevent buffer overflow)
-      if (number && (i_ptr - number > 50))
-	break;
-
-      if (append_number)
-	{
-	  numlen = i_ptr - number;
-	  if (numlen < 5) // Max pad width
-	    {
-	      u8_strcpy(o_ptr, (uint8_t *)"00000");
-	      o_ptr += (5 - numlen);
-	    }
-	  o_ptr = u8_stpncpy(o_ptr, number, numlen + u8_strmblen(i_ptr));
-
-	  number = NULL;
-	  append_number = 0;
-	}
-
-      i_ptr = n_ptr;
-    }
-  while (n_ptr);
-
-  *sort_tag = (char *)u8_normalize(UNINORM_NFD, (uint8_t *)&out, u8_strlen(out) + 1, NULL, &len);
-}
-
-static void
-fixup_tags(struct media_file_info *mfi)
-{
-  cfg_t *lib;
-  size_t len;
-  char *tag;
-  char *sep = " - ";
-  char *ca;
-
-  if (mfi->genre && (strlen(mfi->genre) == 0))
-    {
-      free(mfi->genre);
-      mfi->genre = NULL;
-    }
-
-  if (mfi->artist && (strlen(mfi->artist) == 0))
-    {
-      free(mfi->artist);
-      mfi->artist = NULL;
-    }
-
-  if (mfi->title && (strlen(mfi->title) == 0))
-    {
-      free(mfi->title);
-      mfi->title = NULL;
-    }
-
-  /*
-   * Default to mpeg4 video/audio for unknown file types
-   * in an attempt to allow streaming of DRM-afflicted files
-   */
-  if (mfi->codectype && strcmp(mfi->codectype, "unkn") == 0)
-    {
-      if (mfi->has_video)
-	{
-	  strcpy(mfi->codectype, "mp4v");
-	  strcpy(mfi->type, "m4v");
-	}
-      else
-	{
-	  strcpy(mfi->codectype, "mp4a");
-	  strcpy(mfi->type, "m4a");
-	}
-    }
-
-  if (!mfi->artist)
-    {
-      if (mfi->orchestra && mfi->conductor)
-	{
-	  len = strlen(mfi->orchestra) + strlen(sep) + strlen(mfi->conductor);
-	  tag = (char *)malloc(len + 1);
-	  if (tag)
-	    {
-	      sprintf(tag,"%s%s%s", mfi->orchestra, sep, mfi->conductor);
-	      mfi->artist = tag;
-            }
-        }
-      else if (mfi->orchestra)
-	{
-	  mfi->artist = strdup(mfi->orchestra);
-        }
-      else if (mfi->conductor)
-	{
-	  mfi->artist = strdup(mfi->conductor);
-        }
-    }
-
-  /* Handle TV shows, try to present prettier metadata */
-  if (mfi->tv_series_name && strlen(mfi->tv_series_name) != 0)
-    {
-      mfi->media_kind = MEDIA_KIND_TVSHOW;  /* tv show */
-
-      /* Default to artist = series_name */
-      if (mfi->artist && strlen(mfi->artist) == 0)
-	{
-	  free(mfi->artist);
-	  mfi->artist = NULL;
-	}
-
-      if (!mfi->artist)
-	mfi->artist = strdup(mfi->tv_series_name);
-
-      /* Default to album = "<series_name>, Season <season_num>" */
-      if (mfi->album && strlen(mfi->album) == 0)
-	{
-	  free(mfi->album);
-	  mfi->album = NULL;
-	}
-
-      if (!mfi->album)
-	{
-	  len = snprintf(NULL, 0, "%s, Season %d", mfi->tv_series_name, mfi->tv_season_num);
-
-	  mfi->album = (char *)malloc(len + 1);
-	  if (mfi->album)
-	    sprintf(mfi->album, "%s, Season %d", mfi->tv_series_name, mfi->tv_season_num);
-	}
-    }
-
-  /* Check the 4 top-tags are filled */
-  if (!mfi->artist)
-    mfi->artist = strdup("Unknown artist");
-  if (!mfi->album)
-    mfi->album = strdup("Unknown album");
-  if (!mfi->genre)
-    mfi->genre = strdup("Unknown genre");
-  if (!mfi->title)
-    {
-      /* fname is left untouched by unicode_fixup_mfi() for
-       * obvious reasons, so ensure it is proper UTF-8
-       */
-      mfi->title = unicode_fixup_string(mfi->fname, "ascii");
-      if (mfi->title == mfi->fname)
-	mfi->title = strdup(mfi->fname);
-    }
-
-  /* Ensure sort tags are filled, manipulated and normalized */
-  sort_tag_create(&mfi->artist_sort, mfi->artist);
-  sort_tag_create(&mfi->album_sort, mfi->album);
-  sort_tag_create(&mfi->title_sort, mfi->title);
-
-  /* We need to set album_artist according to media type and config */
-  if (mfi->compilation)          /* Compilation */
-    {
-      lib = cfg_getsec(cfg, "library");
-      ca = cfg_getstr(lib, "compilation_artist");
-      if (ca && mfi->album_artist)
-	{
-	  free(mfi->album_artist);
-	  mfi->album_artist = strdup(ca);
-	}
-      else if (ca && !mfi->album_artist)
-	{
-	  mfi->album_artist = strdup(ca);
-	}
-      else if (!ca && !mfi->album_artist)
-	{
-	  mfi->album_artist = strdup("");
-	  mfi->album_artist_sort = strdup("");
-	}
-    }
-  else if (mfi->media_kind == MEDIA_KIND_PODCAST) /* Podcast */
-    {
-      if (mfi->album_artist)
-	free(mfi->album_artist);
-      mfi->album_artist = strdup("");
-      mfi->album_artist_sort = strdup("");
-    }
-  else if (!mfi->album_artist)   /* Regular media without album_artist */
-    {
-      mfi->album_artist = strdup(mfi->artist);
-    }
-
-  if (!mfi->album_artist_sort && (strcmp(mfi->album_artist, mfi->artist) == 0))
-    mfi->album_artist_sort = strdup(mfi->artist_sort);
-  else
-    sort_tag_create(&mfi->album_artist_sort, mfi->album_artist);
-
-  /* Composer is not one of our mandatory tags, so take extra care */
-  if (mfi->composer_sort || mfi->composer)
-    sort_tag_create(&mfi->composer_sort, mfi->composer);
-}
-
-
-void
-filescanner_process_media(char *path, time_t mtime, off_t size, int type, struct media_file_info *external_mfi, int dir_id)
-{
-  struct media_file_info *mfi;
-  char *filename;
-  time_t stamp;
-  int id;
-  char virtual_path[PATH_MAX];
-  int ret;
-
-  filename = strrchr(path, '/');
-  if ((!filename) || (strlen(filename) == 1))
-    filename = path;
-  else
-    filename++;
-
-  db_file_stamp_bypath(path, &stamp, &id);
-
-  if (stamp && (stamp >= mtime))
-    {
-      db_file_ping(id);
-      return;
-    }
-
-  if (!external_mfi)
-    {
-      mfi = (struct media_file_info*)malloc(sizeof(struct media_file_info));
-      if (!mfi)
-	{
-	  DPRINTF(E_LOG, L_SCAN, "Out of memory for mfi\n");
-	  return;
-	}
-
-      memset(mfi, 0, sizeof(struct media_file_info));
-    }
-  else
-    mfi = external_mfi;
-
-  if (stamp)
-    mfi->id = db_file_id_bypath(path);
-
-  mfi->fname = strdup(filename);
-  if (!mfi->fname)
-    {
-      DPRINTF(E_LOG, L_SCAN, "Out of memory for fname\n");
-      goto out;
-    }
-
-  mfi->path = strdup(path);
-  if (!mfi->path)
-    {
-      DPRINTF(E_LOG, L_SCAN, "Out of memory for path\n");
-      goto out;
-    }
-
-  mfi->time_modified = mtime;
-  mfi->file_size = size;
-
-  if (type & F_SCAN_TYPE_COMPILATION)
-    mfi->compilation = 1;
-  if (type & F_SCAN_TYPE_PODCAST)
-    mfi->media_kind = MEDIA_KIND_PODCAST; /* podcast */
-  if (type & F_SCAN_TYPE_AUDIOBOOK)
-    mfi->media_kind = MEDIA_KIND_AUDIOBOOK; /* audiobook */
-
-  if (type & F_SCAN_TYPE_FILE)
-    {
-      mfi->data_kind = DATA_KIND_FILE;
-      ret = scan_metadata_ffmpeg(path, mfi);
-    }
-  else if (type & F_SCAN_TYPE_URL)
-    {
-      mfi->data_kind = DATA_KIND_HTTP;
-      ret = scan_metadata_ffmpeg(path, mfi);
-      if (ret < 0)
-	{
-	  DPRINTF(E_LOG, L_SCAN, "Playlist URL '%s' is unavailable for probe/metadata, assuming MP3 encoding\n", path);
-	  mfi->type = strdup("mp3");
-	  mfi->codectype = strdup("mpeg");
-	  mfi->description = strdup("MPEG audio file");
-	  ret = 1;
-	}
-    }
-  else if (type & F_SCAN_TYPE_SPOTIFY)
-    {
-      mfi->data_kind = DATA_KIND_SPOTIFY;
-      ret = mfi->artist && mfi->album && mfi->title;
-    }
-  else if (type & F_SCAN_TYPE_PIPE)
-    {
-      mfi->data_kind = DATA_KIND_PIPE;
-      mfi->type = strdup("wav");
-      mfi->codectype = strdup("wav");
-      mfi->description = strdup("PCM16 pipe");
-      ret = 1;
-    }
-  else
-    {
-      DPRINTF(E_LOG, L_SCAN, "Unknown scan type for '%s', this error should not occur\n", path);
-      ret = -1;
-    }
-
-  if (ret < 0)
-    {
-      DPRINTF(E_INFO, L_SCAN, "Could not extract metadata for '%s'\n", path);
-      goto out;
-    }
-
-  if (!mfi->item_kind)
-    mfi->item_kind = 2; /* music */
-  if (!mfi->media_kind)
-    mfi->media_kind = MEDIA_KIND_MUSIC; /* music */
-
-  unicode_fixup_mfi(mfi);
-
-  fixup_tags(mfi);
-
-  if (type & F_SCAN_TYPE_URL)
-    {
-      snprintf(virtual_path, PATH_MAX, "/http:/%s", mfi->title);
-      mfi->virtual_path = strdup(virtual_path);
-    }
-  else if (type & F_SCAN_TYPE_SPOTIFY)
-    {
-      snprintf(virtual_path, PATH_MAX, "/spotify:/%s/%s/%s", mfi->album_artist, mfi->album, mfi->title);
-      mfi->virtual_path = strdup(virtual_path);
-    }
-  else
-    {
-      snprintf(virtual_path, PATH_MAX, "/file:%s", mfi->path);
-      mfi->virtual_path = strdup(virtual_path);
-    }
-
-  mfi->directory_id = dir_id;
-
-  if (mfi->id == 0)
-    db_file_add(mfi);
-  else
-    db_file_update(mfi);
-
- out:
-  if (!external_mfi)
-    free_mfi(mfi, 0);
-}
-
-static void
 process_playlist(char *file, time_t mtime, int dir_id)
 {
   enum file_type ft;
@@ -745,6 +360,25 @@ process_playlist(char *file, time_t mtime, int dir_id)
 #endif
 }
 
+/* If we found a control file we want to kickoff some action */
+static void
+kickoff(void (*kickoff_func)(char **arg), const char *file, int lines)
+{
+  char **file_content;
+  int i;
+
+  file_content = m_readfile(file, lines);
+  if (!file_content)
+    return;
+
+  kickoff_func(file_content);
+
+  for (i = 0; i < lines; i++)
+    free(file_content[i]);
+
+  free(file_content);
+}
+
 /* Thread: scan */
 static void
 defer_playlist(char *path, time_t mtime, int dir_id)
@@ -793,27 +427,88 @@ process_deferred_playlists(void)
       free(pl->path);
       free(pl);
 
-      if (scan_exit)
+      if (library_is_exiting())
 	return;
     }
 }
 
-/* Thread: scan */
 static void
-process_file(char *file, time_t mtime, off_t size, int type, int flags, int dir_id)
+process_regular_file(char *file, struct stat *sb, int type, int flags, int dir_id)
 {
-  int is_bulkscan;
+  time_t stamp;
+  int id;
+  bool is_bulkscan = (flags & F_SCAN_BULK);
+  struct media_file_info mfi;
+  char virtual_path[PATH_MAX];
   int ret;
 
-  is_bulkscan = (flags & F_SCAN_BULK);
+  // Do not rescan metadata if file did not change since last scan
+  db_file_stamp_bypath(file, &stamp, &id);
+  if (stamp && (stamp >= sb->st_mtime))
+    {
+      db_file_ping(id);
+      return;
+    }
+
+  // File is new or modified - (re)scan metadata and update file in library
+  memset(&mfi, 0, sizeof(struct media_file_info));
+
+  mfi.id = id;
+  mfi.fname = strdup(filename_from_path(file));
+  mfi.path = strdup(file);
+
+  mfi.time_modified = sb->st_mtime;
+  mfi.file_size = sb->st_size;
+
+  snprintf(virtual_path, PATH_MAX, "/file:%s", file);
+  mfi.virtual_path = strdup(virtual_path);
+
+  mfi.directory_id = dir_id;
+
+  if (S_ISFIFO(sb->st_mode))
+    {
+      mfi.data_kind = DATA_KIND_PIPE;
+      mfi.type = strdup("wav");
+      mfi.codectype = strdup("wav");
+      mfi.description = strdup("PCM16 pipe");
+      mfi.media_kind = MEDIA_KIND_MUSIC;
+    }
+  else
+    {
+      mfi.data_kind = DATA_KIND_FILE;
+
+      if (type & F_SCAN_TYPE_AUDIOBOOK)
+	mfi.media_kind = MEDIA_KIND_AUDIOBOOK;
+      else if (type & F_SCAN_TYPE_PODCAST)
+	mfi.media_kind = MEDIA_KIND_PODCAST;
+
+      mfi.compilation = (type & F_SCAN_TYPE_COMPILATION);
+      mfi.file_size = sb->st_size;
+
+      ret = scan_metadata_ffmpeg(file, &mfi);
+      if (ret < 0)
+	{
+	  free_mfi(&mfi, 1);
+	  return;
+	}
+    }
+
+  library_add_media(&mfi);
+
+  cache_artwork_ping(file, sb->st_mtime, !is_bulkscan);
+  // TODO [artworkcache] If entry in artwork cache exists for no artwork available, delete the entry if media file has embedded artwork
+
+  free_mfi(&mfi, 1);
+}
 
+/* Thread: scan */
+static void
+process_file(char *file, struct stat *sb, int type, int flags, int dir_id)
+{
   switch (file_type_get(file))
     {
       case FILE_REGULAR:
-	filescanner_process_media(file, mtime, size, type, NULL, dir_id);
-
-	cache_artwork_ping(file, mtime, !is_bulkscan);
-	// TODO [artworkcache] If entry in artwork cache exists for no artwork available, delete the entry if media file has embedded artwork
+	process_regular_file(file, sb, type, flags, dir_id);
 
 	counter++;
 
@@ -829,41 +524,57 @@ process_file(char *file, time_t mtime, off_t size, int type, int flags, int dir_
       case FILE_PLAYLIST:
       case FILE_ITUNES:
 	if (flags & F_SCAN_BULK)
-	  defer_playlist(file, mtime, dir_id);
+	  defer_playlist(file, sb->st_mtime, dir_id);
 	else
-	  process_playlist(file, mtime, dir_id);
+	  process_playlist(file, sb->st_mtime, dir_id);
 	break;
 
       case FILE_SMARTPL:
 	DPRINTF(E_DBG, L_SCAN, "Smart playlist file: %s\n", file);
-	scan_smartpl(file, mtime, dir_id);
+	scan_smartpl(file, sb->st_mtime, dir_id);
 	break;
 
       case FILE_ARTWORK:
 	DPRINTF(E_DBG, L_SCAN, "Artwork file: %s\n", file);
-	cache_artwork_ping(file, mtime, !is_bulkscan);
+	cache_artwork_ping(file, sb->st_mtime, !(flags & F_SCAN_BULK));
 
 	// TODO [artworkcache] If entry in artwork cache exists for no artwork available for a album with files in the same directory, delete the entry
 
 	break;
 
       case FILE_CTRL_REMOTE:
-	remote_pairing_read_pin(file);
+	if (flags & F_SCAN_BULK)
+	  DPRINTF(E_LOG, L_SCAN, "Bulk scan will ignore '%s' (to process, add it after startup)\n", file);
+	else
+	  kickoff(remote_pairing_kickoff, file, 1);
+	break;
+
+      case FILE_CTRL_RAOP_VERIFICATION:
+	if (flags & F_SCAN_BULK)
+	  DPRINTF(E_LOG, L_SCAN, "Bulk scan will ignore '%s' (to process, add it after startup)\n", file);
+	else
+	  kickoff(player_raop_verification_kickoff, file, 1);
 	break;
 
       case FILE_CTRL_LASTFM:
 #ifdef LASTFM
-	lastfm_login(file);
+	if (flags & F_SCAN_BULK)
+	  DPRINTF(E_LOG, L_SCAN, "Bulk scan will ignore '%s' (to process, add it after startup)\n", file);
+	else
+	  kickoff(lastfm_login, file, 2);
 #else
-	DPRINTF(E_LOG, L_SCAN, "Detected LastFM file, but this version was built without LastFM support\n");
+	DPRINTF(E_LOG, L_SCAN, "Found '%s', but this version was built without LastFM support\n", file);
 #endif
 	break;
 
       case FILE_CTRL_SPOTIFY:
 #ifdef HAVE_SPOTIFY_H
-	spotify_login(file);
+	if (flags & F_SCAN_BULK)
+	  DPRINTF(E_LOG, L_SCAN, "Bulk scan will ignore '%s' (to process, add it after startup)\n", file);
+	else
+	  kickoff(spotify_login, file, 2);
 #else
-	DPRINTF(E_LOG, L_SCAN, "Detected Spotify file, but this version was built without Spotify support\n");
+	DPRINTF(E_LOG, L_SCAN, "Found '%s', but this version was built without Spotify support\n", file);
 #endif
 	break;
 
@@ -873,7 +584,7 @@ process_file(char *file, time_t mtime, off_t size, int type, int flags, int dir_
 
 	DPRINTF(E_LOG, L_SCAN, "Startup rescan triggered, found init-rescan file: %s\n", file);
 
-	filescanner_initscan(NULL, &ret);
+	library_rescan();
 	break;
 
       case FILE_CTRL_FULLSCAN:
@@ -882,7 +593,7 @@ process_file(char *file, time_t mtime, off_t size, int type, int flags, int dir_
 
 	DPRINTF(E_LOG, L_SCAN, "Full rescan triggered, found full-rescan file: %s\n", file);
 
-	filescanner_fullrescan(NULL, &ret);
+	library_fullrescan();
 	break;
 
       default:
@@ -924,14 +635,58 @@ create_virtual_path(char *path, char *virtual_path, int virtual_path_len)
   return 0;
 }
 
+/*
+ * Returns informations about the attributes of the file at the given 'path' in the structure
+ * pointed to by 'sb'.
+ *
+ * If path is a symbolic link, the attributes in sb describe the file that the link points to
+ * and resolved_path contains the resolved path (resolved_path must be of length PATH_MAX).
+ * If path is not a symbolic link, resolved_path holds the same value as path.
+ *
+ * The return value is 0 if the operation is successful, or -1 on failure
+ */
+static int
+read_attributes(char *resolved_path, const char *path, struct stat *sb)
+{
+  int ret;
+
+  ret = lstat(path, sb);
+  if (ret < 0)
+    {
+      DPRINTF(E_LOG, L_SCAN, "Skipping %s, lstat() failed: %s\n", path, strerror(errno));
+      return -1;
+    }
+
+  if (S_ISLNK(sb->st_mode))
+    {
+      if (!realpath(path, resolved_path))
+        {
+	  DPRINTF(E_LOG, L_SCAN, "Skipping %s, could not dereference symlink: %s\n", path, strerror(errno));
+	  return -1;
+	}
+
+      ret = stat(resolved_path, sb);
+      if (ret < 0)
+        {
+	  DPRINTF(E_LOG, L_SCAN, "Skipping %s, stat() failed: %s\n", resolved_path, strerror(errno));
+	  return -1;
+	}
+    }
+  else
+    {
+      strcpy(resolved_path, path);
+    }
+
+  return 0;
+}
+
 static void
 process_directory(char *path, int parent_id, int flags)
 {
   DIR *dirp;
-  struct dirent buf;
   struct dirent *de;
   char entry[PATH_MAX];
-  char *deref;
+  char resolved_path[PATH_MAX];
   struct stat sb;
   struct watch_info wi;
   int type;
@@ -972,82 +727,51 @@ process_directory(char *path, int parent_id, int flags)
 
   for (;;)
     {
-      if (scan_exit)
+      if (library_is_exiting())
 	break;
 
-      ret = readdir_r(dirp, &buf, &de);
-      if (ret != 0)
+      errno = 0;
+      de = readdir(dirp);
+      if (errno)
 	{
-	  DPRINTF(E_LOG, L_SCAN, "readdir_r error in %s: %s\n", path, strerror(errno));
+	  DPRINTF(E_LOG, L_SCAN, "readdir error in %s: %s\n", path, strerror(errno));
 
 	  break;
 	}
 
-      if (de == NULL)
+      if (!de)
 	break;
 
-      if (buf.d_name[0] == '.')
+      if (de->d_name[0] == '.')
 	continue;
 
-      ret = snprintf(entry, sizeof(entry), "%s/%s", path, buf.d_name);
+      ret = snprintf(entry, sizeof(entry), "%s/%s", path, de->d_name);
       if ((ret < 0) || (ret >= sizeof(entry)))
 	{
-	  DPRINTF(E_LOG, L_SCAN, "Skipping %s/%s, PATH_MAX exceeded\n", path, buf.d_name);
+	  DPRINTF(E_LOG, L_SCAN, "Skipping %s/%s, PATH_MAX exceeded\n", path, de->d_name);
 
 	  continue;
 	}
 
-      ret = lstat(entry, &sb);
+      ret = read_attributes(resolved_path, entry, &sb);
       if (ret < 0)
 	{
-	  DPRINTF(E_LOG, L_SCAN, "Skipping %s, lstat() failed: %s\n", entry, strerror(errno));
+	  DPRINTF(E_LOG, L_SCAN, "Skipping %s, read_attributes() failed\n", entry);
 
 	  continue;
 	}
 
-      if (S_ISLNK(sb.st_mode))
-	{
-	  deref = m_realpath(entry);
-	  if (!deref)
-	    {
-	      DPRINTF(E_LOG, L_SCAN, "Skipping %s, could not dereference symlink: %s\n", entry, strerror(errno));
-
-	      continue;
-	    }
-
-	  ret = stat(deref, &sb);
-	  if (ret < 0)
-	    {
-	      DPRINTF(E_LOG, L_SCAN, "Skipping %s, stat() failed: %s\n", deref, strerror(errno));
-
-	      free(deref);
-	      continue;
-	    }
-
-	  ret = snprintf(entry, sizeof(entry), "%s", deref);
-	  free(deref);
-	  if ((ret < 0) || (ret >= sizeof(entry)))
-	    {
-	      DPRINTF(E_LOG, L_SCAN, "Skipping %s, PATH_MAX exceeded\n", deref);
-
-	      continue;
-	    }
-	}
-
-      if (S_ISREG(sb.st_mode))
+      if (S_ISDIR(sb.st_mode))
 	{
-	  if (!(flags & F_SCAN_FAST))
-	    process_file(entry, sb.st_mtime, sb.st_size, F_SCAN_TYPE_FILE | type, flags, dir_id);
+	  push_dir(&dirstack, resolved_path, dir_id);
 	}
-      else if (S_ISFIFO(sb.st_mode))
+      else if (!(flags & F_SCAN_FAST))
 	{
-	  if (!(flags & F_SCAN_FAST))
-	    process_file(entry, sb.st_mtime, sb.st_size, F_SCAN_TYPE_PIPE | type, flags, dir_id);
+	  if (S_ISREG(sb.st_mode) || S_ISFIFO(sb.st_mode))
+	    process_file(resolved_path, &sb, type, flags, dir_id);
+	  else
+	    DPRINTF(E_LOG, L_SCAN, "Skipping %s, not a directory, symlink, pipe nor regular file\n", entry);
 	}
-      else if (S_ISDIR(sb.st_mode))
-	push_dir(&dirstack, entry, dir_id);
-      else
-	DPRINTF(E_LOG, L_SCAN, "Skipping %s, not a directory, symlink, pipe nor regular file\n", entry);
     }
 
   closedir(dirp);
@@ -1056,9 +780,9 @@ process_directory(char *path, int parent_id, int flags)
 
   // Add inotify watch (for FreeBSD we limit the flags so only dirs will be
   // opened, otherwise we will be opening way too many files)
-#if defined(__linux__)
+#ifdef __linux__
   wi.wd = inotify_add_watch(inofd, path, IN_ATTRIB | IN_CREATE | IN_DELETE | IN_CLOSE_WRITE | IN_MOVE | IN_DELETE | IN_MOVE_SELF);
-#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
+#else
   wi.wd = inotify_add_watch(inofd, path, IN_CREATE | IN_DELETE | IN_MOVE);
 #endif
   if (wi.wd < 0)
@@ -1126,7 +850,7 @@ process_directories(char *root, int parent_id, int flags)
 
   process_directory(root, parent_id, flags);
 
-  if (scan_exit)
+  if (library_is_exiting())
     return;
 
   while ((dir = pop_dir(&dirstack)))
@@ -1136,7 +860,7 @@ process_directories(char *root, int parent_id, int flags)
       free(dir->path);
       free(dir);
 
-      if (scan_exit)
+      if (library_is_exiting())
 	return;
     }
 }
@@ -1154,9 +878,8 @@ bulk_scan(int flags)
   time_t end;
   int parent_id;
   int i;
-
-  // Set global flag to avoid queued scan requests
-  scanning = 1;
+  char virtual_path[PATH_MAX];
+  int ret;
 
   start = time(NULL);
 
@@ -1184,7 +907,11 @@ bulk_scan(int flags)
 
 	  db_file_ping_bymatch(path, 1);
 	  db_pl_ping_bymatch(path, 1);
-	  db_directory_ping_bymatch(path);
+	  ret = snprintf(virtual_path, sizeof(virtual_path), "/file:%s", path);
+	  if ((ret < 0) || (ret >= sizeof(virtual_path)))
+	    DPRINTF(E_LOG, L_SCAN, "Virtual path exceeds PATH_MAX (/file:%s)\n", path);
+	  else
+	    db_directory_ping_bymatch(virtual_path);
 
 	  continue;
 	}
@@ -1197,14 +924,14 @@ bulk_scan(int flags)
 
       free(deref);
 
-      if (scan_exit)
+      if (library_is_exiting())
 	return;
     }
 
   if (!(flags & F_SCAN_FAST) && playlists)
     process_deferred_playlists();
 
-  if (scan_exit)
+  if (library_is_exiting())
     return;
 
   if (dirstack)
@@ -1218,102 +945,8 @@ bulk_scan(int flags)
     }
   else
     {
-      /* Protect spotify from the imminent purge if rescanning */
-      if (flags & F_SCAN_RESCAN)
-	{
-	  db_file_ping_bymatch("spotify:", 0);
-	  db_pl_ping_bymatch("spotify:", 0);
-	}
-
-      DPRINTF(E_DBG, L_SCAN, "Purging old database content\n");
-      db_purge_cruft(start);
-      cache_artwork_purge_cruft(start);
-
       DPRINTF(E_LOG, L_SCAN, "Bulk library scan completed in %.f sec\n", difftime(end, start));
-
-      DPRINTF(E_DBG, L_SCAN, "Running post library scan jobs\n");
-      db_hook_post_scan();
     }
-
-  // Set scan in progress flag to FALSE
-  scanning = 0;
-}
-
-
-/* Thread: scan */
-static void *
-filescanner(void *arg)
-{
-  int ret;
-#if defined(__linux__)
-  struct sched_param param;
-
-  /* Lower the priority of the thread so forked-daapd may still respond
-   * during file scan on low power devices. Param must be 0 for the SCHED_BATCH
-   * policy.
-   */
-  memset(&param, 0, sizeof(struct sched_param));
-  ret = pthread_setschedparam(pthread_self(), SCHED_BATCH, &param);
-  if (ret != 0)
-    {
-      DPRINTF(E_LOG, L_SCAN, "Warning: Could not set thread priority to SCHED_BATCH\n");
-    }
-#endif
-
-  ret = db_perthread_init();
-  if (ret < 0)
-    {
-      DPRINTF(E_LOG, L_SCAN, "Error: DB init failed\n");
-
-      pthread_exit(NULL);
-    }
-
-  ret = db_watch_clear();
-  if (ret < 0)
-    {
-      DPRINTF(E_LOG, L_SCAN, "Error: could not clear old watches from DB\n");
-
-      pthread_exit(NULL);
-    }
-
-  ret = db_groups_clear();
-  if (ret < 0)
-    {
-      DPRINTF(E_LOG, L_SCAN, "Error: could not clear old groups from DB\n");
-
-      pthread_exit(NULL);
-    }
-
-  /* Recompute all songartistids and songalbumids, in case the SQLite DB got transferred
-   * to a different host; the hash is not portable.
-   * It will also rebuild the groups we just cleared.
-   */
-  db_files_update_songartistid();
-  db_files_update_songalbumid();
-
-  if (cfg_getbool(cfg_getsec(cfg, "library"), "filescan_disable"))
-    bulk_scan(F_SCAN_BULK | F_SCAN_FAST);
-  else
-    bulk_scan(F_SCAN_BULK);
-
-  if (!scan_exit)
-    {
-#ifdef HAVE_SPOTIFY_H
-      spotify_login(NULL);
-#endif
-
-      /* Enable inotify */
-      event_add(inoev, NULL);
-
-      event_base_dispatch(evbase_scan);
-    }
-
-  if (!scan_exit)
-    DPRINTF(E_FATAL, L_SCAN, "Scan event loop terminated ahead of time!\n");
-
-  db_perthread_deinit();
-
-  pthread_exit(NULL);
 }
 
 static int
@@ -1514,8 +1147,8 @@ process_inotify_file(struct watch_info *wi, char *path, struct inotify_event *ie
 {
   struct stat sb;
   uint32_t path_hash;
-  char *deref = NULL;
   char *file = path;
+  char resolved_path[PATH_MAX];
   char *dir;
   char dir_vpath[PATH_MAX];
   int type;
@@ -1651,44 +1284,14 @@ process_inotify_file(struct watch_info *wi, char *path, struct inotify_event *ie
 	    incomingfiles_buffer[i] = 0;
 	  }
 
-      ret = lstat(path, &sb);
+      ret = read_attributes(resolved_path, path, &sb);
       if (ret < 0)
-	{
-	  DPRINTF(E_LOG, L_SCAN, "Could not lstat() '%s': %s\n", path, strerror(errno));
+        {
+	  DPRINTF(E_LOG, L_SCAN, "Skipping %s, read_attributes() failed\n", path);
 
 	  return;
 	}
 
-      if (S_ISLNK(sb.st_mode))
-	{
-	  deref = m_realpath(path);
-	  if (!deref)
-	    {
-	      DPRINTF(E_LOG, L_SCAN, "Could not dereference symlink '%s': %s\n", path, strerror(errno));
-
-	      return;
-	    }
-
-	  file = deref;
-
-	  ret = stat(deref, &sb);
-	  if (ret < 0)
-	    {
-	      DPRINTF(E_LOG, L_SCAN, "Could not stat() '%s': %s\n", file, strerror(errno));
-
-	      free(deref);
-	      return;
-	    }
-
-	  if (S_ISDIR(sb.st_mode))
-	    {
-	      process_inotify_dir(wi, deref, ie);
-
-	      free(deref);
-	      return;
-	    }
-	}
-
       type = 0;
       if (check_speciallib(path, "compilations"))
 	type |= F_SCAN_TYPE_COMPILATION;
@@ -1699,21 +1302,25 @@ process_inotify_file(struct watch_info *wi, char *path, struct inotify_event *ie
 
       dir_id = get_parent_dir_id(file);
 
-      if (S_ISREG(sb.st_mode))
+      if (S_ISDIR(sb.st_mode))
+        {
+	  process_inotify_dir(wi, resolved_path, ie);
+
+	  return;
+	}
+      else if (S_ISREG(sb.st_mode) || S_ISFIFO(sb.st_mode))
 	{
-	  process_file(file, sb.st_mtime, sb.st_size, F_SCAN_TYPE_FILE | type, 0, dir_id);
+	  process_file(resolved_path, &sb, type, 0, dir_id);
 	}
-      else if (S_ISFIFO(sb.st_mode))
-	process_file(file, sb.st_mtime, sb.st_size, F_SCAN_TYPE_PIPE | type, 0, dir_id);
-
-      if (deref)
-	free(deref);
+      else
+	DPRINTF(E_LOG, L_SCAN, "Skipping %s, not a directory, symlink, pipe nor regular file\n", resolved_path);
     }
 }
 
-#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
-/* Since FreeBSD doesn't really have inotify we only get a IN_CREATE. That is
- * a bit too soon to start scanning the file, so we defer it for 10 seconds.
+#ifndef __linux__
+/* Since kexec based inotify doesn't really have inotify we only get
+ * a IN_CREATE. That is a bit too soon to start scanning the file,
+ * so we defer it for 10 seconds.
  */
 static void
 inotify_deferred_cb(int fd, short what, void *arg)
@@ -1752,6 +1359,7 @@ process_inotify_file_defer(struct watch_info *wi, char *path, struct inotify_eve
   f = calloc(1, sizeof(struct deferred_file));
   f->wi = *wi;
   f->wi.path = strdup(wi->path);
+  /* ie->name not copied here, so don't use in process_inotify_* */
   f->ie = *ie;
   strcpy(f->path, path);
 
@@ -1863,9 +1471,9 @@ inotify_cb(int fd, short event, void *arg)
       if ((ie->mask & IN_ISDIR) || (ie->len == 0))
 	process_inotify_dir(&wi, path, ie);
       else
-#if defined(__linux__)
+#ifdef __linux__
 	process_inotify_file(&wi, path, ie);
-#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
+#else
 	process_inotify_file_defer(&wi, path, ie);
 #endif
       free(wi.path);
@@ -1888,10 +1496,10 @@ inofd_event_set(void)
       return -1;
     }
 
-  inoev = event_new(evbase_scan, inofd, EV_READ, inotify_cb, NULL);
+  inoev = event_new(evbase_lib, inofd, EV_READ, inotify_cb, NULL);
 
-#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
-  deferred_inoev = evtimer_new(evbase_scan, inotify_deferred_cb, NULL);
+#ifndef __linux__
+  deferred_inoev = evtimer_new(evbase_lib, inotify_deferred_cb, NULL);
   if (!deferred_inoev)
     {
       DPRINTF(E_LOG, L_SCAN, "Could not create deferred inotify event\n");
@@ -1907,7 +1515,7 @@ inofd_event_set(void)
 static void
 inofd_event_unset(void)
 {
-#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
+#ifndef __linux__
   event_free(deferred_inoev);
 #endif
   event_free(inoev);
@@ -1915,141 +1523,115 @@ inofd_event_unset(void)
 }
 
 /* Thread: scan */
+static int
+filescanner_initscan()
+{
+  int ret;
 
-static enum command_state
-filescanner_initscan(void *arg, int *retval)
+  ret = db_watch_clear();
+  if (ret < 0)
+    {
+      DPRINTF(E_LOG, L_SCAN, "Error: could not clear old watches from DB\n");
+      return -1;
+    }
+
+  if (cfg_getbool(cfg_getsec(cfg, "library"), "filescan_disable"))
+    bulk_scan(F_SCAN_BULK | F_SCAN_FAST);
+  else
+    bulk_scan(F_SCAN_BULK);
+
+  if (!library_is_exiting())
+    {
+      /* Enable inotify */
+      event_add(inoev, NULL);
+    }
+  return 0;
+}
+
+static int
+filescanner_rescan()
 {
   DPRINTF(E_LOG, L_SCAN, "Startup rescan triggered\n");
 
   inofd_event_unset(); // Clears all inotify watches
   db_watch_clear();
-
   inofd_event_set();
   bulk_scan(F_SCAN_BULK | F_SCAN_RESCAN);
 
-  *retval = 0;
-  return COMMAND_END;
+  return 0;
 }
 
-static enum command_state
-filescanner_fullrescan(void *arg, int *retval)
+static int
+filescanner_fullrescan()
 {
   DPRINTF(E_LOG, L_SCAN, "Full rescan triggered\n");
 
-  player_playback_stop();
-  player_queue_clear();
   inofd_event_unset(); // Clears all inotify watches
-  db_purge_all(); // Clears files, playlists, playlistitems, inotify and groups
-
   inofd_event_set();
   bulk_scan(F_SCAN_BULK);
 
-  *retval = 0;
-  return COMMAND_END;
+  return 0;
 }
 
-void
-filescanner_trigger_initscan(void)
+static int
+scan_metadata(const char *path, struct media_file_info *mfi)
 {
-  if (scanning)
+  int ret;
+
+  if (strncasecmp(path, "http://", strlen("http://")) == 0)
     {
-      DPRINTF(E_INFO, L_SCAN, "Scan already running, ignoring request to trigger a new init scan\n");
-      return;
-    }
+      memset(mfi, 0, sizeof(struct media_file_info));
+      mfi->path = strdup(path);
+      mfi->fname = strdup(filename_from_path(mfi->path));
+      mfi->data_kind = DATA_KIND_HTTP;
+      mfi->directory_id = DIR_HTTP;
 
- commands_exec_async(cmdbase, filescanner_initscan, NULL);
-}
+      ret = scan_metadata_ffmpeg(path, mfi);
+      if (ret < 0)
+	{
+	  DPRINTF(E_LOG, L_SCAN, "Playlist URL '%s' is unavailable for probe/metadata, assuming MP3 encoding\n", path);
+	  mfi->type = strdup("mp3");
+	  mfi->codectype = strdup("mpeg");
+	  mfi->description = strdup("MPEG audio file");
+	}
 
-void
-filescanner_trigger_fullrescan(void)
-{
-  if (scanning)
-    {
-      DPRINTF(E_INFO, L_SCAN, "Scan already running, ignoring request to trigger a new init scan\n");
-      return;
+      return LIBRARY_OK;
     }
 
-  commands_exec_async(cmdbase, filescanner_fullrescan, NULL);
-}
-
-/*
- * Query the status of the filescanner
- * @return 1 if scan is running, otherwise 0
- */
-int
-filescanner_scanning(void)
-{
-  return scanning;
+  return LIBRARY_PATH_INVALID;
 }
 
 /* Thread: main */
-int
+static int
 filescanner_init(void)
 {
   int ret;
 
-  scan_exit = 0;
-  scanning = 0;
-
-  evbase_scan = event_base_new();
-  if (!evbase_scan)
-    {
-      DPRINTF(E_FATAL, L_SCAN, "Could not create an event base\n");
-
-      return -1;
-    }
-
   ret = inofd_event_set();
   if (ret < 0)
     {
-      goto ino_fail;
-    }
-
-  cmdbase = commands_base_new(evbase_scan, NULL);
-
-  ret = pthread_create(&tid_scan, NULL, filescanner, NULL);
-  if (ret != 0)
-    {
-      DPRINTF(E_FATAL, L_SCAN, "Could not spawn filescanner thread: %s\n", strerror(errno));
-
-      goto thread_fail;
+      return -1;
     }
 
-#if defined(HAVE_PTHREAD_SETNAME_NP)
-  pthread_setname_np(tid_scan, "filescanner");
-#elif defined(HAVE_PTHREAD_SET_NAME_NP)
-  pthread_set_name_np(tid_scan, "filescanner");
-#endif
-
   return 0;
-
- thread_fail:
-  commands_base_free(cmdbase);
-  close(inofd);
- ino_fail:
-  event_base_free(evbase_scan);
-
-  return -1;
 }
 
 /* Thread: main */
-void
+static void
 filescanner_deinit(void)
 {
-  int ret;
-
-  scan_exit = 1;
-  commands_base_destroy(cmdbase);
-
-  ret = pthread_join(tid_scan, NULL);
-  if (ret != 0)
-    {
-      DPRINTF(E_FATAL, L_SCAN, "Could not join filescanner thread: %s\n", strerror(errno));
-
-      return;
-    }
-
   inofd_event_unset();
-
-  event_base_free(evbase_scan);
 }
+
+
+struct library_source filescanner =
+{
+  .name = "filescanner",
+  .disabled = 0,
+  .init = filescanner_init,
+  .deinit = filescanner_deinit,
+  .initscan = filescanner_initscan,
+  .rescan = filescanner_rescan,
+  .fullrescan = filescanner_fullrescan,
+  .scan_metadata = scan_metadata,
+};
diff --git a/src/library/filescanner.h b/src/library/filescanner.h
new file mode 100644
index 0000000..8279f15
--- /dev/null
+++ b/src/library/filescanner.h
@@ -0,0 +1,26 @@
+
+#ifndef __FILESCANNER_H__
+#define __FILESCANNER_H__
+
+#include "db.h"
+
+
+/* Actual scanners */
+int
+scan_metadata_ffmpeg(const char *file, struct media_file_info *mfi);
+
+void
+scan_playlist(char *file, time_t mtime, int dir_id);
+
+void
+scan_smartpl(char *file, time_t mtime, int dir_id);
+
+#ifdef ITUNES
+void
+scan_itunes_itml(char *file);
+#endif
+
+const char *
+filename_from_path(const char *path);
+
+#endif /* !__FILESCANNER_H__ */
diff --git a/src/filescanner_ffmpeg.c b/src/library/filescanner_ffmpeg.c
similarity index 97%
rename from src/filescanner_ffmpeg.c
rename to src/library/filescanner_ffmpeg.c
index e5103aa..975233d 100644
--- a/src/filescanner_ffmpeg.c
+++ b/src/library/filescanner_ffmpeg.c
@@ -25,16 +25,14 @@
 #include <string.h>
 #include <time.h>
 
-#ifdef HAVE_STDINT_H
 #include <stdint.h>
-#endif
 
 #include <libavcodec/avcodec.h>
 #include <libavformat/avformat.h>
 #include <libavutil/opt.h>
 
+#include "db.h"
 #include "logger.h"
-#include "filescanner.h"
 #include "misc.h"
 #include "http.h"
 
@@ -352,8 +350,18 @@ extract_metadata(struct media_file_info *mfi, AVFormatContext *ctx, AVStream *au
   return mdcount;
 }
 
+/*
+ * Fills metadata read with ffmpeg/libav from the given path into the given mfi
+ *
+ * Following attributes from the given mfi are read to control how to read metadata:
+ * - data_kind: if data_kind is http, icy metadata is used, if the path points to a playlist the first stream-uri in that playlist is used
+ * - media_kind: if media_kind is podcast or audiobook, video streams in the file are ignored
+ * - compilation: like podcast/audiobook video streams are ignored for compilations
+ * - file_size: if bitrate could not be read through ffmpeg/libav, file_size is used to estimate the bitrate
+ * - fname: (filename) used as fallback for artist
+ */
 int
-scan_metadata_ffmpeg(char *file, struct media_file_info *mfi)
+scan_metadata_ffmpeg(const char *file, struct media_file_info *mfi)
 {
   AVFormatContext *ctx;
   AVDictionary *options;
@@ -380,17 +388,14 @@ scan_metadata_ffmpeg(char *file, struct media_file_info *mfi)
   path = strdup(file);
 
 #if LIBAVFORMAT_VERSION_MAJOR >= 54 || (LIBAVFORMAT_VERSION_MAJOR == 53 && LIBAVFORMAT_VERSION_MINOR >= 3)
-# ifndef HAVE_FFMPEG
-  // Without this, libav is slow to probe some internet streams
   if (mfi->data_kind == DATA_KIND_HTTP)
     {
+# ifndef HAVE_FFMPEG
+      // Without this, libav is slow to probe some internet streams
       ctx = avformat_alloc_context();
       ctx->probesize = 64000;
-    }
 # endif
 
-  if (mfi->data_kind == DATA_KIND_HTTP)
-    {
       free(path);
       ret = http_stream_setup(&path, file);
       if (ret < 0)
diff --git a/src/filescanner_itunes.c b/src/library/filescanner_itunes.c
similarity index 99%
rename from src/filescanner_itunes.c
rename to src/library/filescanner_itunes.c
index 6abd842..0896c78 100644
--- a/src/filescanner_itunes.c
+++ b/src/library/filescanner_itunes.c
@@ -41,7 +41,6 @@
 
 #include "logger.h"
 #include "db.h"
-#include "filescanner.h"
 #include "conffile.h"
 #include "misc.h"
 
diff --git a/src/filescanner_playlist.c b/src/library/filescanner_playlist.c
similarity index 63%
rename from src/filescanner_playlist.c
rename to src/library/filescanner_playlist.c
index d393e32..7e51c04 100644
--- a/src/filescanner_playlist.c
+++ b/src/library/filescanner_playlist.c
@@ -36,8 +36,9 @@
 
 #include "logger.h"
 #include "db.h"
-#include "filescanner.h"
+#include "library/filescanner.h"
 #include "misc.h"
+#include "library.h"
 
 /* Formats we can read so far */
 #define PLAYLIST_PLS 1
@@ -73,6 +74,112 @@ extinf_get(char *string, struct media_file_info *mfi, int *extinf)
   return 1;
 }
 
+static int
+process_url(const char *path, time_t mtime, int extinf, struct media_file_info *mfi, char **filename)
+{
+  char virtual_path[PATH_MAX];
+  time_t stamp;
+  int id;
+  int ret;
+
+  *filename = strdup(path);
+
+  db_file_stamp_bypath(path, &stamp, &id);
+  if (stamp && (stamp >= mtime))
+    {
+      db_file_ping(id);
+      return 0;
+    }
+
+  if (extinf)
+    DPRINTF(E_INFO, L_SCAN, "Playlist has EXTINF metadata, artist is '%s', title is '%s'\n", mfi->artist, mfi->title);
+
+  mfi->id = id;
+  mfi->path = strdup(path);
+  mfi->fname = strdup(filename_from_path(path));
+  mfi->data_kind = DATA_KIND_HTTP;
+  mfi->time_modified = mtime;
+  mfi->directory_id = DIR_HTTP;
+
+  ret = scan_metadata_ffmpeg(path, mfi);
+  if (ret < 0)
+    {
+      DPRINTF(E_LOG, L_SCAN, "Playlist URL '%s' is unavailable for probe/metadata, assuming MP3 encoding\n", path);
+      mfi->type = strdup("mp3");
+      mfi->codectype = strdup("mpeg");
+      mfi->description = strdup("MPEG audio file");
+    }
+
+  if (!mfi->title)
+    mfi->title = strdup(mfi->fname);
+
+  snprintf(virtual_path, PATH_MAX, "/http:/%s", mfi->title);
+  mfi->virtual_path = strdup(virtual_path);
+
+  library_add_media(mfi);
+
+  return 0;
+}
+
+static int
+process_regular_file(char **filename, char *path)
+{
+  int i;
+  int mfi_id;
+  char *ptr;
+  char *entry;
+  int ret;
+
+  /* Playlist might be from Windows so we change backslash to forward slash */
+  for (i = 0; i < strlen(path); i++)
+    {
+      if (path[i] == '\\')
+	path[i] = '/';
+    }
+
+  /* Now search for the library item where the path has closest match to playlist item */
+  /* Succes is when we find an unambiguous match, or when we no longer can expand the  */
+  /* the path to refine our search.                                                    */
+  entry = NULL;
+  do
+    {
+      ptr = strrchr(path, '/');
+      if (entry)
+	*(entry - 1) = '/';
+
+      if (ptr)
+	{
+	  *ptr = '\0';
+	  entry = ptr + 1;
+	}
+      else
+	entry = path;
+
+      DPRINTF(E_SPAM, L_SCAN, "Playlist entry is now %s\n", entry);
+      ret = db_files_get_count_bymatch(entry);
+    }
+  while (ptr && (ret > 1));
+
+  if (ret > 0)
+    {
+      mfi_id = db_file_id_bymatch(entry);
+      DPRINTF(E_DBG, L_SCAN, "Found playlist entry match, id is %d, entry is %s\n", mfi_id, entry);
+      *filename = db_file_path_byid(mfi_id);
+      if (!(*filename))
+	{
+	  DPRINTF(E_LOG, L_SCAN, "Playlist entry %s matches file id %d, but file path is missing.\n", entry, mfi_id);
+	  return -1;
+	}
+    }
+  else
+    {
+      DPRINTF(E_DBG, L_SCAN, "No match for playlist entry %s\n", entry);
+      return -1;
+    }
+
+  return 0;
+}
+
 void
 scan_playlist(char *file, time_t mtime, int dir_id)
 {
@@ -82,17 +189,15 @@ scan_playlist(char *file, time_t mtime, int dir_id)
   struct stat sb;
   char buf[PATH_MAX];
   char *path;
-  char *entry;
-  char *filename;
+  const char *filename;
   char *ptr;
   size_t len;
   int extinf;
   int pl_id;
   int pl_format;
-  int mfi_id;
   int ret;
   char virtual_path[PATH_MAX];
-  int i;
+  char *plitem_path;
 
   DPRINTF(E_LOG, L_SCAN, "Processing static playlist: %s\n", file);
 
@@ -107,11 +212,7 @@ scan_playlist(char *file, time_t mtime, int dir_id)
   else
     return;
 
-  filename = strrchr(file, '/');
-  if (!filename)
-    filename = file;
-  else
-    filename++;
+  filename = filename_from_path(file);
 
   ret = stat(file, &sb);
   if (ret < 0)
@@ -225,82 +326,27 @@ scan_playlist(char *file, time_t mtime, int dir_id)
       /* Check if line is an URL, will be added to library */
       if (strncasecmp(path, "http://", strlen("http://")) == 0)
 	{
-	  DPRINTF(E_DBG, L_SCAN, "Playlist contains URL entry\n");
+	  DPRINTF(E_DBG, L_SCAN, "Playlist contains URL entry: '%s'\n", path);
 
-	  filename = strdup(path);
-	  if (!filename)
-	    {
-	      DPRINTF(E_LOG, L_SCAN, "Out of memory for playlist filename\n");
-
-	      continue;
-	    }
-
-	  if (extinf)
-	    DPRINTF(E_INFO, L_SCAN, "Playlist has EXTINF metadata, artist is '%s', title is '%s'\n", mfi.artist, mfi.title);
-
-	  filescanner_process_media(filename, mtime, 0, F_SCAN_TYPE_URL, &mfi, DIR_HTTP);
+	  ret = process_url(path, sb.st_mtime, extinf, &mfi, &plitem_path);
 	}
       /* Regular file, should already be in library */
       else
 	{
-	  /* Playlist might be from Windows so we change backslash to forward slash */
-	  for (i = 0; i < strlen(path); i++)
-	    {
-	      if (path[i] == '\\')
-	        path[i] = '/';
-	    }
-
-          /* Now search for the library item where the path has closest match to playlist item */
-          /* Succes is when we find an unambiguous match, or when we no longer can expand the  */
-          /* the path to refine our search.                                                    */
-	  entry = NULL;
-	  do
-	    {
-	      ptr = strrchr(path, '/');
-	      if (entry)
-		*(entry - 1) = '/';
-	      if (ptr)
-		{
-		  *ptr = '\0';
-		  entry = ptr + 1;
-		}
-	      else
-		entry = path;
-
-	      DPRINTF(E_SPAM, L_SCAN, "Playlist entry is now %s\n", entry);
-	      ret = db_files_get_count_bymatch(entry);
-
-	    } while (ptr && (ret > 1));
-
-	  if (ret > 0)
-	    {
-	      mfi_id = db_file_id_bymatch(entry);
-	      DPRINTF(E_DBG, L_SCAN, "Found playlist entry match, id is %d, entry is %s\n", mfi_id, entry);
-
-	      filename = db_file_path_byid(mfi_id);
-	      if (!filename)
-		{
-		  DPRINTF(E_LOG, L_SCAN, "Playlist entry %s matches file id %d, but file path is missing.\n", entry, mfi_id);
-
-		  continue;
-		}
-	    }
-	  else
-	    {
-	      DPRINTF(E_DBG, L_SCAN, "No match for playlist entry %s\n", entry);
-
-	      continue;
-	    }
+	  ret = process_regular_file(&plitem_path, path);
 	}
 
-      ret = db_pl_add_item_bypath(pl_id, filename);
-      if (ret < 0)
-	DPRINTF(E_WARN, L_SCAN, "Could not add %s to playlist\n", filename);
-
-      /* Clean up in preparation for next item */
-      extinf = 0;
-      free_mfi(&mfi, 1);
-      free(filename);
+      if (ret == 0)
+	{
+	  ret = db_pl_add_item_bypath(pl_id, plitem_path);
+	  if (ret < 0)
+	    DPRINTF(E_WARN, L_SCAN, "Could not add %s to playlist\n", plitem_path);
+
+	  /* Clean up in preparation for next item */
+	  extinf = 0;
+	  free_mfi(&mfi, 1);
+	  free(plitem_path);
+	}
     }
 
   /* We had some extinf that we never got to use, free it now */
diff --git a/src/filescanner_smartpl.c b/src/library/filescanner_smartpl.c
similarity index 95%
rename from src/filescanner_smartpl.c
rename to src/library/filescanner_smartpl.c
index 5c55ef4..341a41c 100644
--- a/src/filescanner_smartpl.c
+++ b/src/library/filescanner_smartpl.c
@@ -33,7 +33,6 @@
 
 #include "logger.h"
 #include "db.h"
-#include "filescanner.h"
 #include "misc.h"
 
 #include "SMARTPLLexer.h"
@@ -183,15 +182,13 @@ scan_smartpl(char *file, time_t mtime, int dir_id)
   pli = db_pl_fetch_bypath(file);
   if (!pli)
     {
-      pli = (struct playlist_info *) malloc(sizeof(struct playlist_info));
+      pli = calloc(1, sizeof(struct playlist_info));
       if (!pli)
 	{
 	  DPRINTF(E_LOG, L_SCAN, "Out of memory\n");
 	  return;
 	}
 
-      memset(pli, 0, sizeof(struct playlist_info));
-
       pli->path = strdup(file);
       snprintf(virtual_path, PATH_MAX, "/file:%s", file);
       ptr = strrchr(virtual_path, '.');
@@ -200,8 +197,6 @@ scan_smartpl(char *file, time_t mtime, int dir_id)
       pli->virtual_path = strdup(virtual_path);
       pli->type = PL_SMART;
     }
-  else
-    pl_id = pli->id;
 
   pli->directory_id = dir_id;
 
@@ -216,6 +211,7 @@ scan_smartpl(char *file, time_t mtime, int dir_id)
 
   if (pli->id)
     {
+      pl_id = pli->id;
       ret = db_pl_update(pli);
     }
   else
@@ -230,7 +226,7 @@ scan_smartpl(char *file, time_t mtime, int dir_id)
       return;
     }
 
-  DPRINTF(E_INFO, L_SCAN, "Added smart playlist as id %d\n", pl_id);
+  DPRINTF(E_INFO, L_SCAN, "Added or updated smart playlist as id %d\n", pl_id);
 
   free_pli(pli, 0);
 
diff --git a/src/listener.h b/src/listener.h
index 8b49355..6142107 100644
--- a/src/listener.h
+++ b/src/listener.h
@@ -6,8 +6,8 @@ enum listener_event_type
 {
   /* The player has been started, stopped or seeked */
   LISTENER_PLAYER    = (1 << 0),
-  /* The current playlist has been modified */
-  LISTENER_PLAYLIST  = (1 << 1),
+  /* The current playback queue has been modified */
+  LISTENER_QUEUE     = (1 << 1),
   /* The volume has been changed */
   LISTENER_VOLUME    = (1 << 2),
   /* A speaker has been enabled or disabled */
diff --git a/src/logger.c b/src/logger.c
index b84dd36..6d7ae1f 100644
--- a/src/logger.c
+++ b/src/logger.c
@@ -20,32 +20,41 @@
 # include <config.h>
 #endif
 
+#include "logger.h"
+
 #include <stdio.h>
 #include <unistd.h>
 #include <stdarg.h>
+#include <stdlib.h>
 #include <string.h>
 #include <time.h>
 #include <errno.h>
 #include <sys/stat.h>
-#include <pthread.h>
 
 #include <event2/event.h>
 
 #include <libavutil/log.h>
 
 #include "conffile.h"
-#include "logger.h"
+#include "misc.h"
 
-
-static pthread_mutex_t logger_lck = PTHREAD_MUTEX_INITIALIZER;
+static pthread_mutex_t logger_lck;
+static int logger_initialized;
 static int logdomains;
 static int threshold;
-static int console;
+static int console = 1;
 static char *logfilename;
 static FILE *logfile;
-static char *labels[] = { "config", "daap", "db", "httpd", "http", "main", "mdns", "misc", "rsp", "scan", "xcode", "event", "remote", "dacp", "ffmpeg", "artwork", "player", "raop", "laudio", "dmap", "dbperf", "spotify", "lastfm", "cache", "mpd", "stream", "cast", "fifo" };
+static char *labels[] = { "config", "daap", "db", "httpd", "http", "main", "mdns", "misc", "rsp", "scan", "xcode", "event", "remote", "dacp", "ffmpeg", "artwork", "player", "raop", "laudio", "dmap", "dbperf", "spotify", "lastfm", "cache", "mpd", "stream", "cast", "fifo", "lib" };
 static char *severities[] = { "FATAL", "LOG", "WARN", "INFO", "DEBUG", "SPAM" };
 
+/* We need our own check to avoid nested locking or recursive calls */
+#define LOGGER_CHECK_ERR(f) \
+  do { int lerr; lerr = f; if (lerr != 0) { \
+      vlogger_fatal("%s failed at line %d, err %d (%s)\n", #f, __LINE__, \
+                    lerr, strerror(lerr)); \
+      abort(); \
+    } } while(0)
 
 static int
 set_logdomains(char *domains)
@@ -80,24 +89,13 @@ set_logdomains(char *domains)
 }
 
 static void
-vlogger(int severity, int domain, const char *fmt, va_list args)
+vlogger_writer(int severity, int domain, const char *fmt, va_list args)
 {
   va_list ap;
   char stamp[32];
   time_t t;
   int ret;
 
-  if (!((1 << domain) & logdomains) || (severity > threshold))
-    return;
-
-  pthread_mutex_lock(&logger_lck);
-
-  if (!logfile && !console)
-    {
-      pthread_mutex_unlock(&logger_lck);
-      return;
-    }
-
   if (logfile)
     {
       t = time(NULL);
@@ -122,8 +120,43 @@ vlogger(int severity, int domain, const char *fmt, va_list args)
       vfprintf(stderr, fmt, ap);
       va_end(ap);
     }
+}
+
+static void
+vlogger_fatal(const char *fmt, ...)
+{
+  va_list ap;
+
+  va_start(ap, fmt);
+  vlogger_writer(E_FATAL, L_MISC, fmt, ap);
+  va_end(ap);
+}
+
+static void
+vlogger(int severity, int domain, const char *fmt, va_list args)
+{
+
+  if(! logger_initialized)
+    {
+      /* lock not initialized, use stderr */
+      vlogger_writer(severity, domain, fmt, args);
+      return;
+    }
+
+  if (!((1 << domain) & logdomains) || (severity > threshold))
+    return;
+
+  LOGGER_CHECK_ERR(pthread_mutex_lock(&logger_lck));
+
+  if (!logfile && !console)
+    {
+      LOGGER_CHECK_ERR(pthread_mutex_unlock(&logger_lck));
+      return;
+    }
+
+  vlogger_writer(severity, domain, fmt, args);
 
-  pthread_mutex_unlock(&logger_lck);
+  LOGGER_CHECK_ERR(pthread_mutex_unlock(&logger_lck));
 }
 
 void
@@ -184,7 +217,7 @@ logger_libevent(int severity, const char *msg)
   DPRINTF(severity, L_EVENT, "%s\n", msg);
 }
 
-#ifdef ALSA
+#ifdef HAVE_ALSA
 void
 logger_alsa(const char *file, int line, const char *function, int err, const char *fmt, ...)
 {
@@ -194,7 +227,7 @@ logger_alsa(const char *file, int line, const char *function, int err, const cha
   vlogger(E_LOG, L_LAUDIO, fmt, ap);
   va_end(ap);
 }
-#endif /* ALSA */
+#endif /* HAVE_ALSA */
 
 void
 logger_reinit(void)
@@ -204,7 +237,7 @@ logger_reinit(void)
   if (!logfile)
     return;
 
-  pthread_mutex_lock(&logger_lck);
+  LOGGER_CHECK_ERR(pthread_mutex_lock(&logger_lck));
 
   fp = fopen(logfilename, "a");
   if (!fp)
@@ -218,7 +251,7 @@ logger_reinit(void)
   logfile = fp;
 
  out:
-  pthread_mutex_unlock(&logger_lck);
+  LOGGER_CHECK_ERR(pthread_mutex_unlock(&logger_lck));
 }
 
 
@@ -287,6 +320,11 @@ logger_init(char *file, char *domains, int severity)
 
   logfilename = file;
 
+  /* logging w/o locks before initialized complete */
+  CHECK_ERR(L_MISC, mutex_init(&logger_lck));
+
+  logger_initialized = 1;
+
   return 0;
 }
 
@@ -294,5 +332,16 @@ void
 logger_deinit(void)
 {
   if (logfile)
-    fclose(logfile);
+    {
+      fclose(logfile);
+      logfile = NULL;
+    }
+
+  if(logger_initialized)
+    {
+      /* logging w/o locks to stderr now */
+      logger_initialized = 0;
+      console = 1;
+      CHECK_ERR(L_MISC, pthread_mutex_destroy(&logger_lck));
+    }
 }
diff --git a/src/logger.h b/src/logger.h
index 7e4f082..dff5258 100644
--- a/src/logger.h
+++ b/src/logger.h
@@ -34,8 +34,9 @@
 #define L_STREAMING   25
 #define L_CAST        26
 #define L_FIFO        27
+#define L_LIB         28
 
-#define N_LOGDOMAINS  28
+#define N_LOGDOMAINS  29
 
 /* Severities */
 #define E_FATAL   0
@@ -55,7 +56,7 @@ logger_ffmpeg(void *ptr, int level, const char *fmt, va_list ap);
 void
 logger_libevent(int severity, const char *msg);
 
-#ifdef ALSA
+#ifdef HAVE_ALSA
 void
 logger_alsa(const char *file, int line, const char *function, int err, const char *fmt, ...);
 #endif
diff --git a/src/main.c b/src/main.c
index 3c0f07d..7fce010 100644
--- a/src/main.c
+++ b/src/main.c
@@ -37,21 +37,23 @@
 #include <grp.h>
 #include <stdint.h>
 
-#if defined(__linux__)
+#ifdef HAVE_SIGNALFD
 # include <sys/signalfd.h>
-#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
+#else
 # include <sys/time.h>
 # include <sys/event.h>
 #endif
 
-#include <pthread.h>
-
 #include <getopt.h>
 #include <event2/event.h>
+#ifdef HAVE_LIBEVENT_PTHREADS
+# include <event2/thread.h>
+#endif
 #include <libavutil/log.h>
 #include <libavformat/avformat.h>
 #include <libavfilter/avfilter.h>
 
+#include <pthread.h>
 #include <gcrypt.h>
 GCRY_THREAD_OPTION_PTHREAD_IMPL;
 
@@ -60,20 +62,17 @@ GCRY_THREAD_OPTION_PTHREAD_IMPL;
 #include "logger.h"
 #include "misc.h"
 #include "cache.h"
-#include "filescanner.h"
 #include "httpd.h"
 #include "mpd.h"
 #include "mdns.h"
 #include "remote_pairing.h"
 #include "player.h"
 #include "worker.h"
+#include "library.h"
 
-#ifdef LASTFM
+#ifdef HAVE_LIBCURL
 # include <curl/curl.h>
 #endif
-#ifdef HAVE_SPOTIFY_H
-# include "spotify.h"
-#endif
 
 #define PIDFILE   STATEDIR "/run/" PACKAGE ".pid"
 
@@ -334,7 +333,7 @@ register_services(char *ffid, int no_rsp, int no_daap, int mdns_no_mpd)
 }
 
 
-#if defined(__linux__)
+#ifdef HAVE_SIGNALFD
 static void
 signal_signalfd_cb(int fd, short event, void *arg)
 {
@@ -374,7 +373,7 @@ signal_signalfd_cb(int fd, short event, void *arg)
     event_add(sig_event, NULL);
 }
 
-#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
+#else
 
 static void
 signal_kqueue_cb(int fd, short event, void *arg)
@@ -422,26 +421,29 @@ signal_kqueue_cb(int fd, short event, void *arg)
 
 
 static int
-ffmpeg_lockmgr(void **mutex, enum AVLockOp op)
+ffmpeg_lockmgr(void **pmutex, enum AVLockOp op)
 {
   switch (op)
     {
       case AV_LOCK_CREATE:
-	*mutex = malloc(sizeof(pthread_mutex_t));
-	if (!*mutex)
+	*pmutex = malloc(sizeof(pthread_mutex_t));
+	if (!*pmutex)
 	  return 1;
-
-	return !!pthread_mutex_init(*mutex, NULL);
+        CHECK_ERR(L_MAIN, mutex_init(*pmutex));
+	return 0;
 
       case AV_LOCK_OBTAIN:
-	return !!pthread_mutex_lock(*mutex);
+        CHECK_ERR(L_MAIN, pthread_mutex_lock(*pmutex));
+	return 0;
 
       case AV_LOCK_RELEASE:
-	return !!pthread_mutex_unlock(*mutex);
+        CHECK_ERR(L_MAIN, pthread_mutex_unlock(*pmutex));
+	return 0;
 
       case AV_LOCK_DESTROY:
-	pthread_mutex_destroy(*mutex);
-	free(*mutex);
+	CHECK_ERR(L_MAIN, pthread_mutex_destroy(*pmutex));
+	free(*pmutex);
+        *pmutex = NULL;
 
 	return 0;
     }
@@ -449,7 +451,6 @@ ffmpeg_lockmgr(void **mutex, enum AVLockOp op)
   return 1;
 }
 
-
 int
 main(int argc, char **argv)
 {
@@ -468,7 +469,7 @@ main(int argc, char **argv)
   const char *gcry_version;
   sigset_t sigs;
   int sigfd;
-#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
+#ifdef HAVE_KQUEUE
   struct kevent ke_sigs[4];
 #endif
   int ret;
@@ -498,7 +499,6 @@ main(int argc, char **argv)
   ffid = NULL;
   mdns_no_rsp = 0;
   mdns_no_daap = 0;
-  mdns_no_mpd = 1; // only announce if mpd protocol support is activated
 
   while ((option = getopt_long(argc, argv, "D:d:c:P:fb:v", option_map, NULL)) != -1)
     {
@@ -608,14 +608,17 @@ main(int argc, char **argv)
 #ifdef MPD
   strcat(buildopts, " --enable-mpd");
 #endif
-#ifdef ALSA
+#ifdef RAOP_VERIFICATION
+  strcat(buildopts, " --enable-verification");
+#endif
+#ifdef HAVE_ALSA
   strcat(buildopts, " --with-alsa");
 #endif
-#ifdef PULSEAUDIO
+#ifdef HAVE_LIBPULSE
   strcat(buildopts, " --with-pulseaudio");
 #endif
 
-  DPRINTF(E_LOG, L_MAIN, "Built %s with:%s\n", BUILDDATE, buildopts);
+  DPRINTF(E_LOG, L_MAIN, "Built %s with:%s\n", __DATE__, buildopts);
 
   ret = av_lockmgr_register(ffmpeg_lockmgr);
   if (ret < 0)
@@ -633,7 +636,7 @@ main(int argc, char **argv)
 #endif
   av_log_set_callback(logger_ffmpeg);
 
-#ifdef LASTFM
+#ifdef HAVE_LIBCURL
   /* Initialize libcurl */
   curl_global_init(CURL_GLOBAL_DEFAULT);
 #endif
@@ -686,7 +689,11 @@ main(int argc, char **argv)
     }
 
   /* Initialize event base (after forking) */
-  evbase_main = event_base_new();
+  CHECK_NULL(L_MAIN, evbase_main = event_base_new());
+
+#ifdef HAVE_LIBEVENT_PTHREADS
+  CHECK_ERR(L_MAIN, evthread_use_pthreads());
+#endif
 
   DPRINTF(E_LOG, L_MAIN, "mDNS init\n");
   ret = mdns_init();
@@ -739,25 +746,16 @@ main(int argc, char **argv)
       goto cache_fail;
     }
 
-  /* Spawn file scanner thread */
-  ret = filescanner_init();
+  /* Spawn library scan thread */
+  ret = library_init();
   if (ret != 0)
     {
-      DPRINTF(E_FATAL, L_MAIN, "File scanner thread failed to start\n");
+      DPRINTF(E_FATAL, L_MAIN, "Library thread failed to start\n");
 
       ret = EXIT_FAILURE;
-      goto filescanner_fail;
+      goto library_fail;
     }
 
-#ifdef HAVE_SPOTIFY_H
-  /* Spawn Spotify thread */
-  ret = spotify_init();
-  if (ret < 0)
-    {
-      DPRINTF(E_INFO, L_MAIN, "Spotify thread not started\n");;
-    }
-#endif
-
   /* Spawn player thread */
   ret = player_init();
   if (ret != 0)
@@ -789,6 +787,8 @@ main(int argc, char **argv)
       goto mpd_fail;
     }
   mdns_no_mpd = 0;
+#else
+  mdns_no_mpd = 1;
 #endif
 
   /* Start Remote pairing service */
@@ -809,7 +809,10 @@ main(int argc, char **argv)
       goto mdns_reg_fail;
     }
 
-#if defined(__linux__)
+  /* Register this CNAME with mDNS for OAuth */
+  mdns_cname("forked-daapd.local");
+
+#ifdef HAVE_SIGNALFD
   /* Set up signal fd */
   sigfd = signalfd(-1, &sigs, SFD_NONBLOCK | SFD_CLOEXEC);
   if (sigfd < 0)
@@ -821,7 +824,7 @@ main(int argc, char **argv)
     }
 
   sig_event = event_new(evbase_main, sigfd, EV_READ, signal_signalfd_cb, NULL);
-#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
+#else
   sigfd = kqueue();
   if (sigfd < 0)
     {
@@ -891,14 +894,10 @@ main(int argc, char **argv)
   player_deinit();
 
  player_fail:
-#ifdef HAVE_SPOTIFY_H
-  DPRINTF(E_LOG, L_MAIN, "Spotify deinit\n");
-  spotify_deinit();
-#endif
-  DPRINTF(E_LOG, L_MAIN, "File scanner deinit\n");
-  filescanner_deinit();
+  DPRINTF(E_LOG, L_MAIN, "Library scaner deinit\n");
+  library_deinit();
 
- filescanner_fail:
+ library_fail:
   DPRINTF(E_LOG, L_MAIN, "Cache deinit\n");
   cache_deinit();
 
@@ -935,7 +934,7 @@ main(int argc, char **argv)
 
  signal_block_fail:
  gcrypt_init_fail:
-#ifdef LASTFM
+#ifdef HAVE_LIBCURL
   curl_global_cleanup();
 #endif
 #if LIBAVFORMAT_VERSION_MAJOR >= 54 || (LIBAVFORMAT_VERSION_MAJOR == 53 && LIBAVFORMAT_VERSION_MINOR >= 13)
diff --git a/src/mdns.h b/src/mdns.h
index 656968b..cfc8ea6 100644
--- a/src/mdns.h
+++ b/src/mdns.h
@@ -38,6 +38,16 @@ int
 mdns_register(char *name, char *type, int port, char **txt);
 
 /*
+ * Register a CNAME record, it will be an alias for hostname
+ * Call only from the main thread!
+ *
+ * @in  name     The CNAME alias, e.g. "forked-daapd.local"
+ * @return       0 on success, -1 on error
+ */
+int
+mdns_cname(char *name);
+
+/*
  * Start a service browser, a callback will be made when the service changes state
  * Call only from the main thread!
  *
diff --git a/src/mdns_avahi.c b/src/mdns_avahi.c
index b1cd1cb..15154a7 100644
--- a/src/mdns_avahi.c
+++ b/src/mdns_avahi.c
@@ -33,6 +33,7 @@
 #include <netinet/in.h>
 #include <arpa/inet.h>
 #include <net/if.h>
+#include <unistd.h>
 
 #include <event2/event.h>
 
@@ -43,6 +44,12 @@
 #include <avahi-client/publish.h>
 #include <avahi-client/lookup.h>
 
+// Hack for FreeBSD, don't want to bother with sysconf()
+#ifndef HOST_NAME_MAX
+# include <limits.h>
+# define HOST_NAME_MAX _POSIX_HOST_NAME_MAX
+#endif
+
 #include "logger.h"
 #include "mdns.h"
 
@@ -351,8 +358,24 @@ struct mdns_record_browser {
   int port;
 };
 
+struct mdns_resolver
+{
+  char *name;
+  AvahiServiceResolver *resolver;
+  AvahiProtocol proto;
+
+  struct mdns_resolver *next;
+};
+
+enum publish
+{
+  MDNS_PUBLISH_SERVICE,
+  MDNS_PUBLISH_CNAME,
+};
+
 struct mdns_group_entry
 {
+  enum publish publish;
   char *name;
   char *type;
   int port;
@@ -362,6 +385,7 @@ struct mdns_group_entry
 };
 
 static struct mdns_browser *browser_list;
+static struct mdns_resolver *resolver_list;
 static struct mdns_group_entry *group_entries;
 
 #define IPV4LL_NETWORK 0xA9FE0000
@@ -416,6 +440,36 @@ avahi_address_make(AvahiAddress *addr, AvahiProtocol proto, const void *rdata, s
   return -1;
 }
 
+// Frees all resolvers for a given service name
+static void
+resolvers_cleanup(const char *name, AvahiProtocol proto)
+{
+  struct mdns_resolver *r;
+  struct mdns_resolver *prev;
+  struct mdns_resolver *next;
+
+  prev = NULL;
+  for (r = resolver_list; r; r = next)
+    {
+      next = r->next;
+
+      if ((strcmp(name, r->name) != 0) || (proto != r->proto))
+	{
+	  prev = r;
+	  continue;
+	}
+
+      if (!prev)
+	resolver_list = r->next;
+      else
+	prev->next = r->next;
+
+      avahi_service_resolver_free(r->resolver);
+      free(r->name);
+      free(r);
+    }
+}
+
 static void
 browse_record_callback(AvahiRecordBrowser *b, AvahiIfIndex intf, AvahiProtocol proto,
                        AvahiBrowserEvent event, const char *hostname, uint16_t clazz, uint16_t type,
@@ -478,33 +532,40 @@ browse_resolve_callback(AvahiServiceResolver *r, AvahiIfIndex intf, AvahiProtoco
 			uint16_t port, AvahiStringList *txt, AvahiLookupResultFlags flags, void *userdata)
 {
   AvahiRecordBrowser *rb;
+  struct mdns_browser *mb;
   struct mdns_record_browser *rb_data;
   char *key;
   char *value;
   uint16_t dns_type;
+  int family;
   int ret;
 
-  if (event == AVAHI_RESOLVER_FAILURE)
-    {
-      DPRINTF(E_LOG, L_MDNS, "Avahi Resolver failure: service '%s' type '%s': %s\n", name, type, MDNSERR);
-      goto out_free_resolver;
-    }
-  else if (event != AVAHI_RESOLVER_FOUND)
+  mb = (struct mdns_browser *)userdata;
+
+  if (event != AVAHI_RESOLVER_FOUND)
     {
-      DPRINTF(E_LOG, L_MDNS, "Avahi Resolver empty callback\n");
-      goto out_free_resolver;
+      if (event == AVAHI_RESOLVER_FAILURE)
+	DPRINTF(E_LOG, L_MDNS, "Avahi Resolver failure: service '%s' type '%s' proto %d: %s\n", name, type, proto, MDNSERR);
+      else
+	DPRINTF(E_LOG, L_MDNS, "Avahi Resolver empty callback\n");
+
+      family = avahi_proto_to_af(proto);
+      if (family != AF_UNSPEC)
+	mb->cb(name, type, domain, NULL, family, NULL, -1, NULL);
+
+      // We don't clean up resolvers because we want a notification from them if
+      // the service reappears (e.g. if device was switched off and then on)
+
+      return;
     }
 
   DPRINTF(E_DBG, L_MDNS, "Avahi Resolver: resolved service '%s' type '%s' proto %d, host %s\n", name, type, proto, hostname);
 
-  rb_data = calloc(1, sizeof(struct mdns_record_browser));
-  if (! (rb_data && (rb_data->name = strdup(name)) && (rb_data->domain = strdup(domain))) )
-    {
-      DPRINTF(E_LOG, L_MDNS, "Out of memory\n");
-      goto out_free_resolver;
-    }
+  CHECK_NULL(L_MDNS, rb_data = calloc(1, sizeof(struct mdns_record_browser)));
 
-  rb_data->mb = (struct mdns_browser *)userdata;
+  rb_data->name = strdup(name);
+  rb_data->domain = strdup(domain);
+  rb_data->mb = mb;
   rb_data->port = port;
 
   while (txt)
@@ -536,9 +597,6 @@ browse_resolve_callback(AvahiServiceResolver *r, AvahiIfIndex intf, AvahiProtoco
   rb = avahi_record_browser_new(mdns_client, intf, proto, hostname, AVAHI_DNS_CLASS_IN, dns_type, 0, browse_record_callback, rb_data);
   if (!rb)
     DPRINTF(E_LOG, L_MDNS, "Could not create record browser for host %s: %s\n", hostname, MDNSERR);
-
- out_free_resolver:
-  avahi_service_resolver_free(r);
 }
 
 static void
@@ -546,7 +604,7 @@ browse_callback(AvahiServiceBrowser *b, AvahiIfIndex intf, AvahiProtocol proto,
 		const char *name, const char *type, const char *domain, AvahiLookupResultFlags flags, void *userdata)
 {
   struct mdns_browser *mb;
-  AvahiServiceResolver *res;
+  struct mdns_resolver *r;
   int family;
 
   mb = (struct mdns_browser *)userdata;
@@ -560,16 +618,30 @@ browse_callback(AvahiServiceBrowser *b, AvahiIfIndex intf, AvahiProtocol proto,
 
 	b = avahi_service_browser_new(mdns_client, AVAHI_IF_UNSPEC, mb->protocol, mb->type, NULL, 0, browse_callback, mb);
 	if (!b)
-	  DPRINTF(E_LOG, L_MDNS, "Failed to recreate service browser (service type %s): %s\n", mb->type, MDNSERR);
+	  {
+	    DPRINTF(E_LOG, L_MDNS, "Failed to recreate service browser (service type %s): %s\n", mb->type, MDNSERR);
+	    return;
+	  }
 
-	return;
+	break;
 
       case AVAHI_BROWSER_NEW:
 	DPRINTF(E_DBG, L_MDNS, "Avahi Browser: NEW service '%s' type '%s' proto %d\n", name, type, proto);
 
-	res = avahi_service_resolver_new(mdns_client, intf, proto, name, type, domain, proto, 0, browse_resolve_callback, mb);
-	if (!res)
-	  DPRINTF(E_LOG, L_MDNS, "Failed to create service resolver: %s\n", MDNSERR);
+	CHECK_NULL(L_MDNS, r = calloc(1, sizeof(struct mdns_resolver)));
+
+	r->resolver = avahi_service_resolver_new(mdns_client, intf, proto, name, type, domain, proto, 0, browse_resolve_callback, mb);
+	if (!r->resolver)
+	  {
+	    DPRINTF(E_LOG, L_MDNS, "Failed to create service resolver: %s\n", MDNSERR);
+	    free(r);
+	    return;
+	  }
+
+	r->name = strdup(name);
+	r->proto = proto;
+	r->next = resolver_list;
+	resolver_list = r;
 
 	break;
 
@@ -579,6 +651,9 @@ browse_callback(AvahiServiceBrowser *b, AvahiIfIndex intf, AvahiProtocol proto,
 	family = avahi_proto_to_af(proto);
 	if (family != AF_UNSPEC)
 	  mb->cb(name, type, domain, NULL, family, NULL, -1, NULL);
+
+	resolvers_cleanup(name, proto);
+
 	break;
 
       case AVAHI_BROWSER_ALL_FOR_NOW:
@@ -620,13 +695,101 @@ entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, AVAHI_GCC_U
     }
 }
 
-static void
-_create_services(void)
+static int
+create_group_entry(struct mdns_group_entry *ge, int commit)
 {
-  struct mdns_group_entry *pentry;
+  char hostname[HOST_NAME_MAX + 1];
+  char rdata[HOST_NAME_MAX + 6 + 1]; // Includes room for ".local" and 0-terminator
+  int count;
+  int i;
   int ret;
 
-  DPRINTF(E_DBG, L_MDNS, "Creating service group\n");
+  if (!mdns_group)
+    {
+      mdns_group = avahi_entry_group_new(mdns_client, entry_group_callback, NULL);
+      if (!mdns_group)
+	{
+	  DPRINTF(E_WARN, L_MDNS, "Could not create Avahi EntryGroup: %s\n", MDNSERR);
+	  return -1;
+	}
+    }
+
+  if (ge->publish == MDNS_PUBLISH_SERVICE)
+    {
+      DPRINTF(E_DBG, L_MDNS, "Adding service %s/%s\n", ge->name, ge->type);
+
+      ret = avahi_entry_group_add_service_strlst(mdns_group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0,
+						 ge->name, ge->type,
+						 NULL, NULL, ge->port, ge->txt);
+      if (ret < 0)
+	{
+	  DPRINTF(E_LOG, L_MDNS, "Could not add mDNS service %s/%s: %s\n", ge->name, ge->type, avahi_strerror(ret));
+	  return -1;
+	}
+    }
+  else if (ge->publish == MDNS_PUBLISH_CNAME)
+    {
+      DPRINTF(E_DBG, L_MDNS, "Adding CNAME record %s\n", ge->name);
+
+      ret = gethostname(hostname, HOST_NAME_MAX);
+      if (ret < 0)
+	{
+	  DPRINTF(E_LOG, L_MDNS, "Could not add CNAME %s, gethostname failed\n", ge->name);
+	  return -1;
+	}
+      // Note, gethostname does not guarantee 0-termination
+      hostname[HOST_NAME_MAX] = 0;
+
+      ret = snprintf(rdata, sizeof(rdata), ".%s.local", hostname);
+      if (!(ret > 0 && ret < sizeof(rdata)))
+        {
+	  DPRINTF(E_LOG, L_MDNS, "Could not add CNAME %s, hostname is invalid\n", ge->name);
+	  return -1;
+        }
+
+      // Convert to dns string: .forked-daapd.local -> \12forked-daapd\6local
+      count = 0;
+      for (i = ret - 1; i >= 0; i--)
+        {
+	  if (rdata[i] == '.')
+	    {
+	      rdata[i] = count;
+	      count = 0;
+	    }
+	  else
+	    count++;
+        }
+
+      // ret + 1 should be the string length of rdata incl. 0-terminator
+      ret = avahi_entry_group_add_record(mdns_group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
+                                         AVAHI_PUBLISH_USE_MULTICAST | AVAHI_PUBLISH_ALLOW_MULTIPLE,
+                                         ge->name, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_CNAME,
+                                         AVAHI_DEFAULT_TTL, rdata, ret + 1);
+      if (ret < 0)
+	{
+	  DPRINTF(E_LOG, L_MDNS, "Could not add CNAME record %s: %s\n", ge->name, avahi_strerror(ret));
+	  return -1;
+	}
+    }
+
+  if (!commit)
+    return 0;
+
+  ret = avahi_entry_group_commit(mdns_group);
+  if (ret < 0)
+    {
+      DPRINTF(E_LOG, L_MDNS, "Could not commit mDNS services: %s\n", MDNSERR);
+      return -1;
+    }
+
+  return 0;
+}
+
+static void
+create_all_group_entries(void)
+{
+  struct mdns_group_entry *ge;
+  int ret;
 
   if (!group_entries)
     {
@@ -634,36 +797,21 @@ _create_services(void)
       return;
     }
 
-    if (mdns_group == NULL)
-      {
-        mdns_group = avahi_entry_group_new(mdns_client, entry_group_callback, NULL);
-	if (!mdns_group)
-	  {
-            DPRINTF(E_WARN, L_MDNS, "Could not create Avahi EntryGroup: %s\n", MDNSERR);
-            return;
-	  }
-      }
-
-    pentry = group_entries;
-    while (pentry)
-      {
-        DPRINTF(E_DBG, L_MDNS, "Re-registering %s/%s\n", pentry->name, pentry->type);
+  if (mdns_group)
+    avahi_entry_group_reset(mdns_group);
 
-        ret = avahi_entry_group_add_service_strlst(mdns_group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0,
-						   pentry->name, pentry->type,
-						   NULL, NULL, pentry->port, pentry->txt);
-	if (ret < 0)
-	  {
-	    DPRINTF(E_WARN, L_MDNS, "Could not add mDNS services: %s\n", avahi_strerror(ret));
-	    return;
-	  }
+  DPRINTF(E_INFO, L_MDNS, "Re-registering mDNS groups (services and records)\n");
 
-	pentry = pentry->next;
-      }
+  for (ge = group_entries; ge; ge = ge->next)
+    {
+      create_group_entry(ge, 0);
+      if (!mdns_group)
+	return;
+    }
 
-    ret = avahi_entry_group_commit(mdns_group);
-    if (ret < 0)
-      DPRINTF(E_WARN, L_MDNS, "Could not commit mDNS services: %s\n", MDNSERR);
+  ret = avahi_entry_group_commit(mdns_group);
+  if (ret < 0)
+    DPRINTF(E_WARN, L_MDNS, "Could not commit mDNS services: %s\n", MDNSERR);
 }
 
 static void
@@ -678,7 +826,7 @@ client_callback(AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void *
       case AVAHI_CLIENT_S_RUNNING:
         DPRINTF(E_LOG, L_MDNS, "Avahi state change: Client running\n");
         if (!mdns_group)
-	  _create_services();
+	  create_all_group_entries();
 
 	for (mb = browser_list; mb; mb = mb->next)
 	  {
@@ -806,8 +954,6 @@ mdns_register(char *name, char *type, int port, char **txt)
   AvahiStringList *txt_sl;
   int i;
 
-  DPRINTF(E_DBG, L_MDNS, "Adding mDNS service %s/%s\n", name, type);
-
   ge = calloc(1, sizeof(struct mdns_group_entry));
   if (!ge)
     {
@@ -815,6 +961,7 @@ mdns_register(char *name, char *type, int port, char **txt)
       return -1;
     }
 
+  ge->publish = MDNS_PUBLISH_SERVICE;
   ge->name = strdup(name);
   ge->type = strdup(type);
   ge->port = port;
@@ -835,14 +982,30 @@ mdns_register(char *name, char *type, int port, char **txt)
   ge->next = group_entries;
   group_entries = ge;
 
-  if (mdns_group)
+  create_all_group_entries(); // TODO why is this required?
+
+  return 0;
+}
+
+int
+mdns_cname(char *name)
+{
+  struct mdns_group_entry *ge;
+
+  ge = calloc(1, sizeof(struct mdns_group_entry));
+  if (!ge)
     {
-      DPRINTF(E_DBG, L_MDNS, "Resetting mDNS group\n");
-      avahi_entry_group_reset(mdns_group);
+      DPRINTF(E_LOG, L_MDNS, "Out of memory for mDNS CNAME\n");
+      return -1;
     }
 
-  DPRINTF(E_DBG, L_MDNS, "Creating service group\n");
-  _create_services();
+  ge->publish = MDNS_PUBLISH_CNAME;
+  ge->name = strdup(name);
+
+  ge->next = group_entries;
+  group_entries = ge;
+
+  create_all_group_entries();
 
   return 0;
 }
diff --git a/src/mdns_dnssd.c b/src/mdns_dnssd.c
new file mode 100644
index 0000000..2b2d5b4
--- /dev/null
+++ b/src/mdns_dnssd.c
@@ -0,0 +1,926 @@
+/*
+ * Bonjour mDNS backend, with libevent polling
+ *
+ * Copyright (c) Scott Shambarger <devel at shambarger.net>
+ * Copyright (C) 2009-2011 Julien BLACHE <jb at jblache.org>
+ * Copyright (C) 2005 Sebastian Dr�ge <slomo at ubuntu.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include "mdns.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <unistd.h>
+
+#include <event2/event.h>
+
+#include <dns_sd.h>
+
+/* timeout for service resolves */
+#define MDNS_RESOLVE_TIMEOUT_SECS 5
+
+// Hack for FreeBSD, don't want to bother with sysconf()
+#ifndef HOST_NAME_MAX
+# include <limits.h>
+# define HOST_NAME_MAX _POSIX_HOST_NAME_MAX
+#endif
+
+#include "logger.h"
+
+/* Main event base, from main.c */
+extern struct event_base *evbase_main;
+
+static DNSServiceRef mdns_sdref_main;
+static struct event *mdns_ev_main;
+
+/* registered services last the life of the program */
+struct mdns_service {
+  struct mdns_service *next;
+  /* allocated */
+  DNSServiceRef sdref;
+  TXTRecordRef txtRecord;
+};
+
+static struct mdns_service *mdns_services = NULL;
+
+/* we keep records forever to display names in logs when
+   registered or renamed */
+struct mdns_record {
+  struct mdns_record *next;
+  /* allocated */
+  char *name;
+  /* references */
+  DNSRecordRef recRef;
+  uint16_t rrtype;
+};
+
+static struct mdns_record *mdns_records = NULL;
+
+struct mdns_addr_lookup {
+  struct mdns_addr_lookup *next;
+  /* allocated */
+  DNSServiceRef sdref;
+  struct keyval txt_kv;
+  /* references */
+  u_int16_t port;
+  struct mdns_resolver *rs;
+};
+
+/* resolvers and address lookups clean themselves up */
+struct mdns_resolver
+{
+  struct mdns_resolver *next;
+  /* allocated */
+  DNSServiceRef sdref;
+  char *service;
+  char *regtype;
+  char *domain;
+  struct event *timer;
+  struct mdns_addr_lookup *lookups;
+  /* references */
+  void *uuid;
+  uint32_t interface;
+  struct mdns_browser *mb;
+};
+
+/* browsers keep running for the life of the program */
+struct mdns_browser
+{
+  struct mdns_browser *next;
+  /* allocated */
+  DNSServiceRef sdref;
+  struct mdns_resolver *resolvers;
+  char *regtype;
+  /* references */
+  mdns_browse_cb cb;
+  DNSServiceProtocol protocol;
+  void *res_uuid;
+};
+
+static struct mdns_browser *mdns_browsers = NULL;
+
+#define IPV4LL_NETWORK 0xA9FE0000
+#define IPV4LL_NETMASK 0xFFFF0000
+#define IPV6LL_NETWORK 0xFE80
+#define IPV6LL_NETMASK 0xFFC0
+
+static int
+is_v4ll(struct in_addr *addr)
+{
+  return ((ntohl(addr->s_addr) & IPV4LL_NETMASK) == IPV4LL_NETWORK);
+}
+
+static int
+is_v6ll(struct in6_addr *addr)
+{
+  return ((((addr->s6_addr[0] << 8) | addr->s6_addr[1]) & IPV6LL_NETMASK)
+          == IPV6LL_NETWORK);
+}
+
+/* mDNS interface - to be called only from the main thread */
+
+static int
+mdns_service_free(struct mdns_service *s)
+{
+  if(! s)
+    return -1;
+
+  /* free sdref, then everything else */
+  if(s->sdref)
+    DNSServiceRefDeallocate(s->sdref);
+  TXTRecordDeallocate(&(s->txtRecord));
+  free(s);
+
+  return -1;
+}
+
+static int
+mdns_addr_lookup_free(struct mdns_addr_lookup *lu)
+{
+  if (! lu)
+    return -1;
+
+  if(lu->sdref)
+    DNSServiceRefDeallocate(lu->sdref);
+  keyval_clear(&lu->txt_kv);
+  free(lu);
+
+  return -1;
+}
+
+static int
+mdns_resolver_free(struct mdns_resolver *rs) {
+
+  struct mdns_addr_lookup *lu;
+
+  if (! rs)
+    return -1;
+
+  /* free/cancel all lookups */
+  for(lu = rs->lookups; lu; lu = rs->lookups)
+    {
+      rs->lookups = lu->next;
+      mdns_addr_lookup_free(lu);
+    }
+
+  if(rs->timer)
+    event_free(rs->timer);
+  if(rs->sdref)
+    DNSServiceRefDeallocate(rs->sdref);
+  free(rs->service);
+  free(rs->regtype);
+  free(rs->domain);
+  free(rs);
+
+  return -1;
+}
+
+static int
+mdns_browser_free(struct mdns_browser *mb)
+{
+  struct mdns_resolver *rs;
+
+  if(! mb)
+    return -1;
+
+  /* free all resolvers */
+  for(rs = mb->resolvers; rs; rs = mb->resolvers)
+    {
+      mb->resolvers = rs->next;
+      mdns_resolver_free(rs);
+    }
+  /* free sdref, then everything else */
+  if(mb->sdref)
+    DNSServiceRefDeallocate(mb->sdref);
+  free(mb->regtype);
+  free(mb);
+
+  return -1;
+}
+
+static int
+mdns_record_free(struct mdns_record *r)
+{
+  if (! r)
+    return -1;
+
+  free(r->name);
+  free(r);
+
+  return -1;
+}
+
+static int
+mdns_main_free(void)
+{
+  struct mdns_service *s;
+  struct mdns_browser *mb;
+  struct mdns_record *r;
+
+  for(s = mdns_services; mdns_services; s = mdns_services)
+    {
+      mdns_services = s->next;
+      mdns_service_free(s);
+    }
+
+  for (mb = mdns_browsers; mdns_browsers; mb = mdns_browsers)
+    {
+      mdns_browsers = mb->next;
+      mdns_browser_free(mb);
+    }
+
+  for (r = mdns_records; mdns_records; r = mdns_records)
+    {
+      mdns_records = r->next;
+      mdns_record_free(r);
+    }
+
+  if(mdns_ev_main)
+    event_free(mdns_ev_main);
+  mdns_ev_main = NULL;
+  if(mdns_sdref_main)
+    DNSServiceRefDeallocate(mdns_sdref_main);
+  mdns_sdref_main = NULL;
+
+  return -1;
+}
+
+void
+mdns_deinit(void)
+{
+  mdns_main_free();
+}
+
+static void
+mdns_event_cb(evutil_socket_t fd, short flags, void *data) {
+
+  DNSServiceErrorType err;
+
+  err = DNSServiceProcessResult(mdns_sdref_main);
+
+  if (err != kDNSServiceErr_NoError)
+    DPRINTF(E_LOG, L_MDNS, "DNSServiceProcessResult error %d\n", err);
+}
+
+int
+mdns_init(void)
+{
+  DNSServiceErrorType err;
+  int fd;
+  int ret;
+
+  DPRINTF(E_DBG, L_MDNS, "Initializing DNS_SD mDNS\n");
+
+  mdns_services = NULL;
+  mdns_browsers = NULL;
+  mdns_records = NULL;
+  mdns_sdref_main = NULL;
+  mdns_ev_main = NULL;
+
+  err = DNSServiceCreateConnection(&mdns_sdref_main);
+  if (err != kDNSServiceErr_NoError)
+    {
+      DPRINTF(E_LOG, L_MDNS, "Could not create mDNS connection\n");
+      return -1;
+    }
+
+  fd = DNSServiceRefSockFD(mdns_sdref_main);
+  if (fd == -1) {
+      DPRINTF(E_LOG, L_MDNS, "DNSServiceRefSockFD failed\n");
+      return mdns_main_free();
+  }
+  mdns_ev_main = event_new(evbase_main, fd, EV_PERSIST | EV_READ,
+                           mdns_event_cb, NULL);
+  if (! mdns_ev_main)
+    {
+      DPRINTF(E_LOG, L_MDNS, "Could not make new event in mdns\n");
+      return mdns_main_free();
+    }
+
+  ret = event_add(mdns_ev_main, NULL);
+  if (ret != 0)
+    {
+      DPRINTF(E_LOG, L_MDNS, "Could not add new event in mdns\n");
+      return mdns_main_free();
+    }
+
+  return 0;
+}
+
+static void
+mdns_register_callback(DNSServiceRef sdRef, DNSServiceFlags flags,
+                       DNSServiceErrorType errorCode, const char *name,
+                       const char *regtype, const char *domain,
+                       void *context) {
+
+  switch (errorCode) {
+    case kDNSServiceErr_NoError:
+      DPRINTF(E_DBG, L_MDNS, "Successfully added mDNS service '%s.%s'\n",
+              name, regtype);
+      break;
+
+    case kDNSServiceErr_NameConflict:
+      DPRINTF(E_DBG, L_MDNS,
+              "Name collision for service '%s.%s' - automatically assigning new name\n",
+              name, regtype);
+      break;
+
+    case kDNSServiceErr_NoMemory:
+      DPRINTF(E_DBG, L_MDNS, "Out of memory registering service %s\n", name);
+      break;
+
+    default:
+      DPRINTF(E_DBG, L_MDNS, "Unspecified error registering service %s, error %d\n",
+              name, errorCode);
+  }
+}
+
+int
+mdns_register(char *name, char *regtype, int port, char **txt)
+{
+  struct mdns_service *s;
+  DNSServiceErrorType err;
+  int i;
+  char *eq;
+
+  DPRINTF(E_DBG, L_MDNS, "Adding mDNS service '%s.%s'\n", name, regtype);
+
+  s = calloc(1, sizeof(*s));
+  if (!s)
+    {
+      DPRINTF(E_LOG, L_MDNS, "Out of memory registering service.\n");
+      return -1;
+    }
+  TXTRecordCreate(&(s->txtRecord), 0, NULL);
+
+  for (i = 0; txt && txt[i]; i++)
+    {
+      if ((eq = strchr(txt[i], '=')))
+        {
+          *eq = '\0';
+          eq++;
+          err = TXTRecordSetValue(&(s->txtRecord), txt[i], strlen(eq) * sizeof(char), eq);
+          *(--eq) = '=';
+          if (err != kDNSServiceErr_NoError)
+            {
+              DPRINTF(E_LOG, L_MDNS, "Could not set TXT record value\n");
+              return mdns_service_free(s);
+            }
+        }
+    }
+
+  s->sdref = mdns_sdref_main;
+  err = DNSServiceRegister(&(s->sdref), kDNSServiceFlagsShareConnection, 0,
+                           name, regtype, NULL, NULL, htons(port),
+                           TXTRecordGetLength(&(s->txtRecord)),
+                           TXTRecordGetBytesPtr(&(s->txtRecord)),
+                           mdns_register_callback, NULL);
+
+  if (err != kDNSServiceErr_NoError)
+    {
+      DPRINTF(E_LOG, L_MDNS, "Error registering service '%s.%s'\n",
+              name, regtype);
+      s->sdref = NULL;
+      return mdns_service_free(s);
+    }
+
+  s->next = mdns_services;
+  mdns_services = s;
+
+  return 0;
+}
+
+static void
+mdns_record_callback(DNSServiceRef sdRef, DNSRecordRef RecordRef,
+                     DNSServiceFlags flags, DNSServiceErrorType errorCode,
+                     void *context)
+{
+  struct mdns_record *r;
+
+  r = context;
+
+  switch (errorCode) {
+    case kDNSServiceErr_NoError:
+      DPRINTF(E_DBG, L_MDNS, "Successfully added mDNS record %s\n", r->name);
+      break;
+
+    case kDNSServiceErr_NameConflict:
+      DPRINTF(E_DBG, L_MDNS, "Record ame collision - automatically assigning new name\n");
+      break;
+
+    case kDNSServiceErr_NoMemory:
+      DPRINTF(E_DBG, L_MDNS, "Out of memory registering record %s\n", r->name);
+      break;
+
+    default:
+      DPRINTF(E_DBG, L_MDNS, "Unspecified error registering record %s, error %d\n",
+              r->name, errorCode);
+  }
+
+}
+
+static int
+mdns_register_record(uint16_t rrtype, const char *name, uint16_t rdlen,
+                     const void *rdata)
+{
+  struct mdns_record *r;
+  DNSServiceErrorType err;
+
+  DPRINTF(E_DBG, L_MDNS, "Adding mDNS record %s/%u\n", name, rrtype);
+
+  r = calloc(1, sizeof(*r));
+  if (!r)
+    {
+      DPRINTF(E_LOG, L_MDNS, "Out of memory adding record.\n");
+      return -1;
+    }
+  r->name = strdup(name);
+  if (!(r->name))
+    {
+      DPRINTF(E_LOG, L_MDNS, "Out of memory adding record.\n");
+      return mdns_record_free(r);
+    }
+
+  r->rrtype = rrtype;
+
+  err = DNSServiceRegisterRecord(mdns_sdref_main, &r->recRef,
+                                 kDNSServiceFlagsShared, 0, r->name,
+                                 r->rrtype, kDNSServiceClass_IN, rdlen, rdata,
+                                 0, mdns_record_callback, r);
+
+  if (err != kDNSServiceErr_NoError)
+    {
+      DPRINTF(E_LOG, L_MDNS, "Error registering record %s, error %d\n", name,
+              err);
+      return mdns_record_free(r);
+    }
+
+  /* keep these around so we can display r->name in the callback */
+  r->next = mdns_records;
+  mdns_records = r;
+
+  return 0;
+}
+
+int
+mdns_cname(char *name)
+{
+  char hostname[HOST_NAME_MAX + 1];
+  // Includes room for "..local" and 0-terminator
+  char rdata[HOST_NAME_MAX + 8];
+  int count;
+  int i;
+  int ret;
+
+  ret = gethostname(hostname, HOST_NAME_MAX);
+  if (ret < 0)
+    {
+      DPRINTF(E_LOG, L_MDNS, "Could not add CNAME %s, gethostname failed\n",
+              name);
+      return -1;
+    }
+  // Note, gethostname does not guarantee 0-termination
+  hostname[HOST_NAME_MAX] = 0;
+
+  ret = snprintf(rdata, sizeof(rdata), ".%s.local", hostname);
+  if (!(ret > 0 && ret < sizeof(rdata)))
+    {
+      DPRINTF(E_LOG, L_MDNS, "Could not add CNAME %s, hostname is invalid\n",
+              name);
+      return -1;
+    }
+
+  // Convert to dns string: .forked-daapd.local -> \12forked-daapd\6local
+  count = 0;
+  for (i = ret - 1; i >= 0; i--)
+    {
+      if (rdata[i] == '.')
+        {
+          rdata[i] = count;
+          count = 0;
+        }
+      else
+        count++;
+    }
+
+  return mdns_register_record(kDNSServiceType_CNAME, name, (uint16_t)ret,
+                              rdata);
+}
+
+static void
+mdns_browse_call_cb(struct mdns_addr_lookup *lu, const char *hostname,
+                    const struct sockaddr *address)
+{
+  char addr_str[INET6_ADDRSTRLEN];
+
+  if (address->sa_family == AF_INET)
+    {
+      struct sockaddr_in *addr = (struct sockaddr_in *)address;
+
+      if (!inet_ntop(AF_INET, &addr->sin_addr, addr_str, sizeof(addr_str)))
+        {
+          DPRINTF(E_LOG, L_MDNS, "Could not print IPv4 address: %s\n",
+                  strerror(errno));
+          return;
+        }
+
+      if (!(lu->rs->mb->protocol & kDNSServiceProtocol_IPv4))
+        {
+          DPRINTF(E_DBG, L_MDNS,
+                  "Discarding IPv4, not interested (service %s)\n",
+                  lu->rs->service);
+          return;
+        }
+      else if (is_v4ll(&addr->sin_addr))
+        {
+          DPRINTF(E_WARN, L_MDNS,
+                  "Ignoring announcement from %s, address %s is link-local\n",
+                  hostname, addr_str);
+          return;
+        }
+
+    }
+  else if (address->sa_family == AF_INET6)
+    {
+      struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)address;
+
+      if (!inet_ntop(AF_INET6, &addr6->sin6_addr, addr_str, sizeof(addr_str)))
+        {
+          DPRINTF(E_LOG, L_MDNS, "Could not print IPv6 address: %s\n",
+                  strerror(errno));
+          return;
+        }
+
+      if (!(lu->rs->mb->protocol & kDNSServiceProtocol_IPv6))
+        {
+          DPRINTF(E_DBG, L_MDNS,
+                  "Discarding IPv6, not interested (service %s)\n",
+                  lu->rs->service);
+          return;
+        }
+      else if (is_v6ll(&addr6->sin6_addr))
+        {
+          DPRINTF(E_WARN, L_MDNS,
+                  "Ignoring announcement from %s, address %s is link-local\n",
+                  hostname, addr_str);
+          return;
+        }
+    }
+
+  DPRINTF(E_DBG, L_MDNS, "Service %s, hostname %s resolved to %s\n",
+          lu->rs->service, hostname, addr_str);
+
+  /* Execute callback (mb->cb) with all the data */
+  lu->rs->mb->cb(lu->rs->service, lu->rs->regtype, lu->rs->domain, hostname,
+                 address->sa_family, addr_str, lu->port, &lu->txt_kv);
+}
+
+static void
+mdns_lookup_callback(DNSServiceRef sdRef, DNSServiceFlags flags,
+                     uint32_t interfaceIndex, DNSServiceErrorType errorCode,
+                     const char *hostname, const struct sockaddr *address,
+                     uint32_t ttl, void *context)
+{
+  struct mdns_addr_lookup *lu;
+
+  lu = context;
+
+  if (errorCode != kDNSServiceErr_NoError )
+    {
+      DPRINTF(E_LOG, L_MDNS, "Error resolving hostname '%s', error %d\n",
+              hostname, errorCode);
+      return;
+    }
+
+  if (flags & kDNSServiceFlagsAdd)
+        mdns_browse_call_cb(lu, hostname, address);
+}
+
+static int
+mdns_addr_lookup_start(struct mdns_resolver *rs, uint32_t interfaceIndex,
+                       const char *hosttarget, uint16_t port, uint16_t txtLen,
+                       const unsigned char *txtRecord)
+{
+  struct mdns_addr_lookup *lu;
+  DNSServiceErrorType err;
+  char key[256];
+  int i;
+  uint8_t valueLen;
+  const char *value;
+  int ret;
+
+  lu = calloc(1, sizeof(*lu));
+  if (!lu)
+    {
+      DPRINTF(E_LOG, L_MDNS, "Out of memory creating address lookup.\n");
+      return -1;
+    }
+  lu->port = port;
+  lu->rs = rs;
+
+  for (i=0; TXTRecordGetItemAtIndex(txtLen, txtRecord, i, sizeof(key),
+                                    key, &valueLen, (const void **)&value)
+         != kDNSServiceErr_Invalid; i++)
+    {
+      ret = keyval_add_size(&lu->txt_kv, key, value, valueLen);
+      if (ret < 0)
+        {
+          DPRINTF(E_LOG, L_MDNS, "Could not build TXT record keyval\n");
+          return mdns_addr_lookup_free(lu);
+        }
+    }
+
+  lu->sdref = mdns_sdref_main;
+  err = DNSServiceGetAddrInfo(&lu->sdref, kDNSServiceFlagsShareConnection,
+                              interfaceIndex, rs->mb->protocol, hosttarget,
+                              mdns_lookup_callback, lu);
+  if (err != kDNSServiceErr_NoError)
+    {
+      DPRINTF(E_LOG, L_MDNS, "Failed to create service resolver.\n");
+      lu->sdref = NULL;
+      return mdns_addr_lookup_free(lu);
+    }
+
+  /* resolver now owns the lookup */
+  lu->next = rs->lookups;
+  rs->lookups = lu;
+
+  return 0;
+}
+
+static void
+mdns_resolver_remove(struct mdns_resolver *rs)
+{
+  struct mdns_resolver *cur;
+
+  /* remove from browser's resolver list */
+  if(rs->mb->resolvers == rs)
+    rs->mb->resolvers = rs->next;
+  else
+    {
+      for(cur = rs->mb->resolvers; cur; cur = cur->next)
+        if (cur->next == rs)
+          {
+            cur->next = rs->next;
+            break;
+          }
+    }
+
+  /* free resolver (which cancels resolve) */
+  mdns_resolver_free(rs);
+}
+
+static void
+mdns_resolve_timeout_cb(evutil_socket_t fd, short flags, void *uuid) {
+
+  struct mdns_browser *mb;
+  struct mdns_resolver *rs = NULL;
+
+  for(mb = mdns_browsers; mb && !rs; mb = mb->next)
+    for(rs = mb->resolvers; rs; rs = rs->next)
+      if(rs->uuid == uuid)
+        {
+          DPRINTF(E_DBG, L_MDNS,
+                  "Resolve finished for '%s' type '%s' interface %d\n",
+                  rs->service, rs->regtype, rs->interface);
+          mdns_resolver_remove(rs);
+          break;
+        }
+}
+
+static void
+mdns_resolve_callback(DNSServiceRef sdRef, DNSServiceFlags flags,
+                      uint32_t interfaceIndex, DNSServiceErrorType errorCode,
+                      const char *fullname, const char *hosttarget,
+                      uint16_t port, uint16_t txtLen,
+                      const unsigned char *txtRecord, void *context)
+{
+  struct mdns_resolver *rs;
+
+  rs = context;
+
+  /* convert port to host order */
+  port = ntohs(port);
+
+  if (errorCode != kDNSServiceErr_NoError)
+    {
+      DPRINTF(E_LOG, L_MDNS, "Error resolving service '%s', error %d\n",
+              rs->service, errorCode);
+    }
+  else
+    {
+      DPRINTF(E_DBG, L_MDNS, "Bonjour resolved '%s' as '%s:%u' on interface %d\n",
+              fullname, hosttarget, port, interfaceIndex);
+
+      mdns_addr_lookup_start(rs, interfaceIndex, hosttarget, port,
+                             txtLen, txtRecord);
+    }
+}
+
+static int
+mdns_resolve_start(struct mdns_browser *mb, uint32_t interfaceIndex,
+                   const char *serviceName, const char *regtype,
+                   const char *replyDomain)
+{
+  DNSServiceErrorType err;
+  struct mdns_resolver *rs;
+  struct timeval tv;
+
+  rs = calloc(1, sizeof(*rs));
+  if (!rs)
+    {
+      DPRINTF(E_LOG, L_MDNS, "Out of memory creating service resolver.\n");
+      return -1;
+    }
+
+  rs->service = strdup(serviceName);
+  if (!rs->service)
+    {
+      DPRINTF(E_LOG, L_MDNS, "Out of memory creating service resolver.\n");
+      return mdns_resolver_free(rs);
+    }
+  rs->regtype = strdup(regtype);
+  if (!rs->regtype)
+    {
+      DPRINTF(E_LOG, L_MDNS, "Out of memory creating service resolver.\n");
+      return mdns_resolver_free(rs);
+    }
+  rs->domain = strdup(replyDomain);
+  if (!rs->domain)
+    {
+      DPRINTF(E_LOG, L_MDNS, "Out of memory creating service resolver.\n");
+      return mdns_resolver_free(rs);
+    }
+  rs->mb = mb;
+  rs->interface = interfaceIndex;
+  /* create a timer with a uuid, so we can search for resolver without
+     leaking */
+  rs->uuid = ++(mb->res_uuid);
+  rs->timer = evtimer_new(evbase_main, mdns_resolve_timeout_cb, rs->uuid);
+  if(! rs->timer)
+    {
+      DPRINTF(E_LOG, L_MDNS, "Out of memory creating service resolver timer.\n");
+      return mdns_resolver_free(rs);
+    }
+
+  rs->sdref = mdns_sdref_main;
+  err = DNSServiceResolve(&(rs->sdref), kDNSServiceFlagsShareConnection,
+                          interfaceIndex, serviceName, regtype, replyDomain,
+                          mdns_resolve_callback, rs);
+  if (err != kDNSServiceErr_NoError)
+    {
+      DPRINTF(E_LOG, L_MDNS, "Failed to create service resolver.\n");
+      rs->sdref = NULL;
+      return mdns_resolver_free(rs);
+    }
+
+  /* add to browser's resolvers */
+  rs->next = mb->resolvers;
+  mb->resolvers = rs;
+
+  /* setup a timeout to cancel the resolve */
+  tv.tv_sec = MDNS_RESOLVE_TIMEOUT_SECS;
+  tv.tv_usec = 0;
+  evtimer_add(rs->timer, &tv);
+
+  return 0;
+}
+
+static void
+mdns_resolve_cancel(const struct mdns_browser *mb, uint32_t interfaceIndex,
+                    const char *serviceName, const char *regtype,
+                    const char *replyDomain) {
+
+  struct mdns_resolver *rs;
+
+  for(rs = mb->resolvers; rs; rs = rs->next)
+    {
+      if ((rs->interface == interfaceIndex)
+          && (! strcasecmp(rs->service, serviceName))
+          && (! strcmp(rs->regtype, regtype))
+          && (! strcasecmp(rs->domain, replyDomain)))
+        {
+          /* remove from resolvers, and free (which cancels resolve) */
+          DPRINTF(E_DBG, L_MDNS, "Cancelling resolve for '%s'\n", rs->service);
+          mdns_resolver_remove(rs);
+          break;
+        }
+    }
+
+  return;
+}
+
+static void
+mdns_browse_callback(DNSServiceRef sdRef, DNSServiceFlags flags,
+                     uint32_t interfaceIndex, DNSServiceErrorType errorCode,
+                     const char *serviceName, const char *regtype,
+                     const char *replyDomain, void *context)
+{
+  struct mdns_browser *mb;
+
+  if (errorCode != kDNSServiceErr_NoError)
+    {
+      // FIXME: if d/c, we sould recreate the browser?
+      DPRINTF(E_LOG, L_MDNS, "Bonjour browsing error %d\n", errorCode);
+      return;
+    }
+
+  mb = context;
+
+  if (flags & kDNSServiceFlagsAdd)
+    {
+      DPRINTF(E_DBG, L_MDNS,
+              "Bonjour Browser: NEW service '%s' type '%s' interface %d\n",
+              serviceName, regtype, interfaceIndex);
+      mdns_resolve_start(mb, interfaceIndex, serviceName, regtype,
+                         replyDomain);
+    }
+  else
+    {
+      DPRINTF(E_DBG, L_MDNS,
+              "Bonjour Browser: REMOVE service '%s' type '%s' interface %d\n",
+              serviceName, regtype, interfaceIndex);
+      mdns_resolve_cancel(mb, interfaceIndex, serviceName, regtype,
+                          replyDomain);
+      mb->cb(serviceName, regtype, replyDomain, NULL, 0, NULL, -1, NULL);
+    }
+}
+
+int
+mdns_browse(char *regtype, int family, mdns_browse_cb cb)
+{
+  struct mdns_browser *mb;
+  DNSServiceErrorType err;
+
+  DPRINTF(E_DBG, L_MDNS, "Adding service browser for type %s\n", regtype);
+
+  mb = calloc(1, sizeof(*mb));
+  if (!mb)
+    {
+      DPRINTF(E_LOG, L_MDNS, "Out of memory creating service browser.\n");
+      return -1;
+    }
+
+  mb->cb = cb;
+
+  /* flags are ignored in DNS-SD implementation */
+  switch(family) {
+  case AF_UNSPEC:
+    mb->protocol = kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6;
+    break;
+  case AF_INET:
+    mb->protocol = kDNSServiceProtocol_IPv4;
+    break;
+  case AF_INET6:
+    mb->protocol = kDNSServiceProtocol_IPv6;
+    break;
+  default:
+    DPRINTF(E_LOG, L_MDNS, "Unrecognized protocol family %d.\n", family);
+    return mdns_browser_free(mb);
+  }
+
+  mb->regtype = strdup(regtype);
+  if (!mb->regtype)
+    {
+      DPRINTF(E_LOG, L_MDNS, "Out of memory creating service browser.\n");
+      return mdns_browser_free(mb);
+    }
+  mb->sdref = mdns_sdref_main;
+  err = DNSServiceBrowse(&(mb->sdref), kDNSServiceFlagsShareConnection, 0,
+                         regtype, NULL, mdns_browse_callback, mb);
+  if (err != kDNSServiceErr_NoError)
+    {
+      DPRINTF(E_LOG, L_MDNS, "Failed to create service browser.\n");
+      mb->sdref = NULL;
+      return mdns_browser_free(mb);
+    }
+
+  mb->next = mdns_browsers;
+  mdns_browsers = mb;
+
+  return 0;
+}
diff --git a/src/misc.c b/src/misc.c
index 25f767f..aa1e77b 100644
--- a/src/misc.c
+++ b/src/misc.c
@@ -27,12 +27,17 @@
 #endif
 
 #include <stdlib.h>
+#include <unistd.h>
 #include <string.h>
 #include <ctype.h>
 #include <errno.h>
 #include <stdint.h>
+#include <stdio.h>
 #include <limits.h>
 #include <sys/param.h>
+#ifndef CLOCK_REALTIME
+#include <sys/time.h>
+#endif
 
 #include <unistr.h>
 #include <uniconv.h>
@@ -47,6 +52,8 @@ safe_atoi32(const char *str, int32_t *val)
   char *end;
   long intval;
 
+  *val = 0;
+
   errno = 0;
   intval = strtol(str, &end, 10);
 
@@ -83,6 +90,8 @@ safe_atou32(const char *str, uint32_t *val)
   char *end;
   unsigned long intval;
 
+  *val = 0;
+
   errno = 0;
   intval = strtoul(str, &end, 10);
 
@@ -119,6 +128,8 @@ safe_hextou32(const char *str, uint32_t *val)
   char *end;
   unsigned long intval;
 
+  *val = 0;
+
   /* A hex shall begin with 0x */
   if (strncmp(str, "0x", 2) != 0)
     return safe_atou32(str, val);
@@ -159,6 +170,8 @@ safe_atoi64(const char *str, int64_t *val)
   char *end;
   long long intval;
 
+  *val = 0;
+
   errno = 0;
   intval = strtoll(str, &end, 10);
 
@@ -195,6 +208,8 @@ safe_atou64(const char *str, uint64_t *val)
   char *end;
   unsigned long long intval;
 
+  *val = 0;
+
   errno = 0;
   intval = strtoull(str, &end, 10);
 
@@ -231,6 +246,8 @@ safe_hextou64(const char *str, uint64_t *val)
   char *end;
   unsigned long long intval;
 
+  *val = 0;
+
   errno = 0;
   intval = strtoull(str, &end, 16);
 
@@ -261,6 +278,36 @@ safe_hextou64(const char *str, uint64_t *val)
   return 0;
 }
 
+char *
+safe_strdup(const char *str)
+{
+  if (str == NULL)
+    return NULL;
+
+  return strdup(str);
+}
+
+/*
+ * Wrapper function for vasprintf by Intel Corporation
+ * Published under the L-GPL 2.1 licence as part of clr-boot-manager
+ *
+ * https://github.com/clearlinux/clr-boot-manager
+ */
+char *
+safe_asprintf(const char *fmt, ...)
+{
+  char *ret = NULL;
+  va_list va;
+  va_start(va, fmt);
+  if (vasprintf(&ret, fmt, va) < 0)
+    {
+      DPRINTF(E_FATAL, L_MISC, "Out of memory for safe_asprintf\n");
+      abort();
+    }
+  va_end(va);
+  return ret;
+}
+
 
 /* Key/value functions */
 struct keyval *
@@ -430,7 +477,7 @@ keyval_sort(struct keyval *kv)
   struct onekeyval *okv;
   struct onekeyval *sokv;
 
-  if (!kv)
+  if (!kv || !kv->head)
     return;
 
   head = kv->head;
@@ -486,6 +533,57 @@ m_realpath(const char *pathname)
   return ret;
 }
 
+char **
+m_readfile(const char *path, int num_lines)
+{
+  char buf[256];
+  FILE *fp;
+  char **lines;
+  char *line;
+  int i;
+
+  // Alloc array of char pointers
+  lines = calloc(num_lines, sizeof(char *));
+  if (!lines)
+    return NULL;
+
+  fp = fopen(path, "rb");
+  if (!fp)
+    {
+      DPRINTF(E_LOG, L_MISC, "Could not open file '%s' for reading: %s\n", path, strerror(errno));
+      free(lines);
+      return NULL;
+    }
+
+  for (i = 0; i < num_lines; i++)
+    {
+      line = fgets(buf, sizeof(buf), fp);
+      if (!line)
+	{
+	  DPRINTF(E_LOG, L_MISC, "File '%s' has fewer lines than expected (found %d, expected %d)\n", path, i, num_lines);
+	  goto error;
+	}
+
+      lines[i] = trimwhitespace(line);
+      if (!lines[i] || (strlen(lines[i]) == 0))
+	{
+	  DPRINTF(E_LOG, L_MISC, "Line %d in '%s' is invalid\n", i+1, path);
+	  goto error;
+	}
+    }
+
+  fclose(fp);
+
+  return lines;
+
+ error:
+  for (i = 0; i < num_lines; i++)
+    free(lines[i]);
+
+  free(lines);
+  fclose(fp);
+  return NULL;
+}
 
 char *
 unicode_fixup_string(char *str, const char *fromcode)
@@ -559,6 +657,14 @@ trimwhitespace(const char *str)
   return out;
 }
 
+void
+swap_pointers(char **a, char **b)
+{
+  char *t = *a;
+  *a = *b;
+  *b = t;
+}
+
 uint32_t
 djb_hash(const void *data, size_t len)
 {
@@ -775,17 +881,17 @@ murmur_hash64(const void *key, int len, uint32_t seed)
   switch (len & 7)
     {
       case 7:
-	h ^= (uint64_t)(data_tail[6]) << 48;
+	h ^= (uint64_t)(data_tail[6]) << 48; /* FALLTHROUGH */
       case 6:
-	h ^= (uint64_t)(data_tail[5]) << 40;
+	h ^= (uint64_t)(data_tail[5]) << 40; /* FALLTHROUGH */
       case 5:
-	h ^= (uint64_t)(data_tail[4]) << 32;
+	h ^= (uint64_t)(data_tail[4]) << 32; /* FALLTHROUGH */
       case 4:
-	h ^= (uint64_t)(data_tail[3]) << 24;
+	h ^= (uint64_t)(data_tail[3]) << 24; /* FALLTHROUGH */
       case 3:
-	h ^= (uint64_t)(data_tail[2]) << 16;
+	h ^= (uint64_t)(data_tail[2]) << 16; /* FALLTHROUGH */
       case 2:
-	h ^= (uint64_t)(data_tail[1]) << 8;
+	h ^= (uint64_t)(data_tail[1]) << 8; /* FALLTHROUGH */
       case 1:
 	h ^= (uint64_t)(data_tail[0]);
 	h *= m;
@@ -918,3 +1024,186 @@ timespec_cmp(struct timespec time1, struct timespec time2)
   else
     return 0;
 }
+
+struct timespec
+timespec_reltoabs(struct timespec relative)
+{
+  struct timespec absolute;
+
+#ifdef CLOCK_REALTIME
+  clock_gettime(CLOCK_REALTIME, &absolute);
+#else
+  struct timeval tv;
+  gettimeofday(&tv, NULL);
+  TIMEVAL_TO_TIMESPEC(&tv, &absolute);
+#endif
+  return timespec_add(absolute, relative);
+}
+
+#if defined(HAVE_MACH_CLOCK) || defined(HAVE_MACH_TIMER)
+
+#include <mach/mach_time.h> /* mach_absolute_time */
+#include <mach/mach.h>      /* host_get_clock_service */
+#include <mach/clock.h>     /* clock_get_time */
+
+/* mach monotonic clock port */
+extern mach_port_t clock_port;
+
+#ifndef HAVE_CLOCK_GETTIME
+
+int
+clock_gettime(clockid_t clock_id, struct timespec *tp)
+{
+  static int clock_init = 0;
+  static clock_serv_t clock;
+
+  mach_timespec_t mts;
+  int ret;
+
+  if (! clock_init) {
+    clock_init = 1;
+    if (host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &clock))
+      abort(); /* unlikely */
+  }
+
+  if(! tp)
+    return -1;
+
+  switch (clock_id) {
+
+  case CLOCK_REALTIME:
+
+    /* query mach for calendar time */
+    ret = clock_get_time(clock, &mts);
+    if (! ret) {
+      tp->tv_sec = mts.tv_sec;
+      tp->tv_nsec = mts.tv_nsec;
+    }
+    break;
+
+  case CLOCK_MONOTONIC:
+
+    /* query mach for monotinic time */
+    ret = clock_get_time(clock_port, &mts);
+    if (! ret) {
+      tp->tv_sec = mts.tv_sec;
+      tp->tv_nsec = mts.tv_nsec;
+    }
+    break;
+
+  default:
+    ret = -1;
+    break;
+  }
+
+  return ret;
+}
+
+int
+clock_getres(clockid_t clock_id, struct timespec *res)
+{
+  if (! res)
+    return -1;
+
+  /* hardcode ms resolution */
+  res->tv_sec = 0;
+  res->tv_nsec = 1000;
+
+  return 0;
+}
+
+#endif /* HAVE_CLOCK_GETTIME */
+
+#ifndef HAVE_TIMER_SETTIME
+
+#include <sys/time.h> /* ITIMER_REAL */
+
+int
+timer_create(clockid_t clock_id, void *sevp, timer_t *timer_id)
+{
+  if (clock_id != CLOCK_MONOTONIC)
+    return -1;
+  if (sevp)
+    return -1;
+
+  /* setitimer only supports one timer */
+  *timer_id = 0;
+
+  return 0;
+}
+
+int
+timer_delete(timer_t timer_id)
+{
+  struct itimerval timerval;
+
+  if (timer_id != 0)
+    return -1;
+
+  memset(&timerval, 0, sizeof(struct itimerval));
+
+  return setitimer(ITIMER_REAL, &timerval, NULL);
+}
+
+int
+timer_settime(timer_t timer_id, int flags, const struct itimerspec *tp, struct itimerspec *old)
+{
+  struct itimerval tv;
+
+  if (timer_id != 0 || ! tp || old)
+    return -1;
+
+  TIMESPEC_TO_TIMEVAL(&(tv.it_value), &(tp->it_value));
+  TIMESPEC_TO_TIMEVAL(&(tv.it_interval), &(tp->it_interval));
+
+  return setitimer(ITIMER_REAL, &tv, NULL);
+}
+
+int
+timer_getoverrun(timer_t timer_id)
+{
+  /* since we don't know if there have been signals that weren't delivered,
+     assume none */
+  return 0;
+}
+
+#endif /* HAVE_TIMER_SETTIME */
+
+#endif /* HAVE_MACH_CLOCK */
+
+int
+mutex_init(pthread_mutex_t *mutex)
+{
+  pthread_mutexattr_t mattr;
+  int err;
+
+  CHECK_ERR(L_MISC, pthread_mutexattr_init(&mattr));
+  CHECK_ERR(L_MISC, pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_ERRORCHECK));
+  err = pthread_mutex_init(mutex, &mattr);
+  CHECK_ERR(L_MISC, pthread_mutexattr_destroy(&mattr));
+
+  return err;
+}
+
+void
+log_fatal_err(int domain, const char *func, int line, int err)
+{
+  DPRINTF(E_FATAL, domain, "%s failed at line %d, error %d (%s)\n", func, line, err, strerror(err));
+  abort();
+}
+
+void
+log_fatal_errno(int domain, const char *func, int line)
+{
+  DPRINTF(E_FATAL, domain, "%s failed at line %d, error %d (%s)\n", func, line, errno, strerror(errno));
+  abort();
+}
+
+void
+log_fatal_null(int domain, const char *func, int line)
+{
+  DPRINTF(E_FATAL, domain, "%s returned NULL at line %d\n", func, line);
+  abort();
+}
+
+
diff --git a/src/misc.h b/src/misc.h
index 8a8d2ee..210748c 100644
--- a/src/misc.h
+++ b/src/misc.h
@@ -2,8 +2,18 @@
 #ifndef __MISC_H__
 #define __MISC_H__
 
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
 #include <stdint.h>
 #include <time.h>
+#include <pthread.h>
+
+/* Samples to bytes, bytes to samples */
+#define STOB(s) ((s) * 4)
+#define BTOS(b) ((b) / 4)
+
 
 struct onekeyval {
   char *name;
@@ -37,6 +47,12 @@ safe_atou64(const char *str, uint64_t *val);
 int
 safe_hextou64(const char *str, uint64_t *val);
 
+char *
+safe_strdup(const char *str);
+
+char *
+safe_asprintf(const char *fmt, ...);
+
 
 /* Key/value functions */
 struct keyval *
@@ -64,12 +80,18 @@ keyval_sort(struct keyval *kv);
 char *
 m_realpath(const char *pathname);
 
+char **
+m_readfile(const char *path, int num_lines);
+
 char *
 unicode_fixup_string(char *str, const char *fromcode);
 
 char *
 trimwhitespace(const char *str);
 
+void
+swap_pointers(char **a, char **b);
+
 uint32_t
 djb_hash(const void *data, size_t len);
 
@@ -82,6 +104,47 @@ b64_encode(const uint8_t *in, size_t len);
 uint64_t
 murmur_hash64(const void *key, int len, uint32_t seed);
 
+#ifndef HAVE_CLOCK_GETTIME
+
+#ifndef CLOCK_REALTIME
+#  define CLOCK_REALTIME 0
+#endif
+#ifndef CLOCK_MONOTONIC
+#  define CLOCK_MONOTONIC 1
+#endif
+
+typedef int clockid_t;
+
+int
+clock_gettime(clockid_t clock_id, struct timespec *tp);
+
+int
+clock_getres(clockid_t clock_id, struct timespec *res);
+#endif
+
+#ifndef HAVE_TIMER_SETTIME
+
+struct itimerspec {
+  struct timespec it_interval;
+  struct timespec it_value;
+};
+typedef uint64_t timer_t;
+
+int
+timer_create(clockid_t clock_id, void *sevp, timer_t *timer_id);
+
+int
+timer_delete(timer_t timer_id);
+
+int
+timer_settime(timer_t timer_id, int flags, const struct itimerspec *tp,
+              struct itimerspec *old);
+
+int
+timer_getoverrun(timer_t timer_id);
+
+#endif
+
 /* Timer function for platforms without hi-res timers */
 int
 clock_gettime_with_res(clockid_t clock_id, struct timespec *tp, struct timespec *res);
@@ -92,4 +155,57 @@ timespec_add(struct timespec time1, struct timespec time2);
 int
 timespec_cmp(struct timespec time1, struct timespec time2);
 
+struct timespec
+timespec_reltoabs(struct timespec relative);
+
+/* initialize mutex with error checking (not default on all platforms) */
+int
+mutex_init(pthread_mutex_t *mutex);
+
+/* Check that the function returns 0, logging a fatal error referencing
+   returned error (type errno) if it fails, and aborts the process.
+   Example: CHECK_ERR(L_MAIN, my_function()); */
+#define CHECK_ERR(d, f) \
+  do { int chk_err; \
+    if ( (chk_err = (f)) != 0) \
+      log_fatal_err(d, #f, __LINE__, chk_err); \
+  } while(0)
+
+/* Check that the function returns 0 or okval, logging a fatal
+   error referencing returned erro (type errno) if not, and aborts the process.
+   Example: int err; CHECK_ERR_EXCEPT(L_MAIN, my_wait(), err, ETIMEDOUT); */
+#define CHECK_ERR_EXCEPT(d, f, var, okval) \
+  do { (var) = (f);                             \
+    if (! (((var) == (okval)) || ((var) == 0))) \
+      log_fatal_err(d, #f, __LINE__, (var)); \
+  } while(0)
+
+/* Check that the function returns value >= 0, logging a fatal error
+   referencing errno if it not, and aborts the process.
+   Example: int ret; CHECK_ERRNO(L_MAIN, ret = my_function()); */
+#define CHECK_ERRNO(d, f) \
+  do { \
+    if ( (f) < 0 ) \
+      log_fatal_errno(d, #f, __LINE__); \
+  } while(0)
+
+/* Check that the function returns non-NULL, logging a fatal error if not,
+   and aborts the process.
+   Example: void *ptr; CHECK_NULL(L_MAIN, ptr = my_create()); */
+#define CHECK_NULL(d, f) \
+  do { \
+    if ( (f) == NULL ) \
+      log_fatal_null(d, #f, __LINE__); \
+  } while(0)
+
+/* Used by CHECK_*() macros */
+void
+log_fatal_err(int domain, const char *func, int line, int err) __attribute__((__noreturn__));
+
+void
+log_fatal_errno(int domain, const char *func, int line)        __attribute__((__noreturn__));
+
+void
+log_fatal_null(int domain, const char *func, int line)         __attribute__((__noreturn__));
+
 #endif /* !__MISC_H__ */
diff --git a/src/mpd.c b/src/mpd.c
index 5da1e20..e9dee23 100644
--- a/src/mpd.c
+++ b/src/mpd.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2009-2010 Julien BLACHE <jb at jblache.org>
+ * Copyright (C) 2016 Christian Meffert <christian.meffert at googlemail.com>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -43,27 +43,21 @@
 #include <event2/http.h>
 #include <event2/listener.h>
 
-#if defined(HAVE_SYS_EVENTFD_H) && defined(HAVE_EVENTFD)
-# define USE_EVENTFD
-# include <sys/eventfd.h>
-#endif
-
 #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
 # include <netinet/in.h>
 #endif
 
-#include "logger.h"
-#include "db.h"
+#include "artwork.h"
+#include "commands.h"
 #include "conffile.h"
+#include "db.h"
 #include "httpd.h"
-#include "misc.h"
+#include "library.h"
 #include "listener.h"
-#include "artwork.h"
-
+#include "logger.h"
+#include "misc.h"
 #include "player.h"
-#include "queue.h"
-#include "filescanner.h"
-#include "commands.h"
+#include "remote_pairing.h"
 
 
 static pthread_t tid_mpd;
@@ -396,18 +390,16 @@ mpd_parse_args(char *args, int *argc, char **argv)
  *   Id: 1
  *
  * @param evbuf the response event buffer
- * @param mfi media information
- * @param item_id queue-item id
- * @param pos_pl position in the playqueue, if -1 the position is ignored
+ * @param queue_item queue item information
  * @return the number of bytes added if successful, or -1 if an error occurred.
  */
 static int
-mpd_add_mediainfo(struct evbuffer *evbuf, struct media_file_info *mfi, unsigned int item_id, int pos_pl)
+mpd_add_db_queue_item(struct evbuffer *evbuf, struct db_queue_item *queue_item)
 {
   char modified[32];
   int ret;
 
-  mpd_time(modified, sizeof(modified), mfi->time_modified);
+  mpd_time(modified, sizeof(modified), queue_item->time_modified);
 
   ret = evbuffer_add_printf(evbuf,
     "file: %s\n"
@@ -422,63 +414,28 @@ mpd_add_mediainfo(struct evbuffer *evbuf, struct media_file_info *mfi, unsigned
     "Track: %d\n"
     "Date: %d\n"
     "Genre: %s\n"
-    "Disc: %d\n",
-    (mfi->virtual_path + 1),
+      "Disc: %d\n"
+      "Pos: %d\n"
+      "Id: %d\n",
+      (queue_item->virtual_path + 1),
     modified,
-    (mfi->song_length / 1000),
-    mfi->artist,
-    mfi->album_artist,
-    mfi->artist_sort,
-    mfi->album_artist_sort,
-    mfi->album,
-    mfi->title,
-    mfi->track,
-    mfi->year,
-    mfi->genre,
-    mfi->disc);
-
-  if (pos_pl >= 0)
-    {
-      ret = evbuffer_add_printf(evbuf,
-	"Pos: %d\n",
-	pos_pl);
-
-      ret = evbuffer_add_printf(evbuf,
-	"Id: %d\n",
-	item_id);
-    }
-
+      (queue_item->song_length / 1000),
+      queue_item->artist,
+      queue_item->album_artist,
+      queue_item->artist_sort,
+      queue_item->album_artist_sort,
+      queue_item->album,
+      queue_item->title,
+      queue_item->track,
+      queue_item->year,
+      queue_item->genre,
+      queue_item->disc,
+      queue_item->pos,
+      queue_item->id);
 
   return ret;
 }
 
-static int
-mpd_add_mediainfo_byid(struct evbuffer *evbuf, int id, unsigned int item_id, int pos_pl)
-{
-  struct media_file_info *mfi;
-  int ret;
-
-  mfi = db_file_fetch_byid(id);
-  if (!mfi)
-    {
-      DPRINTF(E_LOG, L_MPD, "Error fetching file by id: %d\n", id);
-      return -1;
-    }
-
-  ret = mpd_add_mediainfo(evbuf, mfi, item_id, pos_pl);
-  if (ret < 0)
-    {
-      DPRINTF(E_LOG, L_MPD, "Error adding media info for file with id: %d\n", id);
-
-      free_mfi(mfi, 0);
-
-      return -1;
-    }
-
-  free_mfi(mfi, 0);
-  return 0;
-}
-
 /*
  * Adds the informations (path, id, tags, etc.) for the given song to the given buffer.
  *
@@ -566,6 +523,7 @@ mpd_command_currentsong(struct evbuffer *evbuf, int argc, char **argv, char **er
 {
 
   struct player_status status;
+  struct db_queue_item *queue_item;
   int ret;
 
   player_get_status(&status);
@@ -576,13 +534,20 @@ mpd_command_currentsong(struct evbuffer *evbuf, int argc, char **argv, char **er
       return 0;
     }
 
-  ret = mpd_add_mediainfo_byid(evbuf, status.id, status.item_id, status.pos_pl);
-  if (ret < 0)
+  queue_item = db_queue_fetch_byitemid(status.item_id);
+  if (!queue_item)
     {
-      ret = asprintf(errmsg, "Error adding media info for file with id: %d", status.id);
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Error adding queue item info for file with id: %d", status.item_id);
+      return ACK_ERROR_UNKNOWN;
+    }
 
+  ret = mpd_add_db_queue_item(evbuf, queue_item);
+
+  free_queue_item(queue_item, 0);
+
+  if (ret < 0)
+    {
+      *errmsg = safe_asprintf("Error adding media info for file with id: %d", status.id);
       return ACK_ERROR_UNKNOWN;
     }
 
@@ -625,7 +590,7 @@ mpd_command_idle(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
 	    }
 	  else if (0 == strcmp(argv[i], "playlist"))
 	    {
-	      client->events |= LISTENER_PLAYLIST;
+	      client->events |= LISTENER_QUEUE;
 	    }
 	  else if (0 == strcmp(argv[i], "mixer"))
 	    {
@@ -646,7 +611,7 @@ mpd_command_idle(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
 	}
     }
   else
-    client->events = LISTENER_PLAYER | LISTENER_PLAYLIST | LISTENER_VOLUME | LISTENER_SPEAKER | LISTENER_OPTIONS;
+    client->events = LISTENER_PLAYER | LISTENER_QUEUE | LISTENER_VOLUME | LISTENER_SPEAKER | LISTENER_OPTIONS;
 
   idle_clients = client;
 
@@ -715,7 +680,11 @@ static int
 mpd_command_status(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
 {
   struct player_status status;
+  int queue_length;
+  int queue_version;
   char *state;
+  int pos_pl;
+  struct db_queue_item *next_item;
 
   player_get_status(&status);
 
@@ -734,6 +703,9 @@ mpd_command_status(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
 	break;
     }
 
+  queue_version = db_queue_get_version();
+  queue_length = db_queue_get_count();
+
   evbuffer_add_printf(evbuf,
       "volume: %d\n"
       "repeat: %d\n"
@@ -748,13 +720,15 @@ mpd_command_status(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
       (status.repeat == REPEAT_OFF ? 0 : 1),
       status.shuffle,
       (status.repeat == REPEAT_SONG ? 1 : 0),
-      0 /* consume: not supported by forked-daapd, always return 'off' */,
-      status.plversion,
-      status.playlistlength,
+      status.consume,
+      queue_version,
+      queue_length,
       state);
 
   if (status.status != PLAY_STOPPED)
     {
+      pos_pl = db_queue_get_pos(status.item_id, 0);
+
       evbuffer_add_printf(evbuf,
 	  "song: %d\n"
 	  "songid: %d\n"
@@ -762,24 +736,30 @@ mpd_command_status(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
 	  "elapsed: %#.3f\n"
 	  "bitrate: 128\n"
 	  "audio: 44100:16:2\n",
-	  status.pos_pl,
+	  pos_pl,
 	  status.item_id,
 	  (status.pos_ms / 1000), (status.len_ms / 1000),
 	  (status.pos_ms / 1000.0));
     }
 
-  if (filescanner_scanning())
+  if (library_is_scanning())
     {
       evbuffer_add(evbuf, "updating_db: 1\n", 15);
     }
 
   if (status.status != PLAY_STOPPED)
     {
+      next_item = db_queue_fetch_next(status.item_id, status.shuffle);
+      if (next_item)
+	{
       evbuffer_add_printf(evbuf,
 	  "nextsong: %d\n"
 	  "nextsongid: %d\n",
-	  status.next_pos_pl,
-	  status.next_item_id);
+	      next_item->id,
+	      next_item->pos);
+
+	  free_queue_item(next_item, 0);
+	}
     }
 
   return 0;
@@ -805,10 +785,7 @@ mpd_command_stats(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
     {
       db_query_end(&qp);
 
-      ret = asprintf(errmsg, "Could not start query");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
-
+      *errmsg = safe_asprintf("Could not start query");
       return ACK_ERROR_UNKNOWN;
     }
 
@@ -817,10 +794,7 @@ mpd_command_stats(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
     {
       db_query_end(&qp);
 
-      ret = asprintf(errmsg, "Could not fetch query count");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
-
+      *errmsg = safe_asprintf("Could not fetch query count");
       return ACK_ERROR_UNKNOWN;
     }
 
@@ -835,7 +809,7 @@ mpd_command_stats(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
       "albums: %d\n"
       "songs: %d\n"
       "uptime: %d\n" //in seceonds
-      "db_playtime: %d\n"
+      "db_playtime: %" PRIu64 "\n"
       "db_update: %d\n"
       "playtime: %d\n",
         artists,
@@ -850,6 +824,35 @@ mpd_command_stats(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
 }
 
 /*
+ * Command handler function for 'consume'
+ * Sets the consume mode, expects argument argv[1] to be an integer with
+ *   0 = disable consume
+ *   1 = enable consume
+ */
+static int
+mpd_command_consume(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
+{
+  int enable;
+  int ret;
+
+  if (argc < 2)
+    {
+      *errmsg = safe_asprintf("Missing argument for command 'consume'");
+      return ACK_ERROR_ARG;
+    }
+
+  ret = safe_atoi32(argv[1], &enable);
+  if (ret < 0)
+    {
+      *errmsg = safe_asprintf("Argument doesn't convert to integer: '%s'", argv[1]);
+      return ACK_ERROR_ARG;
+    }
+
+  player_consume_set(enable);
+  return 0;
+}
+
+/*
  * Command handler function for 'random'
  * Sets the shuffle mode, expects argument argv[1] to be an integer with
  *   0 = disable shuffle
@@ -863,18 +866,14 @@ mpd_command_random(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
 
   if (argc < 2)
     {
-      ret = asprintf(errmsg, "Missing argument for command 'random'");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Missing argument for command 'random'");
       return ACK_ERROR_ARG;
     }
 
   ret = safe_atoi32(argv[1], &enable);
   if (ret < 0)
     {
-      ret = asprintf(errmsg, "Argument doesn't convert to integer: '%s'", argv[1]);
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Argument doesn't convert to integer: '%s'", argv[1]);
       return ACK_ERROR_ARG;
     }
 
@@ -896,18 +895,14 @@ mpd_command_repeat(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
 
   if (argc < 2)
     {
-      ret = asprintf(errmsg, "Missing argument for command 'repeat'");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Missing argument for command 'repeat'");
       return ACK_ERROR_ARG;
     }
 
   ret = safe_atoi32(argv[1], &enable);
   if (ret < 0)
     {
-      ret = asprintf(errmsg, "Argument doesn't convert to integer: '%s'", argv[1]);
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Argument doesn't convert to integer: '%s'", argv[1]);
       return ACK_ERROR_ARG;
     }
 
@@ -931,18 +926,14 @@ mpd_command_setvol(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
 
   if (argc < 2)
     {
-      ret = asprintf(errmsg, "Missing argument for command 'setvol'");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Missing argument for command 'setvol'");
       return ACK_ERROR_ARG;
     }
 
   ret = safe_atoi32(argv[1], &volume);
   if (ret < 0)
     {
-      ret = asprintf(errmsg, "Argument doesn't convert to integer: '%s'", argv[1]);
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Argument doesn't convert to integer: '%s'", argv[1]);
       return ACK_ERROR_ARG;
     }
 
@@ -975,18 +966,14 @@ mpd_command_single(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
 
   if (argc < 2)
     {
-      ret = asprintf(errmsg, "Missing argument for command 'single'");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Missing argument for command 'single'");
       return ACK_ERROR_ARG;
     }
 
   ret = safe_atoi32(argv[1], &enable);
   if (ret < 0)
     {
-      ret = asprintf(errmsg, "Argument doesn't convert to integer: '%s'", argv[1]);
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Argument doesn't convert to integer: '%s'", argv[1]);
       return ACK_ERROR_ARG;
     }
 
@@ -1029,18 +1016,14 @@ mpd_command_volume(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
 
   if (argc < 2)
     {
-      ret = asprintf(errmsg, "Missing argument for command 'volume'");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Missing argument for command 'volume'");
       return ACK_ERROR_ARG;
     }
 
   ret = safe_atoi32(argv[1], &volume);
   if (ret < 0)
     {
-      ret = asprintf(errmsg, "Argument doesn't convert to integer: '%s'", argv[1]);
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Argument doesn't convert to integer: '%s'", argv[1]);
       return ACK_ERROR_ARG;
     }
 
@@ -1066,18 +1049,14 @@ mpd_command_next(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
 
   if (ret < 0)
     {
-      ret = asprintf(errmsg, "Failed to skip to next song");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Failed to skip to next song");
       return ACK_ERROR_UNKNOWN;
     }
 
-  ret = player_playback_start(NULL);
+  ret = player_playback_start();
   if (ret < 0)
     {
-      ret = asprintf(errmsg, "Player returned an error for start after nextitem");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Player returned an error for start after nextitem");
       return ACK_ERROR_UNKNOWN;
     }
 
@@ -1103,9 +1082,7 @@ mpd_command_pause(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
       ret = safe_atoi32(argv[1], &pause);
       if (ret < 0)
 	{
-	  ret = asprintf(errmsg, "Argument doesn't convert to integer: '%s'", argv[1]);
-	  if (ret < 0)
-	    DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+	  *errmsg = safe_asprintf("Argument doesn't convert to integer: '%s'", argv[1]);
 	  return ACK_ERROR_ARG;
 	}
     }
@@ -1120,13 +1097,11 @@ mpd_command_pause(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
   if (pause == 1)
     ret = player_playback_pause();
   else
-    ret = player_playback_start(NULL);
+    ret = player_playback_start();
 
   if (ret < 0)
     {
-      ret = asprintf(errmsg, "Failed to pause playback");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Failed to pause playback");
       return ACK_ERROR_UNKNOWN;
     }
 
@@ -1143,23 +1118,22 @@ mpd_command_play(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
 {
   int songpos;
   struct player_status status;
+  struct db_queue_item *queue_item;
   int ret;
 
-  player_get_status(&status);
-
   songpos = 0;
   if (argc > 1)
     {
       ret = safe_atoi32(argv[1], &songpos);
       if (ret < 0)
 	{
-	  ret = asprintf(errmsg, "Argument doesn't convert to integer: '%s'", argv[1]);
-	  if (ret < 0)
-	    DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+	  *errmsg = safe_asprintf("Argument doesn't convert to integer: '%s'", argv[1]);
 	  return ACK_ERROR_ARG;
 	}
     }
 
+  player_get_status(&status);
+
   if (status.status == PLAY_PLAYING && songpos < 0)
     {
       DPRINTF(E_DBG, L_MPD, "Ignoring play command with parameter '%s', player is already playing.\n", argv[1]);
@@ -1173,15 +1147,23 @@ mpd_command_play(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
     }
 
   if (songpos > 0)
-    ret = player_playback_start_byindex(songpos, NULL);
+    {
+      queue_item = db_queue_fetch_bypos(songpos, 0);
+      if (!queue_item)
+	{
+	  *errmsg = safe_asprintf("Failed to start playback");
+	  return ACK_ERROR_UNKNOWN;
+	}
+
+      ret = player_playback_start_byitem(queue_item);
+      free_queue_item(queue_item, 0);
+    }
   else
-    ret = player_playback_start(NULL);
+    ret = player_playback_start();
 
   if (ret < 0)
     {
-      ret = asprintf(errmsg, "Failed to start playback");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Failed to start playback");
       return ACK_ERROR_UNKNOWN;
     }
 
@@ -1198,6 +1180,7 @@ mpd_command_playid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
 {
   uint32_t id;
   struct player_status status;
+  struct db_queue_item *queue_item;
   int ret;
 
   player_get_status(&status);
@@ -1209,9 +1192,7 @@ mpd_command_playid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
       ret = safe_atou32(argv[1], &id);
       if (ret < 0)
 	{
-	  ret = asprintf(errmsg, "Argument doesn't convert to integer: '%s'", argv[1]);
-	  if (ret < 0)
-	    DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+	  *errmsg = safe_asprintf("Argument doesn't convert to integer: '%s'", argv[1]);
 	  return ACK_ERROR_ARG;
 	}
     }
@@ -1223,15 +1204,23 @@ mpd_command_playid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
     }
 
   if (id > 0)
-    ret = player_playback_start_byitemid(id, NULL);
+    {
+      queue_item = db_queue_fetch_byitemid(id);
+      if (!queue_item)
+	{
+	  *errmsg = safe_asprintf("Failed to start playback");
+	  return ACK_ERROR_UNKNOWN;
+	}
+
+      ret = player_playback_start_byitem(queue_item);
+      free_queue_item(queue_item, 0);
+    }
   else
-    ret = player_playback_start(NULL);
+    ret = player_playback_start();
 
   if (ret < 0)
     {
-      ret = asprintf(errmsg, "Failed to start playback");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Failed to start playback");
       return ACK_ERROR_UNKNOWN;
     }
 
@@ -1251,18 +1240,14 @@ mpd_command_previous(struct evbuffer *evbuf, int argc, char **argv, char **errms
 
   if (ret < 0)
     {
-      ret = asprintf(errmsg, "Failed to skip to previous song");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Failed to skip to previous song");
       return ACK_ERROR_UNKNOWN;
     }
 
-  ret = player_playback_start(NULL);
+  ret = player_playback_start();
   if (ret < 0)
     {
-      ret = asprintf(errmsg, "Player returned an error for start after previtem");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Player returned an error for start after previtem");
       return ACK_ERROR_UNKNOWN;
     }
 
@@ -1277,7 +1262,6 @@ mpd_command_previous(struct evbuffer *evbuf, int argc, char **argv, char **errms
 static int
 mpd_command_seek(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
 {
-  struct player_status status;
   uint32_t songpos;
   float seek_target_sec;
   int seek_target_msec;
@@ -1285,30 +1269,18 @@ mpd_command_seek(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
 
   if (argc < 3)
     {
-      ret = asprintf(errmsg, "Missing argument for command 'seek'");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Missing argument for command 'seek'");
       return ACK_ERROR_ARG;
     }
 
   ret = safe_atou32(argv[1], &songpos);
   if (ret < 0)
     {
-      ret = asprintf(errmsg, "Argument doesn't convert to integer: '%s'", argv[1]);
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Argument doesn't convert to integer: '%s'", argv[1]);
       return ACK_ERROR_ARG;
     }
 
   //TODO Allow seeking in songs not currently playing
-  player_get_status(&status);
-  if (status.pos_pl != songpos)
-    {
-      ret = asprintf(errmsg, "Given song is not the current playing one, seeking is not supported");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
-      return ACK_ERROR_UNKNOWN;
-    }
 
   seek_target_sec = strtof(argv[2], NULL);
   seek_target_msec = seek_target_sec * 1000;
@@ -1317,18 +1289,14 @@ mpd_command_seek(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
 
   if (ret < 0)
     {
-      ret = asprintf(errmsg, "Failed to seek current song to time %d msec", seek_target_msec);
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Failed to seek current song to time %d msec", seek_target_msec);
       return ACK_ERROR_UNKNOWN;
     }
 
-  ret = player_playback_start(NULL);
+  ret = player_playback_start();
     if (ret < 0)
       {
-        ret = asprintf(errmsg, "Player returned an error for start after seekcur");
-	if (ret < 0)
-	  DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+	*errmsg = safe_asprintf("Player returned an error for start after seekcur");
 	return ACK_ERROR_UNKNOWN;
       }
 
@@ -1351,18 +1319,14 @@ mpd_command_seekid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
 
   if (argc < 3)
     {
-      ret = asprintf(errmsg, "Missing argument for command 'seekcur'");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Missing argument for command 'seekcur'");
       return ACK_ERROR_ARG;
     }
 
   ret = safe_atou32(argv[1], &id);
   if (ret < 0)
     {
-      ret = asprintf(errmsg, "Argument doesn't convert to integer: '%s'", argv[1]);
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Argument doesn't convert to integer: '%s'", argv[1]);
       return ACK_ERROR_ARG;
     }
 
@@ -1370,9 +1334,7 @@ mpd_command_seekid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
   player_get_status(&status);
   if (status.item_id != id)
     {
-      ret = asprintf(errmsg, "Given song is not the current playing one, seeking is not supported");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Given song is not the current playing one, seeking is not supported");
       return ACK_ERROR_UNKNOWN;
     }
 
@@ -1383,18 +1345,14 @@ mpd_command_seekid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
 
   if (ret < 0)
     {
-      ret = asprintf(errmsg, "Failed to seek current song to time %d msec", seek_target_msec);
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Failed to seek current song to time %d msec", seek_target_msec);
       return ACK_ERROR_UNKNOWN;
     }
 
-  ret = player_playback_start(NULL);
+  ret = player_playback_start();
   if (ret < 0)
     {
-      ret = asprintf(errmsg, "Player returned an error for start after seekcur");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Player returned an error for start after seekcur");
       return ACK_ERROR_UNKNOWN;
     }
 
@@ -1414,9 +1372,7 @@ mpd_command_seekcur(struct evbuffer *evbuf, int argc, char **argv, char **errmsg
 
   if (argc < 2)
     {
-      ret = asprintf(errmsg, "Missing argument for command 'seekcur'");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Missing argument for command 'seekcur'");
       return ACK_ERROR_ARG;
     }
 
@@ -1428,18 +1384,14 @@ mpd_command_seekcur(struct evbuffer *evbuf, int argc, char **argv, char **errmsg
 
   if (ret < 0)
     {
-      ret = asprintf(errmsg, "Failed to seek current song to time %d msec", seek_target_msec);
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Failed to seek current song to time %d msec", seek_target_msec);
       return ACK_ERROR_UNKNOWN;
     }
 
-  ret = player_playback_start(NULL);
+  ret = player_playback_start();
   if (ret < 0)
     {
-      ret = asprintf(errmsg, "Player returned an error for start after seekcur");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Player returned an error for start after seekcur");
       return ACK_ERROR_UNKNOWN;
     }
 
@@ -1459,20 +1411,26 @@ mpd_command_stop(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
 
   if (ret != 0)
     {
-      ret = asprintf(errmsg, "Failed to stop playback");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Failed to stop playback");
       return ACK_ERROR_UNKNOWN;
     }
 
   return 0;
 }
 
-static struct queue_item *
-mpd_queueitem_make(char *path, int recursive)
+/*
+ * Add media file item with given virtual path to the queue
+ *
+ * @param path The virtual path
+ * @param recursive If 0 add only item with exact match, otherwise add all items virtual path start with the given path
+ * @return The queue item id of the last inserted item or -1 on failure
+ */
+static int
+mpd_queue_add(char *path, int recursive)
 {
   struct query_params qp;
-  struct queue_item *items;
+  struct player_status status;
+  int ret;
 
   memset(&qp, 0, sizeof(struct query_params));
 
@@ -1493,10 +1451,12 @@ mpd_queueitem_make(char *path, int recursive)
 	DPRINTF(E_DBG, L_PLAYER, "Out of memory\n");
     }
 
-  items = queueitem_make_byquery(&qp);
+  player_get_status(&status);
+
+  ret = db_queue_add_by_query(&qp, status.shuffle, status.item_id);
 
   sqlite3_free(qp.filter);
-  return items;
+  return ret;
 }
 
 /*
@@ -1507,28 +1467,36 @@ mpd_queueitem_make(char *path, int recursive)
 static int
 mpd_command_add(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
 {
-  struct queue_item *items;
+  struct media_file_info mfi;
   int ret;
 
   if (argc < 2)
     {
-      ret = asprintf(errmsg, "Missing argument for command 'add'");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Missing argument for command 'add'");
       return ACK_ERROR_ARG;
     }
 
-  items = mpd_queueitem_make(argv[1], 1);
+  ret = mpd_queue_add(argv[1], 1);
 
-  if (!items)
+  if (ret < 0)
     {
-      ret = asprintf(errmsg, "Failed to add song '%s' to playlist", argv[1]);
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Failed to add song '%s' to playlist", argv[1]);
       return ACK_ERROR_UNKNOWN;
     }
 
-  player_queue_add(items, NULL);
+  if (ret == 0)
+    {
+      // Given path is not in the library, check if it is possible to add as a non-library queue item
+      ret = library_scan_media(argv[1], &mfi);
+      if (ret != LIBRARY_OK)
+	{
+	  *errmsg = safe_asprintf("Failed to add song '%s' to playlist (unkown path)", argv[1]);
+	  return ACK_ERROR_UNKNOWN;
+	}
+
+      library_add_queue_item(&mfi);
+      free_mfi(&mfi, 1);
+    }
 
   return 0;
 }
@@ -1542,15 +1510,12 @@ mpd_command_add(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
 static int
 mpd_command_addid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
 {
-  struct queue_item *items;
-  uint32_t item_id;
+  struct media_file_info mfi;
   int ret;
 
   if (argc < 2)
     {
-      ret = asprintf(errmsg, "Missing argument for command 'addid'");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Missing argument for command 'addid'");
       return ACK_ERROR_ARG;
     }
 
@@ -1560,22 +1525,31 @@ mpd_command_addid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
       DPRINTF(E_LOG, L_MPD, "Adding at a specified position not supported for 'addid', adding songs at end of queue.\n");
     }
 
-  items = mpd_queueitem_make(argv[1], 0);
+  ret = mpd_queue_add(argv[1], 0);
 
-  if (!items)
+  if (ret == 0)
     {
-      ret = asprintf(errmsg, "Failed to add song '%s' to playlist", argv[1]);
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
-      return ACK_ERROR_UNKNOWN;
-    }
+      // Given path is not in the library, directly add it as a new queue item
+      ret = library_scan_media(argv[1], &mfi);
+      if (ret != LIBRARY_OK)
+	{
+	  *errmsg = safe_asprintf("Failed to add song '%s' to playlist (unkown path)", argv[1]);
+	  return ACK_ERROR_UNKNOWN;
+	}
 
+      ret = library_add_queue_item(&mfi);
+      free_mfi(&mfi, 1);
+    }
 
-  player_queue_add(items, &item_id);
+  if (ret < 0)
+    {
+      *errmsg = safe_asprintf("Failed to add song '%s' to playlist", argv[1]);
+      return ACK_ERROR_UNKNOWN;
+    }
 
   evbuffer_add_printf(evbuf,
       "Id: %d\n",
-      item_id);
+      ret); // mpd_queue_add returns the item_id of the last inserted queue item
 
   return 0;
 }
@@ -1595,7 +1569,7 @@ mpd_command_clear(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
       DPRINTF(E_DBG, L_MPD, "Failed to stop playback\n");
     }
 
-  player_queue_clear();
+  db_queue_clear(0);
 
   return 0;
 }
@@ -1614,10 +1588,10 @@ mpd_command_delete(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
   int count;
   int ret;
 
-  // If argv[1] is ommited clear the whole queue except the current playing one
+  // If argv[1] is ommited clear the whole queue
   if (argc < 2)
     {
-      player_queue_clear();
+      db_queue_clear(0);
       return 0;
     }
 
@@ -1625,20 +1599,16 @@ mpd_command_delete(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
   ret = mpd_pars_range_arg(argv[1], &start_pos, &end_pos);
   if (ret < 0)
     {
-      ret = asprintf(errmsg, "Argument doesn't convert to integer or range: '%s'", argv[1]);
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Argument doesn't convert to integer or range: '%s'", argv[1]);
       return ACK_ERROR_ARG;
     }
 
   count = end_pos - start_pos;
 
-  ret = player_queue_remove_byindex(start_pos, count);
+  ret = db_queue_delete_bypos(start_pos, count);
   if (ret < 0)
     {
-      ret = asprintf(errmsg, "Failed to remove %d songs starting at position %d", count, start_pos);
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Failed to remove %d songs starting at position %d", count, start_pos);
       return ACK_ERROR_UNKNOWN;
     }
 
@@ -1657,27 +1627,21 @@ mpd_command_deleteid(struct evbuffer *evbuf, int argc, char **argv, char **errms
 
   if (argc < 2)
     {
-      ret = asprintf(errmsg, "Missing argument for command 'deleteid'");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Missing argument for command 'deleteid'");
       return ACK_ERROR_ARG;
     }
 
   ret = safe_atou32(argv[1], &songid);
   if (ret < 0)
     {
-      ret = asprintf(errmsg, "Argument doesn't convert to integer: '%s'", argv[1]);
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Argument doesn't convert to integer: '%s'", argv[1]);
       return ACK_ERROR_ARG;
     }
 
-  ret = player_queue_remove_byitemid(songid);
+  ret = db_queue_delete_byitemid(songid);
   if (ret < 0)
     {
-      ret = asprintf(errmsg, "Failed to remove song with id '%s'", argv[1]);
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Failed to remove song with id '%s'", argv[1]);
       return ACK_ERROR_UNKNOWN;
     }
 
@@ -1696,18 +1660,14 @@ mpd_command_move(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
 
   if (argc < 3)
     {
-      ret = asprintf(errmsg, "Missing argument for command 'move'");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Missing argument for command 'move'");
       return ACK_ERROR_ARG;
     }
 
   ret = mpd_pars_range_arg(argv[1], &start_pos, &end_pos);
   if (ret < 0)
     {
-      ret = asprintf(errmsg, "Argument doesn't convert to integer or range: '%s'", argv[1]);
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Argument doesn't convert to integer or range: '%s'", argv[1]);
       return ACK_ERROR_ARG;
     }
 
@@ -1718,18 +1678,14 @@ mpd_command_move(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
   ret = safe_atou32(argv[2], &to_pos);
   if (ret < 0)
     {
-      ret = asprintf(errmsg, "Argument doesn't convert to integer: '%s'", argv[2]);
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Argument doesn't convert to integer: '%s'", argv[2]);
       return ACK_ERROR_ARG;
     }
 
-  ret = player_queue_move_byindex(start_pos, to_pos);
+  ret = db_queue_move_bypos(start_pos, to_pos);
   if (ret < 0)
     {
-      ret = asprintf(errmsg, "Failed to move song at position %d to %d", start_pos, to_pos);
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Failed to move song at position %d to %d", start_pos, to_pos);
       return ACK_ERROR_UNKNOWN;
     }
 
@@ -1745,36 +1701,28 @@ mpd_command_moveid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
 
   if (argc < 3)
     {
-      ret = asprintf(errmsg, "Missing argument for command 'moveid'");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Missing argument for command 'moveid'");
       return ACK_ERROR_ARG;
     }
 
   ret = safe_atou32(argv[1], &songid);
   if (ret < 0)
     {
-      ret = asprintf(errmsg, "Argument doesn't convert to integer: '%s'", argv[1]);
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Argument doesn't convert to integer: '%s'", argv[1]);
       return ACK_ERROR_ARG;
     }
 
   ret = safe_atou32(argv[2], &to_pos);
   if (ret < 0)
     {
-      ret = asprintf(errmsg, "Argument doesn't convert to integer: '%s'", argv[2]);
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Argument doesn't convert to integer: '%s'", argv[2]);
       return ACK_ERROR_ARG;
     }
 
-  ret = player_queue_move_byitemid(songid, to_pos);
+  ret = db_queue_move_byitemid(songid, to_pos, 0);
   if (ret < 0)
     {
-      ret = asprintf(errmsg, "Failed to move song with id '%s' to index '%s'", argv[1], argv[2]);
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Failed to move song with id '%s' to index '%s'", argv[1], argv[2]);
       return ACK_ERROR_UNKNOWN;
     }
 
@@ -1791,12 +1739,9 @@ mpd_command_moveid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
 static int
 mpd_command_playlistid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
 {
-  struct queue *queue;
-  struct queue_item *item;
+  struct query_params query_params;
+  struct db_queue_item queue_item;
   uint32_t songid;
-  int pos_pl;
-  int count;
-  int i;
   int ret;
 
   songid = 0;
@@ -1806,46 +1751,39 @@ mpd_command_playlistid(struct evbuffer *evbuf, int argc, char **argv, char **err
       ret = safe_atou32(argv[1], &songid);
       if (ret < 0)
 	{
-	  ret = asprintf(errmsg, "Argument doesn't convert to integer: '%s'", argv[1]);
-	  if (ret < 0)
-	    DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+	  *errmsg = safe_asprintf("Argument doesn't convert to integer: '%s'", argv[1]);
 	  return ACK_ERROR_ARG;
 	}
     }
 
-  // Get the whole queue (start_pos = 0, end_pos = -1)
-  queue = player_queue_get_byindex(0, 0);
+  memset(&query_params, 0, sizeof(struct query_params));
+
+  if (songid > 0)
+    query_params.filter = sqlite3_mprintf("id = %d", songid);
 
-  if (!queue)
+  ret = db_queue_enum_start(&query_params);
+  if (ret < 0)
     {
-      // Queue is emtpy
-      return 0;
+      sqlite3_free(query_params.filter);
+      *errmsg = safe_asprintf("Failed to start queue enum for command playlistid: '%s'", argv[1]);
+      return ACK_ERROR_ARG;
     }
 
-  pos_pl = 0;
-  count = queue_count(queue);
-  for (i = 0; i < count; i++)
+  while ((ret = db_queue_enum_fetch(&query_params, &queue_item)) == 0 && queue_item.id > 0)
     {
-      item = queue_get_byindex(queue, i, 0);
-      if (songid == 0 || songid == queueitem_item_id(item))
-	{
-	  ret = mpd_add_mediainfo_byid(evbuf, queueitem_id(item), queueitem_item_id(item), pos_pl);
+      ret = mpd_add_db_queue_item(evbuf, &queue_item);
 	  if (ret < 0)
 	    {
-	      ret = asprintf(errmsg, "Error adding media info for file with id: %d", queueitem_id(item));
-
-	      queue_free(queue);
+	      *errmsg = safe_asprintf("Error adding media info for file with id: %d", queue_item.file_id);
 
-	      if (ret < 0)
-		DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+	      db_queue_enum_end(&query_params);
+	      sqlite3_free(query_params.filter);
 	      return ACK_ERROR_UNKNOWN;
 	    }
 	}
 
-      pos_pl++;
-    }
-
-  queue_free(queue);
+  db_queue_enum_end(&query_params);
+  sqlite3_free(query_params.filter);
 
   return 0;
 }
@@ -1861,68 +1799,54 @@ mpd_command_playlistid(struct evbuffer *evbuf, int argc, char **argv, char **err
 static int
 mpd_command_playlistinfo(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
 {
-  struct queue *queue;
-  struct queue_item *item;
+  struct query_params query_params;
+  struct db_queue_item queue_item;
   int start_pos;
   int end_pos;
-  int count;
-  int pos_pl;
-  int i;
   int ret;
 
   start_pos = 0;
   end_pos = 0;
+  memset(&query_params, 0, sizeof(struct query_params));
 
   if (argc > 1)
     {
       ret = mpd_pars_range_arg(argv[1], &start_pos, &end_pos);
       if (ret < 0)
 	{
-	  ret = asprintf(errmsg, "Argument doesn't convert to integer or range: '%s'", argv[1]);
-	  if (ret < 0)
-	    DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+	  *errmsg = safe_asprintf("Argument doesn't convert to integer or range: '%s'", argv[1]);
 	  return ACK_ERROR_ARG;
 	}
-    }
-
-  count = end_pos - start_pos;
 
   if (start_pos < 0)
-    {
       DPRINTF(E_DBG, L_MPD, "Command 'playlistinfo' called with pos < 0 (arg = '%s'), ignore arguments and return whole queue\n", argv[1]);
-      start_pos = 0;
-      count = 0;
+      else
+	query_params.filter = sqlite3_mprintf("pos >= %d AND pos < %d", start_pos, end_pos);
     }
 
-  queue = player_queue_get_byindex(start_pos, count);
-
-  if (!queue)
+  ret = db_queue_enum_start(&query_params);
+  if (ret < 0)
     {
-      // Queue is emtpy
-      return 0;
+      sqlite3_free(query_params.filter);
+      *errmsg = safe_asprintf("Failed to start queue enum for command playlistinfo: '%s'", argv[1]);
+      return ACK_ERROR_ARG;
     }
 
-  pos_pl = start_pos;
-  count = queue_count(queue);
-  for (i = 0; i < count; i++)
+  while ((ret = db_queue_enum_fetch(&query_params, &queue_item)) == 0 && queue_item.id > 0)
     {
-      item = queue_get_byindex(queue, i, 0);
-      ret = mpd_add_mediainfo_byid(evbuf, queueitem_id(item), queueitem_item_id(item), pos_pl);
+      ret = mpd_add_db_queue_item(evbuf, &queue_item);
       if (ret < 0)
 	{
-	  ret = asprintf(errmsg, "Error adding media info for file with id: %d", queueitem_id(item));
-
-	  queue_free(queue);
+	  *errmsg = safe_asprintf("Error adding media info for file with id: %d", queue_item.file_id);
 
-	  if (ret < 0)
-	    DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+	  db_queue_enum_end(&query_params);
+	  sqlite3_free(query_params.filter);
 	  return ACK_ERROR_UNKNOWN;
 	}
-
-      pos_pl++;
     }
 
-  queue_free(queue);
+  db_queue_enum_end(&query_params);
+  sqlite3_free(query_params.filter);
 
   return 0;
 }
@@ -1934,46 +1858,37 @@ mpd_command_playlistinfo(struct evbuffer *evbuf, int argc, char **argv, char **e
 static int
 mpd_command_plchanges(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
 {
-  struct queue *queue;
-  struct queue_item *item;
-  int pos_pl;
-  int count;
-  int i;
+  struct query_params query_params;
+  struct db_queue_item queue_item;
   int ret;
 
   /*
    * forked-daapd does not keep track of changes in the queue based on the playlist version,
    * therefor plchanges returns all songs in the queue as changed ignoring the given version.
    */
-  queue = player_queue_get_byindex(0, 0);
+  memset(&query_params, 0, sizeof(struct query_params));
 
-  if (!queue)
+  ret = db_queue_enum_start(&query_params);
+  if (ret < 0)
     {
-      // Queue is emtpy
-      return 0;
+      *errmsg = safe_asprintf("Failed to start queue enum for command plchanges");
+      return ACK_ERROR_ARG;
     }
 
-  pos_pl = 0;
-  count = queue_count(queue);
-  for (i = 0; i < count; i++)
+  while ((ret = db_queue_enum_fetch(&query_params, &queue_item)) == 0 && queue_item.id > 0)
     {
-      item = queue_get_byindex(queue, i, 0);
-      ret = mpd_add_mediainfo_byid(evbuf, queueitem_id(item), queueitem_item_id(item), pos_pl);
+      ret = mpd_add_db_queue_item(evbuf, &queue_item);
       if (ret < 0)
 	{
-	  ret = asprintf(errmsg, "Error adding media info for file with id: %d", queueitem_id(item));
-
-	  queue_free(queue);
+	  *errmsg = safe_asprintf("Error adding media info for file with id: %d", queue_item.file_id);
 
-	  if (ret < 0)
-	    DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+	  db_queue_enum_end(&query_params);
 	  return ACK_ERROR_UNKNOWN;
 	}
-
-      pos_pl++;
     }
 
-  queue_free(queue);
+  db_queue_enum_end(&query_params);
+
   return 0;
 }
 
@@ -1984,36 +1899,34 @@ mpd_command_plchanges(struct evbuffer *evbuf, int argc, char **argv, char **errm
 static int
 mpd_command_plchangesposid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
 {
-  struct queue *queue;
-  struct queue_item *item;
-  int count;
-  int i;
+  struct query_params query_params;
+  struct db_queue_item queue_item;
+  int ret;
 
   /*
    * forked-daapd does not keep track of changes in the queue based on the playlist version,
    * therefor plchangesposid returns all songs in the queue as changed ignoring the given version.
    */
-  queue = player_queue_get_byindex(0, 0);
+  memset(&query_params, 0, sizeof(struct query_params));
 
-  if (!queue)
+  ret = db_queue_enum_start(&query_params);
+  if (ret < 0)
     {
-      // Queue is emtpy
-      return 0;
+      *errmsg = safe_asprintf("Failed to start queue enum for command plchangesposid");
+      return ACK_ERROR_ARG;
     }
 
-  count = queue_count(queue);
-  for (i = 0; i < count; i++)
+  while ((ret = db_queue_enum_fetch(&query_params, &queue_item)) == 0 && queue_item.id > 0)
     {
-      item = queue_get_byindex(queue, i, 0);
-
       evbuffer_add_printf(evbuf,
       	  "cpos: %d\n"
       	  "Id: %d\n",
-      	  i,
-	  queueitem_item_id(item));
+      	  queue_item.pos,
+	  queue_item.id);
     }
 
-  queue_free(queue);
+  db_queue_enum_end(&query_params);
+
   return 0;
 }
 
@@ -2032,27 +1945,25 @@ mpd_command_listplaylist(struct evbuffer *evbuf, int argc, char **argv, char **e
 
   if (argc < 2)
     {
-      ret = asprintf(errmsg, "Missing argument for command 'listplaylist'");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Missing argument for command 'listplaylist'");
       return ACK_ERROR_ARG;
     }
 
   if (strncmp(argv[1], "/", 1) == 0)
-    {
-      ret = snprintf(path, sizeof(path), "%s", argv[1]);
-    }
+    ret = snprintf(path, sizeof(path), "%s", argv[1]);
   else
+    ret = snprintf(path, sizeof(path), "/%s", argv[1]);
+
+  if (ret >= sizeof(path))
     {
-      ret = snprintf(path, sizeof(path), "/%s", argv[1]);
+      *errmsg = safe_asprintf("Length of path exceeds the PATH_MAX value '%s'", argv[1]);
+      return ACK_ERROR_ARG;
     }
 
   pli = db_pl_fetch_byvirtualpath(path);
   if (!pli)
     {
-      ret = asprintf(errmsg, "Playlist not found for path '%s'", argv[1]);
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Playlist not found for path '%s'", argv[1]);
       return ACK_ERROR_ARG;
     }
 
@@ -2069,9 +1980,7 @@ mpd_command_listplaylist(struct evbuffer *evbuf, int argc, char **argv, char **e
 
       free_pli(pli, 0);
 
-      ret = asprintf(errmsg, "Could not start query");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Could not start query");
       return ACK_ERROR_UNKNOWN;
     }
 
@@ -2104,27 +2013,25 @@ mpd_command_listplaylistinfo(struct evbuffer *evbuf, int argc, char **argv, char
 
   if (argc < 2)
     {
-      ret = asprintf(errmsg, "Missing argument for command 'listplaylistinfo'");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Missing argument for command 'listplaylistinfo'");
       return ACK_ERROR_ARG;
     }
 
   if (strncmp(argv[1], "/", 1) == 0)
-    {
-      ret = snprintf(path, sizeof(path), "%s", argv[1]);
-    }
+    ret = snprintf(path, sizeof(path), "%s", argv[1]);
   else
+    ret = snprintf(path, sizeof(path), "/%s", argv[1]);
+
+  if (ret >= sizeof(path))
     {
-      ret = snprintf(path, sizeof(path), "/%s", argv[1]);
+      *errmsg = safe_asprintf("Length of path exceeds the PATH_MAX value '%s'", argv[1]);
+      return ACK_ERROR_ARG;
     }
 
   pli = db_pl_fetch_byvirtualpath(path);
   if (!pli)
     {
-      ret = asprintf(errmsg, "Playlist not found for path '%s'", argv[1]);
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Playlist not found for path '%s'", argv[1]);
       return ACK_ERROR_NO_EXIST;
     }
 
@@ -2141,9 +2048,7 @@ mpd_command_listplaylistinfo(struct evbuffer *evbuf, int argc, char **argv, char
 
       free_pli(pli, 0);
 
-      ret = asprintf(errmsg, "Could not start query");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Could not start query");
       return ACK_ERROR_UNKNOWN;
     }
 
@@ -2188,9 +2093,7 @@ mpd_command_listplaylists(struct evbuffer *evbuf, int argc, char **argv, char **
     {
       db_query_end(&qp);
 
-      ret = asprintf(errmsg, "Could not start query");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Could not start query");
       return ACK_ERROR_UNKNOWN;
     }
 
@@ -2198,9 +2101,7 @@ mpd_command_listplaylists(struct evbuffer *evbuf, int argc, char **argv, char **
     {
       if (safe_atou32(dbpli.db_timestamp, &time_modified) != 0)
         {
-          ret = asprintf(errmsg, "Error converting time modified to uint32_t: %s\n", dbpli.db_timestamp);
-          if (ret < 0)
-            DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+          *errmsg = safe_asprintf("Error converting time modified to uint32_t: %s\n", dbpli.db_timestamp);
           return ACK_ERROR_UNKNOWN;
         }
 
@@ -2229,51 +2130,45 @@ mpd_command_load(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
 {
   char path[PATH_MAX];
   struct playlist_info *pli;
-  struct queue_item *items;
+  struct player_status status;
   int ret;
 
   if (argc < 2)
     {
-      ret = asprintf(errmsg, "Missing argument for command 'load'");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Missing argument for command 'load'");
       return ACK_ERROR_ARG;
     }
 
   if (strncmp(argv[1], "/", 1) == 0)
-    {
-      ret = snprintf(path, sizeof(path), "%s", argv[1]);
-    }
+    ret = snprintf(path, sizeof(path), "%s", argv[1]);
   else
+    ret = snprintf(path, sizeof(path), "/%s", argv[1]);
+
+  if (ret >= sizeof(path))
     {
-      ret = snprintf(path, sizeof(path), "/%s", argv[1]);
+      *errmsg = safe_asprintf("Length of path exceeds the PATH_MAX value '%s'", argv[1]);
+      return ACK_ERROR_ARG;
     }
 
   pli = db_pl_fetch_byvirtualpath(path);
   if (!pli)
     {
-      ret = asprintf(errmsg, "Playlist not found for path '%s'", argv[1]);
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Playlist not found for path '%s'", argv[1]);
       return ACK_ERROR_ARG;
     }
 
   //TODO If a second parameter is given only add the specified range of songs to the playqueue
 
-  items = queueitem_make_byplid(pli->id);
+  player_get_status(&status);
 
-  if (!items)
+  ret = db_queue_add_by_playlistid(pli->id, status.shuffle, status.item_id);
+  free_pli(pli, 0);
+  if (ret < 0)
     {
-      free_pli(pli, 0);
-
-      ret = asprintf(errmsg, "Failed to add song '%s' to playlist", argv[1]);
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Failed to add song '%s' to playlist", argv[1]);
       return ACK_ERROR_UNKNOWN;
     }
 
-  player_queue_add(items, NULL);
-
   return 0;
 }
 
@@ -2406,9 +2301,7 @@ mpd_command_count(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
 
   if (argc < 3 || ((argc - 1) % 2) != 0)
     {
-      ret = asprintf(errmsg, "Missing argument(s) for command 'find'");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Missing argument(s) for command 'find'");
       return ACK_ERROR_ARG;
     }
 
@@ -2422,10 +2315,9 @@ mpd_command_count(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
   if (ret < 0)
     {
       db_query_end(&qp);
+      sqlite3_free(qp.filter);
 
-      ret = asprintf(errmsg, "Could not start query");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Could not start query");
       return ACK_ERROR_UNKNOWN;
     }
 
@@ -2433,20 +2325,20 @@ mpd_command_count(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
   if (ret < 0)
     {
       db_query_end(&qp);
+      sqlite3_free(qp.filter);
 
-      ret = asprintf(errmsg, "Could not fetch query count");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Could not fetch query count");
       return ACK_ERROR_UNKNOWN;
     }
 
   evbuffer_add_printf(evbuf,
       "songs: %d\n"
-      "playtime: %d\n",
+      "playtime: %" PRIu64 "\n",
         fci.count,
         (fci.length / 1000));
 
   db_query_end(&qp);
+  sqlite3_free(qp.filter);
 
   return 0;
 }
@@ -2460,9 +2352,7 @@ mpd_command_find(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
 
   if (argc < 3 || ((argc - 1) % 2) != 0)
     {
-      ret = asprintf(errmsg, "Missing argument(s) for command 'find'");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Missing argument(s) for command 'find'");
       return ACK_ERROR_ARG;
     }
 
@@ -2478,10 +2368,9 @@ mpd_command_find(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
   if (ret < 0)
     {
       db_query_end(&qp);
+      sqlite3_free(qp.filter);
 
-      ret = asprintf(errmsg, "Could not start query");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Could not start query");
       return ACK_ERROR_UNKNOWN;
     }
 
@@ -2495,6 +2384,7 @@ mpd_command_find(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
     }
 
   db_query_end(&qp);
+  sqlite3_free(qp.filter);
 
   return 0;
 }
@@ -2503,14 +2393,12 @@ static int
 mpd_command_findadd(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
 {
   struct query_params qp;
-  struct queue_item *items;
+  struct player_status status;
   int ret;
 
   if (argc < 3 || ((argc - 1) % 2) != 0)
     {
-      ret = asprintf(errmsg, "Missing argument(s) for command 'findadd'");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Missing argument(s) for command 'findadd'");
       return ACK_ERROR_ARG;
     }
 
@@ -2522,18 +2410,16 @@ mpd_command_findadd(struct evbuffer *evbuf, int argc, char **argv, char **errmsg
 
   mpd_get_query_params_find(argc - 1, argv + 1, &qp);
 
-  items = queueitem_make_byquery(&qp);
+  player_get_status(&status);
 
-  if (!items)
+  ret = db_queue_add_by_query(&qp, status.shuffle, status.item_id);
+  sqlite3_free(qp.filter);
+  if (ret < 0)
     {
-      ret = asprintf(errmsg, "Failed to add songs to playlist");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Failed to add songs to playlist");
       return ACK_ERROR_UNKNOWN;
     }
 
-  player_queue_add(items, NULL);
-
   return 0;
 }
 
@@ -2551,9 +2437,7 @@ mpd_command_list(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
     {
       if (argc != 3 || (0 != strcasecmp(argv[1], "album")))
 	{
-	  ret = asprintf(errmsg, "Missing argument(s) for command 'list'");
-	  if (ret < 0)
-	    DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+	  *errmsg = safe_asprintf("Missing argument(s) for command 'list'");
 	  return ACK_ERROR_ARG;
 	}
     }
@@ -2625,10 +2509,9 @@ mpd_command_list(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
   if (ret < 0)
     {
       db_query_end(&qp);
+      sqlite3_free(qp.filter);
 
-      ret = asprintf(errmsg, "Could not start query");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Could not start query");
       return ACK_ERROR_UNKNOWN;
     }
 
@@ -2668,6 +2551,7 @@ mpd_command_list(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
     }
 
   db_query_end(&qp);
+  sqlite3_free(qp.filter);
 
   return 0;
 }
@@ -2694,9 +2578,7 @@ mpd_add_directory(struct evbuffer *evbuf, int directory_id, int listall, int lis
   if (ret < 0)
     {
       db_query_end(&qp);
-      ret = asprintf(errmsg, "Could not start query");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Could not start query");
       return ACK_ERROR_UNKNOWN;
     }
   while (((ret = db_query_fetch_pl(&qp, &dbpli, 0)) == 0) && (dbpli.id))
@@ -2769,9 +2651,7 @@ mpd_add_directory(struct evbuffer *evbuf, int directory_id, int listall, int lis
   if (ret < 0)
     {
       db_query_end(&qp);
-      ret = asprintf(errmsg, "Could not start query");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Could not start query");
       return ACK_ERROR_UNKNOWN;
     }
   while (((ret = db_query_fetch_file(&qp, &dbmfi)) == 0) && (dbmfi.id))
@@ -2819,9 +2699,7 @@ mpd_command_listall(struct evbuffer *evbuf, int argc, char **argv, char **errmsg
 
   if ((ret < 0) || (ret >= sizeof(parent)))
     {
-      ret = asprintf(errmsg, "Parent path exceeds PATH_MAX");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Parent path exceeds PATH_MAX");
       return ACK_ERROR_UNKNOWN;
     }
 
@@ -2829,9 +2707,7 @@ mpd_command_listall(struct evbuffer *evbuf, int argc, char **argv, char **errmsg
   dir_id = db_directory_id_byvirtualpath(parent);
   if (dir_id == 0)
     {
-      ret = asprintf(errmsg, "Directory info not found for virtual-path '%s'", parent);
-      if (ret < 0)
-      	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Directory info not found for virtual-path '%s'", parent);
       return ACK_ERROR_NO_EXIST;
     }
 
@@ -2863,9 +2739,7 @@ mpd_command_listallinfo(struct evbuffer *evbuf, int argc, char **argv, char **er
 
   if ((ret < 0) || (ret >= sizeof(parent)))
     {
-      ret = asprintf(errmsg, "Parent path exceeds PATH_MAX");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Parent path exceeds PATH_MAX");
       return ACK_ERROR_UNKNOWN;
     }
 
@@ -2873,9 +2747,7 @@ mpd_command_listallinfo(struct evbuffer *evbuf, int argc, char **argv, char **er
   dir_id = db_directory_id_byvirtualpath(parent);
   if (dir_id == 0)
     {
-      ret = asprintf(errmsg, "Directory info not found for virtual-path '%s'", parent);
-      if (ret < 0)
-      	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Directory info not found for virtual-path '%s'", parent);
       return ACK_ERROR_NO_EXIST;
     }
 
@@ -2912,9 +2784,7 @@ mpd_command_lsinfo(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
 
   if ((ret < 0) || (ret >= sizeof(parent)))
     {
-      ret = asprintf(errmsg, "Parent path exceeds PATH_MAX");
-      if (ret < 0)
-      	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Parent path exceeds PATH_MAX");
       return ACK_ERROR_UNKNOWN;
     }
 
@@ -2934,9 +2804,7 @@ mpd_command_lsinfo(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
   dir_id = db_directory_id_byvirtualpath(parent);
   if (dir_id == 0)
     {
-      ret = asprintf(errmsg, "Directory info not found for virtual-path '%s'", parent);
-      if (ret < 0)
-      	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Directory info not found for virtual-path '%s'", parent);
       return ACK_ERROR_NO_EXIST;
     }
 
@@ -3089,9 +2957,7 @@ mpd_command_search(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
 
   if (argc < 3 || ((argc - 1) % 2) != 0)
     {
-      ret = asprintf(errmsg, "Missing argument(s) for command 'search'");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Missing argument(s) for command 'search'");
       return ACK_ERROR_ARG;
     }
 
@@ -3107,10 +2973,9 @@ mpd_command_search(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
   if (ret < 0)
     {
       db_query_end(&qp);
+      sqlite3_free(qp.filter);
 
-      ret = asprintf(errmsg, "Could not start query");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Could not start query");
       return ACK_ERROR_UNKNOWN;
     }
 
@@ -3124,6 +2989,7 @@ mpd_command_search(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
     }
 
   db_query_end(&qp);
+  sqlite3_free(qp.filter);
 
   return 0;
 }
@@ -3132,14 +2998,12 @@ static int
 mpd_command_searchadd(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
 {
   struct query_params qp;
-  struct queue_item *items;
+  struct player_status status;
   int ret;
 
   if (argc < 3 || ((argc - 1) % 2) != 0)
     {
-      ret = asprintf(errmsg, "Missing argument(s) for command 'search'");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Missing argument(s) for command 'search'");
       return ACK_ERROR_ARG;
     }
 
@@ -3151,18 +3015,16 @@ mpd_command_searchadd(struct evbuffer *evbuf, int argc, char **argv, char **errm
 
   mpd_get_query_params_search(argc - 1, argv + 1, &qp);
 
-  items = queueitem_make_byquery(&qp);
+  player_get_status(&status);
 
-  if (!items)
+  ret = db_queue_add_by_query(&qp, status.shuffle, status.item_id);
+  sqlite3_free(qp.filter);
+  if (ret < 0)
     {
-      ret = asprintf(errmsg, "Failed to add songs to playlist");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Failed to add songs to playlist");
       return ACK_ERROR_UNKNOWN;
     }
 
-  player_queue_add(items, NULL);
-
   return 0;
 }
 
@@ -3173,17 +3035,13 @@ mpd_command_searchadd(struct evbuffer *evbuf, int argc, char **argv, char **errm
 static int
 mpd_command_update(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
 {
-  int ret;
-
   if (argc > 1 && strlen(argv[1]) > 0)
     {
-      ret = asprintf(errmsg, "Update for specific uri not supported for command 'update'");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Update for specific uri not supported for command 'update'");
       return ACK_ERROR_ARG;
     }
 
-  filescanner_trigger_initscan();
+  library_rescan();
 
   evbuffer_add(evbuf, "updating_db: 1\n", 15);
 
@@ -3199,9 +3057,7 @@ mpd_command_rescan(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
   if (argc > 1)
     {
       DPRINTF(E_LOG, L_MPD, "Rescan for specific uri not supported for command 'rescan'\n");
-      ret = asprintf(errmsg, "Rescan for specific uri not supported for command 'rescan'");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Rescan for specific uri not supported for command 'rescan'");
       return ACK_ERROR_ARG;
     }
 
@@ -3259,18 +3115,14 @@ mpd_command_disableoutput(struct evbuffer *evbuf, int argc, char **argv, char **
 
   if (argc < 2)
     {
-      ret = asprintf(errmsg, "Missing argument for command 'disableoutput'");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Missing argument for command 'disableoutput'");
       return ACK_ERROR_ARG;
     }
 
   ret = safe_atou32(argv[1], &num);
   if (ret < 0)
     {
-      ret = asprintf(errmsg, "Argument doesn't convert to integer: '%s'", argv[1]);
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Argument doesn't convert to integer: '%s'", argv[1]);
       return ACK_ERROR_ARG;
     }
 
@@ -3323,9 +3175,7 @@ mpd_command_disableoutput(struct evbuffer *evbuf, int argc, char **argv, char **
 
   if (ret < 0)
     {
-      ret = asprintf(errmsg, "Speakers deactivation failed: %d", num);
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Speakers deactivation failed: %d", num);
       return ACK_ERROR_UNKNOWN;
     }
 
@@ -3349,18 +3199,14 @@ mpd_command_enableoutput(struct evbuffer *evbuf, int argc, char **argv, char **e
 
   if (argc < 2)
     {
-      ret = asprintf(errmsg, "Missing argument for command 'enableoutput'");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Missing argument for command 'enableoutput'");
       return ACK_ERROR_ARG;
     }
 
   ret = safe_atou32(argv[1], &num);
   if (ret < 0)
     {
-      ret = asprintf(errmsg, "Argument doesn't convert to integer: '%s'", argv[1]);
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Argument doesn't convert to integer: '%s'", argv[1]);
       return ACK_ERROR_ARG;
     }
 
@@ -3414,9 +3260,7 @@ mpd_command_enableoutput(struct evbuffer *evbuf, int argc, char **argv, char **e
 
   if (ret < 0)
     {
-      ret = asprintf(errmsg, "Speakers activation failed: %d", num);
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Speakers activation failed: %d", num);
       return ACK_ERROR_UNKNOWN;
     }
 
@@ -3440,18 +3284,14 @@ mpd_command_toggleoutput(struct evbuffer *evbuf, int argc, char **argv, char **e
 
   if (argc < 2)
     {
-      ret = asprintf(errmsg, "Missing argument for command 'toggleoutput'");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Missing argument for command 'toggleoutput'");
       return ACK_ERROR_ARG;
     }
 
   ret = safe_atou32(argv[1], &num);
   if (ret < 0)
     {
-      ret = asprintf(errmsg, "Argument doesn't convert to integer: '%s'", argv[1]);
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Argument doesn't convert to integer: '%s'", argv[1]);
       return ACK_ERROR_ARG;
     }
 
@@ -3511,9 +3351,7 @@ mpd_command_toggleoutput(struct evbuffer *evbuf, int argc, char **argv, char **e
 
   if (ret < 0)
     {
-      ret = asprintf(errmsg, "Speakers de/activation failed: %d", num);
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Speakers de/activation failed: %d", num);
       return ACK_ERROR_UNKNOWN;
     }
 
@@ -3561,78 +3399,231 @@ mpd_command_outputs(struct evbuffer *evbuf, int argc, char **argv, char **errmsg
 }
 
 static int
+outputvolume_set(uint32_t shortid, int volume, char **errmsg)
+{
+  struct outputs outputs;
+  struct output *output;
+  int ret;
+
+  outputs.count = 0;
+  outputs.active = 0;
+  outputs.outputs = NULL;
+
+  player_speaker_enumerate(outputs_enum_cb, &outputs);
+
+  output = outputs.outputs;
+  while (output)
+    {
+      if (output->shortid == shortid)
+	{
+	  break;
+	}
+      output = output->next;
+    }
+
+  if (!output)
+    {
+      free_outputs(outputs.outputs);
+      *errmsg = safe_asprintf("No speaker found for short id: %d", shortid);
+      return ACK_ERROR_UNKNOWN;
+    }
+
+  ret = player_volume_setabs_speaker(output->id, volume);
+
+  free_outputs(outputs.outputs);
+
+  if (ret < 0)
+    {
+      *errmsg = safe_asprintf("Setting volume to %d for speaker with short-id %d failed", volume, shortid);
+      return ACK_ERROR_UNKNOWN;
+    }
+
+  return 0;
+}
+
+static int
 mpd_command_outputvolume(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
 {
   uint32_t shortid;
   int volume;
-  struct outputs outputs;
-  struct output *output;
   int ret;
 
   if (argc < 3)
     {
-      ret = asprintf(errmsg, "Missing argument for command 'outputvolume'");
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Missing argument for command 'outputvolume'");
       return ACK_ERROR_ARG;
     }
 
   ret = safe_atou32(argv[1], &shortid);
   if (ret < 0)
     {
-      ret = asprintf(errmsg, "Argument doesn't convert to integer: '%s'", argv[1]);
-      if (ret < 0)
-      DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Argument doesn't convert to integer: '%s'", argv[1]);
       return ACK_ERROR_ARG;
     }
 
   ret = safe_atoi32(argv[2], &volume);
   if (ret < 0)
     {
-      ret = asprintf(errmsg, "Argument doesn't convert to integer: '%s'", argv[2]);
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+      *errmsg = safe_asprintf("Argument doesn't convert to integer: '%s'", argv[2]);
       return ACK_ERROR_ARG;
     }
 
-  outputs.count = 0;
-  outputs.active = 0;
-  outputs.outputs = NULL;
+  ret = outputvolume_set(shortid, volume, errmsg);
 
-  player_speaker_enumerate(outputs_enum_cb, &outputs);
+  return ret;
+}
 
-  output = outputs.outputs;
-  while (output)
+static void
+channel_outputvolume(const char *message)
+{
+  uint32_t shortid;
+  int volume;
+  char *tmp;
+  char *ptr;
+  char *errmsg = NULL;
+  int ret;
+
+  tmp = strdup(message);
+  ptr = strrchr(tmp, ':');
+  if (!ptr)
     {
-      if (output->shortid == shortid)
+      free(tmp);
+      DPRINTF(E_LOG, L_MPD, "Failed to parse output id and volume from message '%s' (expected format: \"output-id:volume\"\n", message);
+      return;
+    }
+
+  *ptr = '\0';
+
+  ret = safe_atou32(tmp, &shortid);
+  if (ret < 0)
+    {
+      free(tmp);
+      DPRINTF(E_LOG, L_MPD, "Failed to parse output id from message: '%s'\n", message);
+      return;
+    }
+
+  ret = safe_atoi32((ptr + 1), &volume);
+  if (ret < 0)
+    {
+      free(tmp);
+      DPRINTF(E_LOG, L_MPD, "Failed to parse volume from message: '%s'\n", message);
+      return;
+    }
+
+  outputvolume_set(shortid, volume, &errmsg);
+  if (errmsg)
+    DPRINTF(E_LOG, L_MPD, "Failed to set output volume from message: '%s' (error='%s')\n", message, errmsg);
+
+  free(tmp);
+}
+
+static void
+channel_pairing(const char *message)
+{
+  remote_pairing_kickoff((char **)&message);
+}
+
+static void
+channel_verification(const char *message)
+{
+  player_raop_verification_kickoff((char **)&message);
+}
+
+struct mpd_channel
+{
+  /* The channel name */
+  const char *channel;
+
+  /*
+   * The function to execute the sendmessage command for a specific channel
+   *
+   * @param message message received on this channel
+   */
+  void (*handler)(const char *message);
+};
+
+static struct mpd_channel mpd_channels[] =
+  {
+    {
+      .channel = "outputvolume",
+      .handler = channel_outputvolume
+    },
+    {
+      .channel = "pairing",
+      .handler = channel_pairing
+    },
+    {
+      .channel = "verification",
+      .handler = channel_verification
+    },
+    {
+      .channel = NULL,
+      .handler = NULL
+    },
+  };
+
+/*
+ * Finds the channel handler for the given channel name
+ *
+ * @param name channel name from sendmessage command
+ * @return the channel or NULL if it is an unknown/unsupported channel
+ */
+static struct mpd_channel *
+mpd_find_channel(const char *name)
+{
+  int i;
+
+  for (i = 0; mpd_channels[i].handler; i++)
+    {
+      if (0 == strcmp(name, mpd_channels[i].channel))
 	{
-	  break;
+	  return &mpd_channels[i];
 	}
-      output = output->next;
     }
 
-  if (!output)
+  return NULL;
+}
+
+static int
+mpd_command_channels(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
+{
+  int i;
+
+  for (i = 0; mpd_channels[i].handler; i++)
     {
-      free_outputs(outputs.outputs);
-      ret = asprintf(errmsg, "No speaker found for short id: %d", shortid);
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
-      return ACK_ERROR_UNKNOWN;
+      evbuffer_add_printf(evbuf,
+      		      "channel: %s\n",
+      		      mpd_channels[i].channel);
     }
 
-  ret = player_volume_setabs_speaker(output->id, volume);
+  return 0;
+}
 
-  if (ret < 0)
+static int
+mpd_command_sendmessage(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
+{
+  const char *channelname;
+  const char *message;
+  struct mpd_channel *channel;
+
+  if (argc < 3)
     {
-      free_outputs(outputs.outputs);
-      ret = asprintf(errmsg, "Setting volume to %d for speaker with short-id %d failed", volume, shortid);
-      if (ret < 0)
-	DPRINTF(E_LOG, L_MPD, "Out of memory\n");
-      return ACK_ERROR_UNKNOWN;
+      *errmsg = safe_asprintf("Missing argument for command 'sendmessage'");
+      return ACK_ERROR_ARG;
     }
 
-  free_outputs(outputs.outputs);
+  channelname = argv[1];
+  message = argv[2];
+
+  channel = mpd_find_channel(channelname);
+  if (!channel)
+    {
+      // Just ignore the message, only log an error message
+      DPRINTF(E_LOG, L_MPD, "Unsupported channel '%s'\n", channelname);
+      return 0;
+    }
 
+  channel->handler(message);
   return 0;
 }
 
@@ -3752,7 +3743,7 @@ static struct mpd_command mpd_handlers[] =
      */
     {
       .mpdcommand = "consume",
-      .handler = mpd_command_ignore
+      .handler = mpd_command_consume
     },
     {
       .mpdcommand = "crossfade",
@@ -4167,7 +4158,7 @@ static struct mpd_command mpd_handlers[] =
     },
     {
       .mpdcommand = "channels",
-      .handler = mpd_command_ignore
+      .handler = mpd_command_channels
     },
     {
       .mpdcommand = "readmessages",
@@ -4175,7 +4166,7 @@ static struct mpd_command mpd_handlers[] =
     },
     {
       .mpdcommand = "sendmessage",
-      .handler = mpd_command_ignore
+      .handler = mpd_command_sendmessage
     },
 
     /*
@@ -4268,6 +4259,7 @@ mpd_read_cb(struct bufferevent *bev, void *ctx)
 
   listtype = COMMAND_LIST_NONE;
   ncmd = 0;
+  ret = -1;
 
   while ((line = evbuffer_readln(input, NULL, EVBUFFER_EOL_ANY)))
     {
@@ -4275,13 +4267,11 @@ mpd_read_cb(struct bufferevent *bev, void *ctx)
 
       // Split the read line into command name and arguments
       ret = mpd_parse_args(line, &argc, argv);
-      if (ret != 0)
+      if (ret != 0 || argc <= 0)
 	{
 	  // Error handling for argument parsing error
 	  DPRINTF(E_LOG, L_MPD, "Error parsing arguments for MPD message: %s\n", line);
-	  ret = asprintf(&errmsg, "Error parsing arguments");
-	  if (ret < 0)
-	    DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+	  errmsg = safe_asprintf("Error parsing arguments");
 	  ret = ACK_ERROR_ARG;
 	  evbuffer_add_printf(output, "ACK [%d@%d] {%s} %s\n", ret, ncmd, "unkown", errmsg);
 	  free(errmsg);
@@ -4323,9 +4313,7 @@ mpd_read_cb(struct bufferevent *bev, void *ctx)
 
       if (command == NULL)
 	{
-	  ret = asprintf(&errmsg, "Unsupported command '%s'", argv[0]);
-	  if (ret < 0)
-	    DPRINTF(E_LOG, L_MPD, "Out of memory\n");
+	  errmsg = safe_asprintf("Unsupported command '%s'", argv[0]);
 	  ret = ACK_ERROR_UNKNOWN;
 	}
       else
@@ -4508,7 +4496,7 @@ mpd_notify_idle_client(struct idle_client *client, enum listener_event_type type
 	evbuffer_add(client->evbuffer, "changed: player\n", 16);
 	break;
 
-      case LISTENER_PLAYLIST:
+      case LISTENER_QUEUE:
 	evbuffer_add(client->evbuffer, "changed: playlist\n", 18);
 	break;
 
@@ -4835,7 +4823,7 @@ int mpd_init(void)
 #endif
 
   idle_clients = NULL;
-  listener_add(mpd_listener_cb, LISTENER_PLAYER | LISTENER_PLAYLIST | LISTENER_VOLUME | LISTENER_SPEAKER | LISTENER_OPTIONS);
+  listener_add(mpd_listener_cb, LISTENER_PLAYER | LISTENER_QUEUE | LISTENER_VOLUME | LISTENER_SPEAKER | LISTENER_OPTIONS);
 
   return 0;
 
diff --git a/src/mxml-compat.h b/src/mxml-compat.h
new file mode 100644
index 0000000..1437ad8
--- /dev/null
+++ b/src/mxml-compat.h
@@ -0,0 +1,58 @@
+#ifndef __MXML_COMPAT_H__
+#define __MXML_COMPAT_H__
+
+/* For compability with mxml 2.6 */
+#ifndef HAVE_MXMLGETTEXT
+__attribute__((unused)) static const char *			/* O - Text string or NULL */
+mxmlGetText(mxml_node_t *node,		/* I - Node to get */
+            int         *whitespace)	/* O - 1 if string is preceded by whitespace, 0 otherwise */
+{
+  if (node->type == MXML_TEXT)
+    return (node->value.text.string);
+  else if (node->type == MXML_ELEMENT &&
+           node->child &&
+	   node->child->type == MXML_TEXT)
+    return (node->child->value.text.string);
+  else
+    return (NULL);
+}
+#endif
+
+#ifndef HAVE_MXMLGETOPAQUE
+__attribute__((unused)) static const char *			/* O - Opaque string or NULL */
+mxmlGetOpaque(mxml_node_t *node)	/* I - Node to get */
+{
+  if (!node)
+    return (NULL);
+
+  if (node->type == MXML_OPAQUE)
+    return (node->value.opaque);
+  else if (node->type == MXML_ELEMENT &&
+           node->child &&
+	   node->child->type == MXML_OPAQUE)
+    return (node->child->value.opaque);
+  else
+    return (NULL);
+}
+#endif
+
+#ifndef HAVE_MXMLGETFIRSTCHILD
+__attribute__((unused)) static mxml_node_t *			/* O - First child or NULL */
+mxmlGetFirstChild(mxml_node_t *node)	/* I - Node to get */
+{
+  if (!node || node->type != MXML_ELEMENT)
+    return (NULL);
+
+  return (node->child);
+}
+#endif
+
+#ifndef HAVE_MXMLGETTYPE
+__attribute__((unused)) static mxml_type_t			/* O - Type of node */
+mxmlGetType(mxml_node_t *node)		/* I - Node to get */
+{
+  return (node->type);
+}
+#endif
+
+#endif /* !__MXML_COMPAT_H__ */
diff --git a/src/outputs.c b/src/outputs.c
index c6cd1bb..8dd5449 100644
--- a/src/outputs.c
+++ b/src/outputs.c
@@ -35,10 +35,10 @@ extern struct output_definition output_raop;
 extern struct output_definition output_streaming;
 extern struct output_definition output_dummy;
 extern struct output_definition output_fifo;
-#ifdef ALSA
+#ifdef HAVE_ALSA
 extern struct output_definition output_alsa;
 #endif
-#ifdef PULSEAUDIO
+#ifdef HAVE_LIBPULSE
 extern struct output_definition output_pulse;
 #endif
 #ifdef CHROMECAST
@@ -51,10 +51,10 @@ static struct output_definition *outputs[] = {
     &output_streaming,
     &output_dummy,
     &output_fifo,
-#ifdef ALSA
+#ifdef HAVE_ALSA
     &output_alsa,
 #endif
-#ifdef PULSEAUDIO
+#ifdef HAVE_LIBPULSE
     &output_pulse,
 #endif
 #ifdef CHROMECAST
@@ -100,23 +100,22 @@ outputs_device_probe(struct output_device *device, output_status_cb cb)
 void
 outputs_device_free(struct output_device *device)
 {
+  if (!device)
+    return;
+
   if (outputs[device->type]->disabled)
     DPRINTF(E_LOG, L_PLAYER, "BUG! Freeing device from a disabled output?\n");
 
+  if (device->session)
+    DPRINTF(E_LOG, L_PLAYER, "BUG! Freeing device with active session?\n");
+
   if (outputs[device->type]->device_free_extra)
     outputs[device->type]->device_free_extra(device);
 
-  if (device->name)
-    free(device->name);
-
-  if (device->v4_address)
-    free(device->v4_address);
-
-  if (device->v6_address)
-    free(device->v6_address);
-
-  if (device->session)
-    DPRINTF(E_LOG, L_PLAYER, "BUG! Freeing device with active session?\n");
+  free(device->name);
+  free(device->auth_key);
+  free(device->v4_address);
+  free(device->v6_address);
 
   free(device);
 }
@@ -313,6 +312,16 @@ outputs_metadata_free(struct output_metadata *omd)
     }
 }
 
+void
+outputs_authorize(enum output_types type, const char *pin)
+{
+  if (outputs[type]->disabled)
+    return;
+
+  if (outputs[type]->authorize)
+    outputs[type]->authorize(pin);
+}
+
 int
 outputs_priority(struct output_device *device)
 {
@@ -342,7 +351,10 @@ outputs_init(void)
 	}
 
       if (!outputs[i]->init)
-	continue;
+	{
+	  no_output = 0;
+	  continue;
+	}
 
       ret = outputs[i]->init();
       if (ret < 0)
diff --git a/src/outputs.h b/src/outputs.h
index 52b16a6..7e8b1e0 100644
--- a/src/outputs.h
+++ b/src/outputs.h
@@ -53,10 +53,10 @@ enum output_types
   OUTPUT_TYPE_STREAMING,
   OUTPUT_TYPE_DUMMY,
   OUTPUT_TYPE_FIFO,
-#ifdef ALSA
+#ifdef HAVE_ALSA
   OUTPUT_TYPE_ALSA,
 #endif
-#ifdef PULSEAUDIO
+#ifdef HAVE_LIBPULSE
   OUTPUT_TYPE_PULSE,
 #endif
 #ifdef CHROMECAST
@@ -102,9 +102,11 @@ struct output_device
   unsigned advertised:1;
   unsigned has_password:1;
   unsigned has_video:1;
+  unsigned requires_auth:1;
 
-  // Password if relevant
+  // Credentials if relevant
   const char *password;
+  char *auth_key;
 
   // Device volume
   int volume;
@@ -187,6 +189,9 @@ struct output_definition
   // Flush all sessions, the return must be number of sessions pending the flush
   int (*flush)(output_status_cb cb, uint64_t rtptime);
 
+  // Authorize an output with a pin-code (probably coming from the filescanner)
+  void (*authorize)(const char *pin);
+
   // Change the call back associated with a session
   void (*status_cb)(struct output_session *session, output_status_cb cb);
 
@@ -242,6 +247,9 @@ outputs_metadata_prune(uint64_t rtptime);
 void
 outputs_metadata_free(struct output_metadata *omd);
 
+void
+outputs_authorize(enum output_types type, const char *pin);
+
 int
 outputs_priority(struct output_device *device);
 
diff --git a/src/outputs/alsa.c b/src/outputs/alsa.c
index ee3aa3d..8727c99 100644
--- a/src/outputs/alsa.c
+++ b/src/outputs/alsa.c
@@ -32,6 +32,7 @@
 #include <event2/event.h>
 #include <asoundlib.h>
 
+#include "misc.h"
 #include "conffile.h"
 #include "logger.h"
 #include "player.h"
@@ -170,10 +171,17 @@ alsa_session_make(struct output_device *device, output_status_cb cb)
   struct alsa_session *as;
 
   os = calloc(1, sizeof(struct output_session));
+  if (!os)
+    {
+      DPRINTF(E_LOG, L_LAUDIO, "Out of memory for ALSA session (os)\n");
+      return NULL;
+    }
+
   as = calloc(1, sizeof(struct alsa_session));
-  if (!os || !as)
+  if (!as)
     {
-      DPRINTF(E_LOG, L_LAUDIO, "Out of memory for ALSA session\n");
+      DPRINTF(E_LOG, L_LAUDIO, "Out of memory for ALSA session (as)\n");
+      free(os);
       return NULL;
     }
 
@@ -803,7 +811,10 @@ alsa_device_start(struct output_device *device, output_status_cb cb, uint64_t rt
 
   ret = device_open(as);
   if (ret < 0)
-    return -1;
+    {
+      alsa_session_cleanup(as);
+      return -1;
+    }
 
   as->state = ALSA_STATE_STARTED;
   alsa_status(as);
@@ -1009,7 +1020,7 @@ alsa_init(void)
     }
 
   device->id = 0;
-  device->name = nickname;
+  device->name = strdup(nickname);
   device->type = OUTPUT_TYPE_ALSA;
   device->type_name = outputs_name(device->type);
   device->advertised = 1;
diff --git a/src/outputs/cast.c b/src/outputs/cast.c
index 208fb7c..bc41ec0 100644
--- a/src/outputs/cast.c
+++ b/src/outputs/cast.c
@@ -30,18 +30,23 @@
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <arpa/inet.h>
+#include <net/if.h>
+#include <netinet/in.h>
 #include <ifaddrs.h>
 #include <unistd.h>
 #include <fcntl.h>
-#include <endian.h>
+#ifdef HAVE_ENDIAN_H
+# include <endian.h>
+#elif defined(HAVE_SYS_ENDIAN_H)
+# include <sys/endian.h>
+#elif defined(HAVE_LIBKERN_OSBYTEORDER_H)
+#include <libkern/OSByteOrder.h>
+#define htobe32(x) OSSwapHostToBigInt32(x)
+#define be32toh(x) OSSwapBigToHostInt32(x)
+#endif
 #include <gnutls/gnutls.h>
 #include <event2/event.h>
-
-#ifdef HAVE_JSON_C_OLD
-# include <json/json.h>
-#else
-# include <json-c/json.h>
-#endif
+#include <json.h>
 
 #include "conffile.h"
 #include "mdns.h"
@@ -368,7 +373,11 @@ tcp_connect(const char *address, unsigned int port, int family)
 
   // TODO Open non-block right away so we don't block the player while connecting
   // and during TLS handshake (we would probably need to introduce a deferredev)
+#ifdef SOCK_CLOEXEC
   fd = socket(family, SOCK_STREAM | SOCK_CLOEXEC, 0);
+#else
+  fd = socket(family, SOCK_STREAM, 0);
+#endif
   if (fd < 0)
     {
       DPRINTF(E_LOG, L_CAST, "Could not create socket: %s\n", strerror(errno));
@@ -1220,6 +1229,7 @@ static void
 cast_device_cb(const char *name, const char *type, const char *domain, const char *hostname, int family, const char *address, int port, struct keyval *txt)
 {
   struct output_device *device;
+  const char *friendly_name;
   uint32_t id;
 
   id = djb_hash(name, strlen(name));
@@ -1229,6 +1239,10 @@ cast_device_cb(const char *name, const char *type, const char *domain, const cha
       return;
     }
 
+  friendly_name = keyval_get(txt, "fn");
+  if (friendly_name)
+    name = friendly_name;
+
   DPRINTF(E_DBG, L_CAST, "Event for Chromecast device '%s' (port %d, id %" PRIu32 ")\n", name, port, id);
 
   device = calloc(1, sizeof(struct output_device));
@@ -1323,10 +1337,17 @@ cast_session_make(struct output_device *device, int family, output_status_cb cb)
     }
 
   os = calloc(1, sizeof(struct output_session));
+  if (!os)
+    {
+      DPRINTF(E_LOG, L_CAST, "Out of memory (os)\n");
+      return NULL;
+    }
+
   cs = calloc(1, sizeof(struct cast_session));
-  if (!os || !cs)
+  if (!cs)
     {
-      DPRINTF(E_LOG, L_CAST, "Out of memory for TLS session\n");
+      DPRINTF(E_LOG, L_CAST, "Out of memory (cs)\n");
+      free(os);
       return NULL;
     }
 
@@ -1450,6 +1471,8 @@ cast_session_shutdown(struct cast_session *cs, enum cast_state wanted_state)
 	if ((ret < 0) || (wanted_state >= CAST_STATE_MEDIA_LAUNCHED))
 	  break;
 
+	/* FALLTHROUGH */
+
       case CAST_STATE_MEDIA_LAUNCHED:
 	ret = cast_msg_send(cs, STOP, cast_cb_stop);
 	pending = 1;
diff --git a/src/outputs/dummy.c b/src/outputs/dummy.c
index 058d045..e5dfad2 100644
--- a/src/outputs/dummy.c
+++ b/src/outputs/dummy.c
@@ -91,10 +91,17 @@ dummy_session_make(struct output_device *device, output_status_cb cb)
   struct dummy_session *ds;
 
   os = calloc(1, sizeof(struct output_session));
+  if (!os)
+    {
+      DPRINTF(E_LOG, L_LAUDIO, "Out of memory for dummy session (os)\n");
+      return NULL;
+    }
+
   ds = calloc(1, sizeof(struct dummy_session));
-  if (!os || !ds)
+  if (!ds)
     {
-      DPRINTF(E_LOG, L_LAUDIO, "Out of memory for dummy session\n");
+      DPRINTF(E_LOG, L_LAUDIO, "Out of memory for dummy session (as)\n");
+      free(os);
       return NULL;
     }
 
@@ -259,7 +266,7 @@ dummy_init(void)
     }
 
   device->id = 0;
-  device->name = nickname;
+  device->name = strdup(nickname);
   device->type = OUTPUT_TYPE_DUMMY;
   device->type_name = outputs_name(device->type);
   device->advertised = 1;
diff --git a/src/outputs/fifo.c b/src/outputs/fifo.c
index 6fb81f1..dd44ceb 100644
--- a/src/outputs/fifo.c
+++ b/src/outputs/fifo.c
@@ -33,6 +33,7 @@
 
 #include <event2/event.h>
 
+#include "misc.h"
 #include "conffile.h"
 #include "logger.h"
 #include "player.h"
@@ -40,6 +41,45 @@
 
 #define FIFO_BUFFER_SIZE 65536 /* pipe capacity on Linux >= 2.6.11 */
 
+
+struct fifo_packet
+{
+  /* pcm data */
+  uint8_t samples[1408]; // STOB(AIRTUNES_V2_PACKET_SAMPLES)
+
+  /* RTP-time of the first sample*/
+  uint64_t rtptime;
+
+  struct fifo_packet *next;
+  struct fifo_packet *prev;
+};
+
+struct fifo_buffer
+{
+  struct fifo_packet *head;
+  struct fifo_packet *tail;
+};
+static struct fifo_buffer buffer;
+
+
+static void
+free_buffer()
+{
+  struct fifo_packet *packet;
+  struct fifo_packet *tmp;
+
+  packet = buffer.tail;
+  while (packet)
+    {
+      tmp = packet;
+      packet = packet->next;
+      free(tmp);
+    }
+
+  buffer.tail = NULL;
+  buffer.head = NULL;
+}
+
 struct fifo_session
 {
   enum output_device_state state;
@@ -201,7 +241,7 @@ fifo_session_free(struct fifo_session *fifo_session)
 
   free(fifo_session->output_session);
   free(fifo_session);
-
+  free_buffer();
   fifo_session = NULL;
 }
 
@@ -221,10 +261,17 @@ fifo_session_make(struct output_device *device, output_status_cb cb)
   struct fifo_session *fifo_session;
 
   output_session = calloc(1, sizeof(struct output_session));
+  if (!output_session)
+    {
+      DPRINTF(E_LOG, L_FIFO, "Out of memory (os)\n");
+      return NULL;
+    }
+
   fifo_session = calloc(1, sizeof(struct fifo_session));
-  if (!output_session || !fifo_session)
+  if (!fifo_session)
     {
-      DPRINTF(E_LOG, L_FIFO, "Out of memory for fifo session\n");
+      DPRINTF(E_LOG, L_FIFO, "Out of memory (fs)\n");
+      free(output_session);
       return NULL;
     }
 
@@ -308,6 +355,7 @@ fifo_device_stop(struct output_session *output_session)
   struct fifo_session *fifo_session = output_session->session;
 
   fifo_close(fifo_session);
+  free_buffer();
 
   fifo_session->state = OUTPUT_STATE_STOPPED;
   fifo_status(fifo_session);
@@ -376,6 +424,8 @@ fifo_playback_stop(void)
   if (!fifo_session)
     return;
 
+  free_buffer();
+
   fifo_session->state = OUTPUT_STATE_CONNECTED;
   fifo_status(fifo_session);
 }
@@ -389,11 +439,12 @@ fifo_flush(output_status_cb cb, uint64_t rtptime)
     return 0;
 
   fifo_empty(fifo_session);
+  free_buffer();
 
   fifo_session->status_cb = cb;
   fifo_session->state = OUTPUT_STATE_CONNECTED;
   fifo_status(fifo_session);
-  return 0;
+  return 1;
 }
 
 static void
@@ -402,15 +453,43 @@ fifo_write(uint8_t *buf, uint64_t rtptime)
   struct fifo_session *fifo_session = sessions;
   size_t length = STOB(AIRTUNES_V2_PACKET_SAMPLES);
   ssize_t bytes;
+  struct fifo_packet *packet;
+  uint64_t cur_pos;
+  struct timespec now;
+  int ret;
 
   if (!fifo_session || !fifo_session->device->selected)
     return;
 
-  while (1)
+  packet = (struct fifo_packet *) calloc(1, sizeof(struct fifo_packet));
+  memcpy(packet->samples, buf, sizeof(packet->samples));
+  packet->rtptime = rtptime;
+  if (buffer.head)
+    {
+      buffer.head->next = packet;
+      packet->prev = buffer.head;
+    }
+  buffer.head = packet;
+  if (!buffer.tail)
+    buffer.tail = packet;
+
+  ret = player_get_current_pos(&cur_pos, &now, 0);
+  if (ret < 0)
+    {
+      DPRINTF(E_LOG, L_FIFO, "Could not get playback position\n");
+      return;
+    }
+
+  while (buffer.tail && buffer.tail->rtptime <= cur_pos)
     {
-      bytes = write(fifo_session->output_fd, buf, length);
+      bytes = write(fifo_session->output_fd, buffer.tail->samples, length);
       if (bytes > 0)
-	return;
+	{
+	  packet = buffer.tail;
+	  buffer.tail = buffer.tail->next;
+	  free(packet);
+	  return;
+	}
 
       if (bytes < 0)
 	{
@@ -456,6 +535,8 @@ fifo_init(void)
 
   nickname = cfg_getstr(cfg_fifo, "nickname");
 
+  memset(&buffer, 0, sizeof(struct fifo_buffer));
+
   device = calloc(1, sizeof(struct output_device));
   if (!device)
     {
diff --git a/src/outputs/pulse.c b/src/outputs/pulse.c
index ae7a4a6..859c055 100644
--- a/src/outputs/pulse.c
+++ b/src/outputs/pulse.c
@@ -146,10 +146,17 @@ pulse_session_make(struct output_device *device, output_status_cb cb)
   struct pulse_session *ps;
 
   os = calloc(1, sizeof(struct output_session));
+  if (!os)
+    {
+      DPRINTF(E_LOG, L_LAUDIO, "Out of memory (os)\n");
+      return NULL;
+    }
+
   ps = calloc(1, sizeof(struct pulse_session));
-  if (!os || !ps)
+  if (!ps)
     {
-      DPRINTF(E_LOG, L_LAUDIO, "Out of memory for Pulseaudio session\n");
+      DPRINTF(E_LOG, L_LAUDIO, "Out of memory (ps)\n");
+      free(os);
       return NULL;
     }
 
@@ -896,7 +903,7 @@ pulse_init(void)
   if (!(pulse.cmdbase = commands_base_new(evbase_player, NULL)))
     goto fail;
 
-#ifdef HAVE_PULSE_MAINLOOP_SET_NAME
+#ifdef HAVE_PA_THREADED_MAINLOOP_SET_NAME
   pa_threaded_mainloop_set_name(pulse.mainloop, "pulseaudio");
 #endif
 
diff --git a/src/outputs/raop.c b/src/outputs/raop.c
index ec98cbe..e0a5736 100644
--- a/src/outputs/raop.c
+++ b/src/outputs/raop.c
@@ -1,4 +1,5 @@
 /*
+ * Copyright (C) 2012-2017 Espen Jürgensen <espenjurgensen at gmail.com>
  * Copyright (C) 2010-2011 Julien BLACHE <jb at jblache.org>
  *
  * RAOP AirTunes v2
@@ -27,15 +28,12 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
-/* TODO:
- * - Support RTSP authentication in all requests (only OPTIONS so far)
- */
-
 #ifdef HAVE_CONFIG_H
 # include <config.h>
 #endif
 
 #include <stdio.h>
+#include <stdbool.h>
 #include <unistd.h>
 #include <stdint.h>
 #include <inttypes.h>
@@ -47,11 +45,15 @@
 #include <fcntl.h>
 #include <time.h>
 
-#if defined(__linux__) || defined(__GLIBC__)
+#ifdef HAVE_ENDIAN_H
 # include <endian.h>
-# include <byteswap.h>
-#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
+#elif defined(HAVE_SYS_ENDIAN_H)
 # include <sys/endian.h>
+#elif defined(HAVE_LIBKERN_OSBYTEORDER_H)
+#include <libkern/OSByteOrder.h>
+#define htobe16(x) OSSwapHostToBigInt16(x)
+#define be16toh(x) OSSwapBigToHostInt16(x)
+#define htobe32(x) OSSwapHostToBigInt32(x)
 #endif
 
 #include <arpa/inet.h>
@@ -73,6 +75,10 @@
 #include "dmap_common.h"
 #include "outputs.h"
 
+#ifdef RAOP_VERIFICATION
+#include "raop_verification.h"
+#endif
+
 #ifndef MIN
 # define MIN(a, b) ((a < b) ? a : b)
 #endif
@@ -119,29 +125,33 @@ enum raop_devtype {
 };
 
 // Session is starting up
-#define RAOP_STATE_F_STARTUP    (1 << 14)
+#define RAOP_STATE_F_STARTUP    (1 << 13)
 // Streaming is up (connection established)
-#define RAOP_STATE_F_CONNECTED  (1 << 15)
+#define RAOP_STATE_F_CONNECTED  (1 << 14)
+// Couldn't start device
+#define RAOP_STATE_F_FAILED     (1 << 15)
 
 enum raop_state {
   // Device is stopped (no session)
   RAOP_STATE_STOPPED   = 0,
   // Session startup
-  RAOP_STATE_STARTUP   = RAOP_STATE_F_STARTUP,
-  RAOP_STATE_OPTIONS   = RAOP_STATE_F_STARTUP | 0x01,
-  RAOP_STATE_ANNOUNCE  = RAOP_STATE_F_STARTUP | 0x02,
-  RAOP_STATE_SETUP     = RAOP_STATE_F_STARTUP | 0x03,
-  RAOP_STATE_RECORD    = RAOP_STATE_F_STARTUP | 0x04,
+  RAOP_STATE_STARTUP   = RAOP_STATE_F_STARTUP | 0x01,
+  RAOP_STATE_OPTIONS   = RAOP_STATE_F_STARTUP | 0x02,
+  RAOP_STATE_ANNOUNCE  = RAOP_STATE_F_STARTUP | 0x03,
+  RAOP_STATE_SETUP     = RAOP_STATE_F_STARTUP | 0x04,
+  RAOP_STATE_RECORD    = RAOP_STATE_F_STARTUP | 0x05,
   // Session established
   // - streaming ready (RECORD sent and acked, connection established)
   // - commands (SET_PARAMETER) are possible
-  RAOP_STATE_CONNECTED = RAOP_STATE_F_CONNECTED,
+  RAOP_STATE_CONNECTED = RAOP_STATE_F_CONNECTED | 0x01,
   // Media data is being sent
-  RAOP_STATE_STREAMING = RAOP_STATE_F_CONNECTED | 0x01,
+  RAOP_STATE_STREAMING = RAOP_STATE_F_CONNECTED | 0x02,
   // Session is failed, couldn't startup or error occurred
-  RAOP_STATE_FAILED    = -1,
+  RAOP_STATE_FAILED    = RAOP_STATE_F_FAILED | 0x01,
   // Password issue: unknown password or bad password
-  RAOP_STATE_PASSWORD  = -2,
+  RAOP_STATE_PASSWORD  = RAOP_STATE_F_FAILED | 0x02,
+  // Device requires verification, pending PIN from user
+  RAOP_STATE_UNVERIFIED= RAOP_STATE_F_FAILED | 0x03,
 };
 
 // Info about the device, which is not required by the player, only internally
@@ -149,8 +159,8 @@ struct raop_extra
 {
   enum raop_devtype devtype;
 
-  unsigned encrypt:1;
-  unsigned wants_metadata:1;
+  bool encrypt;
+  bool wants_metadata;
 };
 
 struct raop_session
@@ -158,11 +168,13 @@ struct raop_session
   struct evrtsp_connection *ctrl;
 
   enum raop_state state;
-  unsigned req_has_auth:1;
-  unsigned encrypt:1;
-  unsigned auth_quirk_itunes:1;
-  unsigned wants_metadata:1;
-  unsigned keep_alive:1;
+  bool req_has_auth;
+  bool encrypt;
+  bool auth_quirk_itunes;
+  bool wants_metadata;
+  bool keep_alive;
+
+  bool only_probe;
 
   struct event *deferredev;
 
@@ -177,6 +189,7 @@ struct raop_session
 
   char *devname;
   char *address;
+  int family;
 
   int volume;
   uint64_t start_rtptime;
@@ -191,6 +204,12 @@ struct raop_session
   unsigned short control_port;
   unsigned short timing_port;
 
+#ifdef RAOP_VERIFICATION
+  /* Device verification, see raop_verification.h */
+  struct verification_verify_context *verification_verify_ctx;
+  struct verification_setup_context *verification_setup_ctx;
+#endif
+
   int server_fd;
 
   union sockaddr_all sa;
@@ -821,12 +840,9 @@ raop_metadata_prune(uint64_t rtptime)
 static void *
 raop_metadata_prepare(int id)
 {
-  struct query_params qp;
-  struct db_media_file_info dbmfi;
-  char filter[32];
+  struct db_queue_item *queue_item;
   struct raop_metadata *rmd;
   struct evbuffer *tmp;
-  uint64_t duration;
   int ret;
 
   rmd = (struct raop_metadata *)malloc(sizeof(struct raop_metadata));
@@ -839,6 +855,14 @@ raop_metadata_prepare(int id)
 
   memset(rmd, 0, sizeof(struct raop_metadata));
 
+  queue_item = db_queue_fetch_byitemid(id);
+  if (!queue_item)
+    {
+      DPRINTF(E_LOG, L_RAOP, "Out of memory for queue item\n");
+
+      goto out_rmd;
+    }
+
   /* Get artwork */
   rmd->artwork = evbuffer_new();
   if (!rmd->artwork)
@@ -848,7 +872,7 @@ raop_metadata_prepare(int id)
       goto skip_artwork;
     }
 
-  ret = artwork_get_item(rmd->artwork, id, 600, 600);
+  ret = artwork_get_item(rmd->artwork, queue_item->file_id, 600, 600);
   if (ret < 0)
     {
       DPRINTF(E_INFO, L_RAOP, "Failed to retrieve artwork for file id %d; no artwork will be sent\n", id);
@@ -861,44 +885,13 @@ raop_metadata_prepare(int id)
 
  skip_artwork:
 
-  /* Get dbmfi */
-  memset(&qp, 0, sizeof(struct query_params));
-  qp.type = Q_ITEMS;
-  qp.idx_type = I_NONE;
-  qp.sort = S_NONE;
-  qp.filter = filter;
-
-  ret = snprintf(filter, sizeof(filter), "id = %d", id);
-  if ((ret < 0) || (ret >= sizeof(filter)))
-    {
-      DPRINTF(E_LOG, L_RAOP, "Could not build filter for file id %d; metadata will not be sent\n", id);
-
-      goto out_rmd;
-    }
-
-  ret = db_query_start(&qp);
-  if (ret < 0)
-    {
-      DPRINTF(E_LOG, L_RAOP, "Couldn't start query; no metadata will be sent\n");
-
-      goto out_rmd;
-    }
-
-  ret = db_query_fetch_file(&qp, &dbmfi);
-  if (ret < 0)
-    {
-      DPRINTF(E_LOG, L_RAOP, "Couldn't fetch file id %d; metadata will not be sent\n", id);
-
-      goto out_query;
-    }
-
   /* Turn it into DAAP metadata */
   tmp = evbuffer_new();
   if (!tmp)
     {
       DPRINTF(E_LOG, L_RAOP, "Out of memory for temporary metadata evbuffer; metadata will not be sent\n");
 
-      goto out_query;
+      goto out_qi;
     }
 
   rmd->metadata = evbuffer_new();
@@ -907,10 +900,10 @@ raop_metadata_prepare(int id)
       DPRINTF(E_LOG, L_RAOP, "Out of memory for metadata evbuffer; metadata will not be sent\n");
 
       evbuffer_free(tmp);
-      goto out_query;
+      goto out_qi;
     }
 
-  ret = dmap_encode_file_metadata(rmd->metadata, tmp, &dbmfi, NULL, 0, 0, 1);
+  ret = dmap_encode_queue_metadata(rmd->metadata, tmp, queue_item);
   evbuffer_free(tmp);
   if (ret < 0)
     {
@@ -919,27 +912,18 @@ raop_metadata_prepare(int id)
       goto out_metadata;
     }
 
-  /* Progress */
-  ret = safe_atou64(dbmfi.song_length, &duration);
-  if (ret < 0)
-    {
-      DPRINTF(E_LOG, L_RAOP, "Failed to convert song_length to integer; no metadata will be sent\n");
-
-      goto out_metadata;
-    }
-
-  db_query_end(&qp);
-
-  /* raop_metadata_send() will add rtptime to these */
+  /* Progress - raop_metadata_send() will add rtptime to these */
   rmd->start = 0;
-  rmd->end = (duration * 44100UL) / 1000UL;
+  rmd->end = ((uint64_t)queue_item->song_length * 44100UL) / 1000UL;
+
+  free_queue_item(queue_item, 0);
 
   return rmd;
 
  out_metadata:
   evbuffer_free(rmd->metadata);
- out_query:
-  db_query_end(&qp);
+ out_qi:
+  free_queue_item(queue_item, 0);
  out_rmd:
   free(rmd);
 
@@ -1234,13 +1218,13 @@ raop_check_cseq(struct raop_session *rs, struct evrtsp_request *req)
 }
 
 static int
-raop_make_sdp(struct raop_session *rs, struct evrtsp_request *req, char *address, uint32_t session_id)
+raop_make_sdp(struct raop_session *rs, struct evrtsp_request *req, char *address, int family, uint32_t session_id)
 {
 #define SDP_PLD_FMT							\
   "v=0\r\n"								\
-    "o=iTunes %u 0 IN IP4 %s\r\n"					\
+    "o=iTunes %u 0 IN %s %s\r\n"					\
     "s=iTunes\r\n"							\
-    "c=IN IP4 %s\r\n"							\
+    "c=IN %s %s\r\n"							\
     "t=0 0\r\n"								\
     "m=audio 0 RTP/AVP 96\r\n"						\
     "a=rtpmap:96 AppleLossless\r\n"					\
@@ -1249,17 +1233,22 @@ raop_make_sdp(struct raop_session *rs, struct evrtsp_request *req, char *address
     "a=aesiv:%s\r\n"
 #define SDP_PLD_FMT_NO_ENC						\
   "v=0\r\n"								\
-    "o=iTunes %u 0 IN IP4 %s\r\n"					\
+    "o=iTunes %u 0 IN %s %s\r\n"					\
     "s=iTunes\r\n"							\
-    "c=IN IP4 %s\r\n"							\
+    "c=IN %s %s\r\n"							\
     "t=0 0\r\n"								\
     "m=audio 0 RTP/AVP 96\r\n"						\
     "a=rtpmap:96 AppleLossless\r\n"					\
     "a=fmtp:96 %d 0 16 40 10 14 2 255 0 0 44100\r\n"
 
+  const char *af;
+  const char *rs_af;
   char *p;
   int ret;
 
+  af = (family == AF_INET) ? "IP4" : "IP6";
+  rs_af = (rs->family == AF_INET) ? "IP4" : "IP6";
+
   p = strchr(rs->address, '%');
   if (p)
     *p = '\0';
@@ -1267,11 +1256,11 @@ raop_make_sdp(struct raop_session *rs, struct evrtsp_request *req, char *address
   /* Add SDP payload - but don't add RSA/AES key/iv if no encryption - important for ATV3 update 6.0 */
   if (rs->encrypt)
     ret = evbuffer_add_printf(req->output_buffer, SDP_PLD_FMT,
-			      session_id, address, rs->address, AIRTUNES_V2_PACKET_SAMPLES,
+			      session_id, af, address, rs_af, rs->address, AIRTUNES_V2_PACKET_SAMPLES,
 			      raop_aes_key_b64, raop_aes_iv_b64);
   else
     ret = evbuffer_add_printf(req->output_buffer, SDP_PLD_FMT_NO_ENC,
-			      session_id, address, rs->address, AIRTUNES_V2_PACKET_SAMPLES);
+			      session_id, af, address, rs_af, rs->address, AIRTUNES_V2_PACKET_SAMPLES);
 
   if (p)
     *p = '%';
@@ -1279,7 +1268,6 @@ raop_make_sdp(struct raop_session *rs, struct evrtsp_request *req, char *address
   if (ret < 0)
     {
       DPRINTF(E_LOG, L_RAOP, "Out of memory for SDP payload\n");
-
       return -1;
     }
 
@@ -1559,11 +1547,12 @@ raop_send_req_announce(struct raop_session *rs, evrtsp_req_cb cb)
   char *address;
   char *intf;
   unsigned short port;
+  int family;
   uint32_t session_id;
   int ret;
 
   /* Determine local address, needed for SDP and session URL */
-  evrtsp_connection_get_local_address(rs->ctrl, &address, &port);
+  evrtsp_connection_get_local_address(rs->ctrl, &address, &port, &family);
   if (!address || (port == 0))
     {
       DPRINTF(E_LOG, L_RAOP, "Could not determine local address\n");
@@ -1605,7 +1594,7 @@ raop_send_req_announce(struct raop_session *rs, evrtsp_req_cb cb)
     }
 
   /* SDP payload */
-  ret = raop_make_sdp(rs, req, address, session_id);
+  ret = raop_make_sdp(rs, req, address, family, session_id);
   free(address);
   if (ret < 0)
     {
@@ -1673,7 +1662,6 @@ raop_send_req_options(struct raop_session *rs, evrtsp_req_cb cb)
   if (!req)
     {
       DPRINTF(E_LOG, L_RAOP, "Could not create RTSP request for OPTIONS\n");
-
       return -1;
     }
 
@@ -1688,6 +1676,44 @@ raop_send_req_options(struct raop_session *rs, evrtsp_req_cb cb)
   if (ret < 0)
     {
       DPRINTF(E_LOG, L_RAOP, "Could not make OPTIONS request\n");
+      return -1;
+    }
+
+  rs->reqs_in_flight++;
+
+  evrtsp_connection_set_closecb(rs->ctrl, NULL, NULL);
+
+  return 0;
+}
+
+#ifdef RAOP_VERIFICATION
+static int
+raop_send_req_pin_start(struct raop_session *rs, evrtsp_req_cb cb)
+{
+  struct evrtsp_request *req;
+  int ret;
+
+  req = evrtsp_request_new(cb, rs);
+  if (!req)
+    {
+      DPRINTF(E_LOG, L_RAOP, "Could not create RTSP request for pair-pin-start\n");
+
+      return -1;
+    }
+
+  ret = raop_add_headers(rs, req, EVRTSP_REQ_POST);
+  if (ret < 0)
+    {
+      evrtsp_request_free(req);
+      return -1;
+    }
+
+  DPRINTF(E_LOG, L_RAOP, "Starting device verification for '%s', please submit PIN via a *.verification file\n", rs->devname);
+
+  ret = evrtsp_make_request(rs->ctrl, req, EVRTSP_REQ_POST, "/pair-pin-start");
+  if (ret < 0)
+    {
+      DPRINTF(E_LOG, L_RAOP, "Could not make pair-pin-start request\n");
 
       return -1;
     }
@@ -1698,6 +1724,15 @@ raop_send_req_options(struct raop_session *rs, evrtsp_req_cb cb)
 
   return 0;
 }
+#else
+static int
+raop_send_req_pin_start(struct raop_session *rs, evrtsp_req_cb cb)
+{
+  DPRINTF(E_LOG, L_RAOP, "Device '%s' requires verification, but forked-daapd was built with --disable-verification\n", rs->devname);
+
+  return -1;
+}
+#endif
 
 /* Maps our internal state to the generic output state and then makes a callback
  * to the player to tell that state
@@ -1710,7 +1745,7 @@ raop_status(struct raop_session *rs)
 
   switch (rs->state)
     {
-      case RAOP_STATE_PASSWORD:
+      case RAOP_STATE_PASSWORD ... RAOP_STATE_UNVERIFIED:
 	state = OUTPUT_STATE_PASSWORD;
 	break;
       case RAOP_STATE_FAILED:
@@ -1823,9 +1858,7 @@ raop_session_failure(struct raop_session *rs)
 static void
 raop_deferredev_cb(int fd, short what, void *arg)
 {
-  struct raop_session *rs;
-
-  rs = (struct raop_session *)arg;
+  struct raop_session *rs = arg;
 
   DPRINTF(E_DBG, L_RAOP, "Cleaning up failed session (deferred) on device '%s'\n", rs->devname);
 
@@ -1835,10 +1868,8 @@ raop_deferredev_cb(int fd, short what, void *arg)
 static void
 raop_rtsp_close_cb(struct evrtsp_connection *evcon, void *arg)
 {
+  struct raop_session *rs = arg;
   struct timeval tv;
-  struct raop_session *rs;
-
-  rs = (struct raop_session *)arg;
 
   DPRINTF(E_LOG, L_RAOP, "Device '%s' closed RTSP connection\n", rs->devname);
 
@@ -1849,7 +1880,7 @@ raop_rtsp_close_cb(struct evrtsp_connection *evcon, void *arg)
 }
 
 static struct raop_session *
-raop_session_make(struct output_device *rd, int family, output_status_cb cb)
+raop_session_make(struct output_device *rd, int family, output_status_cb cb, bool only_probe)
 {
   struct output_session *os;
   struct raop_session *rs;
@@ -1885,10 +1916,17 @@ raop_session_make(struct output_device *rd, int family, output_status_cb cb)
     }
 
   os = calloc(1, sizeof(struct output_session));
+  if (!os)
+    {
+      DPRINTF(E_LOG, L_RAOP, "Out of memory (os)\n");
+      return NULL;
+    }
+
   rs = calloc(1, sizeof(struct raop_session));
-  if (!os || !rs)
+  if (!rs)
     {
-      DPRINTF(E_LOG, L_RAOP, "Out of memory for RAOP session\n");
+      DPRINTF(E_LOG, L_RAOP, "Out of memory (rs)\n");
+      free(os);
       return NULL;
     }
 
@@ -1897,6 +1935,7 @@ raop_session_make(struct output_device *rd, int family, output_status_cb cb)
 
   rs->output_session = os;
   rs->state = RAOP_STATE_STOPPED;
+  rs->only_probe = only_probe;
   rs->reqs_in_flight = 0;
   rs->cseq = 1;
 
@@ -1905,6 +1944,7 @@ raop_session_make(struct output_device *rd, int family, output_status_cb cb)
   rs->server_fd = -1;
 
   rs->password = rd->password;
+
   rs->wants_metadata = re->wants_metadata;
 
   switch (re->devtype)
@@ -2016,6 +2056,7 @@ raop_session_make(struct output_device *rd, int family, output_status_cb cb)
 
   rs->devname = strdup(rd->name);
   rs->address = strdup(address);
+  rs->family = family;
 
   rs->volume = rd->volume;
 
@@ -2037,9 +2078,7 @@ raop_session_make(struct output_device *rd, int family, output_status_cb cb)
 static void
 raop_session_failure_cb(struct evrtsp_request *req, void *arg)
 {
-  struct raop_session *rs;
-
-  rs = (struct raop_session *)arg;
+  struct raop_session *rs = arg;
 
   raop_session_failure(rs);
 }
@@ -2049,11 +2088,9 @@ raop_session_failure_cb(struct evrtsp_request *req, void *arg)
 static void
 raop_cb_metadata(struct evrtsp_request *req, void *arg)
 {
-  struct raop_session *rs;
+  struct raop_session *rs = arg;
   int ret;
 
-  rs = (struct raop_session *)arg;
-
   rs->reqs_in_flight--;
 
   if (!req)
@@ -2399,17 +2436,17 @@ raop_set_volume_internal(struct raop_session *rs, int volume, evrtsp_req_cb cb)
 
   evbuffer_free(evbuf);
 
+  rs->volume = volume;
+
   return ret;
 }
 
 static void
 raop_cb_set_volume(struct evrtsp_request *req, void *arg)
 {
-  struct raop_session *rs;
+  struct raop_session *rs = arg;
   int ret;
 
-  rs = (struct raop_session *)arg;
-
   rs->reqs_in_flight--;
 
   if (!req)
@@ -2469,11 +2506,9 @@ raop_set_volume_one(struct output_device *rd, output_status_cb cb)
 static void
 raop_cb_flush(struct evrtsp_request *req, void *arg)
 {
-  struct raop_session *rs;
+  struct raop_session *rs = arg;
   int ret;
 
-  rs = (struct raop_session *)arg;
-
   rs->reqs_in_flight--;
 
   if (!req)
@@ -2529,6 +2564,41 @@ raop_cb_keep_alive(struct evrtsp_request *req, void *arg)
   raop_session_failure(rs);
 }
 
+static void
+raop_cb_pin_start(struct evrtsp_request *req, void *arg)
+{
+  struct raop_session *rs = arg;
+  int ret;
+
+  rs->reqs_in_flight--;
+
+  if (!req)
+    goto error;
+
+  if (req->response_code != RTSP_OK)
+    {
+      DPRINTF(E_LOG, L_RAOP, "Request for starting PIN verification failed: %d %s\n", req->response_code, req->response_code_line);
+
+      goto error;
+    }
+
+  ret = raop_check_cseq(rs, req);
+  if (ret < 0)
+    goto error;
+
+  rs->state = RAOP_STATE_UNVERIFIED;
+
+  raop_status(rs);
+
+  // TODO If the user never verifies the session will remain stale
+
+  return;
+
+ error:
+  raop_session_failure(rs);
+}
+
+
 // Forward
 static void
 raop_device_stop(struct output_session *session);
@@ -2716,7 +2786,7 @@ raop_v2_timing_start_one(struct raop_service *svc, int family)
   int len;
   int ret;
 
-#ifdef __linux__
+#ifdef SOCK_CLOEXEC
   svc->fd = socket(family, SOCK_DGRAM | SOCK_CLOEXEC, 0);
 #else
   svc->fd = socket(family, SOCK_DGRAM, 0);
@@ -3026,7 +3096,7 @@ raop_v2_control_cb(int fd, short what, void *arg)
   seq_start = be16toh(seq_start);
   seq_len = be16toh(seq_len);
 
-  DPRINTF(E_DBG, L_RAOP, "Got retransmit request, seq_start %u len %u\n", seq_start, seq_len);
+  DPRINTF(E_DBG, L_RAOP, "Got retransmit request from '%s', seq_start %u len %u\n", rs->devname, seq_start, seq_len);
 
   raop_v2_resend_range(rs, seq_start, seq_len);
 
@@ -3048,7 +3118,7 @@ raop_v2_control_start_one(struct raop_service *svc, int family)
   int len;
   int ret;
 
-#ifdef __linux__
+#ifdef SOCK_CLOEXEC
   svc->fd = socket(family, SOCK_DGRAM | SOCK_CLOEXEC, 0);
 #else
   svc->fd = socket(family, SOCK_DGRAM, 0);
@@ -3446,7 +3516,7 @@ raop_v2_stream_open(struct raop_session *rs)
   int len;
   int ret;
 
-#ifdef __linux__
+#ifdef SOCK_CLOEXEC
   rs->server_fd = socket(rs->sa.ss.ss_family, SOCK_DGRAM | SOCK_CLOEXEC, 0);
 #else
   rs->server_fd = socket(rs->sa.ss.ss_family, SOCK_DGRAM, 0);
@@ -3515,11 +3585,9 @@ raop_startup_cancel(struct raop_session *rs)
 static void
 raop_cb_startup_volume(struct evrtsp_request *req, void *arg)
 {
-  struct raop_session *rs;
+  struct raop_session *rs = arg;
   int ret;
 
-  rs = (struct raop_session *)arg;
-
   rs->reqs_in_flight--;
 
   if (!req)
@@ -3561,12 +3629,10 @@ raop_cb_startup_volume(struct evrtsp_request *req, void *arg)
 static void
 raop_cb_startup_record(struct evrtsp_request *req, void *arg)
 {
-  struct raop_session *rs;
+  struct raop_session *rs = arg;
   const char *param;
   int ret;
 
-  rs = (struct raop_session *)arg;
-
   rs->reqs_in_flight--;
 
   if (!req)
@@ -3604,7 +3670,7 @@ raop_cb_startup_record(struct evrtsp_request *req, void *arg)
 static void
 raop_cb_startup_setup(struct evrtsp_request *req, void *arg)
 {
-  struct raop_session *rs;
+  struct raop_session *rs = arg;
   const char *param;
   char *transport;
   char *token;
@@ -3612,8 +3678,6 @@ raop_cb_startup_setup(struct evrtsp_request *req, void *arg)
   int tmp;
   int ret;
 
-  rs = (struct raop_session *)arg;
-
   rs->reqs_in_flight--;
 
   if (!req)
@@ -3754,11 +3818,9 @@ raop_cb_startup_setup(struct evrtsp_request *req, void *arg)
 static void
 raop_cb_startup_announce(struct evrtsp_request *req, void *arg)
 {
-  struct raop_session *rs;
+  struct raop_session *rs = arg;
   int ret;
 
-  rs = (struct raop_session *)arg;
-
   rs->reqs_in_flight--;
 
   if (!req)
@@ -3791,11 +3853,9 @@ raop_cb_startup_announce(struct evrtsp_request *req, void *arg)
 static void
 raop_cb_startup_options(struct evrtsp_request *req, void *arg)
 {
-  struct raop_session *rs;
+  struct raop_session *rs = arg;
   int ret;
 
-  rs = (struct raop_session *)arg;
-
   rs->reqs_in_flight--;
 
   if (!req || !req->response_code)
@@ -3813,9 +3873,9 @@ raop_cb_startup_options(struct evrtsp_request *req, void *arg)
       goto cleanup;
     }
 
-  if ((req->response_code != RTSP_OK) && (req->response_code != RTSP_UNAUTHORIZED))
+  if ((req->response_code != RTSP_OK) && (req->response_code != RTSP_UNAUTHORIZED) && (req->response_code != RTSP_FORBIDDEN))
     {
-      DPRINTF(E_LOG, L_RAOP, "OPTIONS request failed starting '%s': %d %s\n", rs->devname, req->response_code, req->response_code_line);
+      DPRINTF(E_LOG, L_RAOP, "OPTIONS request failed '%s': %d %s\n", rs->devname, req->response_code, req->response_code_line);
 
       goto cleanup;
     }
@@ -3842,7 +3902,18 @@ raop_cb_startup_options(struct evrtsp_request *req, void *arg)
       if (ret < 0)
 	{
 	  DPRINTF(E_LOG, L_RAOP, "Could not re-run OPTIONS request with authentication\n");
+	  goto cleanup;
+	}
+
+      return;
+    }
 
+  if (req->response_code == RTSP_FORBIDDEN)
+    {
+      ret = raop_send_req_pin_start(rs, raop_cb_pin_start);
+      if (ret < 0)
+	{
+	  DPRINTF(E_LOG, L_RAOP, "Could not request PIN for device verification\n");
 	  goto cleanup;
 	}
 
@@ -3851,26 +3922,37 @@ raop_cb_startup_options(struct evrtsp_request *req, void *arg)
 
   rs->state = RAOP_STATE_OPTIONS;
 
-  /* Send ANNOUNCE */
-  ret = raop_send_req_announce(rs, raop_cb_startup_announce);
-  if (ret < 0)
-    goto cleanup;
+  if (rs->only_probe)
+    {
+      /* Device probed successfully, tell our user */
+      raop_status(rs);
+
+      /* We're not going further with this session */
+      raop_session_cleanup(rs);
+    }
+  else
+    {
+      /* Send ANNOUNCE */
+      ret = raop_send_req_announce(rs, raop_cb_startup_announce);
+      if (ret < 0)
+	goto cleanup;
+    }
 
   return;
 
  cleanup:
-  raop_startup_cancel(rs);
+  if (rs->only_probe)
+    raop_session_failure(rs);
+  else
+    raop_startup_cancel(rs);
 }
 
-
 static void
 raop_cb_shutdown_teardown(struct evrtsp_request *req, void *arg)
 {
-  struct raop_session *rs;
+  struct raop_session *rs = arg;
   int ret;
 
-  rs = (struct raop_session *)arg;
-
   rs->reqs_in_flight--;
 
   if (!req)
@@ -3900,81 +3982,365 @@ raop_cb_shutdown_teardown(struct evrtsp_request *req, void *arg)
   raop_session_failure(rs);
 }
 
-static void
-raop_cb_probe_options(struct evrtsp_request *req, void *arg)
+
+/* tvOS device verification - e.g. for the ATV4 (read it from the bottom and up) */
+
+#ifdef RAOP_VERIFICATION
+static int
+raop_verification_response_process(int step, struct evrtsp_request *req, struct raop_session *rs)
 {
-  struct raop_session *rs;
+  uint8_t *response;
+  const char *errmsg;
+  size_t len;
   int ret;
 
-  rs = (struct raop_session *)arg;
-
   rs->reqs_in_flight--;
 
-  if (!req || !req->response_code)
+  if (!req)
     {
-      DPRINTF(E_LOG, L_RAOP, "No response from '%s' (%s) to OPTIONS request\n", rs->devname, rs->address);
+      DPRINTF(E_LOG, L_RAOP, "Verification step %d to '%s' failed, empty callback\n", step, rs->devname);
+      return -1;
+    }
 
-      if (rs->device->v4_address && (rs->sa.ss.ss_family == AF_INET6))
-	{
-	  DPRINTF(E_LOG, L_RAOP, "Falling back to ipv4, the ipv6 address is not responding\n");
+  if (req->response_code != RTSP_OK)
+    {
+      DPRINTF(E_LOG, L_RAOP, "Verification step %d to '%s' failed with error code %d: %s\n", step, rs->devname, req->response_code, req->response_code_line);
+      return -1;
+    }
 
-	  free(rs->device->v6_address);
-	  rs->device->v6_address = NULL;
-	}
+  response = evbuffer_pullup(req->input_buffer, -1);
+  len = evbuffer_get_length(req->input_buffer);
 
-      goto cleanup;
+  switch (step)
+    {
+      case 1:
+	ret = verification_setup_response1(rs->verification_setup_ctx, response, len);
+	errmsg = verification_setup_errmsg(rs->verification_setup_ctx);
+	break;
+      case 2:
+	ret = verification_setup_response2(rs->verification_setup_ctx, response, len);
+	errmsg = verification_setup_errmsg(rs->verification_setup_ctx);
+	break;
+      case 3:
+	ret = verification_setup_response3(rs->verification_setup_ctx, response, len);
+	errmsg = verification_setup_errmsg(rs->verification_setup_ctx);
+	break;
+      case 4:
+	ret = verification_verify_response1(rs->verification_verify_ctx, response, len);
+	errmsg = verification_verify_errmsg(rs->verification_verify_ctx);
+	break;
+      case 5:
+	ret = 0;
+	break;
+      default:
+	ret = -1;
+	errmsg = "Bug! Bad step number";
     }
 
-  if ((req->response_code != RTSP_OK) && (req->response_code != RTSP_UNAUTHORIZED))
+  if (ret < 0)
+    DPRINTF(E_LOG, L_RAOP, "Verification step %d response from '%s' error: %s\n", step, rs->devname, errmsg);
+
+  return ret;
+}
+
+static int
+raop_verification_request_send(int step, struct raop_session *rs, void (*cb)(struct evrtsp_request *, void *))
+{
+  struct evrtsp_request *req;
+  uint8_t *body;
+  uint32_t len;
+  const char *errmsg;
+  const char *url;
+  const char *ctype;
+  int ret;
+
+  switch (step)
     {
-      DPRINTF(E_LOG, L_RAOP, "OPTIONS request failed probing '%s': %d %s\n", rs->devname, req->response_code, req->response_code_line);
+      case 1:
+	body    = verification_setup_request1(&len, rs->verification_setup_ctx);
+	errmsg  = verification_setup_errmsg(rs->verification_setup_ctx);
+	url     = "/pair-setup-pin";
+	ctype   = "application/x-apple-binary-plist";
+	break;
+      case 2:
+	body    = verification_setup_request2(&len, rs->verification_setup_ctx);
+	errmsg  = verification_setup_errmsg(rs->verification_setup_ctx);
+	url     = "/pair-setup-pin";
+	ctype   = "application/x-apple-binary-plist";
+	break;
+      case 3:
+	body    = verification_setup_request3(&len, rs->verification_setup_ctx);
+	errmsg  = verification_setup_errmsg(rs->verification_setup_ctx);
+	url     = "/pair-setup-pin";
+	ctype   = "application/x-apple-binary-plist";
+	break;
+      case 4:
+	body    = verification_verify_request1(&len, rs->verification_verify_ctx);
+	errmsg  = verification_verify_errmsg(rs->verification_verify_ctx);
+	url     = "/pair-verify";
+	ctype   = "application/octet-stream";
+	break;
+      case 5:
+	body    = verification_verify_request2(&len, rs->verification_verify_ctx);
+	errmsg  = verification_verify_errmsg(rs->verification_verify_ctx);
+	url     = "/pair-verify";
+	ctype   = "application/octet-stream";
+	break;
+      default:
+	body    = NULL;
+	errmsg  = "Bug! Bad step number";
+    }
 
-      goto cleanup;
+  if (!body)
+    {
+      DPRINTF(E_LOG, L_RAOP, "Verification step %d request error: %s\n", step, errmsg);
+      return -1;
     }
 
-  ret = raop_check_cseq(rs, req);
+  req = evrtsp_request_new(cb, rs);
+  if (!req)
+    {
+      DPRINTF(E_LOG, L_RAOP, "Could not create RTSP request for verification step %d\n", step);
+      return -1;
+    }
+
+  evbuffer_add(req->output_buffer, body, len);
+  free(body);
+
+  ret = raop_add_headers(rs, req, EVRTSP_REQ_POST);
   if (ret < 0)
-    goto cleanup;
+    {
+      evrtsp_request_free(req);
+      return -1;
+    }
 
-  if (req->response_code == RTSP_UNAUTHORIZED)
+  evrtsp_add_header(req->output_headers, "Content-Type", ctype);
+
+  DPRINTF(E_INFO, L_RAOP, "Making verification request step %d to '%s'\n", step, rs->devname);
+
+  ret = evrtsp_make_request(rs->ctrl, req, EVRTSP_REQ_POST, url);
+  if (ret < 0)
     {
-      if (rs->req_has_auth)
-	{
-	  DPRINTF(E_LOG, L_RAOP, "Bad password for device '%s'\n", rs->devname);
+      DPRINTF(E_LOG, L_RAOP, "Verification request step %d to '%s' failed\n", step, rs->devname);
+      return -1;
+    }
 
-	  rs->state = RAOP_STATE_PASSWORD;
-	  goto cleanup;
-	}
+  rs->reqs_in_flight++;
 
-      ret = raop_parse_auth(rs, req);
-      if (ret < 0)
-	goto cleanup;
+  evrtsp_connection_set_closecb(rs->ctrl, NULL, NULL);
 
-      ret = raop_send_req_options(rs, raop_cb_probe_options);
-      if (ret < 0)
-	{
-	  DPRINTF(E_LOG, L_RAOP, "Could not re-run OPTIONS request with authentication\n");
+  return 0;
+}
 
-	  goto cleanup;
-	}
+static void
+raop_cb_verification_verify_step2(struct evrtsp_request *req, void *arg)
+{
+  struct raop_session *rs = arg;
+  int ret;
 
-      return;
+  verification_verify_free(rs->verification_verify_ctx);
+
+  ret = raop_verification_response_process(5, req, rs);
+  if (ret < 0)
+    {
+      // Clear auth_key, the device did not accept it
+      free(rs->device->auth_key);
+      rs->device->auth_key = NULL;
+      goto error;
     }
 
-  rs->state = RAOP_STATE_OPTIONS;
+  DPRINTF(E_INFO, L_RAOP, "Verification of '%s' completed succesfully\n", rs->devname);
 
-  /* Device probed successfully, tell our user */
+  rs->state = RAOP_STATE_STARTUP;
+
+  raop_send_req_options(rs, raop_cb_startup_options);
+
+  return;
+
+ error:
+  rs->state = RAOP_STATE_UNVERIFIED;
   raop_status(rs);
+}
+
+static void
+raop_cb_verification_verify_step1(struct evrtsp_request *req, void *arg)
+{
+  struct raop_session *rs = arg;
+  int ret;
 
-  /* We're not going further with this session */
+  ret = raop_verification_response_process(4, req, rs);
+  if (ret < 0)
+    {
+      // Clear auth_key, the device did not accept it
+      free(rs->device->auth_key);
+      rs->device->auth_key = NULL;
+      goto error;
+    }
+
+  ret = raop_verification_request_send(5, rs, raop_cb_verification_verify_step2);
+  if (ret < 0)
+    goto error;
+
+  return;
+
+ error:
+  rs->state = RAOP_STATE_UNVERIFIED;
+  raop_status(rs);
+
+  verification_verify_free(rs->verification_verify_ctx);
+  rs->verification_verify_ctx = NULL;
+}
+
+static int
+raop_verification_verify(struct raop_session *rs)
+{
+  int ret;
+
+  rs->verification_verify_ctx = verification_verify_new(rs->device->auth_key); // Naughty boy is dereferencing device
+  if (!rs->verification_verify_ctx)
+    {
+      DPRINTF(E_LOG, L_RAOP, "Out of memory for verification verify context\n");
+      return -1;
+    }
+
+  ret = raop_verification_request_send(4, rs, raop_cb_verification_verify_step1);
+  if (ret < 0)
+    goto error;
+
+  return 0;
+
+ error:
+  rs->state = RAOP_STATE_UNVERIFIED;
+  raop_status(rs);
+
+  verification_verify_free(rs->verification_verify_ctx);
+  rs->verification_verify_ctx = NULL;
+  return -1;
+}
+
+
+static void
+raop_cb_verification_setup_step3(struct evrtsp_request *req, void *arg)
+{
+  struct raop_session *rs = arg;
+  const char *authorization_key;
+  int ret;
+
+  ret = raop_verification_response_process(3, req, rs);
+  if (ret < 0)
+    goto error;
+
+  ret = verification_setup_result(&authorization_key, rs->verification_setup_ctx);
+  if (ret < 0)
+    {
+      DPRINTF(E_LOG, L_RAOP, "Verification setup result error: %s\n", verification_setup_errmsg(rs->verification_setup_ctx));
+      goto error;
+    }
+
+  DPRINTF(E_LOG, L_RAOP, "Verification setup stage complete, saving authorization key\n");
+
+  // Dereferencing output_device and a blocking db call... :-~
+  free(rs->device->auth_key);
+  rs->device->auth_key = strdup(authorization_key);
+
+  db_speaker_save(rs->device);
+
+  // The player considers this session failed, so we don't need it any more
   raop_session_cleanup(rs);
 
+  /* Fallthrough */
+
+ error:
+  verification_setup_free(rs->verification_setup_ctx);
+  rs->verification_setup_ctx = NULL;
+}
+
+static void
+raop_cb_verification_setup_step2(struct evrtsp_request *req, void *arg)
+{
+  struct raop_session *rs = arg;
+  int ret;
+
+  ret = raop_verification_response_process(2, req, rs);
+  if (ret < 0)
+    goto error;
+
+  ret = raop_verification_request_send(3, rs, raop_cb_verification_setup_step3);
+  if (ret < 0)
+    goto error;
+
   return;
 
- cleanup:
-  raop_session_failure(rs);
+ error:
+  verification_setup_free(rs->verification_setup_ctx);
+  rs->verification_setup_ctx = NULL;
+}
+
+static void
+raop_cb_verification_setup_step1(struct evrtsp_request *req, void *arg)
+{
+  struct raop_session *rs = arg;
+  int ret;
+
+  ret = raop_verification_response_process(1, req, rs);
+  if (ret < 0)
+    goto error;
+
+  ret = raop_verification_request_send(2, rs, raop_cb_verification_setup_step2);
+  if (ret < 0)
+    goto error;
+
+  return;
+
+ error:
+  verification_setup_free(rs->verification_setup_ctx);
+  rs->verification_setup_ctx = NULL;
 }
 
+static void
+raop_verification_setup(const char *pin)
+{
+  struct raop_session *rs;
+  int ret;
+
+  for (rs = sessions; rs; rs = rs->next)
+    {
+      if (rs->state == RAOP_STATE_UNVERIFIED)
+	break;
+    }
+
+  if (!rs)
+    {
+      DPRINTF(E_LOG, L_RAOP, "Got a PIN for device verification, but no device is awaiting verification\n");
+      return;
+    }
+
+  rs->verification_setup_ctx = verification_setup_new(pin);
+  if (!rs->verification_setup_ctx)
+    {
+      DPRINTF(E_LOG, L_RAOP, "Out of memory for verification setup context\n");
+      return;
+    }
+
+  ret = raop_verification_request_send(1, rs, raop_cb_verification_setup_step1);
+  if (ret < 0)
+    goto error;
+
+  return;
+
+ error:
+  verification_setup_free(rs->verification_setup_ctx);
+  rs->verification_setup_ctx = NULL;
+}
+#else
+static int
+raop_verification_verify(struct raop_session *rs)
+{
+  DPRINTF(E_LOG, L_RAOP, "Device '%s' requires verification, but forked-daapd was built with --disable-verification\n", rs->devname);
+
+  return -1;
+}
+#endif /* RAOP_VERIFICATION */
 
 /* RAOP devices discovery - mDNS callback */
 /* Thread: main (mdns) */
@@ -4012,6 +4378,7 @@ raop_device_cb(const char *name, const char *type, const char *domain, const cha
   char *at_name;
   char *password;
   uint64_t id;
+  uint64_t sf;
   int ret;
 
   ret = safe_hextou64(name, &id);
@@ -4033,11 +4400,27 @@ raop_device_cb(const char *name, const char *type, const char *domain, const cha
 
   DPRINTF(E_DBG, L_RAOP, "Event for AirPlay device %s (port %d, id %" PRIx64 ")\n", at_name, port, id);
 
+  airplay = cfg_gettsec(cfg, "airplay", at_name);
+  if (airplay && cfg_getbool(airplay, "exclude"))
+    {
+      DPRINTF(E_LOG, L_RAOP, "Excluding AirPlay device '%s' as set in config\n", at_name);
+
+      return;
+    }
+
   rd = calloc(1, sizeof(struct output_device));
+  if (!rd)
+    {
+      DPRINTF(E_LOG, L_RAOP, "Out of memory (rd)\n");
+
+      return;
+    }
+
   re = calloc(1, sizeof(struct raop_extra));
-  if (!rd || !re)
+  if (!re)
     {
-      DPRINTF(E_LOG, L_RAOP, "Out of memory for new AirPlay device\n");
+      DPRINTF(E_LOG, L_RAOP, "Out of memory (re)\n");
+      free(rd);
 
       return;
     }
@@ -4126,6 +4509,16 @@ raop_device_cb(const char *name, const char *type, const char *domain, const cha
 
   rd->password = password;
 
+  /* Device verification */
+  p = keyval_get(txt, "sf");
+  if (p && (safe_hextou64(p, &sf) == 0))
+    {
+      if (sf & (1 << 9))
+	rd->requires_auth = 1;
+
+      // Note: device_add() in player.c will get the auth key from the db if available
+    }
+
   /* Device type */
   re->devtype = RAOP_DEV_OTHER;
   p = keyval_get(txt, "am");
@@ -4164,15 +4557,15 @@ raop_device_cb(const char *name, const char *type, const char *domain, const cha
       case AF_INET:
 	rd->v4_address = strdup(address);
 	rd->v4_port = port;
-	DPRINTF(E_INFO, L_RAOP, "Adding AirPlay device %s: password: %u, encrypt: %u, metadata: %u, type %s, address %s:%d\n", 
-	  name, rd->has_password, re->encrypt, re->wants_metadata, raop_devtype[re->devtype], address, port);
+	DPRINTF(E_INFO, L_RAOP, "Adding AirPlay device %s: password: %u, verification: %u, encrypt: %u, metadata: %u, type %s, address %s:%d\n", 
+	  name, rd->has_password, rd->requires_auth, re->encrypt, re->wants_metadata, raop_devtype[re->devtype], address, port);
 	break;
 
       case AF_INET6:
 	rd->v6_address = strdup(address);
 	rd->v6_port = port;
-	DPRINTF(E_INFO, L_RAOP, "Adding AirPlay device %s: password: %u, encrypt: %u, metadata: %u, type %s, address [%s]:%d\n", 
-	  name, rd->has_password, re->encrypt, re->wants_metadata, raop_devtype[re->devtype], address, port);
+	DPRINTF(E_INFO, L_RAOP, "Adding AirPlay device %s: password: %u, verification: %u, encrypt: %u, metadata: %u, type %s, address [%s]:%d\n", 
+	  name, rd->has_password, rd->requires_auth, re->encrypt, re->wants_metadata, raop_devtype[re->devtype], address, port);
 	break;
 
       default:
@@ -4191,38 +4584,53 @@ raop_device_cb(const char *name, const char *type, const char *domain, const cha
 }
 
 static int
-raop_device_probe(struct output_device *rd, output_status_cb cb)
+raop_device_start_generic(struct output_device *rd, output_status_cb cb, uint64_t rtptime, bool only_probe)
 {
   struct raop_session *rs;
   int ret;
 
-  /* Send an OPTIONS request to test our ability to connect to the device,
-   * including the need for and/or validity of the password
+  /* Send an OPTIONS request to establish the connection. If device verification
+   * is required we start with that. After that, we can determine our local
+   * address and build our session URL for all subsequent requests.
    */
 
-  rs = raop_session_make(rd, AF_INET6, cb);
+  rs = raop_session_make(rd, AF_INET6, cb, only_probe);
   if (rs)
     {
-      ret = raop_send_req_options(rs, raop_cb_probe_options);
+      rs->start_rtptime = rtptime;
+
+      if (rd->auth_key)
+	ret = raop_verification_verify(rs);
+      else if (rd->requires_auth)
+	ret = raop_send_req_pin_start(rs, raop_cb_pin_start);
+      else
+	ret = raop_send_req_options(rs, raop_cb_startup_options);
+
       if (ret == 0)
 	return 0;
       else
 	{
-	  DPRINTF(E_WARN, L_RAOP, "Could not send OPTIONS request on IPv6 (probe)\n");
-
+	  DPRINTF(E_WARN, L_RAOP, "Could not send verification or OPTIONS request on IPv6\n");
 	  raop_session_cleanup(rs);
 	}
     }
 
-  rs = raop_session_make(rd, AF_INET, cb);
+  rs = raop_session_make(rd, AF_INET, cb, only_probe);
   if (!rs)
     return -1;
 
-  ret = raop_send_req_options(rs, raop_cb_probe_options);
+  rs->start_rtptime = rtptime;
+
+  if (rd->auth_key)
+    ret = raop_verification_verify(rs);
+  else if (rd->requires_auth)
+    ret = raop_send_req_pin_start(rs, raop_cb_pin_start);
+  else
+    ret = raop_send_req_options(rs, raop_cb_startup_options);
+
   if (ret < 0)
     {
-      DPRINTF(E_WARN, L_RAOP, "Could not send OPTIONS request on IPv4 (probe)\n");
-
+      DPRINTF(E_WARN, L_RAOP, "Could not send verification or OPTIONS request on IPv4\n");
       raop_session_cleanup(rs);
       return -1;
     }
@@ -4231,48 +4639,15 @@ raop_device_probe(struct output_device *rd, output_status_cb cb)
 }
 
 static int
-raop_device_start(struct output_device *rd, output_status_cb cb, uint64_t rtptime)
+raop_device_probe(struct output_device *rd, output_status_cb cb)
 {
-  struct raop_session *rs;
-  int ret;
-
-  /* Send an OPTIONS request to establish the connection
-   * After that, we can determine our local address and build our session URL
-   * for all subsequent requests.
-   */
-
-  rs = raop_session_make(rd, AF_INET6, cb);
-  if (rs)
-    {
-      rs->start_rtptime = rtptime;
-
-      ret = raop_send_req_options(rs, raop_cb_startup_options);
-      if (ret == 0)
-	return 0;
-      else
-	{
-	  DPRINTF(E_WARN, L_RAOP, "Could not send OPTIONS request on IPv6 (start)\n");
-
-	  raop_session_cleanup(rs);
-	}
-    }
-
-  rs = raop_session_make(rd, AF_INET, cb);
-  if (!rs)
-    return -1;
-
-  rs->start_rtptime = rtptime;
-
-  ret = raop_send_req_options(rs, raop_cb_startup_options);
-  if (ret < 0)
-    {
-      DPRINTF(E_WARN, L_RAOP, "Could not send OPTIONS request on IPv4 (start)\n");
-
-      raop_session_cleanup(rs);
-      return -1;
-    }
+  return raop_device_start_generic(rd, cb, 0, 1);
+}
 
-  return 0;
+static int
+raop_device_start(struct output_device *rd, output_status_cb cb, uint64_t rtptime)
+{
+  return raop_device_start_generic(rd, cb, rtptime, 0);
 }
 
 static void
@@ -4290,7 +4665,9 @@ raop_device_stop(struct output_session *session)
 static void
 raop_device_free_extra(struct output_device *device)
 {
-  free(device->extra_device_info);
+  struct raop_extra *re = device->extra_device_info;
+
+  free(re);
 }
 
 static void
@@ -4535,4 +4912,7 @@ struct output_definition output_raop =
   .metadata_send = raop_metadata_send,
   .metadata_purge = raop_metadata_purge,
   .metadata_prune = raop_metadata_prune,
+#ifdef RAOP_VERIFICATION
+  .authorize = raop_verification_setup,
+#endif
 };
diff --git a/src/outputs/raop_verification.c b/src/outputs/raop_verification.c
new file mode 100644
index 0000000..e697211
--- /dev/null
+++ b/src/outputs/raop_verification.c
@@ -0,0 +1,1336 @@
+/*
+ *
+ * The Secure Remote Password 6a implementation included here is by
+ *  - Tom Cocagne
+ *    <https://github.com/cocagne/csrp>
+ *
+ *
+ * The MIT License (MIT)
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is furnished to do
+ * so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ * 
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <plist/plist.h>
+#include <sodium.h>
+
+#include "raop_verification.h"
+
+#define CONFIG_GCRYPT 1
+
+/* -------------------- GCRYPT AND OPENSSL COMPABILITY --------------------- */
+/*                   partly borrowed from ffmpeg (rtmpdh.c)                  */
+
+#if CONFIG_GCRYPT
+#include <gcrypt.h>
+#define SHA512_DIGEST_LENGTH 64
+#define bnum_new(bn)                                            \
+    do {                                                        \
+        if (!gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P)) { \
+            if (!gcry_check_version("1.5.4"))                   \
+                abort();                                        \
+            gcry_control(GCRYCTL_DISABLE_SECMEM, 0);            \
+            gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);   \
+        }                                                       \
+        bn = gcry_mpi_new(1);                                   \
+    } while (0)
+#define bnum_free(bn)                 gcry_mpi_release(bn)
+#define bnum_num_bytes(bn)            (gcry_mpi_get_nbits(bn) + 7) / 8
+#define bnum_is_zero(bn)              (gcry_mpi_cmp_ui(bn, (unsigned long)0) == 0)
+#define bnum_bn2bin(bn, buf, len)     gcry_mpi_print(GCRYMPI_FMT_USG, buf, len, NULL, bn)
+#define bnum_bin2bn(bn, buf, len)     gcry_mpi_scan(&bn, GCRYMPI_FMT_USG, buf, len, NULL)
+#define bnum_hex2bn(bn, buf)          gcry_mpi_scan(&bn, GCRYMPI_FMT_HEX, buf, 0, 0)
+#define bnum_random(bn, num_bits)     gcry_mpi_randomize(bn, num_bits, GCRY_WEAK_RANDOM)
+#define bnum_add(bn, a, b)            gcry_mpi_add(bn, a, b)
+#define bnum_sub(bn, a, b)            gcry_mpi_sub(bn, a, b)
+#define bnum_mul(bn, a, b)            gcry_mpi_mul(bn, a, b)
+typedef gcry_mpi_t bnum;
+static void bnum_modexp(bnum bn, bnum y, bnum q, bnum p)
+{
+  gcry_mpi_powm(bn, y, q, p);
+}
+#elif CONFIG_OPENSSL
+#include <openssl/crypto.h>
+#include <openssl/bn.h>
+#include <openssl/rand.h>
+#include <openssl/sha.h>
+#include <openssl/evp.h>
+#define bnum_new(bn)                  bn = BN_new()
+#define bnum_free(bn)                 BN_free(bn)
+#define bnum_num_bytes(bn)            BN_num_bytes(bn)
+#define bnum_is_zero(bn)              BN_is_zero(bn)
+#define bnum_bn2bin(bn, buf, len)     BN_bn2bin(bn, buf)
+#define bnum_bin2bn(bn, buf, len)     bn = BN_bin2bn(buf, len, 0)
+#define bnum_hex2bn(bn, buf)          BN_hex2bn(&bn, buf)
+#define bnum_random(bn, num_bits)     BN_rand(bn, num_bits, 0, 0)
+#define bnum_add(bn, a, b)            BN_add(bn, a, b)
+#define bnum_sub(bn, a, b)            BN_sub(bn, a, b)
+typedef BIGNUM* bnum;
+static void bnum_mul(bnum bn, bnum a, bnum b)
+{
+  // No error handling
+  BN_CTX *ctx = BN_CTX_new();
+  BN_mul(bn, a, b, ctx);
+  BN_CTX_free(ctx);
+}
+static void bnum_modexp(bnum bn, bnum y, bnum q, bnum p)
+{
+  // No error handling
+  BN_CTX *ctx = BN_CTX_new();
+  BN_mod_exp(bn, y, q, p, ctx);
+  BN_CTX_free(ctx);
+}
+#endif
+
+
+/* ----------------------------- DEFINES ETC ------------------------------- */
+
+#define USERNAME "12:34:56:78:90:AB"
+#define EPK_LENGTH 32
+#define AUTHTAG_LENGTH 16
+#define AES_SETUP_KEY  "Pair-Setup-AES-Key"
+#define AES_SETUP_IV   "Pair-Setup-AES-IV"
+#define AES_VERIFY_KEY "Pair-Verify-AES-Key"
+#define AES_VERIFY_IV  "Pair-Verify-AES-IV"
+
+#ifdef CONFIG_OPENSSL
+enum hash_alg
+{
+  HASH_SHA1,
+  HASH_SHA224,
+  HASH_SHA256,
+  HASH_SHA384,
+  HASH_SHA512,
+};
+#elif CONFIG_GCRYPT
+enum hash_alg
+{
+  HASH_SHA1 = GCRY_MD_SHA1,
+  HASH_SHA224 = GCRY_MD_SHA224,
+  HASH_SHA256 = GCRY_MD_SHA256,
+  HASH_SHA384 = GCRY_MD_SHA384,
+  HASH_SHA512 = GCRY_MD_SHA512,
+};
+#endif
+
+struct verification_setup_context
+{
+  struct SRPUser *user;
+
+  char pin[4];
+
+  const uint8_t *pkA;
+  int pkA_len;
+
+  uint8_t *pkB;
+  uint64_t pkB_len;
+
+  const uint8_t *M1;
+  int M1_len;
+
+  uint8_t *M2;
+  uint64_t M2_len;
+
+  uint8_t *salt;
+  uint64_t salt_len;
+  uint8_t public_key[crypto_sign_PUBLICKEYBYTES];
+  uint8_t private_key[crypto_sign_SECRETKEYBYTES];
+  // Hex-formatet concatenation of public + private, 0-terminated
+  char auth_key[2 * (crypto_sign_PUBLICKEYBYTES + crypto_sign_SECRETKEYBYTES) + 1];
+
+  // We don't actually use the server's epk and authtag for anything
+  uint8_t *epk;
+  uint64_t epk_len;
+  uint8_t *authtag;
+  uint64_t authtag_len;
+
+  const char *errmsg;
+};
+
+struct verification_verify_context
+{
+  uint8_t server_eph_public_key[32];
+  uint8_t server_public_key[64];
+
+  uint8_t client_public_key[crypto_sign_PUBLICKEYBYTES];
+  uint8_t client_private_key[crypto_sign_SECRETKEYBYTES];
+
+  uint8_t client_eph_public_key[32];
+  uint8_t client_eph_private_key[32];
+
+  const char *errmsg;
+};
+
+
+/* ---------------------------------- SRP ---------------------------------- */
+
+typedef enum
+{
+  SRP_NG_2048,
+  SRP_NG_CUSTOM
+} SRP_NGType;
+
+typedef struct
+{
+  bnum N;
+  bnum g;
+} NGConstant;
+
+#if CONFIG_OPENSSL
+typedef union
+{
+  SHA_CTX    sha;
+  SHA256_CTX sha256;
+  SHA512_CTX sha512;
+} HashCTX;
+#elif CONFIG_GCRYPT
+typedef gcry_md_hd_t HashCTX;
+#endif
+
+struct SRPUser
+{
+  enum hash_alg     alg;
+  NGConstant        *ng;
+    
+  bnum a;
+  bnum A;
+  bnum S;
+
+  const unsigned char *bytes_A;
+  int                 authenticated;
+    
+  const char          *username;
+  const unsigned char *password;
+  int                 password_len;
+    
+  unsigned char M           [SHA512_DIGEST_LENGTH];
+  unsigned char H_AMK       [SHA512_DIGEST_LENGTH];
+  unsigned char session_key [2 * SHA512_DIGEST_LENGTH]; // See hash_session_key()
+  int           session_key_len;
+};
+
+struct NGHex 
+{
+  const char *n_hex;
+  const char *g_hex;
+};
+
+// We only need 2048 right now, but keep the array in case we want to add others later
+// All constants here were pulled from Appendix A of RFC 5054
+static struct NGHex global_Ng_constants[] =
+{
+  { /* 2048 */
+    "AC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC3192943DB56050A37329CBB4"
+    "A099ED8193E0757767A13DD52312AB4B03310DCD7F48A9DA04FD50E8083969EDB767B0CF60"
+    "95179A163AB3661A05FBD5FAAAE82918A9962F0B93B855F97993EC975EEAA80D740ADBF4FF"
+    "747359D041D5C33EA71D281E446B14773BCA97B43A23FB801676BD207A436C6481F1D2B907"
+    "8717461A5B9D32E688F87748544523B524B0D57D5EA77A2775D2ECFA032CFBDBF52FB37861"
+    "60279004E57AE6AF874E7303CE53299CCC041C7BC308D82A5698F3A8D0C38271AE35F8E9DB"
+    "FBB694B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F9E4AFF73",
+    "2"
+  },
+  {0,0} /* null sentinel */
+};
+
+
+static NGConstant *
+new_ng(SRP_NGType ng_type, const char *n_hex, const char *g_hex)
+{
+  NGConstant *ng = calloc(1, sizeof(NGConstant));
+
+  if ( ng_type != SRP_NG_CUSTOM )
+    {
+      n_hex = global_Ng_constants[ ng_type ].n_hex;
+      g_hex = global_Ng_constants[ ng_type ].g_hex;
+    }
+        
+  bnum_hex2bn(ng->N, n_hex);
+  bnum_hex2bn(ng->g, g_hex);
+    
+  return ng;
+}
+
+static void
+free_ng(NGConstant * ng)
+{
+  if (!ng)
+    return;
+
+  bnum_free(ng->N);
+  bnum_free(ng->g);
+  free(ng);
+}
+
+static int
+hash_init(enum hash_alg alg, HashCTX *c)
+{
+#if CONFIG_OPENSSL
+  switch (alg)
+    {
+      case HASH_SHA1  : return SHA1_Init(&c->sha);
+      case HASH_SHA224: return SHA224_Init(&c->sha256);
+      case HASH_SHA256: return SHA256_Init(&c->sha256);
+      case HASH_SHA384: return SHA384_Init(&c->sha512);
+      case HASH_SHA512: return SHA512_Init(&c->sha512);
+      default:
+        return -1;
+    };
+#elif CONFIG_GCRYPT
+  return gcry_md_open(c, alg, 0);
+#endif
+}
+
+static int
+hash_update(enum hash_alg alg, HashCTX *c, const void *data, size_t len)
+{
+#if CONFIG_OPENSSL
+  switch (alg)
+    {
+      case HASH_SHA1  : return SHA1_Update(&c->sha, data, len);
+      case HASH_SHA224: return SHA224_Update(&c->sha256, data, len);
+      case HASH_SHA256: return SHA256_Update(&c->sha256, data, len);
+      case HASH_SHA384: return SHA384_Update(&c->sha512, data, len);
+      case HASH_SHA512: return SHA512_Update(&c->sha512, data, len);
+      default:
+        return -1;
+    };
+#elif CONFIG_GCRYPT
+  gcry_md_write(*c, data, len);
+  return 0;
+#endif
+}
+
+static int
+hash_final(enum hash_alg alg, HashCTX *c, unsigned char *md)
+{
+#if CONFIG_OPENSSL
+  switch (alg)
+    {
+      case HASH_SHA1  : return SHA1_Final(md, &c->sha);
+      case HASH_SHA224: return SHA224_Final(md, &c->sha256);
+      case HASH_SHA256: return SHA256_Final(md, &c->sha256);
+      case HASH_SHA384: return SHA384_Final(md, &c->sha512);
+      case HASH_SHA512: return SHA512_Final(md, &c->sha512);
+      default:
+        return -1;
+    };
+#elif CONFIG_GCRYPT
+  unsigned char *buf = gcry_md_read(*c, alg);
+  if (!buf)
+    return -1;
+
+  memcpy(md, buf, gcry_md_get_algo_dlen(alg));
+  gcry_md_close(*c);
+  return 0;
+#endif
+}
+
+static unsigned char *
+hash(enum hash_alg alg, const unsigned char *d, size_t n, unsigned char *md)
+{
+#if CONFIG_OPENSSL
+  switch (alg)
+    {
+      case HASH_SHA1  : return SHA1(d, n, md);
+      case HASH_SHA224: return SHA224(d, n, md);
+      case HASH_SHA256: return SHA256(d, n, md);
+      case HASH_SHA384: return SHA384(d, n, md);
+      case HASH_SHA512: return SHA512(d, n, md);
+      default:
+        return NULL;
+    };
+#elif CONFIG_GCRYPT
+  gcry_md_hash_buffer(alg, md, d, n);
+  return md;
+#endif
+}
+
+static int
+hash_length(enum hash_alg alg)
+{
+#if CONFIG_OPENSSL
+  switch (alg)
+    {
+      case HASH_SHA1  : return SHA_DIGEST_LENGTH;
+      case HASH_SHA224: return SHA224_DIGEST_LENGTH;
+      case HASH_SHA256: return SHA256_DIGEST_LENGTH;
+      case HASH_SHA384: return SHA384_DIGEST_LENGTH;
+      case HASH_SHA512: return SHA512_DIGEST_LENGTH;
+      default:
+        return -1;
+    };
+#elif CONFIG_GCRYPT
+  return gcry_md_get_algo_dlen(alg);
+#endif
+}
+
+static int
+hash_ab(enum hash_alg alg, unsigned char *md, const unsigned char *m1, int m1_len, const unsigned char *m2, int m2_len)
+{
+  HashCTX ctx;
+
+  hash_init(alg, &ctx);
+  hash_update(alg, &ctx, m1, m1_len);
+  hash_update(alg, &ctx, m2, m2_len);
+  return hash_final(alg, &ctx, md);
+}    
+
+static bnum
+H_nn_pad(enum hash_alg alg, const bnum n1, const bnum n2)
+{
+  bnum          bn;
+  unsigned char *bin;
+  unsigned char buff[SHA512_DIGEST_LENGTH];
+  int           len_n1 = bnum_num_bytes(n1);
+  int           len_n2 = bnum_num_bytes(n2);
+  int           nbytes = 2 * len_n1;
+
+  if ((len_n2 < 1) || (len_n2 > len_n1))
+    return 0;
+
+  bin = calloc( 1, nbytes );
+
+  bnum_bn2bin(n1, bin, len_n1);
+  bnum_bn2bin(n2, bin + nbytes - len_n2, len_n2);
+  hash( alg, bin, nbytes, buff );
+  free(bin);
+  bnum_bin2bn(bn, buff, hash_length(alg));
+  return bn;
+}
+
+static bnum
+H_ns(enum hash_alg alg, const bnum n, const unsigned char *bytes, int len_bytes)
+{
+  bnum          bn;
+  unsigned char buff[SHA512_DIGEST_LENGTH];
+  int           len_n  = bnum_num_bytes(n);
+  int           nbytes = len_n + len_bytes;
+  unsigned char *bin   = malloc(nbytes);
+
+  bnum_bn2bin(n, bin, len_n);
+  memcpy( bin + len_n, bytes, len_bytes );
+  hash( alg, bin, nbytes, buff );
+  free(bin);
+  bnum_bin2bn(bn, buff, hash_length(alg));
+  return bn;
+}
+
+static bnum
+calculate_x(enum hash_alg alg, const bnum salt, const char *username, const unsigned char *password, int password_len)
+{
+  unsigned char ucp_hash[SHA512_DIGEST_LENGTH];
+  HashCTX       ctx;
+
+  hash_init( alg, &ctx );
+  hash_update( alg, &ctx, username, strlen(username) );
+  hash_update( alg, &ctx, ":", 1 );
+  hash_update( alg, &ctx, password, password_len );
+  hash_final( alg, &ctx, ucp_hash );
+        
+  return H_ns( alg, salt, ucp_hash, hash_length(alg) );
+}
+
+static void
+update_hash_n(enum hash_alg alg, HashCTX *ctx, const bnum n)
+{
+  unsigned long len = bnum_num_bytes(n);
+  unsigned char *n_bytes = malloc(len);
+  bnum_bn2bin(n, n_bytes, len);
+  hash_update(alg, ctx, n_bytes, len);
+  free(n_bytes);
+}
+
+static void
+hash_num(enum hash_alg alg, const bnum n, unsigned char *dest)
+{
+  int           nbytes = bnum_num_bytes(n);
+  unsigned char *bin   = malloc(nbytes);
+  bnum_bn2bin(n, bin, nbytes);
+  hash( alg, bin, nbytes, dest );
+  free(bin);
+}
+
+static int
+hash_session_key(enum hash_alg alg, const bnum n, unsigned char *dest)
+{
+  int           nbytes = bnum_num_bytes(n);
+  unsigned char *bin   = malloc(nbytes);
+  unsigned char fourbytes[4] = { 0 }; // Only God knows the reason for this, and perhaps some poor soul at Apple
+
+  bnum_bn2bin(n, bin, nbytes);
+
+  hash_ab(alg, dest, bin, nbytes, fourbytes, sizeof(fourbytes));
+
+  fourbytes[3] = 1; // Again, only ...
+
+  hash_ab(alg, dest + hash_length(alg), bin, nbytes, fourbytes, sizeof(fourbytes));
+
+  free(bin);
+
+  return (2 * hash_length(alg));
+}
+
+static void
+calculate_M(enum hash_alg alg, NGConstant *ng, unsigned char *dest, const char *I, const bnum s,
+            const bnum A, const bnum B, const unsigned char *K, int K_len)
+{
+  unsigned char H_N[ SHA512_DIGEST_LENGTH ];
+  unsigned char H_g[ SHA512_DIGEST_LENGTH ];
+  unsigned char H_I[ SHA512_DIGEST_LENGTH ];
+  unsigned char H_xor[ SHA512_DIGEST_LENGTH ];
+  HashCTX       ctx;
+  int           i = 0;
+  int           hash_len = hash_length(alg);
+        
+  hash_num( alg, ng->N, H_N );
+  hash_num( alg, ng->g, H_g );
+    
+  hash(alg, (const unsigned char *)I, strlen(I), H_I);
+    
+  for (i = 0; i < hash_len; i++)
+    H_xor[i] = H_N[i] ^ H_g[i];
+    
+  hash_init( alg, &ctx );
+    
+  hash_update( alg, &ctx, H_xor, hash_len );
+  hash_update( alg, &ctx, H_I,   hash_len );
+  update_hash_n( alg, &ctx, s );
+  update_hash_n( alg, &ctx, A );
+  update_hash_n( alg, &ctx, B );
+  hash_update( alg, &ctx, K, K_len );
+    
+  hash_final( alg, &ctx, dest );
+}
+
+static void
+calculate_H_AMK(enum hash_alg alg, unsigned char *dest, const bnum A, const unsigned char * M, const unsigned char * K, int K_len)
+{
+  HashCTX ctx;
+    
+  hash_init( alg, &ctx );
+    
+  update_hash_n( alg, &ctx, A );
+  hash_update( alg, &ctx, M, hash_length(alg) );
+  hash_update( alg, &ctx, K, K_len );
+    
+  hash_final( alg, &ctx, dest );
+}
+
+static struct SRPUser *
+srp_user_new(enum hash_alg alg, SRP_NGType ng_type, const char *username, 
+             const unsigned char *bytes_password, int len_password,
+             const char *n_hex, const char *g_hex)
+{
+  struct SRPUser  *usr  = calloc(1, sizeof(struct SRPUser));
+  int              ulen = strlen(username) + 1;
+
+  if (!usr)
+    goto err_exit;
+
+  usr->alg = alg;
+  usr->ng  = new_ng( ng_type, n_hex, g_hex );
+    
+  bnum_new(usr->a);
+  bnum_new(usr->A);
+  bnum_new(usr->S);
+
+  if (!usr->ng || !usr->a || !usr->A || !usr->S)
+    goto err_exit;
+    
+  usr->username     = (const char *) malloc(ulen);
+  usr->password     = (const unsigned char *) malloc(len_password);
+  usr->password_len = len_password;
+
+  if (!usr->username || !usr->password)
+    goto err_exit;
+    
+  memcpy((char *)usr->username, username,       ulen);
+  memcpy((char *)usr->password, bytes_password, len_password);
+
+  usr->authenticated = 0;
+  usr->bytes_A = 0;
+    
+  return usr;
+
+ err_exit:
+  if (!usr)
+    return NULL;
+
+  bnum_free(usr->a);
+  bnum_free(usr->A);
+  bnum_free(usr->S);
+  if (usr->username)
+    free((void*)usr->username);
+  if (usr->password)
+    {
+      memset((void*)usr->password, 0, usr->password_len);
+      free((void*)usr->password);
+    }
+  free(usr);
+
+  return NULL;
+}
+
+static void
+srp_user_delete(struct SRPUser *usr)
+{
+  if(!usr)
+    return;
+
+  bnum_free(usr->a);
+  bnum_free(usr->A);
+  bnum_free(usr->S);
+      
+  free_ng(usr->ng);
+
+  memset((void*)usr->password, 0, usr->password_len);
+      
+  free((char *)usr->username);
+  free((char *)usr->password);
+      
+  if (usr->bytes_A) 
+    free( (char *)usr->bytes_A );
+
+  memset(usr, 0, sizeof(*usr));
+  free(usr);
+}
+
+static int
+srp_user_is_authenticated(struct SRPUser *usr)
+{
+  return usr->authenticated;
+}
+
+static const unsigned char *
+srp_user_get_session_key(struct SRPUser *usr, int *key_length)
+{
+  if (key_length)
+    *key_length = usr->session_key_len;
+  return usr->session_key;
+}
+
+/* Output: username, bytes_A, len_A */
+static void
+srp_user_start_authentication(struct SRPUser *usr, const char **username,
+                              const unsigned char **bytes_A, int *len_A)
+{
+  bnum_random(usr->a, 256);
+  bnum_modexp(usr->A, usr->ng->g, usr->a, usr->ng->N);
+    
+  *len_A   = bnum_num_bytes(usr->A);
+  *bytes_A = malloc(*len_A);
+
+  if (!*bytes_A)
+    {
+      *len_A = 0;
+      *bytes_A = 0;
+      *username = 0;
+      return;
+    }
+        
+  bnum_bn2bin(usr->A, (unsigned char *) *bytes_A, *len_A);
+    
+  usr->bytes_A = *bytes_A;
+  *username = usr->username;
+}
+
+/* Output: bytes_M. Buffer length is SHA512_DIGEST_LENGTH */
+static void
+srp_user_process_challenge(struct SRPUser *usr, const unsigned char *bytes_s, int len_s,
+                           const unsigned char *bytes_B, int len_B,
+                           const unsigned char **bytes_M, int *len_M )
+{
+  bnum s, B, k, v;
+  bnum tmp1, tmp2, tmp3;
+  bnum u, x;
+
+  *len_M = 0;
+  *bytes_M = 0;
+
+  bnum_bin2bn(s, bytes_s, len_s);
+  bnum_bin2bn(B, bytes_B, len_B);
+  k    = H_nn_pad(usr->alg, usr->ng->N, usr->ng->g);
+  bnum_new(v);
+  bnum_new(tmp1);
+  bnum_new(tmp2);
+  bnum_new(tmp3);
+
+  if (!s || !B || !k || !v || !tmp1 || !tmp2 || !tmp3)
+    goto cleanup1;
+
+  u = H_nn_pad(usr->alg, usr->A, B);
+  x = calculate_x(usr->alg, s, usr->username, usr->password, usr->password_len);
+  if (!u || !x)
+    goto cleanup2;
+
+  // SRP-6a safety check
+  if (!bnum_is_zero(B) && !bnum_is_zero(u))
+    {
+      bnum_modexp(v, usr->ng->g, x, usr->ng->N);
+        
+      // S = (B - k*(g^x)) ^ (a + ux)
+      bnum_mul(tmp1, u, x);
+      bnum_add(tmp2, usr->a, tmp1);        // tmp2 = (a + ux)
+      bnum_modexp(tmp1, usr->ng->g, x, usr->ng->N);
+      bnum_mul(tmp3, k, tmp1);             // tmp3 = k*(g^x)
+      bnum_sub(tmp1, B, tmp3);             // tmp1 = (B - K*(g^x))
+      bnum_modexp(usr->S, tmp1, tmp2, usr->ng->N);
+
+      usr->session_key_len = hash_session_key(usr->alg, usr->S, usr->session_key);
+        
+      calculate_M(usr->alg, usr->ng, usr->M, usr->username, s, usr->A, B, usr->session_key, usr->session_key_len);
+      calculate_H_AMK(usr->alg, usr->H_AMK, usr->A, usr->M, usr->session_key, usr->session_key_len);
+        
+      *bytes_M = usr->M;
+      if (len_M)
+        *len_M = hash_length(usr->alg);
+    }
+  else
+    {
+      *bytes_M = NULL;
+      if (len_M) 
+        *len_M   = 0;
+    }
+
+ cleanup2:
+  bnum_free(x);
+  bnum_free(u);
+ cleanup1:
+  bnum_free(tmp3);
+  bnum_free(tmp2);
+  bnum_free(tmp1);
+  bnum_free(v);
+  bnum_free(k);
+  bnum_free(B);
+  bnum_free(s);
+}
+
+static void
+srp_user_verify_session(struct SRPUser *usr, const unsigned char *bytes_HAMK)
+{
+  if (memcmp(usr->H_AMK, bytes_HAMK, hash_length(usr->alg)) == 0)
+    usr->authenticated = 1;
+}
+
+
+/* -------------------------------- HELPERS -------------------------------- */
+
+static int
+encrypt_gcm(unsigned char *ciphertext, int ciphertext_len, unsigned char *tag, unsigned char *plaintext, int plaintext_len, unsigned char *key, unsigned char *iv, const char **errmsg)
+{
+#ifdef CONFIG_OPENSSL
+  EVP_CIPHER_CTX *ctx;
+  int len;
+
+  *errmsg = NULL;
+
+  if ( !(ctx = EVP_CIPHER_CTX_new()) ||
+       (EVP_EncryptInit_ex(ctx, EVP_aes_128_gcm(), NULL, NULL, NULL) != 1) ||
+       (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, 16, NULL) != 1) ||
+       (EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv) != 1) )
+    {
+      *errmsg = "Error initialising AES 128 GCM encryption";
+      goto error;
+    }
+
+  if (EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len) != 1)
+    {
+      *errmsg = "Error GCM encrypting";
+      goto error;
+    }
+
+  if (len > ciphertext_len)
+    {
+      *errmsg = "Bug! Buffer overflow";
+      goto error;
+    }
+
+  if (EVP_EncryptFinal_ex(ctx, ciphertext + len, &len) != 1)
+    {
+      *errmsg = "Error finalising GCM encryption";
+      goto error;
+    }
+
+  if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, AUTHTAG_LENGTH, tag) != 1)
+    {
+      *errmsg = "Error getting authtag";
+      goto error;
+    }
+
+  EVP_CIPHER_CTX_free(ctx);
+  return 0;
+
+ error:
+  EVP_CIPHER_CTX_free(ctx);
+  return -1;
+#elif CONFIG_GCRYPT
+  gcry_cipher_hd_t hd;
+  int ret;
+
+  ret = gcry_cipher_open(&hd, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_GCM, 0);
+  if (ret < 0)
+    {
+      *errmsg = "Error initialising AES 128 GCM encryption";
+      return -1;
+    }
+
+  if ( (gcry_cipher_setkey(hd, key, gcry_cipher_get_algo_keylen(GCRY_CIPHER_AES128)) < 0) ||
+       (gcry_cipher_setiv(hd, iv, gcry_cipher_get_algo_blklen(GCRY_CIPHER_AES128)) < 0))
+    {
+      *errmsg = "Could not set key or iv for AES 128 GCM";
+      goto error;
+    }
+
+  ret = gcry_cipher_encrypt(hd, ciphertext, ciphertext_len, plaintext, plaintext_len);
+  if (ret < 0)
+    {
+      *errmsg = "Error GCM encrypting";
+      goto error;
+    }
+
+  ret = gcry_cipher_gettag(hd, tag, AUTHTAG_LENGTH);
+  if (ret < 0)
+    {
+      *errmsg = "Error getting authtag";
+      goto error;
+    }
+
+  gcry_cipher_close(hd);
+  return 0;
+
+ error:
+  gcry_cipher_close(hd);
+  return -1;
+#endif
+}
+
+static int
+encrypt_ctr(unsigned char *ciphertext, int ciphertext_len,
+            unsigned char *plaintext1, int plaintext1_len, unsigned char *plaintext2, int plaintext2_len,
+            unsigned char *key, unsigned char *iv, const char **errmsg)
+{
+#ifdef CONFIG_OPENSSL
+  EVP_CIPHER_CTX *ctx;
+  int len;
+
+  *errmsg = NULL;
+
+  if ( !(ctx = EVP_CIPHER_CTX_new()) || (EVP_EncryptInit_ex(ctx, EVP_aes_128_ctr(), NULL, key, iv) != 1) )
+    {
+      *errmsg = "Error initialising AES 128 CTR encryption";
+      goto error;
+    }
+
+  if ( (EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext1, plaintext1_len) != 1) ||
+       (EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext2, plaintext2_len) != 1) )
+    {
+      *errmsg = "Error CTR encrypting";
+      goto error;
+    }
+
+  if (EVP_EncryptFinal_ex(ctx, ciphertext + len, &len) != 1)
+    {
+      *errmsg = "Error finalising encryption";
+      goto error;
+    }
+
+  EVP_CIPHER_CTX_free(ctx);
+  return 0;
+
+ error:
+  EVP_CIPHER_CTX_free(ctx);
+  return -1;
+#elif CONFIG_GCRYPT
+  gcry_cipher_hd_t hd;
+
+  if (gcry_cipher_open(&hd, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_CTR, 0) < 0)
+    {
+      *errmsg = "Error initialising AES 128 CTR encryption";
+      return -1;
+    }
+
+  if ( (gcry_cipher_setkey(hd, key, gcry_cipher_get_algo_keylen(GCRY_CIPHER_AES128)) < 0) ||
+       (gcry_cipher_setctr(hd, iv, gcry_cipher_get_algo_blklen(GCRY_CIPHER_AES128)) < 0) )
+    {
+      *errmsg = "Could not set key or iv for AES 128 CTR";
+      goto error;
+    }
+
+  if ( (gcry_cipher_encrypt(hd, ciphertext, ciphertext_len, plaintext1, plaintext1_len) < 0) ||
+       (gcry_cipher_encrypt(hd, ciphertext, ciphertext_len, plaintext2, plaintext2_len) < 0) )
+    {
+      *errmsg = "Error CTR encrypting";
+      goto error;
+    }
+
+  gcry_cipher_close(hd);
+  return 0;
+
+ error:
+  gcry_cipher_close(hd);
+  return -1;
+#endif
+}
+
+
+/* ---------------------------------- API ---------------------------------- */
+
+struct verification_setup_context *
+verification_setup_new(const char *pin)
+{
+  struct verification_setup_context *sctx;
+
+  if (sodium_init() == -1)
+    return NULL;
+
+  sctx = calloc(1, sizeof(struct verification_setup_context));
+  if (!sctx)
+    return NULL;
+
+  memcpy(sctx->pin, pin, sizeof(sctx->pin));
+
+  return sctx;
+}
+
+void
+verification_setup_free(struct verification_setup_context *sctx)
+{
+  if (!sctx)
+    return;
+
+  srp_user_delete(sctx->user);
+
+  free(sctx->pkB);
+  free(sctx->M2);
+  free(sctx->salt);
+  free(sctx->epk);
+  free(sctx->authtag);
+
+  free(sctx);
+}
+
+const char *
+verification_setup_errmsg(struct verification_setup_context *sctx)
+{
+  return sctx->errmsg;
+}
+
+uint8_t *
+verification_setup_request1(uint32_t *len, struct verification_setup_context *sctx)
+{
+  plist_t dict;
+  plist_t method;
+  plist_t user;
+  char *data = NULL; // Necessary to initialize because plist_to_bin() uses value
+
+  sctx->user = srp_user_new(HASH_SHA1, SRP_NG_2048, USERNAME, (unsigned char *)sctx->pin, sizeof(sctx->pin), 0, 0);
+
+  dict = plist_new_dict();
+
+  method = plist_new_string("pin");
+  user = plist_new_string(USERNAME);
+
+  plist_dict_set_item(dict, "method", method);
+  plist_dict_set_item(dict, "user", user);
+  plist_to_bin(dict, &data, len);
+  plist_free(dict);
+
+  return (uint8_t *)data;
+}
+
+uint8_t *
+verification_setup_request2(uint32_t *len, struct verification_setup_context *sctx)
+{
+  plist_t dict;
+  plist_t pk;
+  plist_t proof;
+  const char *auth_username = NULL;
+  char *data = NULL;
+
+  // Calculate A
+  srp_user_start_authentication(sctx->user, &auth_username, &sctx->pkA, &sctx->pkA_len);
+
+  // Calculate M1 (client proof)
+  srp_user_process_challenge(sctx->user, (const unsigned char *)sctx->salt, sctx->salt_len, (const unsigned char *)sctx->pkB, sctx->pkB_len, &sctx->M1, &sctx->M1_len);
+
+  pk = plist_new_data((char *)sctx->pkA, sctx->pkA_len);
+  proof = plist_new_data((char *)sctx->M1, sctx->M1_len);
+
+  dict = plist_new_dict();
+  plist_dict_set_item(dict, "pk", pk);
+  plist_dict_set_item(dict, "proof", proof);
+  plist_to_bin(dict, &data, len);
+  plist_free(dict);
+
+  return (uint8_t *)data;
+}
+
+uint8_t *
+verification_setup_request3(uint32_t *len, struct verification_setup_context *sctx)
+{
+  plist_t dict;
+  plist_t epk;
+  plist_t authtag;
+  char *data = NULL;
+  const unsigned char *session_key;
+  int session_key_len;
+  unsigned char key[SHA512_DIGEST_LENGTH];
+  unsigned char iv[SHA512_DIGEST_LENGTH];
+  unsigned char encrypted[128]; // Alloc a bit extra - should only need 2*16
+  unsigned char tag[16];
+  const char *errmsg;
+  int ret;
+
+  session_key = srp_user_get_session_key(sctx->user, &session_key_len);
+  if (!session_key)
+    {
+      sctx->errmsg = "Setup request 3: No valid session key";
+      return NULL;
+    }
+
+  ret = hash_ab(HASH_SHA512, key, (unsigned char *)AES_SETUP_KEY, strlen(AES_SETUP_KEY), session_key, session_key_len);
+  if (ret < 0)
+    {
+      sctx->errmsg = "Setup request 3: Hashing of key string and shared secret failed";
+      return NULL;
+    }
+
+  ret = hash_ab(HASH_SHA512, iv, (unsigned char *)AES_SETUP_IV, strlen(AES_SETUP_IV), session_key, session_key_len);
+  if (ret < 0)
+    {
+      sctx->errmsg = "Setup request 3: Hashing of iv string and shared secret failed";
+      return NULL;
+    }
+
+  iv[15]++; // Magic
+/*
+  if (iv[15] == 0x00 || iv[15] == 0xff)
+    printf("- note that value of last byte is %d!\n", iv[15]);
+*/
+  crypto_sign_keypair(sctx->public_key, sctx->private_key);
+
+  ret = encrypt_gcm(encrypted, sizeof(encrypted), tag, sctx->public_key, sizeof(sctx->public_key), key, iv, &errmsg);
+  if (ret < 0)
+    {
+      sctx->errmsg = errmsg;
+      return NULL;
+    }
+
+  epk = plist_new_data((char *)encrypted, EPK_LENGTH);
+  authtag = plist_new_data((char *)tag, AUTHTAG_LENGTH);
+
+  dict = plist_new_dict();
+  plist_dict_set_item(dict, "epk", epk);
+  plist_dict_set_item(dict, "authTag", authtag);
+  plist_to_bin(dict, &data, len);
+  plist_free(dict);
+
+  return (uint8_t *)data;
+}
+
+int
+verification_setup_response1(struct verification_setup_context *sctx, const uint8_t *data, uint32_t data_len)
+{
+  plist_t dict;
+  plist_t pk;
+  plist_t salt;
+
+  plist_from_bin((const char *)data, data_len, &dict);
+
+  pk = plist_dict_get_item(dict, "pk");
+  salt = plist_dict_get_item(dict, "salt");
+  if (!pk || !salt)
+    {
+      sctx->errmsg = "Setup response 1: Missing pk or salt";
+      plist_free(dict);
+      return -1;
+    }
+
+  plist_get_data_val(pk, (char **)&sctx->pkB, &sctx->pkB_len); // B
+  plist_get_data_val(salt, (char **)&sctx->salt, &sctx->salt_len);
+
+  plist_free(dict);
+
+  return 0;
+}
+
+int
+verification_setup_response2(struct verification_setup_context *sctx, const uint8_t *data, uint32_t data_len)
+{
+  plist_t dict;
+  plist_t proof;
+
+  plist_from_bin((const char *)data, data_len, &dict);
+
+  proof = plist_dict_get_item(dict, "proof");
+  if (!proof)
+    {
+      sctx->errmsg = "Setup response 2: Missing proof";
+      plist_free(dict);
+      return -1;
+    }
+
+  plist_get_data_val(proof, (char **)&sctx->M2, &sctx->M2_len); // M2
+
+  plist_free(dict);
+
+  // Check M2
+  srp_user_verify_session(sctx->user, (const unsigned char *)sctx->M2);
+  if (!srp_user_is_authenticated(sctx->user))
+    {
+      sctx->errmsg = "Setup response 2: Server authentication failed";
+      return -1;
+    }
+
+  return 0;
+}
+
+int
+verification_setup_response3(struct verification_setup_context *sctx, const uint8_t *data, uint32_t data_len)
+{
+  plist_t dict;
+  plist_t epk;
+  plist_t authtag;
+
+  plist_from_bin((const char *)data, data_len, &dict);
+
+  epk = plist_dict_get_item(dict, "epk");
+  if (!epk)
+    {
+      sctx->errmsg = "Setup response 3: Missing epk";
+      plist_free(dict);
+      return -1;
+    }
+
+  plist_get_data_val(epk, (char **)&sctx->epk, &sctx->epk_len);
+
+  authtag = plist_dict_get_item(dict, "authTag");
+  if (!authtag)
+    {
+      sctx->errmsg = "Setup response 3: Missing authTag";
+      plist_free(dict);
+      return -1;
+    }
+
+  plist_get_data_val(authtag, (char **)&sctx->authtag, &sctx->authtag_len);
+
+  plist_free(dict);
+
+  return 0;
+}
+
+int
+verification_setup_result(const char **authorisation_key, struct verification_setup_context *sctx)
+{
+  struct verification_verify_context *vctx;
+  char *ptr;
+  int i;
+
+  if (sizeof(vctx->client_public_key) != sizeof(sctx->public_key) || sizeof(vctx->client_private_key) != sizeof(sctx->private_key))
+    {
+      sctx->errmsg = "Setup result: Bug!";
+      return -1;
+    }
+
+  // Fills out the auth_key with public + private in hex. It seems that the private
+  // key actually includes the public key (last 32 bytes), so we could in
+  // principle just export the private key
+  ptr = sctx->auth_key;
+  for (i = 0; i < sizeof(sctx->public_key); i++)
+    ptr += sprintf(ptr, "%02x", sctx->public_key[i]);
+  for (i = 0; i < sizeof(sctx->private_key); i++)
+    ptr += sprintf(ptr, "%02x", sctx->private_key[i]);
+  *ptr = '\0';
+
+  *authorisation_key = sctx->auth_key;
+  return 0;
+}
+
+
+struct verification_verify_context *
+verification_verify_new(const char *authorisation_key)
+{
+  struct verification_verify_context *vctx;
+  char hex[] = { 0, 0, 0 };
+  const char *ptr;
+  int i;
+
+  if (sodium_init() == -1)
+    return NULL;
+
+  if (!authorisation_key)
+    return NULL;
+
+  if (strlen(authorisation_key) != 2 * (sizeof(vctx->client_public_key) + sizeof(vctx->client_private_key)))
+    return NULL;
+
+  vctx = calloc(1, sizeof(struct verification_verify_context));
+  if (!vctx)
+    return NULL;
+
+  ptr = authorisation_key;
+  for (i = 0; i < sizeof(vctx->client_public_key); i++, ptr+=2)
+    {
+      hex[0] = ptr[0];
+      hex[1] = ptr[1];
+      vctx->client_public_key[i] = strtol(hex, NULL, 16);
+    }
+  for (i = 0; i < sizeof(vctx->client_private_key); i++, ptr+=2)
+    {
+      hex[0] = ptr[0];
+      hex[1] = ptr[1];
+      vctx->client_private_key[i] = strtol(hex, NULL, 16);
+    }
+
+  return vctx;
+}
+
+void
+verification_verify_free(struct verification_verify_context *vctx)
+{
+  if (!vctx)
+    return;
+
+  free(vctx);
+}
+
+const char *
+verification_verify_errmsg(struct verification_verify_context *vctx)
+{
+  return vctx->errmsg;
+}
+
+uint8_t *
+verification_verify_request1(uint32_t *len, struct verification_verify_context *vctx)
+{
+  const uint8_t basepoint[32] = {9};
+  uint8_t *data;
+  int ret;
+
+  ret = crypto_scalarmult(vctx->client_eph_public_key, vctx->client_eph_private_key, basepoint);
+  if (ret < 0)
+    {
+      vctx->errmsg = "Verify request 1: Curve 25519 returned an error";
+      return NULL;
+    }
+
+  *len = 4 + sizeof(vctx->client_eph_public_key) + sizeof(vctx->client_public_key);
+  data = calloc(1, *len);
+  if (!data)
+    {
+      vctx->errmsg = "Verify request 1: Out of memory";
+      return NULL;
+    }
+
+  data[0] = 1; // Magic
+  memcpy(data + 4, vctx->client_eph_public_key, sizeof(vctx->client_eph_public_key));
+  memcpy(data + 4 + sizeof(vctx->client_eph_public_key), vctx->client_public_key, sizeof(vctx->client_public_key));
+
+  return data;
+}
+
+uint8_t *
+verification_verify_request2(uint32_t *len, struct verification_verify_context *vctx)
+{
+  uint8_t shared_secret[crypto_scalarmult_BYTES];
+  uint8_t key[SHA512_DIGEST_LENGTH];
+  uint8_t iv[SHA512_DIGEST_LENGTH];
+  uint8_t encrypted[128]; // Alloc a bit extra, should only really need size of public key len
+  uint8_t signature[crypto_sign_BYTES];
+  uint8_t *data;
+  int ret;
+  const char *errmsg;
+
+  *len = sizeof(vctx->client_eph_public_key) + sizeof(vctx->server_eph_public_key);
+  data = calloc(1, *len);
+  if (!data)
+    {
+      vctx->errmsg = "Verify request 2: Out of memory";
+      return NULL;
+    }
+
+  memcpy(data, vctx->client_eph_public_key, sizeof(vctx->client_eph_public_key));
+  memcpy(data + sizeof(vctx->client_eph_public_key), vctx->server_eph_public_key, sizeof(vctx->server_eph_public_key));
+
+  crypto_sign_detached(signature, NULL, data, *len, vctx->client_private_key);
+
+  free(data);
+
+  ret = crypto_scalarmult(shared_secret, vctx->client_eph_private_key, vctx->server_eph_public_key);
+  if (ret < 0)
+    {
+      vctx->errmsg = "Verify request 2: Curve 25519 returned an error";
+      return NULL;
+    }
+
+  ret = hash_ab(HASH_SHA512, key, (unsigned char *)AES_VERIFY_KEY, strlen(AES_VERIFY_KEY), shared_secret, sizeof(shared_secret));
+  if (ret < 0)
+    {
+      vctx->errmsg = "Verify request 2: Hashing of key string and shared secret failed";
+      return NULL;
+    }
+
+  ret = hash_ab(HASH_SHA512, iv, (unsigned char *)AES_VERIFY_IV, strlen(AES_VERIFY_IV), shared_secret, sizeof(shared_secret));
+  if (ret < 0)
+    {
+      vctx->errmsg = "Verify request 2: Hashing of iv string and shared secret failed";
+      return NULL;
+    }
+
+  ret = encrypt_ctr(encrypted, sizeof(encrypted), vctx->server_public_key, sizeof(vctx->server_public_key), signature, sizeof(signature), key, iv, &errmsg);
+  if (ret < 0)
+    {
+      vctx->errmsg = errmsg;
+      return NULL;
+    }
+
+  *len = 4 + sizeof(vctx->server_public_key);
+  data = calloc(1, *len);
+  if (!data)
+    {
+      vctx->errmsg = "Verify request 2: Out of memory";
+      return NULL;
+    }
+
+  memcpy(data + 4, encrypted, sizeof(vctx->server_public_key));
+
+  return data;
+}
+
+int
+verification_verify_response1(struct verification_verify_context *vctx, const uint8_t *data, uint32_t data_len)
+{
+  uint32_t wanted;
+
+  wanted = sizeof(vctx->server_eph_public_key) + sizeof(vctx->server_public_key);
+  if (data_len < wanted)
+    {
+      vctx->errmsg = "Verify response 2: Unexpected response (too short)";
+      return -1;
+    }
+
+  memcpy(vctx->server_eph_public_key, data, sizeof(vctx->server_eph_public_key));
+  memcpy(vctx->server_public_key, data + sizeof(vctx->server_eph_public_key), sizeof(vctx->server_public_key));
+
+  return 0;
+}
diff --git a/src/outputs/raop_verification.h b/src/outputs/raop_verification.h
new file mode 100644
index 0000000..e175a08
--- /dev/null
+++ b/src/outputs/raop_verification.h
@@ -0,0 +1,66 @@
+#ifndef __VERIFICATION_H__
+#define __VERIFICATION_H__
+
+#include <stdint.h>
+
+struct verification_setup_context;
+struct verification_verify_context;
+
+/* When you have the pin-code (must be 4 bytes), create a new context with this
+ * function and then call verification_setup_request1()
+ */
+struct verification_setup_context *
+verification_setup_new(const char *pin);
+void
+verification_setup_free(struct verification_setup_context *sctx);
+
+/* Returns last error message
+ */
+const char *
+verification_setup_errmsg(struct verification_setup_context *sctx);
+
+uint8_t *
+verification_setup_request1(uint32_t *len, struct verification_setup_context *sctx);
+uint8_t *
+verification_setup_request2(uint32_t *len, struct verification_setup_context *sctx);
+uint8_t *
+verification_setup_request3(uint32_t *len, struct verification_setup_context *sctx);
+
+int
+verification_setup_response1(struct verification_setup_context *sctx, const uint8_t *data, uint32_t data_len);
+int
+verification_setup_response2(struct verification_setup_context *sctx, const uint8_t *data, uint32_t data_len);
+int
+verification_setup_response3(struct verification_setup_context *sctx, const uint8_t *data, uint32_t data_len);
+
+/* Returns a 0-terminated string that is the authorisation key. The caller
+ * should save it and use it later to initialize verification_verify_new().
+ * Note that the pointer becomes invalid when you free sctx.
+ */
+int
+verification_setup_result(const char **authorisation_key, struct verification_setup_context *sctx);
+
+
+/* When you have completed the setup you can extract a key with
+ * verification_setup_result(). Give the string as input to this function to
+ * create a verification context and then call verification_verify_request1()
+ */
+struct verification_verify_context *
+verification_verify_new(const char *authorisation_key);
+void
+verification_verify_free(struct verification_verify_context *vctx);
+
+/* Returns last error message
+ */
+const char *
+verification_verify_errmsg(struct verification_verify_context *vctx);
+
+uint8_t *
+verification_verify_request1(uint32_t *len, struct verification_verify_context *vctx);
+uint8_t *
+verification_verify_request2(uint32_t *len, struct verification_verify_context *vctx);
+
+int
+verification_verify_response1(struct verification_verify_context *vctx, const uint8_t *data, uint32_t data_len);
+
+#endif  /* !__VERIFICATION_H__ */
diff --git a/src/pipe.c b/src/pipe.c
deleted file mode 100644
index 76bc672..0000000
--- a/src/pipe.c
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Copyright (C) 2014 Espen Jürgensen <espenjurgensen at gmail.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- */
-
-#ifdef HAVE_CONFIG_H
-# include <config.h>
-#endif
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdint.h>
-#include <fcntl.h>
-#include <string.h>
-#include <time.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/ioctl.h>
-
-#include "pipe.h"
-#include "logger.h"
-
-#define PIPE_BUFFER_SIZE 8192
-
-static int g_fd = -1;
-static uint16_t *g_buf = NULL;
-
-int
-pipe_setup(struct media_file_info *mfi)
-{
-  struct stat sb;
-
-  if (!mfi->path)
-    {
-      DPRINTF(E_LOG, L_PLAYER, "Path to pipe is NULL\n");
-      return -1;
-    }
-
-  DPRINTF(E_DBG, L_PLAYER, "Setting up pipe: %s\n", mfi->path);
-
-  if (lstat(mfi->path, &sb) < 0)
-    {
-      DPRINTF(E_LOG, L_PLAYER, "Could not lstat() '%s': %s\n", mfi->path, strerror(errno));
-      return -1;
-    }
-
-  if (!S_ISFIFO(sb.st_mode))
-    {
-      DPRINTF(E_LOG, L_PLAYER, "Source type is pipe, but path is not a fifo: %s\n", mfi->path);
-      return -1;
-    }
-
-  pipe_cleanup();
-
-  g_fd = open(mfi->path, O_RDONLY | O_NONBLOCK);
-  if (g_fd < 0)
-    {
-      DPRINTF(E_LOG, L_PLAYER, "Could not open pipe for reading '%s': %s\n", mfi->path, strerror(errno));
-      return -1;
-    }
-
-  g_buf = (uint16_t *)malloc(PIPE_BUFFER_SIZE);
-  if (!g_buf)
-    {
-      DPRINTF(E_LOG, L_PLAYER, "Out of memory for buffer\n");
-      return -1;
-    }
-
-  return 0;
-}
-
-void
-pipe_cleanup(void)
-{
-  if (g_fd >= 0)
-    close(g_fd);
-  g_fd = -1;
-
-  if (g_buf)
-    free(g_buf);
-  g_buf = NULL;
-
-  return;
-}
-
-int
-pipe_audio_get(struct evbuffer *evbuf, int wanted)
-{
-  int got;
-
-  if (wanted > PIPE_BUFFER_SIZE)
-    wanted = PIPE_BUFFER_SIZE;
-
-  got = read(g_fd, g_buf, wanted);
-
-  if ((got < 0) && (errno != EAGAIN))
-    {
-      DPRINTF(E_LOG, L_PLAYER, "Could not read from pipe: %s\n", strerror(errno));
-      return -1;
-    }
-
-  // If the other end of the pipe is not writing or the read was blocked,
-  // we just return silence
-  if (got <= 0)
-    {
-      memset(g_buf, 0, wanted);
-      got = wanted;
-    }
-
-  evbuffer_add(evbuf, g_buf, got);
-
-  return got;
-}
-
diff --git a/src/pipe.h b/src/pipe.h
deleted file mode 100644
index 21d923b..0000000
--- a/src/pipe.h
+++ /dev/null
@@ -1,17 +0,0 @@
-
-#ifndef __PIPE_H__
-#define __PIPE_H__
-
-#include <event2/buffer.h>
-#include "db.h"
-
-int
-pipe_setup(struct media_file_info *mfi);
-
-void
-pipe_cleanup(void);
-
-int
-pipe_audio_get(struct evbuffer *evbuf, int wanted);
-
-#endif /* !__PIPE_H__ */
diff --git a/src/player.c b/src/player.c
index d6423b9..3b15ed1 100644
--- a/src/player.c
+++ b/src/player.c
@@ -1,6 +1,6 @@
 /*
  * Copyright (C) 2010-2011 Julien BLACHE <jb at jblache.org>
- * Copyright (C) 2016 Espen Jürgensen <espenjurgensen at gmail.com>
+ * Copyright (C) 2016-2017 Espen Jürgensen <espenjurgensen at gmail.com>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -15,6 +15,40 @@
  * You should have received a copy of the GNU General Public License
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+
+ * About player.c
+ * --------------
+ * The main tasks of the player are the following:
+ * - handle playback commands, status checks and events from other threads
+ * - receive audio from the input thread and to own the playback buffer
+ * - feed the outputs at the appropriate rate (controlled by the playback timer)
+ * - output device handling (partly outsourced to outputs.c)
+ * - notify about playback status changes
+ * - maintain the playback queue
+ * 
+ * The player thread should never be making operations that may block, since
+ * that could block callers requesting status (effectively making forked-daapd
+ * unresponsive) and it could also starve the outputs. In practice this rule is
+ * not always obeyed, for instance some outputs do their setup in ways that
+ * could block.
+ *
+ *
+ * About metadata
+ * --------------
+ * The player gets metadata from library + inputs and passes it to the outputs
+ * and other clients (e.g. Remotes).
+ *
+ *  1. On playback start, metadata from the library is loaded into the queue
+ *     items, and these items are then the source of metadata for clients.
+ *  2. During playback, the input may signal new metadata by making a
+ *     input_write() with the INPUT_FLAG_METADATA flag. When the player read
+ *     reaches that data, the player will request the metadata from the input
+ *     with input_metadata_get(). This metadata is then saved to the currently
+ *     playing queue item, and the clients are told to update metadata.
+ *  3. Artwork works differently than textual metadata. The artwork module will
+ *     look for artwork in the library, and addition also check the artwork_url
+ *     of the queue_item.
  */
 
 #ifdef HAVE_CONFIG_H
@@ -22,6 +56,7 @@
 #endif
 
 #include <stdio.h>
+#include <stdbool.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <fcntl.h>
@@ -35,7 +70,7 @@
 # include <pthread_np.h>
 #endif
 
-#if defined(__linux__)
+#ifdef HAVE_TIMERFD
 # include <sys/timerfd.h>
 #elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
 # include <signal.h>
@@ -55,18 +90,13 @@
 #include "listener.h"
 #include "commands.h"
 
-/* Audio outputs */
+// Audio and metadata outputs
 #include "outputs.h"
 
-/* Audio inputs */
-#include "transcode.h"
-#include "pipe.h"
-#ifdef HAVE_SPOTIFY_H
-# include "spotify.h"
-#endif
+// Audio and metadata input
+#include "input.h"
 
-/* Metadata input/output */
-#include "http.h"
+// Scrobbling
 #ifdef LASTFM
 # include "lastfm.h"
 #endif
@@ -81,49 +111,18 @@
 
 // Default volume (must be from 0 - 100)
 #define PLAYER_DEFAULT_VOLUME 50
-// Used to keep the player from getting ahead of a rate limited source (see below)
-#define PLAYER_TICKS_MAX_OVERRUN 2
-
-struct player_source
-{
-  /* Id of the file/item in the files database */
-  uint32_t id;
-
-  /* Item-Id of the file/item in the queue */
-  uint32_t item_id;
-
-  /* Length of the file/item in milliseconds */
-  uint32_t len_ms;
-
-  enum data_kind data_kind;
-  enum media_kind media_kind;
-
-  /* Start time of the media item as rtp-time
-     The stream-start is the rtp-time the media item did or would have
-     started playing (after seek or pause), therefor the elapsed time of the
-     media item is always:
-     elapsed time = current rtptime - stream-start */
-  uint64_t stream_start;
-
-  /* Output start time of the media item as rtp-time
-     The output start time is the rtp-time of the first audio packet send
-     to the audio outputs.
-     It differs from stream-start especially after a seek, where the first audio
-     packet has the next rtp-time as output start and stream start becomes the
-     rtp-time the media item would have been started playing if the seek did
-     not happen. */
-  uint64_t output_start;
-
-  /* End time of media item as rtp-time
-     The end time is set if the reading (source_read) of the media item reached
-     end of file, until then it is 0. */
-  uint64_t end;
-
-  struct transcode_ctx *xcode;
-  int setup_done;
-
-  struct player_source *play_next;
-};
+// For every tick_interval, we will read a packet from the input buffer and
+// write it to the outputs. If the input is empty, we will try to catch up next
+// tick. However, at some point we will owe the outputs so much data that we
+// have to suspend playback and wait for the input to get its act together.
+// (value is in milliseconds and should be low enough to avoid output underrun)
+#define PLAYER_READ_BEHIND_MAX 1500
+// Generally, an output must not block (for long) when outputs_write() is
+// called. If an output does that anyway, the next tick event will be late, and
+// by extension playback_cb(). We will try to catch up, but if the delay
+// gets above this value, we will suspend playback and reset the output.
+// (value is in milliseconds)
+#define PLAYER_WRITE_BEHIND_MAX 1500
 
 struct volume_param {
   int volume;
@@ -136,64 +135,22 @@ struct spk_enum
   void *arg;
 };
 
-struct playback_start_param
-{
-  uint32_t id;
-  int pos;
-
-  uint32_t *id_ptr;
-};
-
-struct playerqueue_get_param
-{
-  int pos;
-  int count;
-
-  struct queue *queue;
-};
-
-struct playerqueue_add_param
-{
-  struct queue_item *items;
-  int pos;
-
-  uint32_t *item_id_ptr;
-};
-
-struct playerqueue_move_param
-{
-  uint32_t item_id;
-  int from_pos;
-  int to_pos;
-  int count;
-};
-
-struct playerqueue_remove_param
+struct speaker_set_param
 {
-  int from_pos;
-  int count;
+  uint64_t *device_ids;
+  int intval;
 };
 
-struct icy_artwork
+struct metadata_param
 {
-  uint32_t id;
-  char *artwork_url;
+  struct input_metadata *input;
+  struct output_metadata *output;
 };
 
-struct player_metadata
+struct speaker_auth_param
 {
-  int id;
-  uint64_t rtptime;
-  uint64_t offset;
-  int startup;
-
-  struct output_metadata *omd;
-};
-
-struct speaker_set_param
-{
-  uint64_t *device_ids;
-  int intval;
+  enum output_types type;
+  char pin[5];
 };
 
 union player_arg
@@ -204,18 +161,13 @@ union player_arg
   struct output_device *device;
   struct player_status *status;
   struct player_source *ps;
-  struct player_metadata *pmd;
+  struct metadata_param metadata_param;
   uint32_t *id_ptr;
   struct speaker_set_param speaker_set_param;
   enum repeat_mode mode;
+  struct speaker_auth_param auth;
   uint32_t id;
   int intval;
-  struct icy_artwork icy;
-  struct playback_start_param playback_start_param;
-  struct playerqueue_get_param queue_get_param;
-  struct playerqueue_add_param queue_add_param;
-  struct playerqueue_move_param queue_move_param;
-  struct playerqueue_remove_param queue_remove_param;
 };
 
 struct event_base *evbase_player;
@@ -224,17 +176,22 @@ static int player_exit;
 static pthread_t tid_player;
 static struct commands_base *cmdbase;
 
-/* Config values */
+// Keep track of how many outputs need to call back when flushing internally
+// from the player thread (where we can't use player_playback_pause)
+static int player_flush_pending;
+
+// Config values
 static int speaker_autoselect;
 static int clear_queue_on_stop_disabled;
 
-/* Player status */
+// Player status
 static enum play_status player_state;
 static enum repeat_mode repeat;
 static char shuffle;
+static char consume;
 
-/* Playback timer */
-#if defined(__linux__)
+// Playback timer
+#ifdef HAVE_TIMERFD
 static int pb_timer_fd;
 #else
 timer_t pb_timer;
@@ -243,58 +200,64 @@ static struct event *pb_timer_ev;
 static struct timespec pb_timer_last;
 static struct timespec packet_timer_last;
 
-// How often the playback timer triggers player_playback_cb
+// How often the playback timer triggers playback_cb()
 static struct timespec tick_interval;
 // Timer resolution
 static struct timespec timer_res;
 // Time between two packets
 static struct timespec packet_time = { 0, AIRTUNES_V2_STREAM_PERIOD };
-// Will be positive if we need to skip some source reads (see below)
-static int ticks_skip;
 
-/* Sync values */
+// How many writes we owe the output (when the input is underrunning)
+static int pb_read_deficit;
+
+// PLAYER_READ_BEHIND_MAX and PLAYER_WRITE_BEHIND_MAX converted to clock ticks
+static int pb_read_deficit_max;
+static int pb_write_deficit_max;
+
+// True if we are trying to recover from a major playback timer overrun (write problems)
+static bool pb_write_recovery;
+
+// Sync values
 static struct timespec pb_pos_stamp;
 static uint64_t pb_pos;
 
-/* Stream position (packets) */
+// Stream position (packets)
 static uint64_t last_rtptime;
 
-/* Output devices */
+// Output devices
 static struct output_device *dev_list;
 
-/* Output status */
+// Output status
 static int output_sessions;
 
-/* Last commanded volume */
+// Last commanded volume
 static int master_volume;
 
-/* Audio source */
+// Audio source
 static struct player_source *cur_playing;
 static struct player_source *cur_streaming;
 static uint32_t cur_plid;
 static uint32_t cur_plversion;
 
-static struct evbuffer *audio_buf;
-static uint8_t rawbuf[STOB(AIRTUNES_V2_PACKET_SAMPLES)];
+// Player buffer (holds one packet)
+static uint8_t pb_buffer[STOB(AIRTUNES_V2_PACKET_SAMPLES)];
+static size_t pb_buffer_offset;
 
+// Play history
+static struct player_history *history;
 
-/* Play queue */
-static struct queue *queue;
 
-/* Play history */
-static struct player_history *history;
+/* -------------------------------- Forwards -------------------------------- */
 
+static void
+playback_abort(void);
 
 static void
-status_update(enum play_status status)
-{
-  player_state = status;
+playback_suspend(void);
 
-  listener_notify(LISTENER_PLAYER);
-}
 
+/* ----------------------------- Volume helpers ----------------------------- */
 
-/* Volume helpers */
 static int
 rel_to_vol(int relvol)
 {
@@ -321,7 +284,7 @@ vol_to_rel(int volume)
   return (int)rel;
 }
 
-/* Master volume helpers */
+// Master volume helpers
 static void
 volume_master_update(int newvol)
 {
@@ -354,7 +317,8 @@ volume_master_find(void)
 }
 
 
-/* Device select/deselect hooks */
+/* ---------------------- Device select/deselect hooks ---------------------- */
+
 static void
 speaker_select_output(struct output_device *device)
 {
@@ -381,107 +345,9 @@ speaker_deselect_output(struct output_device *device)
 }
 
 
-int
-player_get_current_pos(uint64_t *pos, struct timespec *ts, int commit)
-{
-  uint64_t delta;
-  int ret;
-
-  ret = clock_gettime_with_res(CLOCK_MONOTONIC, ts, &timer_res);
-  if (ret < 0)
-    {
-      DPRINTF(E_LOG, L_PLAYER, "Couldn't get clock: %s\n", strerror(errno));
-
-      return -1;
-    }
-
-  delta = (ts->tv_sec - pb_pos_stamp.tv_sec) * 1000000 + (ts->tv_nsec - pb_pos_stamp.tv_nsec) / 1000;
-
-#ifdef DEBUG_SYNC
-  DPRINTF(E_DBG, L_PLAYER, "Delta is %" PRIu64 " usec\n", delta);
-#endif
-
-  delta = (delta * 44100) / 1000000;
-
-#ifdef DEBUG_SYNC
-  DPRINTF(E_DBG, L_PLAYER, "Delta is %" PRIu64 " samples\n", delta);
-#endif
-
-  *pos = pb_pos + delta;
-
-  if (commit)
-    {
-      pb_pos = *pos;
-
-      pb_pos_stamp.tv_sec = ts->tv_sec;
-      pb_pos_stamp.tv_nsec = ts->tv_nsec;
-
-#ifdef DEBUG_SYNC
-      DPRINTF(E_DBG, L_PLAYER, "Pos: %" PRIu64 " (clock)\n", *pos);
-#endif
-    }
-
-  return 0;
-}
-
-static int
-pb_timer_start(void)
-{
-  struct itimerspec tick;
-  int ret;
-
-  tick.it_interval = tick_interval;
-  tick.it_value = tick_interval;
-
-#if defined(__linux__)
-  ret = timerfd_settime(pb_timer_fd, 0, &tick, NULL);
-#else
-  ret = timer_settime(pb_timer, 0, &tick, NULL);
-#endif
-  if (ret < 0)
-    {
-      DPRINTF(E_LOG, L_PLAYER, "Could not arm playback timer: %s\n", strerror(errno));
-
-      return -1;
-    }
+/* ----------------------- Misc helpers and callbacks ----------------------- */
 
-  return 0;
-}
-
-static int
-pb_timer_stop(void)
-{
-  struct itimerspec tick;
-  int ret;
-
-  memset(&tick, 0, sizeof(struct itimerspec));
-
-#if defined(__linux__)
-  ret = timerfd_settime(pb_timer_fd, 0, &tick, NULL);
-#else
-  ret = timer_settime(pb_timer, 0, &tick, NULL);
-#endif
-  if (ret < 0)
-    {
-      DPRINTF(E_LOG, L_PLAYER, "Could not disarm playback timer: %s\n", strerror(errno));
-
-      return -1;
-    }
-
-  return 0;
-}
-
-/* Forward */
-static void
-playback_abort(void);
-
-static enum command_state
-playerqueue_clear(void *arg, int *retval);
-
-static void
-player_metadata_send(struct player_metadata *pmd);
-
-/* Callback from the worker thread (async operation as it may block) */
+// Callback from the worker thread (async operation as it may block)
 static void
 playcount_inc_cb(void *arg)
 {
@@ -491,7 +357,7 @@ playcount_inc_cb(void *arg)
 }
 
 #ifdef LASTFM
-/* Callback from the worker thread (async operation as it may block) */
+// Callback from the worker thread (async operation as it may block)
 static void
 scrobble_cb(void *arg)
 {
@@ -501,112 +367,73 @@ scrobble_cb(void *arg)
 }
 #endif
 
-/* Callback from the worker thread
- * This prepares metadata in the worker thread, since especially the artwork
- * retrieval may take some time. outputs_metadata_prepare() must be thread safe.
- * The sending must be done in the player thread.
- */
+// Callback from the worker thread. Here the heavy lifting is done: updating the
+// db_queue_item, retrieving artwork (through outputs_metadata_prepare) and
+// when done, telling the player to send the metadata to the clients
 static void
-metadata_prepare_cb(void *arg)
+metadata_update_cb(void *arg)
 {
-  struct player_metadata *pmd = arg;
+  struct input_metadata *metadata = arg;
+  struct output_metadata *o_metadata;
+  struct db_queue_item *queue_item;
+  int ret;
 
-  pmd->omd = outputs_metadata_prepare(pmd->id);
+  queue_item = db_queue_fetch_byitemid(metadata->item_id);
+  if (!queue_item)
+    {
+      DPRINTF(E_LOG, L_PLAYER, "Bug! Input metadata item_id does not match anything in queue\n");
+      goto out_free_metadata;
+    }
 
-  if (pmd->omd)
-    player_metadata_send(pmd);
+  // Since we won't be using the metadata struct values for anything else than
+  // this we just swap pointers
+  if (metadata->artist)
+    swap_pointers(&queue_item->artist, &metadata->artist);
+  if (metadata->title)
+    swap_pointers(&queue_item->title, &metadata->title);
+  if (metadata->album)
+    swap_pointers(&queue_item->album, &metadata->album);
+  if (metadata->genre)
+    swap_pointers(&queue_item->genre, &metadata->genre);
+  if (metadata->artwork_url)
+    swap_pointers(&queue_item->artwork_url, &metadata->artwork_url);
+  if (metadata->song_length)
+    queue_item->song_length = metadata->song_length;
 
-  outputs_metadata_free(pmd->omd);
-}
+  ret = db_queue_update_item(queue_item);
+  if (ret < 0)
+    {
+      DPRINTF(E_LOG, L_PLAYER, "Database error while updating queue with new metadata\n");
+      goto out_free_queueitem;
+    }
 
-/* Callback from the worker thread (async operation as it may block) */
-static void
-update_icy_cb(void *arg)
-{
-  struct http_icy_metadata *metadata = arg;
+  o_metadata = outputs_metadata_prepare(metadata->item_id);
 
-  db_file_update_icy(metadata->id, metadata->artist, metadata->title);
+  // Actual sending must be done by player, since the worker does not own the outputs
+  player_metadata_send(metadata, o_metadata);
 
-  http_icy_metadata_free(metadata, 1);
-}
+  outputs_metadata_free(o_metadata);
 
-/* Metadata */
-static void
-metadata_prune(uint64_t pos)
-{
-  outputs_metadata_prune(pos);
-}
+ out_free_queueitem:
+  free_queue_item(queue_item, 0);
 
-static void
-metadata_purge(void)
-{
-  outputs_metadata_purge();
+ out_free_metadata:
+  input_metadata_free(metadata, 1);
 }
 
+// Gets the metadata, but since the actual update requires db writes and
+// possibly retrieving artwork we let the worker do the next step
 static void
 metadata_trigger(int startup)
 {
-  struct player_metadata pmd;
-
-  memset(&pmd, 0, sizeof(struct player_metadata));
-
-  pmd.id = cur_streaming->id;
-  pmd.startup = startup;
-
-  if (cur_streaming->stream_start && cur_streaming->output_start)
-    {
-      pmd.offset = cur_streaming->output_start - cur_streaming->stream_start;
-      pmd.rtptime = cur_streaming->stream_start;
-    }
-  else
-    {
-      DPRINTF(E_LOG, L_PLAYER, "PTOH! Unhandled song boundary case in metadata_trigger()\n");
-    }
-
-  /* Defer the actual work of preparing the metadata to the worker thread */
-  worker_execute(metadata_prepare_cb, &pmd, sizeof(struct player_metadata), 0);
-}
-
-/* Checks if there is new HTTP ICY metadata, and if so sends updates to clients */
-void
-metadata_check_icy(void)
-{
-  struct http_icy_metadata *metadata;
-  int changed;
+  struct input_metadata metadata;
+  int ret;
 
-  metadata = transcode_metadata(cur_streaming->xcode, &changed);
-  if (!metadata)
+  ret = input_metadata_get(&metadata, cur_streaming, startup, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES);
+  if (ret < 0)
     return;
 
-  if (!changed || !metadata->title)
-    goto no_update;
-
-  if (metadata->title[0] == '\0')
-    goto no_update;
-
-  metadata->id = cur_streaming->id;
-
-  /* Defer the database update to the worker thread */
-  worker_execute(update_icy_cb, metadata, sizeof(struct http_icy_metadata), 0);
-
-  /* Triggers preparing and sending output metadata */
-  metadata_trigger(0);
-
-  /* Only free the struct, the content must be preserved for update_icy_cb */
-  free(metadata);
-
-  status_update(player_state);
-
-  return;
-
- no_update:
-  http_icy_metadata_free(metadata, 0);
-}
-
-struct player_history *
-player_history_get(void)
-{
-  return history;
+  worker_execute(metadata_update_cb, &metadata, sizeof(metadata), 0);
 }
 
 /*
@@ -618,7 +445,7 @@ history_add(uint32_t id, uint32_t item_id)
   unsigned int cur_index;
   unsigned int next_index;
 
-  /* Check if the current song is already the last in the history to avoid duplicates */
+  // Check if the current song is already the last in the history to avoid duplicates
   cur_index = (history->start_index + history->count - 1) % MAX_HISTORY_COUNT;
   if (id == history->id[cur_index])
     {
@@ -626,7 +453,7 @@ history_add(uint32_t id, uint32_t item_id)
       return;
     }
 
-  /* Calculate the next index and update the start-index and count for the id-buffer */
+  // Calculate the next index and update the start-index and count for the id-buffer
   next_index = (history->start_index + history->count) % MAX_HISTORY_COUNT;
   if (next_index == history->start_index && history->count > 0)
     history->start_index = (history->start_index + 1) % MAX_HISTORY_COUNT;
@@ -638,317 +465,31 @@ history_add(uint32_t id, uint32_t item_id)
     history->count++;
 }
 
-
-/* Audio sources */
-
-/*
- * Initializes the given player source for playback
- */
-static int
-stream_setup(struct player_source *ps, struct media_file_info *mfi)
-{
-  char *url;
-  int ret;
-
-  if (!ps || !mfi)
-    {
-      DPRINTF(E_LOG, L_PLAYER, "No player source and/or media info given to stream_setup\n");
-      return -1;
-    }
-
-  if (ps->setup_done)
-    {
-      DPRINTF(E_LOG, L_PLAYER, "Given player source already setup (id = %d)\n", ps->id);
-      return -1;
-    }
-
-  // Setup depending on data kind
-  switch (ps->data_kind)
-    {
-      case DATA_KIND_FILE:
-	ps->xcode = transcode_setup(mfi, XCODE_PCM16_NOHEADER, NULL);
-	ret = ps->xcode ? 0 : -1;
-	break;
-
-      case DATA_KIND_HTTP:
-	ret = http_stream_setup(&url, mfi->path);
-	if (ret < 0)
-	  break;
-
-	free(mfi->path);
-	mfi->path = url;
-
-	ps->xcode = transcode_setup(mfi, XCODE_PCM16_NOHEADER, NULL);
-	ret = ps->xcode ? 0 : -1;
-	break;
-
-      case DATA_KIND_SPOTIFY:
-#ifdef HAVE_SPOTIFY_H
-	ret = spotify_playback_setup(mfi);
-#else
-	DPRINTF(E_LOG, L_PLAYER, "Player source has data kind 'spotify' (%d), but forked-daapd is compiled without spotify support - cannot setup source '%s' (%s)\n",
-		    ps->data_kind, mfi->title, mfi->path);
-	ret = -1;
-#endif
-	break;
-
-      case DATA_KIND_PIPE:
-	ret = pipe_setup(mfi);
-	break;
-
-      default:
-	DPRINTF(E_LOG, L_PLAYER, "Unknown data kind (%d) for player source - cannot setup source '%s' (%s)\n",
-	    ps->data_kind, mfi->title, mfi->path);
-	ret = -1;
-    }
-
-  if (ret == 0)
-      ps->setup_done = 1;
-  else
-      DPRINTF(E_LOG, L_PLAYER, "Failed to setup player source (id = %d)\n", ps->id);
-
-  return ret;
-}
-
-/*
- * Starts or resumes plaback for the given player source
- */
-static int
-stream_play(struct player_source *ps)
-{
-  int ret;
-
-  if (!ps)
-    {
-      DPRINTF(E_LOG, L_PLAYER, "Stream play called with no active streaming player source\n");
-      return -1;
-    }
-
-  if (!ps->setup_done)
-    {
-      DPRINTF(E_LOG, L_PLAYER, "Given player source not setup, play not possible\n");
-      return -1;
-    }
-
-  // Start/resume playback depending on data kind
-  switch (ps->data_kind)
-    {
-      case DATA_KIND_HTTP:
-      case DATA_KIND_FILE:
-	ret = 0;
-	break;
-
-#ifdef HAVE_SPOTIFY_H
-      case DATA_KIND_SPOTIFY:
-	ret = spotify_playback_play();
-	break;
-#endif
-
-      case DATA_KIND_PIPE:
-	ret = 0;
-	break;
-
-      default:
-	ret = -1;
-    }
-
-  return ret;
-}
-
-/*
- * Read up to "len" data from the given player source and returns
- * the actual amount of data read.
- */
-static int
-stream_read(struct player_source *ps, int len)
-{
-  int icy_timer;
-  int ret;
-
-  if (!ps)
-    {
-      DPRINTF(E_LOG, L_PLAYER, "Stream read called with no active streaming player source\n");
-      return -1;
-    }
-
-  if (!ps->setup_done)
-    {
-      DPRINTF(E_LOG, L_PLAYER, "Given player source not setup for reading data\n");
-      return -1;
-    }
-
-  // Read up to len data depending on data kind
-  switch (ps->data_kind)
-    {
-      case DATA_KIND_HTTP:
-	ret = transcode(audio_buf, len, ps->xcode, &icy_timer);
-
-	if (icy_timer)
-	  metadata_check_icy();
-	break;
-
-      case DATA_KIND_FILE:
-	ret = transcode(audio_buf, len, ps->xcode, &icy_timer);
-	break;
-
-#ifdef HAVE_SPOTIFY_H
-      case DATA_KIND_SPOTIFY:
-	ret = spotify_audio_get(audio_buf, len);
-	break;
-#endif
-
-      case DATA_KIND_PIPE:
-	ret = pipe_audio_get(audio_buf, len);
-	break;
-
-      default:
-	ret = -1;
-    }
-
-  return ret;
-}
-
-/*
- * Pauses playback of the given player source
- */
-static int
-stream_pause(struct player_source *ps)
+static void
+seek_save(void)
 {
-  int ret;
+  int seek;
 
-  if (!ps)
-    {
-      DPRINTF(E_LOG, L_PLAYER, "Stream pause called with no active streaming player source\n");
-      return -1;
-    }
-
-  if (!ps->setup_done)
-    {
-      DPRINTF(E_LOG, L_PLAYER, "Given player source not setup, pause not possible\n");
-      return -1;
-    }
+  if (!cur_streaming)
+    return;
 
-  // Pause playback depending on data kind
-  switch (ps->data_kind)
+  if (cur_streaming->media_kind & (MEDIA_KIND_MOVIE | MEDIA_KIND_PODCAST | MEDIA_KIND_AUDIOBOOK | MEDIA_KIND_TVSHOW))
     {
-      case DATA_KIND_HTTP:
-	ret = 0;
-	break;
-
-      case DATA_KIND_FILE:
-	ret = 0;
-	break;
-
-#ifdef HAVE_SPOTIFY_H
-      case DATA_KIND_SPOTIFY:
-	ret = spotify_playback_pause();
-	break;
-#endif
-
-      case DATA_KIND_PIPE:
-	ret = 0;
-	break;
-
-      default:
-	ret = -1;
+      seek = (cur_streaming->output_start - cur_streaming->stream_start) / 44100 * 1000;
+      db_file_seek_update(cur_streaming->id, seek);
     }
-
-  return ret;
 }
 
-/*
- * Seeks to the given position in milliseconds of the given player source
- */
-static int
-stream_seek(struct player_source *ps, int seek_ms)
+static void
+status_update(enum play_status status)
 {
-  int ret;
-
-  if (!ps)
-    {
-      DPRINTF(E_LOG, L_PLAYER, "Stream seek called with no active streaming player source\n");
-      return -1;
-    }
-
-  if (!ps->setup_done)
-    {
-      DPRINTF(E_LOG, L_PLAYER, "Given player source not setup, seek not possible\n");
-      return -1;
-    }
-
-  // Seek depending on data kind
-  switch (ps->data_kind)
-    {
-      case DATA_KIND_HTTP:
-	ret = 0;
-	break;
-
-      case DATA_KIND_FILE:
-	ret = transcode_seek(ps->xcode, seek_ms);
-	break;
-
-#ifdef HAVE_SPOTIFY_H
-      case DATA_KIND_SPOTIFY:
-	ret = spotify_playback_seek(seek_ms);
-	break;
-#endif
-
-      case DATA_KIND_PIPE:
-	ret = 0;
-	break;
-
-      default:
-	ret = -1;
-    }
+  player_state = status;
 
-  return ret;
+  listener_notify(LISTENER_PLAYER);
 }
 
-/*
- * Stops playback and cleanup for the given player source
- */
-static int
-stream_stop(struct player_source *ps)
-{
-  if (!ps)
-    {
-      DPRINTF(E_LOG, L_PLAYER, "Stream cleanup called with no active streaming player source\n");
-      return -1;
-    }
-
-  if (!ps->setup_done)
-    {
-      DPRINTF(E_LOG, L_PLAYER, "Given player source not setup, cleanup not possible\n");
-      return -1;
-    }
-
-  switch (ps->data_kind)
-    {
-      case DATA_KIND_FILE:
-      case DATA_KIND_HTTP:
-	if (ps->xcode)
-	  {
-	    transcode_cleanup(ps->xcode);
-	    ps->xcode = NULL;
-	  }
-	break;
-
-      case DATA_KIND_SPOTIFY:
-#ifdef HAVE_SPOTIFY_H
-	spotify_playback_stop();
-#endif
-	break;
-
-      case DATA_KIND_PIPE:
-	pipe_cleanup();
-	break;
-    }
-
-  ps->setup_done = 0;
-
-  return 0;
-}
 
+/* ----------- Audio source handling (interfaces with input module) --------- */
 
 static struct player_source *
 source_now_playing()
@@ -963,22 +504,37 @@ source_now_playing()
  * Creates a new player source for the given queue item
  */
 static struct player_source *
-source_new(struct queue_item *item)
+source_new(struct db_queue_item *queue_item)
 {
   struct player_source *ps;
 
-  ps = (struct player_source *)calloc(1, sizeof(struct player_source));
+  ps = calloc(1, sizeof(struct player_source));
+  if (!ps)
+    {
+      DPRINTF(E_LOG, L_PLAYER, "Out of memory (ps)\n");
+      return NULL;
+    }
 
-  ps->id = queueitem_id(item);
-  ps->item_id = queueitem_item_id(item);
-  ps->data_kind = queueitem_data_kind(item);
-  ps->media_kind = queueitem_media_kind(item);
-  ps->len_ms = queueitem_len(item);
+  ps->id = queue_item->file_id;
+  ps->item_id = queue_item->id;
+  ps->data_kind = queue_item->data_kind;
+  ps->media_kind = queue_item->media_kind;
+  ps->len_ms = queue_item->song_length;
   ps->play_next = NULL;
+  ps->path = strdup(queue_item->path);
 
   return ps;
 }
 
+static void
+source_free(struct player_source *ps)
+{
+  if (ps->path)
+    free(ps->path);
+
+  free(ps);
+}
+
 /*
  * Stops playback for the current streaming source and frees all
  * player sources (starting from the playing source). Sets current streaming
@@ -991,7 +547,7 @@ source_stop()
   struct player_source *ps_temp;
 
   if (cur_streaming)
-    stream_stop(cur_streaming);
+    input_stop(cur_streaming);
 
   ps_playing = source_now_playing();
 
@@ -1001,7 +557,7 @@ source_stop()
       ps_playing = ps_playing->play_next;
 
       ps_temp->play_next = NULL;
-      free(ps_temp);
+      source_free(ps_temp);
     }
 
   cur_playing = NULL;
@@ -1021,27 +577,28 @@ source_pause(uint64_t pos)
   struct player_source *ps_playing;
   struct player_source *ps_playnext;
   struct player_source *ps_temp;
-  struct media_file_info *mfi;
   uint64_t seek_frames;
   int seek_ms;
   int ret;
 
   ps_playing = source_now_playing();
+  if (!ps_playing)
+    return -1;
 
-  if (cur_streaming)
+  if (cur_streaming && (cur_streaming == ps_playing))
     {
       if (ps_playing != cur_streaming)
 	{
 	  DPRINTF(E_DBG, L_PLAYER,
 	      "Pause called on playing source (id=%d) and streaming source already "
 	      "switched to the next item (id=%d)\n", ps_playing->id, cur_streaming->id);
-	  ret = stream_stop(cur_streaming);
+	  ret = input_stop(cur_streaming);
 	  if (ret < 0)
 	    return -1;
 	}
       else
 	{
-	  ret = stream_pause(cur_streaming);
+	  ret = input_pause(cur_streaming);
 	  if (ret < 0)
 	    return -1;
 	}
@@ -1054,7 +611,7 @@ source_pause(uint64_t pos)
       ps_playnext = ps_playnext->play_next;
 
       ps_temp->play_next = NULL;
-      free(ps_temp);
+      source_free(ps_temp);
     }
   ps_playing->play_next = NULL;
 
@@ -1063,41 +620,24 @@ source_pause(uint64_t pos)
 
   if (!cur_streaming->setup_done)
     {
-      mfi = db_file_fetch_byid(cur_streaming->id);
-      if (!mfi)
-	{
-	  DPRINTF(E_LOG, L_PLAYER, "Couldn't fetch file id %d\n", cur_streaming->id);
+      DPRINTF(E_INFO, L_PLAYER, "Opening '%s'\n", cur_streaming->path);
 
-	  return -1;
-	}
-
-      if (mfi->disabled)
-	{
-	  DPRINTF(E_DBG, L_PLAYER, "File id %d is disabled, skipping\n", cur_streaming->id);
-
-	  free_mfi(mfi, 0);
-	  return -1;
-	}
-
-      DPRINTF(E_INFO, L_PLAYER, "Opening '%s' (%s)\n", mfi->title, mfi->path);
-
-      ret = stream_setup(cur_streaming, mfi);
+      ret = input_setup(cur_streaming);
       if (ret < 0)
 	{
-	  DPRINTF(E_LOG, L_PLAYER, "Failed to open '%s' (%s)\n", mfi->title, mfi->path);
-	  free_mfi(mfi, 0);
+	  DPRINTF(E_LOG, L_PLAYER, "Failed to open '%s'\n", cur_streaming->path);
 	  return -1;
 	}
-
-      free_mfi(mfi, 0);
     }
 
-  /* Seek back to the pause position */
+  // Seek back to the pause position
   seek_frames = (pos - cur_streaming->stream_start);
   seek_ms = (int)((seek_frames * 1000) / 44100);
-  ret = stream_seek(cur_streaming, seek_ms);
+  ret = input_seek(cur_streaming, seek_ms);
+
+// TODO what if ret < 0?
 
-  /* Adjust start_pos to take into account the pause and seek back */
+  // Adjust start_pos to take into account the pause and seek back
   cur_streaming->stream_start = last_rtptime + AIRTUNES_V2_PACKET_SAMPLES - ((uint64_t)ret * 44100) / 1000;
   cur_streaming->output_start = last_rtptime + AIRTUNES_V2_PACKET_SAMPLES;
   cur_streaming->end = 0;
@@ -1117,11 +657,11 @@ source_seek(int seek_ms)
 {
   int ret;
 
-  ret = stream_seek(cur_streaming, seek_ms);
+  ret = input_seek(cur_streaming, seek_ms);
   if (ret < 0)
     return -1;
 
-  /* Adjust start_pos to take into account the pause and seek back */
+  // Adjust start_pos to take into account the pause and seek back
   cur_streaming->stream_start = last_rtptime + AIRTUNES_V2_PACKET_SAMPLES - ((uint64_t)ret * 44100) / 1000;
   cur_streaming->output_start = last_rtptime + AIRTUNES_V2_PACKET_SAMPLES;
 
@@ -1136,72 +676,44 @@ source_play()
 {
   int ret;
 
-  ret = stream_play(cur_streaming);
-
-  ticks_skip = 0;
-  memset(rawbuf, 0, sizeof(rawbuf));
+  ret = input_start(cur_streaming);
 
   return ret;
 }
 
 /*
- * Initializes playback of the given queue item (but does not start playback)
+ * Opens the given player source for playback (but does not start playback)
  *
- * A new source is created for the given queue item and is set as the current
- * streaming source. If a streaming source already existed (and reached eof)
- * the new source is appended as the play-next item to it.
+ * The given source is appended to the current streaming source (if one exists) and
+ * becomes the new current streaming source.
  *
  * Stream-start and output-start values are set to the given start position.
  */
 static int
-source_open(struct queue_item *qii, uint64_t start_pos, int seek)
+source_open(struct player_source *ps, uint64_t start_pos, int seek_ms)
 {
-  struct player_source *ps;
-  struct media_file_info *mfi;
-  uint32_t id;
   int ret;
 
-  if (cur_streaming && cur_streaming->end == 0)
-    {
-      DPRINTF(E_LOG, L_PLAYER, "Current streaming source not at eof %d\n", cur_streaming->id);
-      return -1;
-    }
-
-  id = queueitem_id(qii);
-  mfi = db_file_fetch_byid(id);
-  if (!mfi)
-    {
-      DPRINTF(E_LOG, L_PLAYER, "Couldn't fetch file id %d\n", id);
-
-      return -1;
-    }
+  DPRINTF(E_INFO, L_PLAYER, "Opening '%s' (id=%d, item-id=%d)\n", ps->path, ps->id, ps->item_id);
 
-  if (mfi->disabled)
+  if (cur_streaming && cur_streaming->end == 0)
     {
-      DPRINTF(E_DBG, L_PLAYER, "File id %d is disabled, skipping\n", id);
-
-      free_mfi(mfi, 0);
+      DPRINTF(E_LOG, L_PLAYER, "Current streaming source not at eof '%s' (id=%d, item-id=%d)\n",
+	      cur_streaming->path, cur_streaming->id, cur_streaming->item_id);
       return -1;
     }
 
-  DPRINTF(E_INFO, L_PLAYER, "Opening '%s' (%s)\n", mfi->title, mfi->path);
-
-  ps = source_new(qii);
-
-  ret = stream_setup(ps, mfi);
+  ret = input_setup(ps);
   if (ret < 0)
     {
-      DPRINTF(E_LOG, L_PLAYER, "Failed to open '%s' (%s)\n", mfi->title, mfi->path);
-      free_mfi(mfi, 0);
+      DPRINTF(E_LOG, L_PLAYER, "Failed to open '%s' (id=%d, item-id=%d)\n", ps->path, ps->id, ps->item_id);
       return -1;
     }
 
-  /* If a streaming source exists, append the new source as play-next and set it
-     as the new streaming source */
+  // If a streaming source exists, append the new source as play-next and set it
+  // as the new streaming source
   if (cur_streaming)
-    {
       cur_streaming->play_next = ps;
-    }
 
   cur_streaming = ps;
 
@@ -1209,11 +721,13 @@ source_open(struct queue_item *qii, uint64_t start_pos, int seek)
   cur_streaming->output_start = cur_streaming->stream_start;
   cur_streaming->end = 0;
 
-  /* Seek to the saved seek position */
-  if (seek && mfi->seek)
-    source_seek(mfi->seek);
+  // Seek to the given seek position
+  if (seek_ms)
+    {
+      DPRINTF(E_INFO, L_PLAYER, "Seek to %d ms for '%s' (id=%d, item-id=%d)\n", seek_ms, ps->path, ps->id, ps->item_id);
+      source_seek(seek_ms);
+    }
 
-  free_mfi(mfi, 0);
   return ret;
 }
 
@@ -1224,7 +738,7 @@ source_open(struct queue_item *qii, uint64_t start_pos, int seek)
 static int
 source_close(uint64_t end_pos)
 {
-  stream_stop(cur_streaming);
+  input_stop(cur_streaming);
 
   cur_streaming->end = end_pos;
 
@@ -1262,7 +776,7 @@ source_check(void)
       return pos;
     }
 
-  /* If cur_playing is NULL, we are still in the first two seconds after starting the stream */
+  // If cur_playing is NULL, we are still in the first two seconds after starting the stream
   if (!cur_playing)
     {
       if (pos >= cur_streaming->output_start)
@@ -1270,18 +784,18 @@ source_check(void)
 	  cur_playing = cur_streaming;
 	  status_update(PLAY_PLAYING);
 
-	  /* Start of streaming, no metadata to prune yet */
+	  // Start of streaming, no metadata to prune yet
 	}
 
       return pos;
     }
 
-  /* Check if we are still in the middle of the current playing song */
+  // Check if we are still in the middle of the current playing song
   if ((cur_playing->end == 0) || (pos < cur_playing->end))
     return pos;
 
-  /* We have reached the end of the current playing song, update cur_playing to the next song in the queue
-     and initialize stream_start and output_start values. */
+  // We have reached the end of the current playing song, update cur_playing to 
+  // the next song in the queue and initialize stream_start and output_start values.
 
   i = 0;
   while (cur_playing && (cur_playing->end != 0) && (pos > cur_playing->end))
@@ -1295,150 +809,265 @@ source_check(void)
 #endif
       history_add(cur_playing->id, cur_playing->item_id);
 
-      /* Stop playback */
+      if (consume)
+	db_queue_delete_byitemid(cur_playing->item_id);
+
       if (!cur_playing->play_next)
 	{
 	  playback_abort();
-
 	  return pos;
         }
 
       ps = cur_playing;
       cur_playing = cur_playing->play_next;
 
-      free(ps);
-    }
+      source_free(ps);
+    }
+
+  if (i > 0)
+    {
+      DPRINTF(E_DBG, L_PLAYER, "Playback switched to next song\n");
+
+      status_update(PLAY_PLAYING);
+
+      outputs_metadata_prune(pos);
+    }
+
+  return pos;
+}
+
+/*
+ * Returns the next player source based on the current streaming source and repeat mode
+ *
+ * If repeat mode is repeat all, shuffle is active and the current streaming source is the
+ * last item in the queue, the queue is reshuffled prior to returning the first item of the
+ * queue.
+ */
+static struct player_source *
+source_next()
+{
+  struct player_source *ps = NULL;
+  struct db_queue_item *queue_item;
+
+  if (!cur_streaming)
+    {
+      DPRINTF(E_LOG, L_PLAYER, "source_next() called with no current streaming source available\n");
+      return NULL;
+    }
+
+  if (repeat == REPEAT_SONG)
+    {
+      queue_item = db_queue_fetch_byitemid(cur_streaming->item_id);
+      if (!queue_item)
+	{
+	  DPRINTF(E_LOG, L_PLAYER, "Error fetching item from queue '%s' (id=%d, item-id=%d)\n", cur_streaming->path, cur_streaming->id, cur_streaming->item_id);
+	  return NULL;
+	}
+    }
+  else
+    {
+      queue_item = db_queue_fetch_next(cur_streaming->item_id, shuffle);
+      if (!queue_item && repeat == REPEAT_ALL)
+	{
+	  if (shuffle)
+	    {
+	      db_queue_reshuffle(0);
+	    }
+
+	  queue_item = db_queue_fetch_bypos(0, shuffle);
+	  if (!queue_item)
+	    {
+	      DPRINTF(E_LOG, L_PLAYER, "Error fetching item from queue '%s' (id=%d, item-id=%d)\n", cur_streaming->path, cur_streaming->id, cur_streaming->item_id);
+	      return NULL;
+	    }
+	}
+    }
+
+  if (!queue_item)
+    {
+      DPRINTF(E_DBG, L_PLAYER, "Reached end of queue\n");
+      return NULL;
+    }
+
+  ps = source_new(queue_item);
+  free_queue_item(queue_item, 0);
+  return ps;
+}
+
+/*
+ * Returns the previous player source based on the current streaming source
+ */
+static struct player_source *
+source_prev()
+{
+  struct player_source *ps = NULL;
+  struct db_queue_item *queue_item;
 
-  if (i > 0)
+  if (!cur_streaming)
     {
-      DPRINTF(E_DBG, L_PLAYER, "Playback switched to next song\n");
+      DPRINTF(E_LOG, L_PLAYER, "source_prev() called with no current streaming source available\n");
+      return NULL;
+    }
 
-      status_update(PLAY_PLAYING);
+  queue_item = db_queue_fetch_prev(cur_streaming->item_id, shuffle);
+  if (!queue_item)
+    return NULL;
 
-      metadata_prune(pos);
-    }
+  ps = source_new(queue_item);
+  free_queue_item(queue_item, 0);
 
-  return pos;
+  return ps;
 }
 
 static int
-source_read(uint8_t *buf, int len, uint64_t rtptime)
+source_switch(int nbytes)
 {
+  struct player_source *ps;
   int ret;
-  int nbytes;
-  char *silence_buf;
-  struct queue_item *item;
 
-  if (!cur_streaming)
-    return 0;
+  DPRINTF(E_DBG, L_PLAYER, "Switching track\n");
 
-  nbytes = 0;
-  while (nbytes < len)
+  source_close(last_rtptime + AIRTUNES_V2_PACKET_SAMPLES + BTOS(nbytes) - 1);
+
+  while ((ps = source_next()))
     {
-      if (evbuffer_get_length(audio_buf) == 0)
+      ret = source_open(ps, cur_streaming->end + 1, 0);
+      if (ret < 0)
 	{
-	  if (cur_streaming)
-	    {
-	      ret = stream_read(cur_streaming, len - nbytes);
-	    }
-	  else if (cur_playing)
-	    {
-	      // Reached end of playlist (cur_playing is NULL) send silence and source_check will abort playback if the last item was played
-	      DPRINTF(E_SPAM, L_PLAYER, "End of playlist reached, stream silence until playback of last item ends\n");
-	      silence_buf = (char *)calloc((len - nbytes), sizeof(char));
-	      evbuffer_add(audio_buf, silence_buf, (len - nbytes));
-	      free(silence_buf);
-	      ret = len - nbytes;
-	    }
-	  else
-	    {
-	      // If cur_streaming and cur_playing are NULL, source_read for all queue items failed. Playback will be aborted in the calling function
-	      return -1;
-	    }
+	  db_queue_delete_byitemid(ps->item_id);
+	  continue;
+	}
 
-	  if (ret <= 0)
-	    {
-	      /* EOF or error */
-	      source_close(rtptime + BTOS(nbytes) - 1);
+      ret = source_play();
+      if (ret < 0)
+	{
+	  db_queue_delete_byitemid(ps->item_id);
+	  source_close(last_rtptime + AIRTUNES_V2_PACKET_SAMPLES + BTOS(nbytes) - 1);
+	  continue;
+	}
 
-	      DPRINTF(E_DBG, L_PLAYER, "New file\n");
+      break;
+    }
 
-	      item = queue_next(queue, cur_streaming->item_id, shuffle, repeat, 1);
+  if (!ps) // End of queue
+    {
+      cur_streaming = NULL;
+      return 0;
+    }
 
-	      if (ret < 0)
-		{
-		  DPRINTF(E_LOG, L_PLAYER, "Error reading source %d\n", cur_streaming->id);
-		  queue_remove_byitemid(queue, cur_streaming->item_id);
-		}
+  metadata_trigger(0);
 
-	      if (item)
-		{
-		  ret = source_open(item, cur_streaming->end + 1, 0);
-		  if (ret < 0)
-		    return -1;
+  return 0;
+}
 
-		  ret = source_play();
-		  if (ret < 0)
-		    return -1;
 
-		  metadata_trigger(0);
-		}
-	      else
-		{
-		  cur_streaming = NULL;
-		}
-	      continue;
-	    }
-	}
+/* ----------------- Main read, write and playback timer event -------------- */
+
+// Returns -1 on error (caller should abort playback), or bytes read (possibly 0)
+static int
+source_read(uint8_t *buf, int len)
+{
+  int nbytes;
+  uint32_t item_id;
+  int ret;
+  short flags;
+
+  // Nothing to read, stream silence until source_check() stops playback
+  if (!cur_streaming)
+    {
+      memset(buf, 0, len);
+      return len;
+    }
+
+  nbytes = input_read(buf, len, &flags);
+  if ((nbytes < 0) || (flags & INPUT_FLAG_ERROR))
+    {
+      DPRINTF(E_LOG, L_PLAYER, "Error reading source %d\n", cur_streaming->id);
 
-      nbytes += evbuffer_remove(audio_buf, buf + nbytes, len - nbytes);
+      nbytes = 0;
+      item_id = cur_streaming->item_id;
+      ret = source_switch(0);
+      db_queue_delete_byitemid(item_id);
+      if (ret < 0)
+	return -1;
+    }
+  else if (flags & INPUT_FLAG_EOF)
+    {
+      ret = source_switch(nbytes);
+      if (ret < 0)
+	return -1;
+    }
+  else if (flags & INPUT_FLAG_METADATA)
+    {
+      metadata_trigger(0);
+    }
+
+  // We pad the output buffer with silence if we don't have enough data for a
+  // full packet and there is no more data coming up (no more tracks in queue)
+  if ((nbytes < len) && (!cur_streaming))
+    {
+      memset(buf + nbytes, 0, len - nbytes);
+      nbytes = len;
     }
 
   return nbytes;
 }
 
 static void
-playback_write(int read_skip)
+playback_write(void)
 {
-  int ret;
+  int want;
+  int got;
 
   source_check();
 
-  /* Make sure playback is still running after source_check() */
+  // Make sure playback is still running after source_check()
   if (player_state == PLAY_STOPPED)
     return;
 
-  last_rtptime += AIRTUNES_V2_PACKET_SAMPLES;
-
-  if (!read_skip)
+  pb_read_deficit++;
+  while (pb_read_deficit)
     {
-      ret = source_read(rawbuf, sizeof(rawbuf), last_rtptime);
-      if (ret < 0)
+      want = sizeof(pb_buffer) - pb_buffer_offset;
+      got = source_read(pb_buffer + pb_buffer_offset, want);
+      if (got == want)
 	{
-	  DPRINTF(E_DBG, L_PLAYER, "Error reading from source, aborting playback\n");
-
+	  pb_read_deficit--;
+	  last_rtptime += AIRTUNES_V2_PACKET_SAMPLES;
+	  outputs_write(pb_buffer, last_rtptime);
+	  pb_buffer_offset = 0;
+	}
+      else if (got < 0)
+	{
+	  DPRINTF(E_LOG, L_PLAYER, "Error reading from source, aborting playback\n");
 	  playback_abort();
 	  return;
 	}
+      else if (pb_read_deficit > pb_read_deficit_max)
+	{
+	  DPRINTF(E_LOG, L_PLAYER, "Source is not providing sufficient data, temporarily suspending playback (deficit=%d)\n", pb_read_deficit);
+	  playback_suspend();
+	  return;
+	}
+      else
+	{
+	  DPRINTF(E_SPAM, L_PLAYER, "Partial read (offset=%zu, deficit=%d)\n", pb_buffer_offset, pb_read_deficit);
+	  pb_buffer_offset += got;
+	  return;
+	}
     }
-  else
-    DPRINTF(E_SPAM, L_PLAYER, "Skipping read\n");
-
-  outputs_write(rawbuf, last_rtptime);
 }
 
 static void
-player_playback_cb(int fd, short what, void *arg)
+playback_cb(int fd, short what, void *arg)
 {
   struct timespec next_tick;
   uint64_t overrun;
   int ret;
-  int skip;
-  int skip_first;
 
   // Check if we missed any timer expirations
   overrun = 0;
-#if defined(__linux__)
+#ifdef HAVE_TIMERFD
   ret = read(fd, &overrun, sizeof(overrun));
   if (ret <= 0)
     DPRINTF(E_LOG, L_PLAYER, "Error reading timer\n");
@@ -1450,69 +1079,55 @@ player_playback_cb(int fd, short what, void *arg)
     DPRINTF(E_LOG, L_PLAYER, "Error getting timer overrun\n");
   else
     overrun = ret;
-#endif /* __linux__ */
-
-  // The reason we get behind the playback timer may be that we are playing a 
-  // network stream OR that the source is slow to open OR some interruption.
-  // For streams, we might be consuming faster than the stream delivers, so
-  // when ffmpeg's buffer empties (might take a few hours) our av_read_frame()
-  // in transcode.c will begin to block, because ffmpeg has to wait for new data
-  // from the stream server.
-  //
-  // Our strategy to catch up with the timer depends on the source:
-  //   - streams: We will skip reading data every second until we have countered
-  //              the overrun by skipping reads for a number of ticks that is
-  //              3 times the overrun. That should make the source catch up. To
-  //              keep the output happy we resend the previous rawbuf when we
-  //              have skipped a read.
-  //   - files:   Just read and write like crazy until we have caught up.
-
-  skip_first = 0;
-  if (overrun > PLAYER_TICKS_MAX_OVERRUN)
-    {
-      DPRINTF(E_WARN, L_PLAYER, "Behind the playback timer with %" PRIu64 " ticks\n", overrun);
+#endif /* HAVE_TIMERFD */
 
-      if (cur_streaming && (cur_streaming->data_kind == DATA_KIND_HTTP || cur_streaming->data_kind == DATA_KIND_PIPE))
+  // We are too delayed, probably some output blocked: reset if first overrun or abort if second overrun
+  if (overrun > pb_write_deficit_max)
+    {
+      if (pb_write_recovery)
 	{
-          ticks_skip = 3 * overrun;
+	  DPRINTF(E_LOG, L_PLAYER, "Permanent output delay detected (behind=%" PRIu64 ", max=%d), aborting\n", overrun, pb_write_deficit_max);
+	  playback_abort();
+	  return;
+	}
 
-	  DPRINTF(E_WARN, L_PLAYER, "Will skip reading for a total of %d ticks to catch up\n", ticks_skip);
+      DPRINTF(E_LOG, L_PLAYER, "Output delay detected (behind=%" PRIu64 ", max=%d), resetting all outputs\n", overrun, pb_write_deficit_max);
+      pb_write_recovery = true;
+      playback_suspend();
+      return;
+    }
+  else
+    {
+      if (overrun > 0)
+	DPRINTF(E_WARN, L_PLAYER, "Output delay detected: player is %" PRIu64 " ticks behind, catching up\n", overrun);
 
-	  // We always skip after a timer overrun, since another read will
-	  // probably just give another time overrun
-	  skip_first = 1;
-	}
-      else
-	ticks_skip = 0;
+      pb_write_recovery = false;
     }
 
-  // Decide how many packets to send
+  // If there was an overrun, we will try to read/write a corresponding number
+  // of times so we catch up. The read from the input is non-blocking, so it
+  // should not bring us further behind, even if there is no data.
   next_tick = timespec_add(pb_timer_last, tick_interval);
   for (; overrun > 0; overrun--)
     next_tick = timespec_add(next_tick, tick_interval);
 
   do
     {
-	skip = skip_first || ((ticks_skip > 0) && ((last_rtptime / AIRTUNES_V2_PACKET_SAMPLES) % 126 == 0));
-
-	playback_write(skip);
-
-	skip_first = 0;
-	if (skip)
-	  ticks_skip--;
-
+      playback_write();
       packet_timer_last = timespec_add(packet_timer_last, packet_time);
     }
   while ((timespec_cmp(packet_timer_last, next_tick) < 0) && (player_state == PLAY_PLAYING));
 
-  /* Make sure playback is still running */
+  // Make sure playback is still running
   if (player_state == PLAY_STOPPED)
     return;
 
   pb_timer_last = next_tick;
 }
 
-/* Helpers */
+
+/* ----------------- Output device handling (add/remove etc) ---------------- */
+
 static void
 device_list_sort(void)
 {
@@ -1566,14 +1181,14 @@ device_remove(struct output_device *remove)
   if (!device)
     return;
 
-  /* Save device volume */
-  ret = db_speaker_save(remove->id, remove->selected, remove->volume, remove->name);
+  // Save device volume
+  ret = db_speaker_save(remove);
   if (ret < 0)
     DPRINTF(E_LOG, L_PLAYER, "Could not save state for %s device '%s'\n", remove->type_name, remove->name);
 
-  DPRINTF(E_DBG, L_PLAYER, "Removing %s device '%s'; stopped advertising\n", remove->type_name, remove->name);
+  DPRINTF(E_INFO, L_PLAYER, "Removing %s device '%s'; stopped advertising\n", remove->type_name, remove->name);
 
-  /* Make sure device isn't selected anymore */
+  // Make sure device isn't selected anymore
   if (remove->selected)
     speaker_deselect_output(remove);
 
@@ -1605,7 +1220,7 @@ device_add(void *arg, int *retval)
   union player_arg *cmdarg;
   struct output_device *add;
   struct output_device *device;
-  int selected;
+  char *keep_name;
   int ret;
 
   cmdarg = arg;
@@ -1617,20 +1232,26 @@ device_add(void *arg, int *retval)
 	break;
     }
 
-  /* New device */
+  // New device
   if (!device)
     {
       device = add;
 
-      ret = db_speaker_get(device->id, &selected, &device->volume);
+      keep_name = strdup(device->name);
+      ret = db_speaker_get(device, device->id);
       if (ret < 0)
 	{
-	  selected = 0;
+	  device->selected = 0;
 	  device->volume = (master_volume >= 0) ? master_volume : PLAYER_DEFAULT_VOLUME;
 	}
 
-      if (selected && (player_state != PLAY_PLAYING))
+      free(device->name);
+      device->name = keep_name;
+
+      if (device->selected && (player_state != PLAY_PLAYING))
 	speaker_select_output(device);
+      else
+	device->selected = 0;
 
       device->next = dev_list;
       dev_list = device;
@@ -1648,7 +1269,7 @@ device_add(void *arg, int *retval)
 	  device->v4_address = add->v4_address;
 	  device->v4_port = add->v4_port;
 
-	  /* Address is ours now */
+	  // Address is ours now
 	  add->v4_address = NULL;
 	}
 
@@ -1660,7 +1281,7 @@ device_add(void *arg, int *retval)
 	  device->v6_address = add->v6_address;
 	  device->v6_port = add->v6_port;
 
-	  /* Address is ours now */
+	  // Address is ours now
 	  add->v6_address = NULL;
 	}
 
@@ -1706,7 +1327,7 @@ device_remove_family(void *arg, int *retval)
       return COMMAND_END;
     }
 
-  /* v{4,6}_port non-zero indicates the address family stopped advertising */
+  // v{4,6}_port non-zero indicates the address family stopped advertising
   if (remove->v4_port && device->v4_address)
     {
       free(device->v4_address);
@@ -1736,27 +1357,39 @@ device_remove_family(void *arg, int *retval)
 }
 
 static enum command_state
-metadata_send(void *arg, int *retval)
+device_auth_kickoff(void *arg, int *retval)
+{
+  union player_arg *cmdarg = arg;
+
+  outputs_authorize(cmdarg->auth.type, cmdarg->auth.pin);
+
+  *retval = 0;
+  return COMMAND_END;
+}
+
+
+static enum command_state
+device_metadata_send(void *arg, int *retval)
 {
   union player_arg *cmdarg;
-  struct player_metadata *pmd;
+  struct input_metadata *imd;
+  struct output_metadata *omd;
 
   cmdarg = arg;
-  pmd = cmdarg->pmd;
+  imd = cmdarg->metadata_param.input;
+  omd = cmdarg->metadata_param.output;
 
-  /* Do the setting of rtptime which was deferred in metadata_trigger because we
-   * wanted to wait until we had the actual last_rtptime
-   */
-  if ((pmd->rtptime == 0) && (pmd->startup))
-    pmd->rtptime = last_rtptime + AIRTUNES_V2_PACKET_SAMPLES;
+  outputs_metadata_send(omd, imd->rtptime, imd->offset, imd->startup);
 
-  outputs_metadata_send(pmd->omd, pmd->rtptime, pmd->offset, pmd->startup);
+  status_update(player_state);
 
   *retval = 0;
   return COMMAND_END;
 }
 
-/* Output device callbacks executed in the player thread */
+
+/* -------- Output device callbacks executed in the player thread ----------- */
+
 static void
 device_streaming_cb(struct output_device *device, struct output_session *session, enum output_device_state status)
 {
@@ -1815,6 +1448,14 @@ device_command_cb(struct output_device *device, struct output_session *session,
   if (status == OUTPUT_STATE_FAILED)
     device_streaming_cb(device, session, status);
 
+  // Used by playback_suspend - is basically the bottom half
+  if (player_flush_pending > 0)
+    {
+      player_flush_pending--;
+      if (player_flush_pending == 0)
+	input_buffer_full_cb(player_playback_start);
+    }
+
   commands_exec_end(cmdbase, 0);
 }
 
@@ -1858,7 +1499,7 @@ device_lost_cb(struct output_device *device, struct output_session *session, enu
 {
   DPRINTF(E_DBG, L_PLAYER, "Callback from %s to device_lost_cb\n", outputs_name(device->type));
 
-  /* We lost that device during startup for some reason, not much we can do here */
+  // We lost that device during startup for some reason, not much we can do here
   if (status == OUTPUT_STATE_FAILED)
     DPRINTF(E_WARN, L_PLAYER, "Failed to stop lost device\n");
   else
@@ -1917,7 +1558,7 @@ device_activate_cb(struct output_device *device, struct output_session *session,
 	{
 	  DPRINTF(E_LOG, L_PLAYER, "Could not get current time: %s\n", strerror(errno));
 
-	  /* Fallback to nearest timer expiration time */
+	  // Fallback to nearest timer expiration time
 	  ts.tv_sec = pb_timer_last.tv_sec;
 	  ts.tv_nsec = pb_timer_last.tv_nsec;
 	}
@@ -1985,10 +1626,12 @@ device_probe_cb(struct output_device *device, struct output_session *session, en
 static void
 device_restart_cb(struct output_device *device, struct output_session *session, enum output_device_state status)
 {
+  int retval;
   int ret;
 
   DPRINTF(E_DBG, L_PLAYER, "Callback from %s to device_restart_cb\n", outputs_name(device->type));
 
+  retval = commands_exec_returnvalue(cmdbase);
   ret = device_check(device);
   if (ret < 0)
     {
@@ -1997,9 +1640,17 @@ device_restart_cb(struct output_device *device, struct output_session *session,
       outputs_status_cb(session, device_lost_cb);
       outputs_device_stop(session);
 
+      if (retval != -2)
+	retval = -1;
       goto out;
     }
 
+  if (status == OUTPUT_STATE_PASSWORD)
+    {
+      status = OUTPUT_STATE_FAILED;
+      retval = -2;
+    }
+
   if (status == OUTPUT_STATE_FAILED)
     {
       speaker_deselect_output(device);
@@ -2007,6 +1658,8 @@ device_restart_cb(struct output_device *device, struct output_session *session,
       if (!device->advertised)
 	device_remove(device);
 
+      if (retval != -2)
+	retval = -1;
       goto out;
     }
 
@@ -2016,32 +1669,107 @@ device_restart_cb(struct output_device *device, struct output_session *session,
   outputs_status_cb(session, device_streaming_cb);
 
  out:
-  commands_exec_end(cmdbase, 0);
+  commands_exec_end(cmdbase, retval);
 }
 
-/* Internal abort routine */
-static void
-playback_abort(void)
+
+/* ------------------------- Internal playback routines --------------------- */
+
+static int
+playback_timer_start(void)
+{
+  struct itimerspec tick;
+  int ret;
+
+  ret = event_add(pb_timer_ev, NULL);
+  if (ret < 0)
+    {
+      DPRINTF(E_LOG, L_PLAYER, "Could not add playback timer\n");
+
+      return -1;
+    }
+
+  tick.it_interval = tick_interval;
+  tick.it_value = tick_interval;
+
+#ifdef HAVE_TIMERFD
+  ret = timerfd_settime(pb_timer_fd, 0, &tick, NULL);
+#else
+  ret = timer_settime(pb_timer, 0, &tick, NULL);
+#endif
+  if (ret < 0)
+    {
+      DPRINTF(E_LOG, L_PLAYER, "Could not arm playback timer: %s\n", strerror(errno));
+
+      return -1;
+    }
+
+  return 0;
+}
+
+static int
+playback_timer_stop(void)
 {
+  struct itimerspec tick;
   int ret;
 
+  event_del(pb_timer_ev);
+
+  memset(&tick, 0, sizeof(struct itimerspec));
+
+#ifdef HAVE_TIMERFD
+  ret = timerfd_settime(pb_timer_fd, 0, &tick, NULL);
+#else
+  ret = timer_settime(pb_timer, 0, &tick, NULL);
+#endif
+  if (ret < 0)
+    {
+      DPRINTF(E_LOG, L_PLAYER, "Could not disarm playback timer: %s\n", strerror(errno));
+
+      return -1;
+    }
+
+  return 0;
+}
+
+static void
+playback_abort(void)
+{
   outputs_playback_stop();
 
-  pb_timer_stop();
+  playback_timer_stop();
 
   source_stop();
 
-  evbuffer_drain(audio_buf, evbuffer_get_length(audio_buf));
-
   if (!clear_queue_on_stop_disabled)
-    playerqueue_clear(NULL, &ret);
+    db_queue_clear(0);
 
   status_update(PLAY_STOPPED);
 
-  metadata_purge();
+  outputs_metadata_purge();
+}
+
+// Temporarily suspends/resets playback, used when input buffer underruns or in
+// case of problems writing to the outputs
+static void
+playback_suspend(void)
+{
+  player_flush_pending = outputs_flush(device_command_cb, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES);
+
+  playback_timer_stop();
+
+  status_update(PLAY_PAUSED);
+
+  seek_save();
+
+  // No devices to wait for, just set the restart cb right away
+  if (player_flush_pending == 0)
+    input_buffer_full_cb(player_playback_start);
 }
 
-/* Actual commands, executed in the player thread */
+
+/* --------------- Actual commands, executed in the player thread ----------- */
+
 static enum command_state
 get_status(void *arg, int *retval)
 {
@@ -2049,7 +1777,6 @@ get_status(void *arg, int *retval)
   struct timespec ts;
   struct player_source *ps;
   struct player_status *status;
-  struct queue_item *item_next;
   uint64_t pos;
   int ret;
 
@@ -2058,13 +1785,12 @@ get_status(void *arg, int *retval)
   memset(status, 0, sizeof(struct player_status));
 
   status->shuffle = shuffle;
+  status->consume = consume;
   status->repeat = repeat;
 
   status->volume = master_volume;
 
   status->plid = cur_plid;
-  status->plversion = cur_plversion;
-  status->playlistlength = queue_count(queue);
 
   switch (player_state)
     {
@@ -2085,8 +1811,6 @@ get_status(void *arg, int *retval)
 	status->pos_ms = (pos * 1000) / 44100;
 	status->len_ms = cur_streaming->len_ms;
 
-	status->pos_pl = queue_index_byitemid(queue, cur_streaming->item_id, 0);
-
 	break;
 
       case PLAY_PLAYING:
@@ -2097,7 +1821,7 @@ get_status(void *arg, int *retval)
 	    status->status = PLAY_PAUSED;
 	    ps = cur_streaming;
 
-	    /* Avoid a visible 2-second jump backward for the client */
+	    // Avoid a visible 2-second jump backward for the client
 	    pos = ps->output_start - ps->stream_start;
 	  }
 	else
@@ -2126,21 +1850,6 @@ get_status(void *arg, int *retval)
 
 	status->id = ps->id;
 	status->item_id = ps->item_id;
-	status->pos_pl = queue_index_byitemid(queue, ps->item_id, 0);
-
-	item_next = queue_next(queue, ps->item_id, shuffle, repeat, 0);
-	if (item_next)
-	  {
-	    status->next_id = queueitem_id(item_next);
-	    status->next_item_id = queueitem_item_id(item_next);
-	    status->next_pos_pl = queue_index_byitemid(queue, status->next_item_id, 0);
-	  }
-	else
-	  {
-	    //TODO [queue/mpd] Check how mpd sets the next-id/-pos if the last song is playing
-	    status->next_id = 0;
-	    status->next_pos_pl = 0;
-	  }
 
 	break;
     }
@@ -2173,48 +1882,15 @@ now_playing(void *arg, int *retval)
 }
 
 static enum command_state
-artwork_url_get(void *arg, int *retval)
-{
-  union player_arg *cmdarg = arg;
-  struct player_source *ps;
-
-  cmdarg->icy.artwork_url = NULL;
-
-  if (cur_playing)
-    ps = cur_playing;
-  else if (cur_streaming)
-    ps = cur_streaming;
-  else
-    {
-      *retval = -1;
-      return COMMAND_END;
-    }
-
-  /* Check that we are playing a viable stream, and that it has the requested id */
-  if (!ps->xcode || ps->data_kind != DATA_KIND_HTTP || ps->id != cmdarg->icy.id)
-    {
-      *retval = -1;
-      return COMMAND_END;
-    }
-
-  cmdarg->icy.artwork_url = transcode_metadata_artwork_url(ps->xcode);
-
-  *retval = 0;
-  return COMMAND_END;
-}
-
-static enum command_state
 playback_stop(void *arg, int *retval)
 {
   struct player_source *ps_playing;
 
-  /* We may be restarting very soon, so we don't bring the devices to a
-   * full stop just yet; this saves time when restarting, which is nicer
-   * for the user.
-   */
+  // We may be restarting very soon, so we don't bring the devices to a full
+  // stop just yet; this saves time when restarting, which is nicer for the user
   *retval = outputs_flush(device_command_cb, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES);
 
-  pb_timer_stop();
+  playback_timer_stop();
 
   ps_playing = source_now_playing();
   if (ps_playing)
@@ -2224,32 +1900,22 @@ playback_stop(void *arg, int *retval)
 
   source_stop();
 
-  evbuffer_drain(audio_buf, evbuffer_get_length(audio_buf));
-
   status_update(PLAY_STOPPED);
 
-  metadata_purge();
+  outputs_metadata_purge();
 
-  /* We're async if we need to flush devices */
+  // We're async if we need to flush devices
   if (*retval > 0)
-    return COMMAND_PENDING; /* async */
+    return COMMAND_PENDING;
 
   return COMMAND_END;
 }
 
-/* Playback startup bottom half */
 static enum command_state
 playback_start_bh(void *arg, int *retval)
 {
   int ret;
 
-  if (output_sessions == 0)
-    {
-      DPRINTF(E_LOG, L_PLAYER, "Cannot start playback: no output started\n");
-
-      goto out_fail;
-    }
-
   ret = clock_gettime_with_res(CLOCK_MONOTONIC, &pb_pos_stamp, &timer_res);
   if (ret < 0)
     {
@@ -2258,23 +1924,24 @@ playback_start_bh(void *arg, int *retval)
       goto out_fail;
     }
 
-  pb_timer_stop();
+  playback_timer_stop();
 
-  /*
-   * initialize the packet timer to the same relative time that we have 
-   * for the playback timer.
-   */
+  // initialize the packet timer to the same relative time that we have 
+  // for the playback timer.
   packet_timer_last.tv_sec = pb_pos_stamp.tv_sec;
   packet_timer_last.tv_nsec = pb_pos_stamp.tv_nsec;
 
   pb_timer_last.tv_sec = pb_pos_stamp.tv_sec;
   pb_timer_last.tv_nsec = pb_pos_stamp.tv_nsec;
 
-  ret = pb_timer_start();
+  pb_buffer_offset = 0;
+  pb_read_deficit = 0;
+
+  ret = playback_timer_start();
   if (ret < 0)
     goto out_fail;
 
-  /* Everything OK, start outputs */
+  // Everything OK, start outputs
   outputs_playback_start(last_rtptime + AIRTUNES_V2_PACKET_SAMPLES, &pb_pos_stamp);
 
   status_update(PLAY_PLAYING);
@@ -2290,29 +1957,18 @@ playback_start_bh(void *arg, int *retval)
 }
 
 static enum command_state
-playback_start_item(union player_arg *cmdarg, int *retval, struct queue_item *qii)
+playback_start_item(void *arg, int *retval)
 {
-  uint32_t *dbmfi_id;
+  struct db_queue_item *queue_item = arg;
+  struct media_file_info *mfi;
   struct output_device *device;
-  struct player_source *ps_playing;
-  struct queue_item *item;
+  struct player_source *ps;
+  int seek_ms;
   int ret;
 
-  dbmfi_id = cmdarg->playback_start_param.id_ptr;
-
-  ps_playing = source_now_playing();
-
   if (player_state == PLAY_PLAYING)
     {
-      /*
-       * If player is already playing a song, only return current playing song id
-       * and do not change player state (ignores given arguments for playing a
-       * specified song by pos or id).
-       */
-      if (dbmfi_id && ps_playing)
-	{
-	  *dbmfi_id = ps_playing->id;
-	}
+      DPRINTF(E_DBG, L_PLAYER, "Player is already playing, ignoring call to playback start\n");
 
       status_update(player_state);
 
@@ -2323,22 +1979,46 @@ playback_start_item(union player_arg *cmdarg, int *retval, struct queue_item *qi
   // Update global playback position
   pb_pos = last_rtptime + AIRTUNES_V2_PACKET_SAMPLES - 88200;
 
-  item = NULL;
-  if (qii)
+  if (player_state == PLAY_STOPPED && !queue_item)
     {
-      item = qii;
+      DPRINTF(E_LOG, L_PLAYER, "Failed to start/resume playback, no queue item given\n");
+
+      *retval = -1;
+      return COMMAND_END;
     }
-  else if (!cur_streaming)
+
+  if (!queue_item)
     {
-      if (shuffle)
-      	queue_shuffle(queue, 0);
-      item = queue_next(queue, 0, shuffle, repeat, 0);
+      // Resume playback of current source
+      ps = source_now_playing();
+      DPRINTF(E_DBG, L_PLAYER, "Resume playback of '%s' (id=%d, item-id=%d)\n", ps->path, ps->id, ps->item_id);
     }
-
-  if (item)
+  else
     {
+      // Start playback for given queue item
+      DPRINTF(E_DBG, L_PLAYER, "Start playback of '%s' (id=%d, item-id=%d)\n", queue_item->path, queue_item->file_id, queue_item->id);
       source_stop();
-      ret = source_open(item, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES, 1);
+
+      ps = source_new(queue_item);
+      if (!ps)
+	{
+	  playback_abort();
+	  *retval = -1;
+	  return COMMAND_END;
+	}
+
+      seek_ms = 0;
+      if (queue_item->file_id > 0)
+	{
+	  mfi = db_file_fetch_byid(queue_item->file_id);
+	  if (mfi)
+	    {
+	      seek_ms = mfi->seek;
+	      free_mfi(mfi, 0);
+	    }
+	}
+
+      ret = source_open(ps, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES, seek_ms);
       if (ret < 0)
 	{
 	  playback_abort();
@@ -2355,13 +2035,9 @@ playback_start_item(union player_arg *cmdarg, int *retval, struct queue_item *qi
       return COMMAND_END;
     }
 
-
-  if (dbmfi_id)
-    *dbmfi_id = cur_streaming->id;
-
   metadata_trigger(1);
 
-  /* Start sessions on selected devices */
+  // Start sessions on selected devices
   *retval = 0;
 
   for (device = dev_list; device; device = device->next)
@@ -2380,7 +2056,7 @@ playback_start_item(union player_arg *cmdarg, int *retval, struct queue_item *qi
 	}
     }
 
-  /* If autoselecting is enabled, try to autoselect a non-selected device if the above failed */
+  // If autoselecting is enabled, try to autoselect a non-selected device if the above failed
   if (speaker_autoselect && (*retval == 0) && (output_sessions == 0))
     for (device = dev_list; device; device = device->next)
       {
@@ -2401,81 +2077,66 @@ playback_start_item(union player_arg *cmdarg, int *retval, struct queue_item *qi
 	break;
       }
 
-  /* No luck finding valid output */
-  if ((*retval == 0) && (output_sessions == 0))
-    {
-      DPRINTF(E_LOG, L_PLAYER, "Could not start playback: no output selected or couldn't start any output\n");
-
-      playback_abort();
-      *retval = -1;
-      return COMMAND_END;
-    }
-
-  /* We're async if we need to start devices */
+  // We're async if we need to start devices
   if (*retval > 0)
-    return COMMAND_PENDING; /* async */
+    return COMMAND_PENDING; // async
 
-  /* Otherwise, just run the bottom half */
+  // Otherwise, just run the bottom half
   *retval = 0;
   return COMMAND_END;
 }
 
 static enum command_state
-playback_start(void *arg, int *retval)
-{
-  return playback_start_item(arg, retval, NULL);
-}
-
-static enum command_state
-playback_start_byitemid(void *arg, int *retval)
+playback_start_id(void *arg, int *retval)
 {
+  struct db_queue_item *queue_item = NULL;
   union player_arg *cmdarg = arg;
-  int item_id;
-  struct queue_item *qii;
+  enum command_state cmd_state;
+  int ret;
 
-  item_id = cmdarg->playback_start_param.id;
+  *retval = -1;
 
-  qii = queue_get_byitemid(queue, item_id);
+  if (player_state == PLAY_STOPPED)
+    {
+      db_queue_clear(0);
 
-  return playback_start_item(cmdarg, retval, qii);
-}
+      ret = db_queue_add_by_fileid(cmdarg->id, 0, 0);
+      if (ret < 0)
+	return COMMAND_END;
 
-static enum command_state
-playback_start_byindex(void *arg, int *retval)
-{
-  union player_arg *cmdarg = arg;
-  int pos;
-  struct queue_item *qii;
+      queue_item = db_queue_fetch_byfileid(cmdarg->id);
+      if (!queue_item)
+	return COMMAND_END;
+    }
 
-  pos = cmdarg->playback_start_param.pos;
+  cmd_state = playback_start_item(queue_item, retval);
 
-  qii = queue_get_byindex(queue, pos, 0);
+  free_queue_item(queue_item, 0);
 
-  return playback_start_item(cmdarg, retval, qii);
+  return cmd_state;
 }
 
 static enum command_state
-playback_start_bypos(void *arg, int *retval)
+playback_start(void *arg, int *retval)
 {
-  union player_arg *cmdarg = arg;
-  int offset;
-  struct player_source *ps_playing;
-  struct queue_item *qii;
-
-  offset = cmdarg->playback_start_param.pos;
+  struct db_queue_item *queue_item = NULL;
+  enum command_state cmd_state;
 
-  ps_playing = source_now_playing();
+  *retval = -1;
 
-  if (ps_playing)
-    {
-      qii = queue_get_bypos(queue, ps_playing->item_id, offset, shuffle);
-    }
-  else
+  if (player_state == PLAY_STOPPED)
     {
-      qii = queue_get_byindex(queue, offset, shuffle);
+      // Start playback of first item in queue
+      queue_item = db_queue_fetch_bypos(0, shuffle);
+      if (!queue_item)
+	return COMMAND_END;
     }
 
-  return playback_start_item(cmdarg, retval, qii);
+  cmd_state = playback_start_item(queue_item, retval);
+
+  free_queue_item(queue_item, 0);
+
+  return cmd_state;
 }
 
 static enum command_state
@@ -2483,12 +2144,10 @@ playback_prev_bh(void *arg, int *retval)
 {
   int ret;
   int pos_sec;
-  struct queue_item *item;
+  struct player_source *ps;
 
-  /*
-   * The upper half is playback_pause, therefor the current playing item is
-   * already set as the cur_streaming (cur_playing is NULL).
-   */
+  // The upper half is playback_pause, therefor the current playing item is
+  // already set as the cur_streaming (cur_playing is NULL).
   if (!cur_streaming)
     {
       DPRINTF(E_LOG, L_PLAYER, "Could not get current stream source\n");
@@ -2496,23 +2155,23 @@ playback_prev_bh(void *arg, int *retval)
       return COMMAND_END;
     }
 
-  /* Only add to history if playback started. */
+  // Only add to history if playback started
   if (cur_streaming->output_start > cur_streaming->stream_start)
     history_add(cur_streaming->id, cur_streaming->item_id);
 
-  /* Compute the playing time in seconds for the current song. */
+  // Compute the playing time in seconds for the current song
   if (cur_streaming->output_start > cur_streaming->stream_start)
     pos_sec = (cur_streaming->output_start - cur_streaming->stream_start) / 44100;
   else
     pos_sec = 0;
 
-  /* Only skip to the previous song if the playing time is less than 3 seconds,
-   otherwise restart the current song. */
+  // Only skip to the previous song if the playing time is less than 3 seconds,
+  // otherwise restart the current song.
   DPRINTF(E_DBG, L_PLAYER, "Skipping song played %d sec\n", pos_sec);
   if (pos_sec < 3)
     {
-      item = queue_prev(queue, cur_streaming->item_id, shuffle, repeat);
-      if (!item)
+      ps = source_prev();
+      if (!ps)
         {
           playback_abort();
           *retval = -1;
@@ -2521,9 +2180,10 @@ playback_prev_bh(void *arg, int *retval)
 
       source_stop();
 
-      ret = source_open(item, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES, 0);
+      ret = source_open(ps, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES, 0);
       if (ret < 0)
 	{
+	  source_free(ps);
 	  playback_abort();
 
           *retval = -1;
@@ -2548,26 +2208,22 @@ playback_prev_bh(void *arg, int *retval)
       return COMMAND_END;
     }
 
-  /* Silent status change - playback_start() sends the real status update */
+  // Silent status change - playback_start() sends the real status update
   player_state = PLAY_PAUSED;
 
   *retval = 0;
   return COMMAND_END;
 }
 
-/*
- * The bottom half of the next command
- */
 static enum command_state
 playback_next_bh(void *arg, int *retval)
 {
+  struct player_source *ps;
   int ret;
-  struct queue_item *item;
+  uint32_t item_id;
 
-  /*
-   * The upper half is playback_pause, therefor the current playing item is
-   * already set as the cur_streaming (cur_playing is NULL).
-   */
+  // The upper half is playback_pause, therefor the current playing item is
+  // already set as the cur_streaming (cur_playing is NULL).
   if (!cur_streaming)
     {
       DPRINTF(E_LOG, L_PLAYER, "Could not get current stream source\n");
@@ -2575,12 +2231,14 @@ playback_next_bh(void *arg, int *retval)
       return COMMAND_END;
     }
 
-  /* Only add to history if playback started. */
+  // Only add to history if playback started
   if (cur_streaming->output_start > cur_streaming->stream_start)
     history_add(cur_streaming->id, cur_streaming->item_id);
 
-  item = queue_next(queue, cur_streaming->item_id, shuffle, repeat, 0);
-  if (!item)
+  item_id = cur_streaming->item_id;
+
+  ps = source_next();
+  if (!ps)
     {
       playback_abort();
       *retval = -1;
@@ -2589,9 +2247,10 @@ playback_next_bh(void *arg, int *retval)
 
   source_stop();
 
-  ret = source_open(item, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES, 0);
+  ret = source_open(ps, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES, 0);
   if (ret < 0)
     {
+      source_free(ps);
       playback_abort();
       *retval = -1;
       return COMMAND_END;
@@ -2603,7 +2262,10 @@ playback_next_bh(void *arg, int *retval)
       return COMMAND_END;
     }
 
-  /* Silent status change - playback_start() sends the real status update */
+  if (consume)
+    db_queue_delete_byitemid(item_id);
+
+  // Silent status change - playback_start() sends the real status update
   player_state = PLAY_PAUSED;
 
   *retval = 0;
@@ -2617,19 +2279,21 @@ playback_seek_bh(void *arg, int *retval)
   int ms;
   int ret;
 
+  *retval = -1;
+
+  if (!cur_streaming)
+    return COMMAND_END;
+
   ms = cmdarg->intval;
 
   ret = source_seek(ms);
-
   if (ret < 0)
     {
       playback_abort();
-
-      *retval = -1;
       return COMMAND_END;
     }
 
-  /* Silent status change - playback_start() sends the real status update */
+  // Silent status change - playback_start() sends the real status update
   player_state = PLAY_PAUSED;
 
   *retval = 0;
@@ -2639,24 +2303,24 @@ playback_seek_bh(void *arg, int *retval)
 static enum command_state
 playback_pause_bh(void *arg, int *retval)
 {
-  int ret;
+  *retval = -1;
 
-  if (cur_streaming->data_kind == DATA_KIND_HTTP
-      || cur_streaming->data_kind == DATA_KIND_PIPE)
+  // outputs_flush() in playback_pause() may have a caused a failure callback
+  // from the output, which in streaming_cb() can cause playback_abort() ->
+  // cur_streaming is NULL
+  if (!cur_streaming)
+    return COMMAND_END;
+
+  if (cur_streaming->data_kind == DATA_KIND_HTTP || cur_streaming->data_kind == DATA_KIND_PIPE)
     {
       DPRINTF(E_DBG, L_PLAYER, "Source is not pausable, abort playback\n");
 
       playback_abort();
-      *retval = -1;
       return COMMAND_END;
     }
   status_update(PLAY_PAUSED);
 
-  if (cur_streaming->media_kind & (MEDIA_KIND_MOVIE | MEDIA_KIND_PODCAST | MEDIA_KIND_AUDIOBOOK | MEDIA_KIND_TVSHOW))
-    {
-      ret = (cur_streaming->output_start - cur_streaming->stream_start) / 44100 * 1000;
-      db_file_save_seek(cur_streaming->id, ret);
-    }
+  seek_save();
 
   *retval = 0;
   return COMMAND_END;
@@ -2677,7 +2341,7 @@ playback_pause(void *arg, int *retval)
       return COMMAND_END;
     }
 
-  /* Make sure playback is still running after source_check() */
+  // Make sure playback is still running after source_check()
   if (player_state == PLAY_STOPPED)
     {
       *retval = -1;
@@ -2686,19 +2350,17 @@ playback_pause(void *arg, int *retval)
 
   *retval = outputs_flush(device_command_cb, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES);
 
-  pb_timer_stop();
+  playback_timer_stop();
 
   source_pause(pos);
 
-  evbuffer_drain(audio_buf, evbuffer_get_length(audio_buf));
-
-  metadata_purge();
+  outputs_metadata_purge();
 
-  /* We're async if we need to flush devices */
+  // We're async if we need to flush devices
   if (*retval > 0)
-    return COMMAND_PENDING; /* async */
+    return COMMAND_PENDING; // async
 
-  /* Otherwise, just run the bottom half */
+  // Otherwise, just run the bottom half
   return COMMAND_END;
 }
 
@@ -2873,7 +2535,7 @@ speaker_set(void *arg, int *retval)
   listener_notify(LISTENER_SPEAKER);
 
   if (*retval > 0)
-    return COMMAND_PENDING; /* async */
+    return COMMAND_PENDING; // async
 
   *retval = cmdarg->speaker_set_param.intval;
   return COMMAND_END;
@@ -2912,7 +2574,7 @@ volume_set(void *arg, int *retval)
   listener_notify(LISTENER_VOLUME);
 
   if (*retval > 0)
-    return COMMAND_PENDING; /* async */
+    return COMMAND_PENDING; // async
 
   return COMMAND_END;
 }
@@ -2956,7 +2618,7 @@ volume_setrel_speaker(void *arg, int *retval)
   listener_notify(LISTENER_VOLUME);
 
   if (*retval > 0)
-    return COMMAND_PENDING; /* async */
+    return COMMAND_PENDING; // async
 
   return COMMAND_END;
 }
@@ -3006,7 +2668,7 @@ volume_setabs_speaker(void *arg, int *retval)
   listener_notify(LISTENER_VOLUME);
 
   if (*retval > 0)
-    return COMMAND_PENDING; /* async */
+    return COMMAND_PENDING; // async
 
   return COMMAND_END;
 }
@@ -3054,9 +2716,9 @@ shuffle_set(void *arg, int *retval)
 	if (!shuffle)
 	  {
 	    cur_id = cur_streaming ? cur_streaming->item_id : 0;
-	    queue_shuffle(queue, cur_id);
+	    db_queue_reshuffle(cur_id);
 	  }
-	/* FALLTHROUGH*/
+	/* FALLTHROUGH */
       case 0:
 	shuffle = cmdarg->intval;
 	break;
@@ -3074,303 +2736,90 @@ shuffle_set(void *arg, int *retval)
 }
 
 static enum command_state
-playerqueue_get_bypos(void *arg, int *retval)
-{
-  union player_arg *cmdarg = arg;
-  int count;
-  struct queue *qi;
-  struct player_source *ps;
-  int item_id;
-
-  count = cmdarg->queue_get_param.count;
-
-  ps = source_now_playing();
-
-  item_id = 0;
-  if (ps)
-    {
-      item_id = ps->item_id;
-    }
-
-  qi = queue_new_bypos(queue, item_id, count, shuffle);
-
-  cmdarg->queue_get_param.queue = qi;
-
-  *retval = 0;
-  return COMMAND_END;
-}
-
-static enum command_state
-playerqueue_get_byindex(void *arg, int *retval)
-{
-  union player_arg *cmdarg = arg;
-  int pos;
-  int count;
-  struct queue *qi;
-
-  pos = cmdarg->queue_get_param.pos;
-  count = cmdarg->queue_get_param.count;
-
-  qi = queue_new_byindex(queue, pos, count, 0);
-  cmdarg->queue_get_param.queue = qi;
-
-  *retval = 0;
-  return COMMAND_END;
-}
-
-static enum command_state
-playerqueue_add(void *arg, int *retval)
-{
-  union player_arg *cmdarg = arg;
-  struct queue_item *items;
-  uint32_t cur_id;
-  uint32_t *item_id;
-
-  items = cmdarg->queue_add_param.items;
-  item_id = cmdarg->queue_add_param.item_id_ptr;
-
-  queue_add(queue, items);
-
-  if (shuffle)
-    {
-      cur_id = cur_streaming ? cur_streaming->item_id : 0;
-      queue_shuffle(queue, cur_id);
-    }
-
-  if (item_id)
-    *item_id = queueitem_item_id(items);
-
-  cur_plid = 0;
-  cur_plversion++;
-
-  listener_notify(LISTENER_PLAYLIST);
-
-  *retval = 0;
-  return COMMAND_END;
-}
-
-static enum command_state
-playerqueue_add_next(void *arg, int *retval)
+consume_set(void *arg, int *retval)
 {
   union player_arg *cmdarg = arg;
-  struct queue_item *items;
-  uint32_t cur_id;
-
-  items = cmdarg->queue_add_param.items;
-
-  cur_id = cur_streaming ? cur_streaming->item_id : 0;
 
-  queue_add_after(queue, items, cur_id);
+  consume = cmdarg->intval;
 
-  if (shuffle)
-    queue_shuffle(queue, cur_id);
-
-  cur_plid = 0;
-  cur_plversion++;
-
-  listener_notify(LISTENER_PLAYLIST);
+  listener_notify(LISTENER_OPTIONS);
 
   *retval = 0;
   return COMMAND_END;
 }
 
+/*
+ * Removes all items from the history
+ */
 static enum command_state
-playerqueue_move_bypos(void *arg, int *retval)
+playerqueue_clear_history(void *arg, int *retval)
 {
-  union player_arg *cmdarg = arg;
-  struct player_source *ps_playing;
-  uint32_t item_id;
-
-  DPRINTF(E_DBG, L_PLAYER, "Moving song from position %d to be the next song after %d\n",
-      cmdarg->queue_move_param.from_pos, cmdarg->queue_move_param.to_pos);
-
-  ps_playing = source_now_playing();
-
-  if (!ps_playing)
-    {
-      DPRINTF(E_DBG, L_PLAYER, "No playing item found for move by pos\n");
-      item_id = 0;
-    }
-  else
-    item_id = ps_playing->item_id;
-
-  queue_move_bypos(queue, item_id, cmdarg->queue_move_param.from_pos, cmdarg->queue_move_param.to_pos, shuffle);
+  memset(history, 0, sizeof(struct player_history));
 
-  cur_plversion++;
+  cur_plversion++; // TODO [db_queue] need to update db queue version
 
-  listener_notify(LISTENER_PLAYLIST);
+  listener_notify(LISTENER_QUEUE);
 
   *retval = 0;
   return COMMAND_END;
 }
 
 static enum command_state
-playerqueue_move_byindex(void *arg, int *retval)
+playerqueue_plid(void *arg, int *retval)
 {
   union player_arg *cmdarg = arg;
-
-  DPRINTF(E_DBG, L_PLAYER, "Moving song from index %d to be the next song after %d\n",
-      cmdarg->queue_move_param.from_pos, cmdarg->queue_move_param.to_pos);
-
-  queue_move_byindex(queue, cmdarg->queue_move_param.from_pos, cmdarg->queue_move_param.to_pos, 0);
-
-  cur_plversion++;
-
-  listener_notify(LISTENER_PLAYLIST);
+  cur_plid = cmdarg->id;
 
   *retval = 0;
   return COMMAND_END;
 }
 
-static enum command_state
-playerqueue_move_byitemid(void *arg, int *retval)
-{
-  union player_arg *cmdarg = arg;
-
-  DPRINTF(E_DBG, L_PLAYER, "Moving song with item-id %d to be the next song after index %d\n",
-      cmdarg->queue_move_param.item_id, cmdarg->queue_move_param.to_pos);
-
-  queue_move_byitemid(queue, cmdarg->queue_move_param.item_id, cmdarg->queue_move_param.to_pos, 0);
 
-  cur_plversion++;
-
-  listener_notify(LISTENER_PLAYLIST);
-
-  *retval = 0;
-  return COMMAND_END;
-}
+/* ------------------------------- Player API ------------------------------- */
 
-static enum command_state
-playerqueue_remove_bypos(void *arg, int *retval)
+int
+player_get_current_pos(uint64_t *pos, struct timespec *ts, int commit)
 {
-  union player_arg *cmdarg = arg;
-  int pos;
-  struct player_source *ps_playing;
-  uint32_t item_id;
+  uint64_t delta;
+  int ret;
 
-  pos = cmdarg->intval;
-  if (pos < 1)
+  ret = clock_gettime_with_res(CLOCK_MONOTONIC, ts, &timer_res);
+  if (ret < 0)
     {
-      DPRINTF(E_LOG, L_PLAYER, "Can't remove item, invalid position %d\n", pos);
-      *retval = -1;
-      return COMMAND_END;
-    }
-
-  ps_playing = source_now_playing();
+      DPRINTF(E_LOG, L_PLAYER, "Couldn't get clock: %s\n", strerror(errno));
 
-  if (!ps_playing)
-    {
-      DPRINTF(E_DBG, L_PLAYER, "No playing item for remove by pos\n");
-      item_id = 0;
+      return -1;
     }
-  else
-    item_id = ps_playing->item_id;
-
-  DPRINTF(E_DBG, L_PLAYER, "Removing item from position %d\n", pos);
-  queue_remove_bypos(queue, item_id, pos, shuffle);
-
-  cur_plversion++;
-
-  listener_notify(LISTENER_PLAYLIST);
 
-  *retval = 0;
-  return COMMAND_END;
-}
-
-static enum command_state
-playerqueue_remove_byindex(void *arg, int *retval)
-{
-  union player_arg *cmdarg = arg;
-  int pos;
-  int count;
-  int i;
-
-  pos = cmdarg->queue_remove_param.from_pos;
-  count = cmdarg->queue_remove_param.count;
-
-  DPRINTF(E_DBG, L_PLAYER, "Removing %d items starting from position %d\n", count, pos);
-
-  for (i = 0; i < count; i++)
-    queue_remove_byindex(queue, pos, 0);
+  delta = (ts->tv_sec - pb_pos_stamp.tv_sec) * 1000000 + (ts->tv_nsec - pb_pos_stamp.tv_nsec) / 1000;
 
-  cur_plversion++;
+#ifdef DEBUG_SYNC
+  DPRINTF(E_DBG, L_PLAYER, "Delta is %" PRIu64 " usec\n", delta);
+#endif
 
-  listener_notify(LISTENER_PLAYLIST);
+  delta = (delta * 44100) / 1000000;
 
-  *retval = 0;
-  return COMMAND_END;
-}
+#ifdef DEBUG_SYNC
+  DPRINTF(E_DBG, L_PLAYER, "Delta is %" PRIu64 " samples\n", delta);
+#endif
 
-static enum command_state
-playerqueue_remove_byitemid(void *arg, int *retval)
-{
-  union player_arg *cmdarg = arg;
-  uint32_t id;
+  *pos = pb_pos + delta;
 
-  id = cmdarg->id;
-  if (id < 1)
+  if (commit)
     {
-      DPRINTF(E_LOG, L_PLAYER, "Can't remove item, invalid id %d\n", id);
-      *retval = -1;
-      return COMMAND_END;
-    }
-
-  DPRINTF(E_DBG, L_PLAYER, "Removing item with id %d\n", id);
-  queue_remove_byitemid(queue, id);
-
-  cur_plversion++;
-
-  listener_notify(LISTENER_PLAYLIST);
-
-  *retval = 0;
-  return COMMAND_END;
-}
-
-/*
- * Removes all media items from the queue
- */
-static enum command_state
-playerqueue_clear(void *arg, int *retval)
-{
-  queue_clear(queue);
-
-  cur_plid = 0;
-  cur_plversion++;
-
-  listener_notify(LISTENER_PLAYLIST);
-
-  *retval = 0;
-  return COMMAND_END;
-}
-
-/*
- * Removes all items from the history
- */
-static enum command_state
-playerqueue_clear_history(void *arg, int *retval)
-{
-  memset(history, 0, sizeof(struct player_history));
-
-  cur_plversion++;
-
-  listener_notify(LISTENER_PLAYLIST);
-
-  *retval = 0;
-  return COMMAND_END;
-}
+      pb_pos = *pos;
 
-static enum command_state
-playerqueue_plid(void *arg, int *retval)
-{
-  union player_arg *cmdarg = arg;
-  cur_plid = cmdarg->id;
+      pb_pos_stamp.tv_sec = ts->tv_sec;
+      pb_pos_stamp.tv_nsec = ts->tv_nsec;
 
-  *retval = 0;
-  return COMMAND_END;
-}
+#ifdef DEBUG_SYNC
+      DPRINTF(E_DBG, L_PLAYER, "Pos: %" PRIu64 " (clock)\n", *pos);
+#endif
+    }
 
+  return 0;
+}
 
-/* Player API executed in the httpd (DACP) thread */
 int
 player_get_status(struct player_status *status)
 {
@@ -3383,6 +2832,9 @@ player_get_status(struct player_status *status)
   return ret;
 }
 
+
+/* --------------------------- Thread: httpd (DACP) ------------------------- */
+
 /*
  * Stores the now playing media item dbmfi-id in the given id pointer.
  *
@@ -3401,122 +2853,53 @@ player_now_playing(uint32_t *id)
   return ret;
 }
 
-char *
-player_get_icy_artwork_url(uint32_t id)
-{
-  union player_arg cmdarg;
-  int ret;
-
-  cmdarg.icy.id = id;
-
-  if (pthread_self() != tid_player)
-    ret = commands_exec_sync(cmdbase, artwork_url_get, NULL, &cmdarg);
-  else
-    artwork_url_get(&cmdarg, &ret);
-
-  if (ret < 0)
-    return NULL;
-  else
-    return cmdarg.icy.artwork_url;
-}
-
 /*
  * Starts/resumes playback
  *
- * Depending on the player state, this will either resume playing the current item (player is paused)
- * or begin playing the queue from the beginning.
+ * Depending on the player state, this will either resume playing the current
+ * item (player is paused) or begin playing the queue from the beginning.
  *
  * If shuffle is set, the queue is reshuffled prior to starting playback.
  *
- * If a pointer is given as argument "itemid", its value will be set to the playing item dbmfi-id.
- *
- * @param *id if not NULL, will be set to the playing item dbmfi-id
  * @return 0 if successful, -1 if an error occurred
  */
 int
-player_playback_start(uint32_t *id)
+player_playback_start(void)
 {
-  union player_arg cmdarg;
   int ret;
 
-  cmdarg.playback_start_param.id_ptr = id;
-
-  ret = commands_exec_sync(cmdbase, playback_start, playback_start_bh, &cmdarg);
+  ret = commands_exec_sync(cmdbase, playback_start, playback_start_bh, NULL);
   return ret;
 }
 
 /*
- * Starts playback with the media item at the given index of the play-queue.
+ * Starts/resumes playback of the given queue_item
  *
  * If shuffle is set, the queue is reshuffled prior to starting playback.
  *
  * If a pointer is given as argument "itemid", its value will be set to the playing item id.
  *
- * @param index the index of the item in the play-queue
- * @param *id if not NULL, will be set to the playing item id
- * @return 0 if successful, -1 if an error occurred
- */
-int
-player_playback_start_byindex(int index, uint32_t *id)
-{
-  union player_arg cmdarg;
-  int ret;
-
-  cmdarg.playback_start_param.pos = index;
-  cmdarg.playback_start_param.id_ptr = id;
-
-  ret = commands_exec_sync(cmdbase, playback_start_byindex, playback_start_bh, &cmdarg);
-  return ret;
-}
-
-/*
- * Starts playback with the media item at the given position in the UpNext-queue.
- * The UpNext-queue consists of all items of the play-queue (shuffle off) or shuffle-queue
- * (shuffle on) after the current playing item (starting with position 0).
- *
- * If shuffle is set, the queue is reshuffled prior to starting playback.
- *
- * If a pointer is given as argument "itemid", its value will be set to the playing item dbmfi-id.
- *
- * @param pos the position in the UpNext-queue (zero-based)
- * @param *id if not NULL, will be set to the playing item dbmfi-id
+ * @param queue_item to start playing
  * @return 0 if successful, -1 if an error occurred
  */
 int
-player_playback_start_bypos(int pos, uint32_t *id)
+player_playback_start_byitem(struct db_queue_item *queue_item)
 {
-  union player_arg cmdarg;
   int ret;
 
-  cmdarg.playback_start_param.pos = pos;
-  cmdarg.playback_start_param.id_ptr = id;
-
-  ret = commands_exec_sync(cmdbase, playback_start_bypos, playback_start_bh, &cmdarg);
+  ret = commands_exec_sync(cmdbase, playback_start_item, playback_start_bh, queue_item);
   return ret;
 }
 
-/*
- * Starts playback with the media item with the given (queueitem) item-id in queue
- *
- * If shuffle is set, the queue is reshuffled prior to starting playback.
- *
- * If a pointer is given as argument "itemid", its value will be set to the playing item dbmfi-id.
- *
- * @param item_id The queue-item-id
- * @param *id if not NULL, will be set to the playing item dbmfi-id
- * @return 0 if successful, -1 if an error occurred
- */
 int
-player_playback_start_byitemid(uint32_t item_id, uint32_t *id)
+player_playback_start_byid(uint32_t id)
 {
   union player_arg cmdarg;
   int ret;
 
-  cmdarg.playback_start_param.id = item_id;
-  cmdarg.playback_start_param.id_ptr = id;
-  ret = commands_exec_sync(cmdbase, playback_start_byitemid, playback_start_bh, &cmdarg);
-  return ret;
+  cmdarg.id = id;
 
+  ret = commands_exec_sync(cmdbase, playback_start_id, playback_start_bh, &cmdarg);
   return ret;
 }
 
@@ -3568,7 +2951,6 @@ player_playback_prev(void)
   return ret;
 }
 
-
 void
 player_speaker_enumerate(spk_enum_cb cb, void *arg)
 {
@@ -3602,6 +2984,12 @@ player_volume_set(int vol)
   union player_arg cmdarg;
   int ret;
 
+  if (vol < 0 || vol > 100)
+    {
+      DPRINTF(E_LOG, L_PLAYER, "Volume (%d) for player_volume_set is out of range\n", vol);
+      return -1;
+    }
+
   cmdarg.intval = vol;
 
   ret = commands_exec_sync(cmdbase, volume_set, NULL, &cmdarg);
@@ -3614,6 +3002,12 @@ player_volume_setrel_speaker(uint64_t id, int relvol)
   union player_arg cmdarg;
   int ret;
 
+  if (relvol < 0 || relvol > 100)
+    {
+      DPRINTF(E_LOG, L_PLAYER, "Volume (%d) for player_volume_setrel_speaker is out of range\n", relvol);
+      return -1;
+    }
+
   cmdarg.vol_param.spk_id = id;
   cmdarg.vol_param.volume = relvol;
 
@@ -3627,6 +3021,12 @@ player_volume_setabs_speaker(uint64_t id, int vol)
   union player_arg cmdarg;
   int ret;
 
+  if (vol < 0 || vol > 100)
+    {
+      DPRINTF(E_LOG, L_PLAYER, "Volume (%d) for player_volume_setabs_speaker is out of range\n", vol);
+      return -1;
+    }
+
   cmdarg.vol_param.spk_id = id;
   cmdarg.vol_param.volume = vol;
 
@@ -3658,203 +3058,19 @@ player_shuffle_set(int enable)
   return ret;
 }
 
-/*
- * Returns the queue info for max "count" media items in the UpNext-queue
- *
- * The UpNext-queue consists of all items of the play-queue (shuffle off) or shuffle-queue
- * (shuffle on) after the current playing item (starting with position 0).
- *
- * @param count max number of media items to return
- * @return queue info
- */
-struct queue *
-player_queue_get_bypos(int count)
-{
-  union player_arg cmdarg;
-  int ret;
-
-  cmdarg.queue_get_param.pos = -1;
-  cmdarg.queue_get_param.count = count;
-  cmdarg.queue_get_param.queue = NULL;
-
-  ret = commands_exec_sync(cmdbase, playerqueue_get_bypos, NULL, &cmdarg);
-
-  if (ret != 0)
-    return NULL;
-
-  return cmdarg.queue_get_param.queue;
-}
-
-/*
- * Returns the queue info for max "count" media items starting with the item at the given
- * index in the play-queue
- *
- * @param index Index of the play-queue for the first item
- * @param count max number of media items to return
- * @return queue info
- */
-struct queue *
-player_queue_get_byindex(int index, int count)
-{
-  union player_arg cmdarg;
-  int ret;
-
-  cmdarg.queue_get_param.pos = index;
-  cmdarg.queue_get_param.count = count;
-  cmdarg.queue_get_param.queue = NULL;
-
-  ret = commands_exec_sync(cmdbase, playerqueue_get_byindex, NULL, &cmdarg);
-
-  if (ret != 0)
-    return NULL;
-
-  return cmdarg.queue_get_param.queue;
-}
-
-/*
- * Appends the given media items to the queue
- */
-int
-player_queue_add(struct queue_item *items, uint32_t *item_id)
-{
-  union player_arg cmdarg;
-  int ret;
-
-  cmdarg.queue_add_param.items = items;
-  cmdarg.queue_add_param.item_id_ptr = item_id;
-
-  ret = commands_exec_sync(cmdbase, playerqueue_add, NULL, &cmdarg);
-  return ret;
-}
-
-/*
- * Adds the given media items directly after the current playing/streaming media item
- */
-int
-player_queue_add_next(struct queue_item *items)
-{
-  union player_arg cmdarg;
-  int ret;
-
-  cmdarg.queue_add_param.items = items;
-
-  ret = commands_exec_sync(cmdbase, playerqueue_add_next, NULL, &cmdarg);
-  return ret;
-}
-
-/*
- * Moves the media item at 'pos_from' to 'pos_to' in the UpNext-queue.
- *
- * The UpNext-queue consists of all items of the play-queue (shuffle off) or shuffle-queue
- * (shuffle on) after the current playing item (starting with position 0).
- */
-int
-player_queue_move_bypos(int pos_from, int pos_to)
-{
-  union player_arg cmdarg;
-  int ret;
-
-  cmdarg.queue_move_param.from_pos = pos_from;
-  cmdarg.queue_move_param.to_pos = pos_to;
-
-  ret = commands_exec_sync(cmdbase, playerqueue_move_bypos, NULL, &cmdarg);
-  return ret;
-}
-
-int
-player_queue_move_byindex(int pos_from, int pos_to)
-{
-  union player_arg cmdarg;
-  int ret;
-
-  cmdarg.queue_move_param.from_pos = pos_from;
-  cmdarg.queue_move_param.to_pos = pos_to;
-
-  ret = commands_exec_sync(cmdbase, playerqueue_move_byindex, NULL, &cmdarg);
-  return ret;
-}
-
-int
-player_queue_move_byitemid(uint32_t item_id, int pos_to)
-{
-  union player_arg cmdarg;
-  int ret;
-
-  cmdarg.queue_move_param.item_id = item_id;
-  cmdarg.queue_move_param.to_pos = pos_to;
-
-  ret = commands_exec_sync(cmdbase, playerqueue_move_byitemid, NULL, &cmdarg);
-  return ret;
-}
-
-/*
- * Removes the media item at the given position from the UpNext-queue
- *
- * The UpNext-queue consists of all items of the play-queue (shuffle off) or shuffle-queue
- * (shuffle on) after the current playing item (starting with position 0).
- *
- * @param pos Position in the UpNext-queue (0-based)
- * @return 0 on success, -1 on failure
- */
-int
-player_queue_remove_bypos(int pos)
-{
-  union player_arg cmdarg;
-  int ret;
-
-  cmdarg.intval = pos;
-
-  ret = commands_exec_sync(cmdbase, playerqueue_remove_bypos, NULL, &cmdarg);
-  return ret;
-}
-
-/*
- * Removes the media item at the given position from the UpNext-queue
- *
- * The UpNext-queue consists of all items of the play-queue (shuffle off) or shuffle-queue
- * (shuffle on) after the current playing item (starting with position 0).
- *
- * @param pos Position in the UpNext-queue (0-based)
- * @return 0 on success, -1 on failure
- */
-int
-player_queue_remove_byindex(int pos, int count)
-{
-  union player_arg cmdarg;
-  int ret;
-
-  cmdarg.queue_remove_param.from_pos = pos;
-  cmdarg.queue_remove_param.count = count;
-
-  ret = commands_exec_sync(cmdbase, playerqueue_remove_byindex, NULL, &cmdarg);
-  return ret;
-}
-
-/*
- * Removes the item with the given (queueitem) item id from the queue
- *
- * @param id Id of the queue item to remove
- * @return 0 on success, -1 on failure
- */
 int
-player_queue_remove_byitemid(uint32_t id)
+player_consume_set(int enable)
 {
   union player_arg cmdarg;
   int ret;
 
-  cmdarg.id = id;
+  cmdarg.intval = enable;
 
-  ret = commands_exec_sync(cmdbase, playerqueue_remove_byitemid, NULL, &cmdarg);
+  ret = commands_exec_sync(cmdbase, consume_set, NULL, &cmdarg);
   return ret;
 }
 
 void
-player_queue_clear(void)
-{
-  commands_exec_sync(cmdbase, playerqueue_clear, NULL, NULL);
-}
-
-void
 player_queue_clear_history()
 {
   commands_exec_sync(cmdbase, playerqueue_clear_history, NULL, NULL);
@@ -3870,7 +3086,15 @@ player_queue_plid(uint32_t plid)
   commands_exec_sync(cmdbase, playerqueue_plid, NULL, &cmdarg);
 }
 
-/* Non-blocking commands used by mDNS */
+struct player_history *
+player_history_get(void)
+{
+  return history;
+}
+
+
+/* ------------------- Non-blocking commands used by mDNS ------------------- */
+
 int
 player_device_add(void *device)
 {
@@ -3909,18 +3133,50 @@ player_device_remove(void *device)
   return ret;
 }
 
-/* Thread: worker */
 static void
-player_metadata_send(struct player_metadata *pmd)
+player_device_auth_kickoff(enum output_types type, char **arglist)
+{
+  union player_arg *cmdarg;
+
+  cmdarg = calloc(1, sizeof(union player_arg));
+  if (!cmdarg)
+    {
+      DPRINTF(E_LOG, L_PLAYER, "Could not allocate player_command\n");
+      return;
+    }
+
+  cmdarg->auth.type = type;
+  memcpy(cmdarg->auth.pin, arglist[0], 4);
+
+  commands_exec_async(cmdbase, device_auth_kickoff, cmdarg);
+}
+
+
+/* --------------------------- Thread: filescanner -------------------------- */
+
+void
+player_raop_verification_kickoff(char **arglist)
+{
+  player_device_auth_kickoff(OUTPUT_TYPE_RAOP, arglist);
+}
+
+
+/* ---------------------------- Thread: worker ------------------------------ */
+
+void
+player_metadata_send(void *imd, void *omd)
 {
   union player_arg cmdarg;
 
-  cmdarg.pmd = pmd;
+  cmdarg.metadata_param.input = imd;
+  cmdarg.metadata_param.output = omd;
 
-  commands_exec_sync(cmdbase, metadata_send, NULL, &cmdarg);
+  commands_exec_sync(cmdbase, device_metadata_send, NULL, &cmdarg);
 }
 
-/* Thread: player */
+
+/* ---------------------------- Thread: player ------------------------------ */
+
 static void *
 player(void *arg)
 {
@@ -3940,12 +3196,11 @@ player(void *arg)
   if (!player_exit)
     DPRINTF(E_LOG, L_PLAYER, "Player event loop terminated ahead of time!\n");
 
-  /* Save selected devices */
   db_speaker_clear_all();
 
   for (device = dev_list; device; device = device->next)
     {
-      ret = db_speaker_save(device->id, device->selected, device->volume, device->name);
+      ret = db_speaker_save(device);
       if (ret < 0)
 	DPRINTF(E_LOG, L_PLAYER, "Could not save state for %s device '%s'\n", device->type_name, device->name);
     }
@@ -3956,7 +3211,8 @@ player(void *arg)
 }
 
 
-/* Thread: main */
+/* ----------------------------- Thread: main ------------------------------- */
+
 int
 player_init(void)
 {
@@ -3983,15 +3239,13 @@ player_init(void)
   player_state = PLAY_STOPPED;
   repeat = REPEAT_OFF;
   shuffle = 0;
+  consume = 0;
 
-  queue = queue_new();
   history = (struct player_history *)calloc(1, sizeof(struct player_history));
 
-  /*
-   * Determine if the resolution of the system timer is > or < the size
-   * of an audio packet. NOTE: this assumes the system clock resolution
-   * is less than one second.
-   */
+  // Determine if the resolution of the system timer is > or < the size
+  // of an audio packet. NOTE: this assumes the system clock resolution
+  // is less than one second.
   if (clock_getres(CLOCK_MONOTONIC, &timer_res) < 0)
     {
       DPRINTF(E_LOG, L_PLAYER, "Could not get the system timer resolution.\n");
@@ -3999,20 +3253,22 @@ player_init(void)
       return -1;
     }
 
-#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
-  /* FreeBSD will report a resolution of 1, but actually has a resolution
-   * larger than an audio packet
-   */
-  if (timer_res.tv_nsec == 1)
-    timer_res.tv_nsec = 2 * AIRTUNES_V2_STREAM_PERIOD;
-#endif
+  if (!cfg_getbool(cfg_getsec(cfg, "general"), "high_resolution_clock"))
+    {
+      DPRINTF(E_INFO, L_PLAYER, "High resolution clock not enabled on this system (res is %ld)\n", timer_res.tv_nsec);
+
+      timer_res.tv_nsec = 2 * AIRTUNES_V2_STREAM_PERIOD;
+    }
 
   // Set the tick interval for the playback timer
   interval = MAX(timer_res.tv_nsec, AIRTUNES_V2_STREAM_PERIOD);
   tick_interval.tv_nsec = interval;
 
+  pb_write_deficit_max = (PLAYER_WRITE_BEHIND_MAX * 1000000 / interval);
+  pb_read_deficit_max  = (PLAYER_READ_BEHIND_MAX * 1000000 / interval);
+
   // Create the playback timer
-#if defined(__linux__)
+#ifdef HAVE_TIMERFD
   pb_timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK);
   ret = pb_timer_fd;
 #else
@@ -4029,14 +3285,6 @@ player_init(void)
   gcry_randomize(&rnd, sizeof(rnd), GCRY_STRONG_RANDOM);
   last_rtptime = ((uint64_t)1 << 32) | rnd;
 
-  audio_buf = evbuffer_new();
-  if (!audio_buf)
-    {
-      DPRINTF(E_LOG, L_PLAYER, "Could not allocate evbuffer for audio buffer\n");
-
-      goto audio_fail;
-    }
-
   evbase_player = event_base_new();
   if (!evbase_player)
     {
@@ -4045,10 +3293,10 @@ player_init(void)
       goto evbase_fail;
     }
 
-#if defined(__linux__)
-  pb_timer_ev = event_new(evbase_player, pb_timer_fd, EV_READ | EV_PERSIST, player_playback_cb, NULL);
+#ifdef HAVE_TIMERFD
+  pb_timer_ev = event_new(evbase_player, pb_timer_fd, EV_READ | EV_PERSIST, playback_cb, NULL);
 #else
-  pb_timer_ev = event_new(evbase_player, SIGALRM, EV_SIGNAL | EV_PERSIST, player_playback_cb, NULL);
+  pb_timer_ev = event_new(evbase_player, SIGALRM, EV_SIGNAL | EV_PERSIST, playback_cb, NULL);
 #endif
   if (!pb_timer_ev)
     {
@@ -4056,8 +3304,6 @@ player_init(void)
       goto evnew_fail;
     }
 
-  event_add(pb_timer_ev, NULL);
-
   cmdbase = commands_base_new(evbase_player, NULL);
 
   ret = outputs_init();
@@ -4067,6 +3313,13 @@ player_init(void)
       goto outputs_fail;
     }
 
+  ret = input_init();
+  if (ret < 0)
+    {
+      DPRINTF(E_FATAL, L_PLAYER, "Input initiation failed\n");
+      goto input_fail;
+    }
+
   ret = pthread_create(&tid_player, NULL, player, NULL);
   if (ret < 0)
     {
@@ -4082,15 +3335,15 @@ player_init(void)
   return 0;
 
  thread_fail:
+  input_deinit();
+ input_fail:
   outputs_deinit();
  outputs_fail:
   commands_base_free(cmdbase);
  evnew_fail:
   event_base_free(evbase_player);
  evbase_fail:
-  evbuffer_free(audio_buf);
- audio_fail:
-#if defined(__linux__)
+#ifdef HAVE_TIMERFD
   close(pb_timer_fd);
 #else
   timer_delete(pb_timer);
@@ -4099,12 +3352,23 @@ player_init(void)
   return -1;
 }
 
-/* Thread: main */
 void
 player_deinit(void)
 {
   int ret;
 
+  player_playback_stop();
+
+#ifdef HAVE_TIMERFD
+  close(pb_timer_fd);
+#else
+  timer_delete(pb_timer);
+#endif
+
+  input_deinit();
+
+  outputs_deinit();
+
   player_exit = 1;
   commands_base_destroy(cmdbase);
 
@@ -4116,19 +3380,7 @@ player_deinit(void)
       return;
     }
 
-  queue_free(queue);
   free(history);
 
-  pb_timer_stop();
-#if defined(__linux__)
-  close(pb_timer_fd);
-#else
-  timer_delete(pb_timer);
-#endif
-
-  evbuffer_free(audio_buf);
-
-  outputs_deinit();
-
   event_base_free(evbase_player);
 }
diff --git a/src/player.h b/src/player.h
index d0b4040..ff64ba3 100644
--- a/src/player.h
+++ b/src/player.h
@@ -5,7 +5,6 @@
 #include <stdint.h>
 
 #include "db.h"
-#include "queue.h"
 
 /* AirTunes v2 packet interval in ns */
 /* (352 samples/packet * 1e9 ns/s) / 44100 samples/s = 7981859 ns/packet */
@@ -14,11 +13,6 @@
 /* AirTunes v2 number of samples per packet */
 #define AIRTUNES_V2_PACKET_SAMPLES  352
 
-
-/* Samples to bytes, bytes to samples */
-#define STOB(s) ((s) * 4)
-#define BTOS(b) ((b) / 4)
-
 /* Maximum number of previously played songs that are remembered */
 #define MAX_HISTORY_COUNT 20
 
@@ -28,6 +22,12 @@ enum play_status {
   PLAY_PLAYING = 4,
 };
 
+enum repeat_mode {
+  REPEAT_OFF  = 0,
+  REPEAT_SONG = 1,
+  REPEAT_ALL  = 2,
+};
+
 struct spk_flags {
   unsigned selected:1;
   unsigned has_password:1;
@@ -39,18 +39,12 @@ struct player_status {
   enum play_status status;
   enum repeat_mode repeat;
   char shuffle;
+  char consume;
 
   int volume;
 
   /* Playlist id */
   uint32_t plid;
-  /* Playlist version
-     After startup plversion is 0 and gets incremented after each change of the playlist
-     (e. g. after adding/moving/removing items). It is used by mpd clients to recognize if
-     they need to update the current playlist. */
-  uint32_t plversion;
-  /* Playlist length */
-  uint32_t playlistlength;
   /* Id of the playing file/item in the files database */
   uint32_t id;
   /* Item-Id of the playing file/item in the queue */
@@ -59,14 +53,6 @@ struct player_status {
   uint32_t pos_ms;
   /* Length in ms of playing item */
   uint32_t len_ms;
-  /* Playlist position of playing item*/
-  int pos_pl;
-  /* Item id of next item in playlist */
-  uint32_t next_id;
-  /* Item-Id of the next file/item in the queue */
-  uint32_t next_item_id;
-  /* Playlist position of next item */
-  int next_pos_pl;
 };
 
 typedef void (*spk_enum_cb)(uint64_t id, const char *name, int relvol, int absvol, struct spk_flags flags, void *arg);
@@ -94,9 +80,6 @@ player_get_status(struct player_status *status);
 int
 player_now_playing(uint32_t *id);
 
-char *
-player_get_icy_artwork_url(uint32_t id);
-
 void
 player_speaker_enumerate(spk_enum_cb cb, void *arg);
 
@@ -104,16 +87,13 @@ int
 player_speaker_set(uint64_t *ids);
 
 int
-player_playback_start(uint32_t *id);
+player_playback_start(void);
 
 int
-player_playback_start_byindex(int pos, uint32_t *id);
+player_playback_start_byitem(struct db_queue_item *queue_item);
 
 int
-player_playback_start_bypos(int pos, uint32_t *id);
-
-int
-player_playback_start_byitemid(uint32_t item_id, uint32_t *id);
+player_playback_start_byid(uint32_t id);
 
 int
 player_playback_stop(void);
@@ -130,7 +110,6 @@ player_playback_next(void);
 int
 player_playback_prev(void);
 
-
 int
 player_volume_set(int vol);
 
@@ -146,39 +125,9 @@ player_repeat_set(enum repeat_mode mode);
 int
 player_shuffle_set(int enable);
 
-
-struct queue *
-player_queue_get_bypos(int count);
-
-struct queue *
-player_queue_get_byindex(int pos, int count);
-
-int
-player_queue_add(struct queue_item *items, uint32_t *item_id);
-
-int
-player_queue_add_next(struct queue_item *items);
-
-int
-player_queue_move_bypos(int ps_pos_from, int ps_pos_to);
-
 int
-player_queue_move_byindex(int pos_from, int pos_to);
+player_consume_set(int enable);
 
-int
-player_queue_move_byitemid(uint32_t item_id, int pos_to);
-
-int
-player_queue_remove_bypos(int pos);
-
-int
-player_queue_remove_byindex(int pos, int count);
-
-int
-player_queue_remove_byitemid(uint32_t id);
-
-void
-player_queue_clear(void);
 
 void
 player_queue_clear_history(void);
@@ -186,14 +135,20 @@ player_queue_clear_history(void);
 void
 player_queue_plid(uint32_t plid);
 
+struct player_history *
+player_history_get(void);
+
 int
 player_device_add(void *device);
 
 int
 player_device_remove(void *device);
 
-struct player_history *
-player_history_get(void);
+void
+player_raop_verification_kickoff(char **arglist);
+
+void
+player_metadata_send(void *imd, void *omd);
 
 int
 player_init(void);
diff --git a/src/queue.c b/src/queue.c
deleted file mode 100644
index b6b4ad8..0000000
--- a/src/queue.c
+++ /dev/null
@@ -1,1266 +0,0 @@
-/*
- * Copyright (C) 2015 Christian Meffert <christian.meffert at googlemail.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- */
-
-
-#include "queue.h"
-
-#include <inttypes.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "logger.h"
-#include "misc.h"
-#include "rng.h"
-
-
-/*
- * Internal representation of an item in a queue. It links to the previous and the next item
- * in the queue for shuffle on/off. To access the properties use the queueitem_* functions.
- */
-struct queue_item
-{
-  /* Item-Id is a unique id for this queue item. If the same item appears multiple
-     times in the queue each corresponding queue item has its own id. */
-  unsigned int item_id;
-
-  /* Id of the file/item in the files database */
-  uint32_t id;
-
-  /* Length of the item in ms */
-  unsigned int len_ms;
-
-  /* Data type of the item */
-  enum data_kind data_kind;
-  /* Media type of the item */
-  enum media_kind media_kind;
-
-  /* Link to the previous/next item in the queue */
-  struct queue_item *next;
-  struct queue_item *prev;
-
-  /* Link to the previous/next item in the shuffle queue */
-  struct queue_item *shuffle_next;
-  struct queue_item *shuffle_prev;
-};
-
-/*
- * The queue struct references two (double) linked lists of queue_item. One for the play-queue
- * and one for the shuffle-queue.
- *
- * Both linked lists start with the "head" item. The head item is not a media item, instead it is
- * an internal item created during initialization of a new queue struct (see queue_new() function).
- * The head item is always the first item in the queue and will only be removed when queue is
- * destructed.
- *
- * The linked lists are circular, therefor the last item in a list has the first item (the head item)
- * as "next" and the first item in the queue (the head item) has the last item as "prev" linked.
- *
- * An empty queue (with no media items) will only consist of the head item pointing to itself.
- */
-struct queue
-{
-  /* The queue item id of the last inserted item */
-  unsigned int last_inserted_item_id;
-
-  /* The version number of the queue */
-  unsigned int version;
-
-  /* Shuffle RNG state */
-  struct rng_ctx shuffle_rng;
-
-  /*
-   * The head item in the queue is not an actual media item, instead it is the
-   * starting point for the play-queue and the shuffle-queue. It always has the
-   * item-id 0. The queue is circular, the last item of the queue has the head
-   * item as "next" and the head item has the last item as "prev".
-   */
-  struct queue_item *head;
-};
-
-
-/*
- * Creates and initializes a new queue
- */
-struct queue *
-queue_new()
-{
-  struct queue *queue;
-
-  queue = (struct queue *)calloc(1, sizeof(struct queue));
-  queue->head = (struct queue_item *)calloc(1, sizeof(struct queue_item));
-
-  // Create the head item and make the queue circular (head points to itself)
-  queue->head->next = queue->head;
-  queue->head->prev = queue->head;
-  queue->head->shuffle_next = queue->head;
-  queue->head->shuffle_prev = queue->head;
-
-  rng_init(&queue->shuffle_rng);
-
-  return queue;
-}
-
-/*
- * Frees the given item and all linked items
- */
-static void
-queue_items_free(struct queue_item *item)
-{
-  struct queue_item *temp;
-  struct queue_item *next;
-
-  item->prev->next = NULL;
-
-  next = item;
-  while (next)
-    {
-      temp = next->next;
-      free(next);
-      next = temp;
-    }
-}
-
-/*
- * Frees the given queue and all the items in it
- */
-void
-queue_free(struct queue *queue)
-{
-  queue_items_free(queue->head);
-  free(queue);
-}
-
-/*
- * Returns the number of media items in the queue
- *
- * @param queue The queue
- * @return      The number of items in the queue
- */
-unsigned int
-queue_count(struct queue *queue)
-{
-  struct queue_item *item;
-  int count;
-
-  count = 0;
-
-  for (item = queue->head->next; item != queue->head; item = item->next)
-    {
-      count++;
-    }
-
-  return count;
-}
-
-/*
- * Returns the next item in the play-queue (shuffle = 0) or shuffle-queue (shuffle = 1)
- */
-static struct queue_item *
-item_next(struct queue_item *item, char shuffle)
-{
-  if (shuffle)
-    return item->shuffle_next;
-  return item->next;
-}
-
-/*
- * Returns the previous item in the play-queue (shuffle = 0) or shuffle-queue (shuffle = 1)
- */
-static struct queue_item *
-item_prev(struct queue_item *item, char shuffle)
-{
-  if (shuffle)
-    return item->shuffle_prev;
-  return item->prev;
-}
-
-/*
- * Returns the (0-based) position of the first item with the given dbmfi-id.
- * If no item is found for the given id, it returns -1.
- */
-int
-queueitem_pos(struct queue_item *item, uint32_t id)
-{
-  struct queue_item *temp;
-  int pos;
-
-  if (id == 0 || item->id == id)
-    return 0;
-
-  pos = 1;
-  for (temp = item->next; (temp != item) && temp->id != id; temp = temp->next)
-    {
-      pos++;
-    }
-
-  if (temp == item)
-    {
-      // Item with given (database) id does not exists
-      return -1;
-    }
-
-  return pos;
-}
-
-/*
- * Returns the id of the item/file in the files database table
- */
-uint32_t
-queueitem_id(struct queue_item *item)
-{
-  return item->id;
-}
-
-/*
- * Returns the queue-item-id
- */
-unsigned int
-queueitem_item_id(struct queue_item *item)
-{
-  return item->item_id;
-}
-
-/*
- * Returns the length of the item in milliseconds
- */
-unsigned int
-queueitem_len(struct queue_item *item)
-{
-  return item->len_ms;
-}
-
-/*
- * Returns the data-kind
- */
-enum data_kind
-queueitem_data_kind(struct queue_item *item)
-{
-  return item->data_kind;
-}
-
-/*
- * Returns the media-kind
- */
-enum media_kind
-queueitem_media_kind(struct queue_item *item)
-{
-  return item->media_kind;
-}
-
-/*
- * Returns the item with the given item_id in the queue
- *
- * @param queue   The queue
- * @param item_id The unique id of the item in the queue
- * @return        Item with the given item_id or NULL if not found
- */
-static struct queue_item *
-queueitem_get_byitemid(struct queue *queue, int item_id)
-{
-  struct queue_item *item;
-
-  for (item = queue->head->next; item != queue->head && item->item_id != item_id; item = item->next)
-    {
-      // Iterate through the queue until the item with item_id is found
-    }
-
-  if (item == queue->head && item_id != 0)
-    return NULL;
-
-  return item;
-}
-
-/*
- * Returns the item at the given index (0-based) in the play-queue (shuffle = 0) or shuffle-queue (shuffle = 1)
- *
- * @param queue   The queue
- * @param index   Index of item in the queue (0-based)
- * @param shuffle Play-queue (shuffle = 0) or shuffle-queue (shuffle = 1)
- * @return        Item at position in the queue or NULL if not found
- */
-static struct queue_item *
-queueitem_get_byindex(struct queue *queue, unsigned int index, char shuffle)
-{
-  struct queue_item *item;
-  int i;
-
-  i = 0;
-  for (item = item_next(queue->head, shuffle); item != queue->head && i < index; item = item_next(item, shuffle))
-    {
-      i++;
-    }
-
-  if (item == queue->head)
-    return NULL;
-
-  return item;
-}
-
-/*
- * Returns the item at the given position relative to the item with the given item_id in the
- * play queue (shuffle = 0) or shuffle queue (shuffle = 1).
- *
- * The item with item_id is at pos == 0.
- *
- * @param queue   The queue
- * @param pos     The position relative to the item with given queue-item-id
- * @param shuffle If 0 the position in the play-queue, 1 the position in the shuffle-queue
- * @return        Item at position in the queue or NULL if not found
- */
-static struct queue_item *
-queueitem_get_bypos(struct queue *queue, unsigned int item_id, unsigned int pos, char shuffle)
-{
-  struct queue_item *item_base;
-  struct queue_item *item;
-  int i;
-
-  item_base = queueitem_get_byitemid(queue, item_id);
-
-  if (!item_base)
-    return NULL;
-
-  i = 0;
-  for (item = item_base; i < pos; item = item_next(item, shuffle))
-    {
-      i++;
-    }
-
-  if (item == queue->head)
-    return NULL;
-
-  return item;
-}
-
-/*
- * Returns the item with the given item_id in the queue
- *
- * @param queue   The queue
- * @param item_id The unique id of the item in the queue
- * @return        Item with the given item_id or NULL if not found
- */
-struct queue_item *
-queue_get_byitemid(struct queue *queue, unsigned int item_id)
-{
-  struct queue_item *item;
-
-  item = queueitem_get_byitemid(queue, item_id);
-
-  if (!item)
-    return NULL;
-
-  return item;
-}
-
-/*
- * Returns the item at the given index (0-based) in the play queue (shuffle = 0) or shuffle queue (shuffle = 1)
- *
- * @param queue   The queue
- * @param index   Position of item in the queue (zero-based)
- * @param shuffle Play queue (shuffle = 0) or shuffle queue (shuffle = 1)
- * @return        Item at index in the queue or NULL if not found
- */
-struct queue_item *
-queue_get_byindex(struct queue *queue, unsigned int index, char shuffle)
-{
-  struct queue_item *item;
-
-  item = queueitem_get_byindex(queue, index, shuffle);
-
-  if (!item)
-    return NULL;
-
-  return item;
-}
-
-/*
- * Returns the item at the given position relative to the item with the given item_id in the
- * play queue (shuffle = 0) or shuffle queue (shuffle = 1).
- *
- * The item with item_id is at pos == 0.
- *
- * @param queue   The queue
- * @param item_id The unique id of the item in the queue
- * @param pos     The position relative to the item with given queue-item-id
- * @param shuffle If 0 the position in the play-queue, 1 the position in the shuffle-queue
- * @return        Item at position in the queue or NULL if not found
- */
-struct queue_item *
-queue_get_bypos(struct queue *queue, unsigned int item_id, unsigned int pos, char shuffle)
-{
-  struct queue_item *item;
-
-  item = queueitem_get_bypos(queue, item_id, pos, shuffle);
-
-  if (!item)
-    return NULL;
-
-  return item;
-}
-
-/*
- * Returns the index of the item with the given item-id (unique id in the queue)
- * or -1 if the item does not exist. Depending on the given shuffle value, the position
- * is either the on in the play-queue (shuffle = 0) or the shuffle-queue (shuffle = 1).
- *
- * @param queue   The queue to search the item
- * @param item_id The id of the item in the queue
- * @param shuffle If 0 the position in the play-queue, 1 the position in the shuffle-queue
- * @return        Index (0-based) of the item in the given queue or -1 if it does not exist
- */
-int
-queue_index_byitemid(struct queue *queue, unsigned int item_id, char shuffle)
-{
-  struct queue_item *item;
-  int pos;
-
-  pos = 0;
-  for (item = item_next(queue->head, shuffle); item != queue->head && item->item_id != item_id; item = item_next(item, shuffle))
-    {
-      pos++;
-    }
-
-  if (item == queue->head)
-    // Item not found
-    return -1;
-
-  return pos;
-}
-
-/*
- * Return the next item in the queue for the item with the given item-id.
- *
- * @param queue   The queue
- * @param item_id The id of the item in the queue
- * @param shuffle If 0 return the next item in the play-queue, if 1 the next item in the shuffle-queue
- * @param r_mode  Repeat mode
- * @param reshuffle If 1 and repeat mode is "repeat all" reshuffles the queue on wrap around
- * @return The next item
- */
-struct queue_item *
-queue_next(struct queue *queue, unsigned int item_id, char shuffle, enum repeat_mode r_mode, int reshuffle)
-{
-  struct queue_item *item;
-
-  item = queueitem_get_byitemid(queue, item_id);
-
-  if (!item)
-    // Item not found, start playing from the start of the queue
-    item = queue->head;
-
-  if (r_mode == REPEAT_SONG && item != queue->head)
-    return item;
-
-  item = item_next(item, shuffle);
-
-  if (item == queue->head && r_mode == REPEAT_ALL)
-    {
-      // Repeat all and end of queue reached, return first item in the queue
-      if (reshuffle)
-	queue_shuffle(queue, 0);
-      item = item_next(queue->head, shuffle);
-    }
-
-  if (item == queue->head)
-    return NULL;
-
-  return item;
-}
-
-/*
- * Return the previous item in the queue for the item with the given item-id.
- *
- * @param queue   The queue
- * @param item_id The id of the item in the queue
- * @param shuffle If 0 return the next item in the play-queue, if 1 the next item in the shuffle-queue
- * @param r_mode  Repeat mode
- * @return The previous item
- */
-struct queue_item *
-queue_prev(struct queue *queue, unsigned int item_id, char shuffle, enum repeat_mode r_mode)
-{
-  struct queue_item *item;
-
-  item = queueitem_get_byitemid(queue, item_id);
-
-  if (!item)
-    // Item not found
-    return NULL;
-
-  if (r_mode == REPEAT_SONG && item != queue->head)
-    return item;
-
-  item = item_prev(item, shuffle);
-
-  if (item == queue->head && r_mode == REPEAT_ALL)
-    {
-      // Repeat all and start of queue reached, return last item in the queue
-      item = item_prev(queue->head, shuffle);
-    }
-
-  if (item == queue->head)
-    return NULL;
-
-  return item;
-}
-
-/*
- * Creates a new queue with a copy of the items of the given queue.
- *
- * The given number of items (count) are copied from the play-queue (shuffle = 0) or shuffle-queue (shuffle = 1)
- * starting with the item at the given index (0-based).
- *
- * If count == 0, all items from the given index up to the end of the queue will be returned.
- *
- * @param queue   The queue
- * @param index   Index of the first item in the queue
- * @param count   Maximum number of items to copy (if 0 all remaining items after index)
- * @param shuffle If 0 the play-queue, if 1 the shuffle queue
- * @return A new queue with the specified items
- */
-struct queue *
-queue_new_byindex(struct queue *queue, unsigned int index, unsigned int count, char shuffle)
-{
-  struct queue *qi;
-  struct queue_item *qii;
-  struct queue_item *item;
-  int i;
-  unsigned int qlength;
-  int qii_size;
-
-  qi = queue_new();
-
-  qlength = queue_count(queue);
-
-  qii_size = qlength - index;
-  if (count > 0 && count < qii_size)
-    qii_size = count;
-
-  if (qii_size <= 0)
-    {
-      return qi;
-    }
-
-  item = queueitem_get_byindex(queue, index, shuffle);
-
-  if (!item)
-    return NULL;
-
-  i = 0;
-  for (; item != queue->head && i < qii_size; item = item_next(item, shuffle))
-    {
-      qii = malloc(sizeof(struct queue_item));
-      qii->id = item->id;
-      qii->item_id = item->item_id;
-      qii->len_ms = item->len_ms;
-      qii->data_kind = item->data_kind;
-      qii->media_kind = item->media_kind;
-      qii->next = qii;
-      qii->prev = qii;
-      qii->shuffle_next = qii;
-      qii->shuffle_prev = qii;
-
-      queue_add(qi, qii);
-
-      // queue_add(...) changes the queue item-id, reset the item-id to the original value
-      qii->item_id = item->item_id;
-
-      i++;
-    }
-
-  return qi;
-}
-
-/*
- * Creates a new queue with a copy of the items of the given queue.
- *
- * The given number of items (count) are copied from the play-queue (shuffle = 0) or shuffle-queue (shuffle = 1)
- * starting after the item with the given item_id. The item with item_id is excluded, therefor the first item
- * is the one after the item with item_id.
- *
- * If count == 0, all items from the given index up to the end of the queue will be returned.
- *
- * @param queue   The queue
- * @param item_id The unique id of the item in the queue
- * @param count   Maximum number of items to copy (if 0 all remaining items after index)
- * @param shuffle If 0 the play-queue, if 1 the shuffle queue
- * @return A new queue with the specified items
- */
-struct queue *
-queue_new_bypos(struct queue *queue, unsigned int item_id, unsigned int count, char shuffle)
-{
-  int pos;
-  struct queue *qi;
-
-  pos = queue_index_byitemid(queue, item_id, shuffle);
-
-  if (pos < 0)
-    pos = 0;
-  else
-    pos = pos + 1; // exclude the item with the given item-id
-
-  qi = queue_new_byindex(queue, pos, count, shuffle);
-
-  return qi;
-}
-
-/*
- * Adds items to the queue after the given item
- *
- * @param queue      The queue to add the new items
- * @param item_new   The item(s) to add
- * @param item_prev  The item to append the new items
- */
-static void
-queue_add_afteritem(struct queue *queue, struct queue_item *item_new, struct queue_item *item_prev)
-{
-  struct queue_item *item;
-  struct queue_item *item_tail;
-
-  if (!item_new)
-    {
-      DPRINTF(E_LOG, L_PLAYER, "Invalid new item given to add items\n");
-      return;
-    }
-
-  // Check the item after which the new items will be added
-  if (!item_prev)
-    {
-      DPRINTF(E_LOG, L_PLAYER, "Invalid previous item given to add items\n");
-      queue_items_free(item_new);
-      return;
-    }
-
-  // Set item-id for all new items
-  queue->last_inserted_item_id++;
-  item_new->item_id = queue->last_inserted_item_id;
-  for (item = item_new->next; item != item_new; item = item->next)
-    {
-      queue->last_inserted_item_id++;
-      item->item_id = queue->last_inserted_item_id;
-    }
-
-  // Add items into the queue
-  item_tail = item_new->prev;
-
-  item_tail->next = item_prev->next;
-  item_tail->shuffle_next = item_prev->shuffle_next;
-  item_prev->next->prev = item_tail;
-  item_prev->shuffle_next->shuffle_prev = item_tail;
-
-  item_prev->next = item_new;
-  item_prev->shuffle_next = item_new;
-  item_new->prev = item_prev;
-  item_new->shuffle_prev = item_prev;
-}
-
-/*
- * Adds items to the end of the queue
- *
- * @param queue   The queue to add the new items
- * @param item    The item(s) to add
- */
-void
-queue_add(struct queue *queue, struct queue_item *item)
-{
-  queue_add_afteritem(queue, item, queue->head->prev);
-}
-
-/*
- * Adds items to the queue after the item with the given item id (id of the item in the queue)
- *
- * @param queue   The queue to add the new items
- * @param item    The item(s) to add
- * @param item_id The item id after which the new items will be inserted
- */
-void
-queue_add_after(struct queue *queue, struct queue_item *item, unsigned int item_id)
-{
-  struct queue_item *item_prev;
-
-  // Get the item after which the new items will be added
-  item_prev = queueitem_get_byitemid(queue, item_id);
-  queue_add_afteritem(queue, item, item_prev);
-}
-
-static void
-queue_move_item_before_item(struct queue *queue, struct queue_item *item, struct queue_item *item_next, char shuffle)
-{
-  if (!item_next)
-    {
-      // If item_next is NULL the item should be inserted at the end of the queue (directly before the head item)
-      item_next = queue->head;
-    }
-
-  // Remove item from the queue
-  if (shuffle)
-    {
-      item->shuffle_prev->shuffle_next = item->shuffle_next;
-      item->shuffle_next->shuffle_prev = item->shuffle_prev;
-    }
-  else
-    {
-      item->prev->next = item->next;
-      item->next->prev = item->prev;
-    }
-
-  // Insert item into the queue before the item at the target postion
-  if (shuffle)
-    {
-      item_next->shuffle_prev->shuffle_next = item;
-      item->shuffle_prev = item_next->shuffle_prev;
-
-      item_next->shuffle_prev = item;
-      item->shuffle_next = item_next;
-    }
-  else
-    {
-      item_next->prev->next = item;
-      item->prev = item_next->prev;
-
-      item_next->prev = item;
-      item->next = item_next;
-    }
-}
-
-/*
- * Moves the item at from_pos to to_pos in the play-queue (shuffle = 0) or shuffle-queue (shuffle = 1)
- *
- * The position arguments are relativ to the item with the given id. At position = 1 is the first item
- * after the item with the given id (either in the play-queue or shuffle-queue, depending on the shuffle
- * argument).
- *
- * @param queue     The queue to move items
- * @param from_pos  The position of the first item to be moved
- * @param to_pos    The position to move the items
- * @param shuffle   If 0 the position in the play-queue, 1 the position in the shuffle-queue
- */
-void
-queue_move_bypos(struct queue *queue, unsigned int item_id, unsigned int from_pos, unsigned int to_offset, char shuffle)
-{
-  struct queue_item *item;
-  struct queue_item *item_next;
-
-  // Get the item to be moved
-  item = queueitem_get_bypos(queue, item_id, from_pos, shuffle);
-  if (!item)
-    {
-      DPRINTF(E_LOG, L_PLAYER, "Invalid position given to move items\n");
-      return;
-    }
-
-  // Get the item at the target position
-  item_next = queueitem_get_bypos(queue, item_id, (to_offset + 1), shuffle);
-
-  queue_move_item_before_item(queue, item, item_next, shuffle);
-}
-
-void
-queue_move_byindex(struct queue *queue, unsigned int from_pos, unsigned int to_pos, char shuffle)
-{
-  struct queue_item *item;
-  struct queue_item *item_next;
-
-  if (from_pos == to_pos)
-    return;
-
-  // Get the item to be moved
-  item = queueitem_get_byindex(queue, from_pos, shuffle);
-  if (!item)
-    {
-      DPRINTF(E_LOG, L_PLAYER, "Invalid position given to move items\n");
-      return;
-    }
-
-  // Check if the index of the item to move is lower than the target index
-  // If that is the case, increment the target position, because the given to_pos
-  // is based on the queue without the moved item.
-  if (from_pos < to_pos)
-    to_pos++;
-
-  // Get the item at the target position
-  item_next = queueitem_get_byindex(queue, to_pos, shuffle);
-
-  queue_move_item_before_item(queue, item, item_next, shuffle);
-}
-
-/*
- * Moves the item with the given item-id to the index to_pos in the play-queue (shuffle = 0)
- * or shuffle-queue (shuffle = 1)
- *
- * @param queue     The queue to move item
- * @param item_id   The item-id of the to be moved
- * @param to_pos    The index to move the item
- * @param shuffle   If 0 the position in the play-queue, 1 the position in the shuffle-queue
- */
-void
-queue_move_byitemid(struct queue *queue, unsigned int item_id, unsigned int to_pos, char shuffle)
-{
-  struct queue_item *item;
-  struct queue_item *item_next;
-  int from_pos;
-
-  // Get the item to be moved
-  item = queueitem_get_byitemid(queue, item_id);
-  if (!item)
-    {
-      DPRINTF(E_LOG, L_PLAYER, "Item with item-id %d does not exist in the queue\n", item_id);
-      return;
-    }
-
-  from_pos = queue_index_byitemid(queue, item_id, shuffle);
-
-  if (from_pos == to_pos)
-    {
-      DPRINTF(E_DBG, L_PLAYER, "Ignore moving item %d from index %d to %d\n", item_id, from_pos, to_pos);
-      return;
-    }
-
-  // Check if the index of the item to move is lower than the target index
-  // If that is the case, increment the target position, because the given to_pos
-  // is based on the queue without the moved item.
-  if (from_pos < to_pos)
-    to_pos++;
-
-  // Get the item at the target position
-  item_next = queueitem_get_byindex(queue, to_pos, shuffle);
-
-  queue_move_item_before_item(queue, item, item_next, shuffle);
-}
-
-/*
- * Removes the item from the queue and frees it
- */
-static void
-queue_remove_item(struct queue_item *item)
-{
-  struct queue_item *item_next;
-  struct queue_item *item_prev;
-
-  item_next = item->next;
-  item_prev = item->prev;
-
-  item_prev->next = item_next;
-  item_next->prev = item_prev;
-
-  item_next = item->shuffle_next;
-  item_prev = item->shuffle_prev;
-
-  item_prev->shuffle_next = item_next;
-  item_next->shuffle_prev = item_prev;
-
-  item->next = NULL;
-  item->prev = NULL;
-  item->shuffle_next = NULL;
-  item->shuffle_prev = NULL;
-
-  free(item);
-}
-
-/*
- * Removes the item with the given item-id from the queue
- */
-void
-queue_remove_byitemid(struct queue *queue, unsigned int item_id)
-{
-  struct queue_item *item;
-
-  // Do not remove the head item
-  if (item_id <= 0)
-    return;
-
-  // Get the item after which the items will be removed from the queue
-  item = queueitem_get_byitemid(queue, item_id);
-  if (!item)
-    {
-      DPRINTF(E_LOG, L_PLAYER, "Invalid item-id given to remove items\n");
-      return;
-    }
-
-  queue_remove_item(item);
-}
-
-/*
- * Remove item at index from the play-queue (shuffle = 0) or shuffle-queue (shuffle = 1)
- *
- * @param queue   The queue
- * @param index   The index of the item to be removed (0-based)
- * @param shuffle If 0 the position in the play-queue, 1 the position in the shuffle-queue
- */
-void
-queue_remove_byindex(struct queue *queue, unsigned int index, char shuffle)
-{
-  struct queue_item *item;
-
-  // Get the item after which the items will be removed from the queue
-  item = queueitem_get_byindex(queue, index, shuffle);
-  if (!item)
-    {
-      DPRINTF(E_LOG, L_PLAYER, "Invalid position given to remove items\n");
-      return;
-    }
-
-  queue_remove_item(item);
-}
-
-/*
- * Removes the item at pos from the play-queue (shuffle = 0) or shuffle-queue (shuffle = 1)
- *
- * The position argument is relativ to the item with the given id. At position = 1 is the first item
- * after the item with the given id (either in the play-queue or shuffle-queue, depending on the shuffle
- * argument).
- *
- * @param queue   The queue to add the new items
- * @param item_id The unique id of the item in the queue
- * @param pos     The position of the first item to be removed
- * @param shuffle If 0 the position in the play-queue, 1 the position in the shuffle-queue
- */
-void
-queue_remove_bypos(struct queue *queue, unsigned int item_id, unsigned int pos, char shuffle)
-{
-  struct queue_item *item;
-
-  // Get the item after which the items will be removed from the queue
-  item = queueitem_get_bypos(queue, item_id, pos, shuffle);
-  if (!item)
-    {
-      DPRINTF(E_LOG, L_PLAYER, "Invalid position given to remove items\n");
-      return;
-    }
-
-  queue_remove_item(item);
-}
-
-/*
- * Removes all items from the queue
- *
- * @param queue The queue to clear
- */
-void
-queue_clear(struct queue *queue)
-{
-  struct queue_item *item;
-
-  // Check if the queue is already empty
-  if (queue->head->next == queue->head)
-    return;
-
-  // Remove the head item from the shuffle-queue
-  item = queue->head->shuffle_next;
-  item->shuffle_prev = queue->head->shuffle_prev;
-  queue->head->shuffle_prev->shuffle_next = item;
-
-  // Remove the head item from the play-queue
-  item = queue->head->next;
-  item->prev = queue->head->prev;
-  queue->head->prev->next = item;
-
-  // item now points to the first item in the play-queue (excluding the head item)
-  queue_items_free(item);
-
-  // Make the queue circular again
-  queue->head->next = queue->head;
-  queue->head->prev = queue->head;
-  queue->head->shuffle_next = queue->head;
-  queue->head->shuffle_prev = queue->head;
-}
-
-/*
- * Resets the shuffle-queue to be identical to the play-queue and returns the item
- * with the given item_id.
- *
- * If no item was found with the given item_id, it returns the head item.
- */
-static struct queue_item *
-queue_reset_and_find(struct queue *queue, unsigned int item_id)
-{
-  struct queue_item *item;
-  struct queue_item *temp;
-
-  item = queue->head;
-
-  item->shuffle_next = item->next;
-  item->shuffle_prev = item->prev;
-
-  for (temp = item->next; temp != queue->head; temp = temp->next)
-    {
-      temp->shuffle_next = temp->next;
-      temp->shuffle_prev = temp->prev;
-
-      if (temp->item_id == item_id)
-	item = temp;
-    }
-
-  return item;
-}
-
-/*
- * Shuffles the queue
- *
- * If the item_id > 0, only the items in the queue after the item (excluding it)
- * with the given id are shuffled.
- *
- * @param queue   The queue to shuffle
- * @param item_id 0 to shuffle the whole queue or the item-id after which the queue gets shuffled
- */
-void
-queue_shuffle(struct queue *queue, unsigned int item_id)
-{
-  struct queue_item *temp;
-  struct queue_item *item;
-  struct queue_item **item_array;
-  int nitems;
-  int i;
-
-  item = queue_reset_and_find(queue, item_id);
-
-  // Count items to reshuffle
-  nitems = 0;
-  for (temp = item->next; temp != queue->head; temp = temp->next)
-    {
-      nitems++;
-    }
-
-  // Do not reshuffle queue with one item
-  if (nitems < 2)
-    return;
-
-  // Construct array for number of items in queue
-  item_array = (struct queue_item **)malloc(nitems * sizeof(struct queue_item *));
-  if (!item_array)
-    {
-      DPRINTF(E_LOG, L_PLAYER, "Could not allocate memory for shuffle array\n");
-      return;
-    }
-
-  // Fill array with items in queue
-  i = 0;
-  for (temp = item->next; temp != queue->head; temp = temp->next)
-    {
-      item_array[i] = temp;
-      i++;
-    }
-
-  // Shuffle item array
-  shuffle_ptr(&queue->shuffle_rng, (void **)item_array, nitems);
-
-  // Update shuffle-next/-prev for shuffled items
-  for (i = 0; i < nitems; i++)
-    {
-      temp = item_array[i];
-
-      if (i > 0)
-	temp->shuffle_prev = item_array[i - 1];
-      else
-	temp->shuffle_prev = NULL;
-
-      if (i < (nitems - 1))
-	temp->shuffle_next = item_array[i + 1];
-      else
-	temp->shuffle_next = NULL;
-    }
-
-  // Insert shuffled items after item with given item_id
-  item->shuffle_next = item_array[0];
-  item_array[0]->shuffle_prev = item;
-
-  queue->head->shuffle_prev = item_array[nitems - 1];
-  item_array[nitems - 1]->shuffle_next = queue->head;
-
-  free(item_array);
-}
-
-/*
- * Creates a new queue item for the given media file
- *
- * @param dbmfi media file info
- * @return The new queue item or NULL if an error occured
- */
-static struct queue_item *
-queue_item_new(struct db_media_file_info *dbmfi)
-{
-  struct queue_item *item;
-  uint32_t id;
-  uint32_t len_ms;
-  uint32_t data_kind;
-  uint32_t media_kind;
-  int ret;
-
-  ret = safe_atou32(dbmfi->id, &id);
-  if (ret < 0)
-    {
-      DPRINTF(E_LOG, L_PLAYER, "Invalid song id in query result!\n");
-      return NULL;
-    }
-
-  ret = safe_atou32(dbmfi->song_length, &len_ms);
-  if (ret < 0)
-    {
-      DPRINTF(E_LOG, L_PLAYER, "Invalid song length in query result!\n");
-      return NULL;
-    }
-
-  ret = safe_atou32(dbmfi->data_kind, &data_kind);
-  if (ret < 0)
-    {
-      DPRINTF(E_LOG, L_PLAYER, "Invalid data kind in query result!\n");
-      return NULL;
-    }
-
-  ret = safe_atou32(dbmfi->media_kind, &media_kind);
-  if (ret < 0)
-    {
-      DPRINTF(E_LOG, L_PLAYER, "Invalid media kind in query result!\n");
-      return NULL;
-    }
-
-  item = (struct queue_item *) calloc(1, sizeof(struct queue_item));
-  if (!item)
-    {
-      DPRINTF(E_LOG, L_PLAYER, "Out of memory for struct queue_item\n");
-      return NULL;
-    }
-
-  item->id = id;
-  item->len_ms = len_ms;
-  item->data_kind = data_kind;
-  item->media_kind = media_kind;
-
-  return item;
-}
-
-struct queue_item *
-queueitem_make_byquery(struct query_params *qp)
-{
-  struct db_media_file_info dbmfi;
-  struct queue_item *item_head;
-  struct queue_item *item_tail;
-  struct queue_item *item_temp;
-  int ret;
-
-  ret = db_query_start(qp);
-  if (ret < 0)
-    {
-      DPRINTF(E_LOG, L_PLAYER, "Could not start query\n");
-      return NULL;
-    }
-
-  DPRINTF(E_DBG, L_PLAYER, "Player queue query returned %d items\n", qp->results);
-
-  item_head = NULL;
-  item_tail = NULL;
-  while (((ret = db_query_fetch_file(qp, &dbmfi)) == 0) && (dbmfi.id))
-    {
-      item_temp = queue_item_new(&dbmfi);
-      if (!item_temp)
-	{
-	  DPRINTF(E_LOG, L_PLAYER, "Error creating new queue_item for id '%s'\n", dbmfi.id);
-	  continue;
-	}
-
-      if (!item_head)
-	item_head = item_temp;
-
-      if (item_tail)
-	{
-	  item_tail->next = item_temp;
-	  item_temp->prev = item_tail;
-	  item_tail->shuffle_next = item_temp;
-	  item_temp->shuffle_prev = item_tail;
-	}
-
-      item_tail = item_temp;
-
-      DPRINTF(E_DBG, L_PLAYER, "Added song id %s (%s)\n", dbmfi.id, dbmfi.title);
-    }
-
-  db_query_end(qp);
-
-  if (ret < 0)
-    {
-      DPRINTF(E_LOG, L_PLAYER, "Error fetching results\n");
-      return NULL;
-    }
-
-  if (!item_head || !item_tail)
-    {
-      DPRINTF(E_INFO, L_PLAYER, "No item found to add to queue\n");
-      return NULL;
-    }
-
-  item_head->prev = item_tail;
-  item_tail->next = item_head;
-  item_head->shuffle_prev = item_tail;
-  item_tail->shuffle_next = item_head;
-
-  return item_head;
-}
-
-/*
- * Makes a list of queue-items for the given playlist id (plid)
- *
- * @param plid Id of the playlist
- * @return List of items for all playlist items
- */
-struct queue_item *
-queueitem_make_byplid(int plid)
-{
-  struct query_params qp;
-  struct queue_item *item;
-
-  memset(&qp, 0, sizeof(struct query_params));
-
-  qp.id = plid;
-  qp.type = Q_PLITEMS;
-  qp.offset = 0;
-  qp.limit = 0;
-  qp.sort = S_NONE;
-  qp.idx_type = I_NONE;
-
-  item = queueitem_make_byquery(&qp);
-
-  return item;
-}
-
-/*
- * Makes a queue-item for the item/file with the given id
- *
- * @param id Id of the item/file in the db
- * @return List of items containing only the item with the given id
- */
-struct queue_item *
-queueitem_make_byid(uint32_t id)
-{
-  struct query_params qp;
-  struct queue_item *item;
-  char buf[124];
-
-  memset(&qp, 0, sizeof(struct query_params));
-
-  qp.id = 0;
-  qp.type = Q_ITEMS;
-  qp.offset = 0;
-  qp.limit = 0;
-  qp.sort = S_NONE;
-  snprintf(buf, sizeof(buf), "f.id = %" PRIu32, id);
-  qp.filter = buf;
-
-  item = queueitem_make_byquery(&qp);
-
-  return item;
-}
diff --git a/src/queue.h b/src/queue.h
deleted file mode 100644
index 556aec9..0000000
--- a/src/queue.h
+++ /dev/null
@@ -1,116 +0,0 @@
-
-#ifndef SRC_QUEUE_H_
-#define SRC_QUEUE_H_
-
-
-#include "db.h"
-
-enum repeat_mode {
-  REPEAT_OFF  = 0,
-  REPEAT_SONG = 1,
-  REPEAT_ALL  = 2,
-};
-
-
-/*
- * Internal representation of a queue
- */
-struct queue;
-
-/*
- * Internal representation of a list of queue items
- */
-struct queue_item;
-
-
-struct queue *
-queue_new();
-
-void
-queue_free(struct queue *queue);
-
-unsigned int
-queue_count(struct queue *queue);
-
-int
-queueitem_pos(struct queue_item *item, uint32_t id);
-
-uint32_t
-queueitem_id(struct queue_item *item);
-
-unsigned int
-queueitem_item_id(struct queue_item *item);
-
-unsigned int
-queueitem_len(struct queue_item *item);
-
-enum data_kind
-queueitem_data_kind(struct queue_item *item);
-
-enum media_kind
-queueitem_media_kind(struct queue_item *item);
-
-struct queue_item *
-queue_get_byitemid(struct queue *queue, unsigned int item_id);
-
-struct queue_item *
-queue_get_byindex(struct queue *queue, unsigned int index, char shuffle);
-
-struct queue_item *
-queue_get_bypos(struct queue *queue, unsigned int item_id, unsigned int pos, char shuffle);
-
-int
-queue_index_byitemid(struct queue *queue, unsigned int item_id, char shuffle);
-
-struct queue_item *
-queue_next(struct queue *queue, unsigned int item_id, char shuffle, enum repeat_mode r_mode, int reshuffle);
-
-struct queue_item *
-queue_prev(struct queue *queue, unsigned int item_id, char shuffle, enum repeat_mode r_mode);
-
-struct queue *
-queue_new_byindex(struct queue *queue, unsigned int index, unsigned int count, char shuffle);
-
-struct queue *
-queue_new_bypos(struct queue *queue, unsigned int item_id, unsigned int count, char shuffle);
-
-void
-queue_add(struct queue *queue, struct queue_item *item);
-
-void
-queue_add_after(struct queue *queue, struct queue_item *item, unsigned int item_id);
-
-void
-queue_move_bypos(struct queue *queue, unsigned int item_id, unsigned int from_pos, unsigned int to_offset, char shuffle);
-
-void
-queue_move_byindex(struct queue *queue, unsigned int from_pos, unsigned int to_pos, char shuffle);
-
-void
-queue_move_byitemid(struct queue *queue, unsigned int item_id, unsigned int to_pos, char shuffle);
-
-void
-queue_remove_byitemid(struct queue *queue, unsigned int item_id);
-
-void
-queue_remove_byindex(struct queue *queue, unsigned int index, char shuffle);
-
-void
-queue_remove_bypos(struct queue *queue, unsigned int item_id, unsigned int pos, char shuffle);
-
-void
-queue_clear(struct queue *queue);
-
-void
-queue_shuffle(struct queue *queue, unsigned int item_id);
-
-struct queue_item *
-queueitem_make_byquery(struct query_params *qp);
-
-struct queue_item *
-queueitem_make_byplid(int plid);
-
-struct queue_item *
-queueitem_make_byid(uint32_t id);
-
-#endif /* SRC_QUEUE_H_ */
diff --git a/src/remote_pairing.c b/src/remote_pairing.c
index 49ca5c0..9d5fd9b 100644
--- a/src/remote_pairing.c
+++ b/src/remote_pairing.c
@@ -39,10 +39,8 @@
 #include <stdint.h>
 #include <inttypes.h>
 #include <errno.h>
-#include <pthread.h>
 
-#if defined(HAVE_SYS_EVENTFD_H) && defined(HAVE_EVENTFD)
-# define USE_EVENTFD
+#ifdef HAVE_EVENTFD
 # include <sys/eventfd.h>
 #endif
 
@@ -80,14 +78,14 @@ struct remote_info {
 /* Main event base, from main.c */
 extern struct event_base *evbase_main;
 
-#ifdef USE_EVENTFD
+#ifdef HAVE_EVENTFD
 static int pairing_efd;
 #else
 static int pairing_pipe[2];
 #endif
 static struct event *pairingev;
-static pthread_mutex_t remote_lck = PTHREAD_MUTEX_INITIALIZER;
-static struct remote_info *remote_list;
+static pthread_mutex_t remote_lck;
+static struct remote_info *remote_info;
 
 
 /* iTunes - Remote pairing hash */
@@ -156,45 +154,27 @@ itunes_pairing_hash(char *paircode, char *pin)
  * with the list lock held by the caller
  */
 static struct remote_info *
-add_remote(void)
+create_remote(void)
 {
   struct remote_info *ri;
 
-  ri = (struct remote_info *)malloc(sizeof(struct remote_info));
+  ri = calloc(1, sizeof(struct remote_info));
   if (!ri)
     {
       DPRINTF(E_WARN, L_REMOTE, "Out of memory for struct remote_info\n");
       return NULL;
     }
 
-  memset(ri, 0, sizeof(struct remote_info));
-
-  ri->next = remote_list;
-  remote_list = ri;
-
   return ri;
 }
 
 static void
 unlink_remote(struct remote_info *ri)
 {
-  struct remote_info *p;
-
-  if (ri == remote_list)
-    remote_list = ri->next;
+  if (ri == remote_info)
+    remote_info = NULL;
   else
-    {
-      for (p = remote_list; p && (p->next != ri); p = p->next)
-	; /* EMPTY */
-
-      if (!p)
-	{
-	  DPRINTF(E_LOG, L_REMOTE, "WARNING: struct remote_info not found in list; BUG!\n");
-	  return;
-	}
-
-      p->next = ri->next;
-    }
+    DPRINTF(E_LOG, L_REMOTE, "WARNING: struct remote_info not found in list; BUG!\n");
 }
 
 static void
@@ -228,16 +208,10 @@ remove_remote(struct remote_info *ri)
 static void
 remove_remote_address_byid(const char *id, int family)
 {
-  struct remote_info *ri;
-
-  for (ri = remote_list; ri; ri = ri->next)
-    {
-      if (!ri->pi.remote_id)
-	continue;
+  struct remote_info *ri = NULL;
 
-      if (strcmp(ri->pi.remote_id, id) == 0)
-	break;
-    }
+  if (remote_info && strcmp(remote_info->pi.remote_id, id) == 0)
+    ri = remote_info;
 
   if (!ri)
     {
@@ -271,70 +245,48 @@ remove_remote_address_byid(const char *id, int family)
 static int
 add_remote_mdns_data(const char *id, int family, const char *address, int port, char *name, char *paircode)
 {
-  struct remote_info *ri;
   char *check_addr;
   int ret;
 
-  for (ri = remote_list; ri; ri = ri->next)
+  if (remote_info && strcmp(remote_info->pi.remote_id, id) == 0)
     {
-      if (!ri->pi.remote_id)
-	continue;
-
-      if (strcmp(ri->pi.remote_id, id) == 0)
-	break;
-    }
-
-  if (!ri)
-    {
-      DPRINTF(E_DBG, L_REMOTE, "Remote id %s not known, adding\n", id);
-
-      ri = add_remote();
-      if (!ri)
-	return -1;
-
-      ret = 0;
+      DPRINTF(E_DBG, L_REMOTE, "Remote id %s found\n", id);
+      free_pi(&remote_info->pi, 1);
+      ret = 1;
     }
   else
     {
-      DPRINTF(E_DBG, L_REMOTE, "Remote id %s found\n", id);
-
-      free_pi(&ri->pi, 1);
-
-      switch (family)
+      DPRINTF(E_DBG, L_REMOTE, "Remote id %s not known, adding\n", id);
+      if (remote_info)
 	{
-	  case AF_INET:
-	    if (ri->v4_address)
-	      free(ri->v4_address);
-	    break;
-
-	  case AF_INET6:
-	    if (ri->v6_address)
-	      free(ri->v6_address);
-	    break;
+	  DPRINTF(E_DBG, L_REMOTE, "Removing existing remote with id %s\n", remote_info->pi.remote_id);
+	  remove_remote(remote_info);
 	}
 
-      if (ri->paircode)
-	free(ri->paircode);
-
-      ret = 1;
+      remote_info = create_remote();
+      ret = 0;
     }
 
-  ri->pi.remote_id = strdup(id);
+  free(remote_info->paircode);
+  free(remote_info->pi.remote_id);
+  remote_info->pi.remote_id = strdup(id);
 
   switch (family)
     {
       case AF_INET:
-	ri->v4_address = strdup(address);
-	ri->v4_port = port;
+	free(remote_info->v4_address);
+	remote_info->v4_address = strdup(address);
+	remote_info->v4_port = port;
 
-	check_addr = ri->v4_address;
+	check_addr = remote_info->v4_address;
 	break;
 
       case AF_INET6:
-	ri->v6_address = strdup(address);
-	ri->v6_port = port;
+	free(remote_info->v6_address);
+	remote_info->v6_address = strdup(address);
+	remote_info->v6_port = port;
 
-	check_addr = ri->v6_address;
+	check_addr = remote_info->v6_address;
 	break;
 
       default:
@@ -344,44 +296,34 @@ add_remote_mdns_data(const char *id, int family, const char *address, int port,
 	break;
     }
 
-  if (!ri->pi.remote_id || !check_addr)
+  if (!remote_info->pi.remote_id || !check_addr)
     {
       DPRINTF(E_LOG, L_REMOTE, "Out of memory for remote pairing data\n");
 
-      remove_remote(ri);
+      remove_remote(remote_info);
       return -1;
     }
 
-  ri->pi.name = name;
-  ri->paircode = paircode;
+  remote_info->pi.name = name;
+  remote_info->paircode = paircode;
 
   return ret;
 }
 
 static int
-add_remote_pin_data(char *devname, char *pin)
+add_remote_pin_data(const char *pin)
 {
-  struct remote_info *ri;
-
-  for (ri = remote_list; ri; ri = ri->next)
-    {
-      if (strcmp(ri->pi.name, devname) == 0)
-	break;
-    }
-
-  if (!ri)
+  if (!remote_info)
     {
-      DPRINTF(E_LOG, L_REMOTE, "Remote '%s' not known from mDNS, ignoring\n", devname);
+      DPRINTF(E_LOG, L_REMOTE, "No remote known from mDNS, ignoring\n");
 
       return -1;
     }
 
-  DPRINTF(E_DBG, L_REMOTE, "Remote '%s' found\n", devname);
-
-  if (ri->pin)
-    free(ri->pin);
+  DPRINTF(E_DBG, L_REMOTE, "Adding pin to remote '%s'\n", remote_info->pi.name);
 
-  ri->pin = pin;
+  free(remote_info->pin);
+  remote_info->pin = strdup(pin);
 
   return 0;
 }
@@ -389,7 +331,7 @@ add_remote_pin_data(char *devname, char *pin)
 static void
 kickoff_pairing(void)
 {
-#ifdef USE_EVENTFD
+#ifdef HAVE_EVENTFD
   int ret;
 
   ret = eventfd_write(pairing_efd, 1);
@@ -642,7 +584,7 @@ pairing_cb(int fd, short event, void *arg)
 {
   struct remote_info *ri;
 
-#ifdef USE_EVENTFD
+#ifdef HAVE_EVENTFD
   eventfd_t count;
   int ret;
 
@@ -660,28 +602,17 @@ pairing_cb(int fd, short event, void *arg)
     ; /* EMPTY */
 #endif
 
-  for (;;)
-    {
-      pthread_mutex_lock(&remote_lck);
-
-      for (ri = remote_list; ri; ri = ri->next)
-	{
-	  /* We've got both the mDNS data and the pin */
-	  if (ri->paircode && ri->pin)
-	    {
-	      unlink_remote(ri);
-	      break;
-	    }
-	}
-
-      pthread_mutex_unlock(&remote_lck);
-
-      if (!ri)
-	break;
+  CHECK_ERR(L_REMOTE, pthread_mutex_lock(&remote_lck));
 
+  if (remote_info && remote_info->paircode && remote_info->pin)
+    {
+      ri = remote_info;
+      unlink_remote(ri);
       do_pairing(ri);
     }
 
+  CHECK_ERR(L_REMOTE, pthread_mutex_unlock(&remote_lck));
+
   event_add(pairingev, NULL);
 }
 
@@ -701,11 +632,11 @@ touch_remote_cb(const char *name, const char *type, const char *domain, const ch
        * failed; any subsequent attempt will need a new pairing pin, so
        * we can just forget everything we know about the remote.
        */
-      pthread_mutex_lock(&remote_lck);
+      CHECK_ERR(L_REMOTE, pthread_mutex_lock(&remote_lck));
 
       remove_remote_address_byid(name, family);
 
-      pthread_mutex_unlock(&remote_lck);
+      CHECK_ERR(L_REMOTE, pthread_mutex_unlock(&remote_lck));
     }
   else
     {
@@ -763,7 +694,7 @@ touch_remote_cb(const char *name, const char *type, const char *domain, const ch
       DPRINTF(E_LOG, L_REMOTE, "Discovered remote '%s' (id %s) at %s:%d, paircode %s\n", devname, name, address, port, paircode);
 
       /* Add the data to the list, adding the remote to the list if needed */
-      pthread_mutex_lock(&remote_lck);
+      CHECK_ERR(L_REMOTE, pthread_mutex_lock(&remote_lck));
 
       ret = add_remote_mdns_data(name, family, address, port, devname, paircode);
 
@@ -777,126 +708,32 @@ touch_remote_cb(const char *name, const char *type, const char *domain, const ch
       else if (ret == 1)
 	kickoff_pairing();
 
-      pthread_mutex_unlock(&remote_lck);
+      CHECK_ERR(L_REMOTE, pthread_mutex_unlock(&remote_lck));
     }
 }
 
-/* Thread: filescanner */
+/* Thread: filescanner, mpd */
 void
-remote_pairing_read_pin(char *path)
+remote_pairing_kickoff(char **arglist)
 {
-  char buf[256];
-  FILE *fp;
-  char *devname;
-  char *pin;
-  int len;
   int ret;
 
-  fp = fopen(path, "rb");
-  if (!fp)
-    {
-      DPRINTF(E_LOG, L_REMOTE, "Could not open Remote pairing file %s: %s\n", path, strerror(errno));
-      return;
-    }
-
-  devname = fgets(buf, sizeof(buf), fp);
-  if (!devname)
-    {
-      DPRINTF(E_LOG, L_REMOTE, "Empty Remote pairing file %s\n", path);
-
-      fclose(fp);
-      return;
-    }
-
-  len = strlen(devname);
-  if (buf[len - 1] != '\n')
-    {
-      DPRINTF(E_LOG, L_REMOTE, "Invalid Remote pairing file %s: device name too long or missing pin\n", path);
-
-      fclose(fp);
-      return;
-    }
-
-  while (len)
-    {
-      if ((buf[len - 1] == '\r') || (buf[len - 1] == '\n'))
-	{
-	  buf[len - 1] = '\0';
-	  len--;
-	}
-      else
-	break;
-    }
-
-  if (!len)
-    {
-      DPRINTF(E_LOG, L_REMOTE, "Invalid Remote pairing file %s: empty line where device name expected\n", path);
-
-      fclose(fp);
-      return;
-    }
-
-  devname = strdup(buf);
-  if (!devname)
-    {
-      DPRINTF(E_LOG, L_REMOTE, "Out of memory for device name while reading %s\n", path);
-
-      fclose(fp);
-      return;
-    }
-
-  pin = fgets(buf, sizeof(buf), fp);
-  fclose(fp);
-  if (!pin)
-    {
-      DPRINTF(E_LOG, L_REMOTE, "Invalid Remote pairing file %s: no pin\n", path);
-
-      free(devname);
-      return;
-    }
-
-  len = strlen(pin);
-
-  while (len)
-    {
-      if ((buf[len - 1] == '\r') || (buf[len - 1] == '\n'))
-	{
-	  buf[len - 1] = '\0';
-	  len--;
-	}
-      else
-	break;
-    }
-
-  if (len != 4)
+  ret = strlen(arglist[0]);
+  if (ret != 4)
     {
-      DPRINTF(E_LOG, L_REMOTE, "Invalid pin in Remote pairing file %s: pin length should be 4, got %d\n", path, len);
-
-      free(devname);
-      return;
-    }
-
-  pin = strdup(buf);
-  if (!pin)
-    {
-      DPRINTF(E_LOG, L_REMOTE, "Out of memory for device pin while reading %s\n", path);
-
-      free(devname);
+      DPRINTF(E_LOG, L_REMOTE, "Kickoff pairing failed, first line did not contain a 4-digit pin (got %d)\n", ret);
       return;
     }
 
-  DPRINTF(E_LOG, L_REMOTE, "Read Remote pairing data (name '%s', pin '%s') from %s\n", devname, pin, path);
+  DPRINTF(E_LOG, L_REMOTE, "Kickoff pairing with pin '%s'\n", arglist[0]);
 
-  pthread_mutex_lock(&remote_lck);
+  CHECK_ERR(L_REMOTE, pthread_mutex_lock(&remote_lck));
 
-  ret = add_remote_pin_data(devname, pin);
-  free(devname);
-  if (ret < 0)
-    free(pin);
-  else
+  ret = add_remote_pin_data(arglist[0]);
+  if (ret == 0)
     kickoff_pairing();
 
-  pthread_mutex_unlock(&remote_lck);
+  CHECK_ERR(L_REMOTE, pthread_mutex_unlock(&remote_lck));
 }
 
 
@@ -906,9 +743,11 @@ remote_pairing_init(void)
 {
   int ret;
 
-  remote_list = NULL;
+  remote_info = NULL;
 
-#ifdef USE_EVENTFD
+  CHECK_ERR(L_REMOTE, mutex_init(&remote_lck));
+
+#ifdef HAVE_EVENTFD
   pairing_efd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
   if (pairing_efd < 0)
     {
@@ -933,7 +772,7 @@ remote_pairing_init(void)
 
       return -1;
     }
-#endif /* USE_EVENTFD */
+#endif /* HAVE_EVENTFD */
 
   // No ipv6 for remote at the moment
   ret = mdns_browse("_touch-remote._tcp", AF_INET, touch_remote_cb);
@@ -944,7 +783,7 @@ remote_pairing_init(void)
       goto mdns_browse_fail;
     }
 
-#ifdef USE_EVENTFD
+#ifdef HAVE_EVENTFD
   pairingev = event_new(evbase_main, pairing_efd, EV_READ, pairing_cb, NULL);
 #else
   pairingev = event_new(evbase_main, pairing_pipe[0], EV_READ, pairing_cb, NULL);
@@ -962,7 +801,7 @@ remote_pairing_init(void)
 
  pairingev_fail:
  mdns_browse_fail:
-#ifdef USE_EVENTFD
+#ifdef HAVE_EVENTFD
   close(pairing_efd);
 #else
   close(pairing_pipe[0]);
@@ -976,19 +815,15 @@ remote_pairing_init(void)
 void
 remote_pairing_deinit(void)
 {
-  struct remote_info *ri;
-
-  for (ri = remote_list; remote_list; ri = remote_list)
-    {
-      remote_list = ri->next;
+  if (remote_info)
+    free_remote(remote_info);
 
-      free_remote(ri);
-    }
-
-#ifdef USE_EVENTFD
+#ifdef HAVE_EVENTFD
   close(pairing_efd);
 #else
   close(pairing_pipe[0]);
   close(pairing_pipe[1]);
 #endif
+
+  CHECK_ERR(L_REMOTE, pthread_mutex_destroy(&remote_lck));
 }
diff --git a/src/remote_pairing.h b/src/remote_pairing.h
index bf21477..6a34d29 100644
--- a/src/remote_pairing.h
+++ b/src/remote_pairing.h
@@ -3,7 +3,7 @@
 #define __REMOTE_PAIRING_H__
 
 void
-remote_pairing_read_pin(char *path);
+remote_pairing_kickoff(char **arglist);
 
 int
 remote_pairing_init(void);
diff --git a/src/rng.c b/src/rng.c
index 5b57e06..00d0e5c 100644
--- a/src/rng.c
+++ b/src/rng.c
@@ -128,11 +128,11 @@ rng_rand_range(struct rng_ctx *ctx, int32_t min, int32_t max)
  * Durstenfeld in-place shuffling variant
  */
 void
-shuffle_ptr(struct rng_ctx *ctx, void **values, int len)
+shuffle_int(struct rng_ctx *ctx, int *values, int len)
 {
   int i;
   int32_t j;
-  void *tmp;
+  int tmp;
 
   for (i = len - 1; i > 0; i--)
     {
@@ -143,4 +143,3 @@ shuffle_ptr(struct rng_ctx *ctx, void **values, int len)
       values[j] = tmp;
     }
 }
-
diff --git a/src/rng.h b/src/rng.h
index b6a2c34..200f4a5 100644
--- a/src/rng.h
+++ b/src/rng.h
@@ -19,7 +19,7 @@ int32_t
 rng_rand_range(struct rng_ctx *ctx, int32_t min, int32_t max);
 
 void
-shuffle_ptr(struct rng_ctx *ctx, void **values, int len);
+shuffle_int(struct rng_ctx *ctx, int *values, int len);
 
 #endif /* !__RNG_H__ */
 
diff --git a/src/spotify.c b/src/spotify.c
index a75be67..9dc47b9 100644
--- a/src/spotify.c
+++ b/src/spotify.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014 Espen Jürgensen <espenjurgensen at gmail.com>
+ * Copyright (C) 2016 Espen Jürgensen <espenjurgensen at gmail.com>
  *
  * Stiched together from libspotify examples
  *
@@ -24,6 +24,7 @@
 
 #include <stdio.h>
 #include <stdlib.h>
+#include <stdbool.h>
 #include <stdint.h>
 #include <fcntl.h>
 #include <unistd.h>
@@ -35,7 +36,6 @@
 #include <sys/stat.h>
 #include <sys/ioctl.h>
 #include <sys/queue.h>
-#include <time.h>
 #include <pthread.h>
 #ifdef HAVE_PTHREAD_NP_H
 # include <pthread_np.h>
@@ -43,37 +43,49 @@
 
 #include <dlfcn.h>
 #include <libspotify/api.h>
+#include <json.h>
 
 #include "spotify.h"
+#include "spotify_webapi.h"
 #include "logger.h"
+#include "misc.h"
+#include "http.h"
 #include "conffile.h"
-#include "filescanner.h"
 #include "cache.h"
 #include "commands.h"
+#include "library.h"
+#include "input.h"
+
+/* TODO for the web api:
+ * - UI should be prettier
+ * - map "added_at" to time_added
+ * - what to do about the lack of push?
+ * - use the web api more, implement proper init
+*/
 
+/* A few words on our reloading sequence of saved tracks
+ *
+ *   1. libspotify will not tell us about the user's saved tracks when loading
+ *      so we keep track of them with the special playlist spotify:savedtracks.
+ *   2. spotify_login will copy all paths in spotify:savedtracks to a temporary
+ *      spotify_reload_list before all Spotify items in the database get purged.
+ *   3. when the connection to Spotify is established after login, we register
+ *      all the paths with libspotify, and we also add them back to the
+ *      spotify:savedtracks playlist - however, that's just for the
+ *      playlistsitems table. Adding the items to the files table is done when
+ *      libspotify calls back with metadata - see spotify_pending_process().
+ *   4. if the user reloads saved tracks, we first clear all items in the
+ *      playlist, then add those back that are returned from the web api, and
+ *      then use our normal cleanup of stray files to tidy db and cache.
+ */
 
-/* How long to wait for audio (in sec) before giving up */
-#define SPOTIFY_TIMEOUT 20
-/* How long to wait for artwork (in sec) before giving up */
+// How long to wait for artwork (in sec) before giving up
 #define SPOTIFY_ARTWORK_TIMEOUT 3
+// An upper limit on sequential requests to Spotify's web api
+// - each request will return 50 objects (tracks)
+#define SPOTIFY_WEB_REQUESTS_MAX 20
 
 /* --- Types --- */
-typedef struct audio_fifo_data
-{
-  TAILQ_ENTRY(audio_fifo_data) link;
-  int nsamples;
-  int16_t samples[0];
-} audio_fifo_data_t;
-
-typedef struct audio_fifo
-{
-  TAILQ_HEAD(, audio_fifo_data) q;
-  int qlen;
-  int fullcount;
-  pthread_mutex_t mutex;
-  pthread_cond_t cond;
-} audio_fifo_t;
-
 enum spotify_state
 {
   SPOTIFY_STATE_INACTIVE,
@@ -84,12 +96,6 @@ enum spotify_state
   SPOTIFY_STATE_STOPPED,
 };
 
-struct audio_get_param
-{
-  struct evbuffer *evbuf;
-  int wanted;
-};
-
 struct artwork_get_param
 {
   struct evbuffer *evbuf;
@@ -103,7 +109,6 @@ struct artwork_get_param
   int is_loaded;
 };
 
-
 /* --- Globals --- */
 // Spotify thread
 static pthread_t tid_spotify;
@@ -119,17 +124,27 @@ static struct event *g_notifyev;
 
 static struct commands_base *cmdbase;
 
-// The global session handle
+// The session handle
 static sp_session *g_sess;
-// The global library handle
+// The library handle
 static void *g_libhandle;
-// The global state telling us what the thread is currently doing
+// The state telling us what the thread is currently doing
 static enum spotify_state g_state;
-// The global base playlist id (parent of all Spotify playlists in the db)
-static int g_base_plid;
+// The base playlist id for all Spotify playlists in the db
+static int spotify_base_plid;
+// Flag telling us if access to the web api was granted
+static bool spotify_access_token_valid;
+// The base playlist id for Spotify saved tracks in the db
+static int spotify_saved_plid;
+
+// Flag to avoid triggering playlist change events while the (re)scan is running
+static bool scanning;
 
-// Audio fifo
-static audio_fifo_t *g_audio_fifo;
+// Timeout timespec
+static struct timespec spotify_artwork_timeout = { SPOTIFY_ARTWORK_TIMEOUT, 0 };
+
+// Audio buffer
+static struct evbuffer *spotify_audio_buffer;
 
 /**
  * The application key is specific to forked-daapd, and allows Spotify
@@ -382,6 +397,16 @@ fptr_assign_all()
 // End of ugly part
 
 
+static enum command_state
+webapi_scan(void *arg, int *ret);
+static enum command_state
+webapi_pl_save(void *arg, int *ret);
+static enum command_state
+webapi_pl_remove(void *arg, int *ret);
+static void
+create_base_playlist();
+
+
 /* --------------------------  PLAYLIST HELPERS    ------------------------- */
 /*            Should only be called from within the spotify thread           */
 
@@ -422,7 +447,7 @@ spotify_metadata_get(sp_track *track, struct media_file_info *mfi, const char *p
   compilation = ((albumtype == SP_ALBUMTYPE_COMPILATION)
 		  || artist_override);
 
-  if (album_override)
+  if (album_override && pltitle)
     albumname = strdup(pltitle);
   else
     albumname = strdup(fptr_sp_album_name(album));
@@ -462,6 +487,48 @@ spotify_metadata_get(sp_track *track, struct media_file_info *mfi, const char *p
   return 0;
 }
 
+/*
+ * Returns the directory id for /spotify:/<artist>/<album>, if the directory (or the parent
+ * directories) does not yet exist, they will be created.
+ * If an error occured the return value is -1.
+ *
+ * @return directory id for the given artist/album directory
+ */
+static int
+prepare_directories(const char *artist, const char *album)
+{
+  int dir_id;
+  char virtual_path[PATH_MAX];
+  int ret;
+
+  ret = snprintf(virtual_path, sizeof(virtual_path), "/spotify:/%s", artist);
+  if ((ret < 0) || (ret >= sizeof(virtual_path)))
+    {
+      DPRINTF(E_LOG, L_SPOTIFY, "Virtual path exceeds PATH_MAX (/spotify:/%s)\n", artist);
+      return -1;
+    }
+  dir_id = db_directory_addorupdate(virtual_path, 0, DIR_SPOTIFY);
+  if (dir_id <= 0)
+    {
+      DPRINTF(E_LOG, L_SPOTIFY, "Could not add or update directory '%s'\n", virtual_path);
+      return -1;
+    }
+  ret = snprintf(virtual_path, sizeof(virtual_path), "/spotify:/%s/%s", artist, album);
+  if ((ret < 0) || (ret >= sizeof(virtual_path)))
+    {
+      DPRINTF(E_LOG, L_SPOTIFY, "Virtual path exceeds PATH_MAX (/spotify:/%s/%s)\n", artist, album);
+      return -1;
+    }
+  dir_id = db_directory_addorupdate(virtual_path, 0, dir_id);
+  if (dir_id <= 0)
+    {
+      DPRINTF(E_LOG, L_SPOTIFY, "Could not add or update directory '%s'\n", virtual_path);
+      return -1;
+    }
+
+  return dir_id;
+}
+
 static int
 spotify_track_save(int plid, sp_track *track, const char *pltitle, int time_added)
 {
@@ -469,9 +536,12 @@ spotify_track_save(int plid, sp_track *track, const char *pltitle, int time_adde
   sp_link *link;
   char url[1024];
   int ret;
-  int dir_id;
   char virtual_path[PATH_MAX];
+  int dir_id;
+  time_t stamp;
+  int id;
 
+  memset(&mfi, 0, sizeof(struct media_file_info));
 
   if (!fptr_sp_track_is_loaded(track))
     {
@@ -500,61 +570,56 @@ spotify_track_save(int plid, sp_track *track, const char *pltitle, int time_adde
   fptr_sp_link_release(link);
 
   /* Add to playlistitems table */
-  ret = db_pl_add_item_bypath(plid, url);
-  if (ret < 0)
+  if (plid)
     {
-      DPRINTF(E_LOG, L_SPOTIFY, "Could not save playlist item: '%s'\n", url);
-      return -1;
+      ret = db_pl_add_item_bypath(plid, url);
+      if (ret < 0)
+	{
+	  DPRINTF(E_LOG, L_SPOTIFY, "Could not save playlist item: '%s'\n", url);
+	  goto fail;
+	}
     }
 
-  memset(&mfi, 0, sizeof(struct media_file_info));
-
   ret = spotify_metadata_get(track, &mfi, pltitle, time_added);
   if (ret < 0)
     {
       DPRINTF(E_LOG, L_SPOTIFY, "Metadata missing (but track should be loaded?): '%s'\n", fptr_sp_track_name(track));
-      free_mfi(&mfi, 1);
-      return -1;
+      goto fail;
     }
 
-  ret = snprintf(virtual_path, sizeof(virtual_path), "/spotify:/%s", mfi.artist);
-  if ((ret < 0) || (ret >= sizeof(virtual_path)))
-    {
-      DPRINTF(E_LOG, L_SPOTIFY, "Virtual path exceeds PATH_MAX (/spotify:/%s)\n", mfi.artist);
-      free_mfi(&mfi, 1);
-      return -1;
-    }
-  dir_id = db_directory_addorupdate(virtual_path, 0, DIR_SPOTIFY);
-  if (dir_id <= 0)
-    {
-      DPRINTF(E_LOG, L_SPOTIFY, "Could not add or update directory '%s'\n", virtual_path);
-      free_mfi(&mfi, 1);
-      return -1;
-    }
-  ret = snprintf(virtual_path, sizeof(virtual_path), "/spotify:/%s/%s", mfi.artist, mfi.album);
-  if ((ret < 0) || (ret >= sizeof(virtual_path)))
-    {
-      DPRINTF(E_LOG, L_SPOTIFY, "Virtual path exceeds PATH_MAX (/spotify:/%s/%s)\n", mfi.artist, mfi.album);
-      free_mfi(&mfi, 1);
-      return -1;
-    }
-  dir_id = db_directory_addorupdate(virtual_path, 0, dir_id);
+  dir_id = prepare_directories(mfi.artist, mfi.album);
   if (dir_id <= 0)
     {
-      DPRINTF(E_LOG, L_SPOTIFY, "Could not add or update directory '%s'\n", virtual_path);
-      free_mfi(&mfi, 1);
-      return -1;
+      DPRINTF(E_LOG, L_SPOTIFY, "Could not add or update directory for item: '%s'\n", url);
+      goto fail;
     }
 
-  filescanner_process_media(url, time(NULL), 0, F_SCAN_TYPE_SPOTIFY, &mfi, dir_id);
+//  DPRINTF(E_DBG, L_SPOTIFY, "Saving track '%s': '%s' by %s (%s)\n", url, mfi.title, mfi.artist, mfi.album);
+
+  db_file_stamp_bypath(url, &stamp, &id);
+
+  mfi.id = id;
+  mfi.path = strdup(url);
+  mfi.fname = strdup(url);
+  mfi.time_modified = time(NULL);
+  mfi.data_kind = DATA_KIND_SPOTIFY;
+  snprintf(virtual_path, PATH_MAX, "/spotify:/%s/%s/%s", mfi.album_artist, mfi.album, mfi.title);
+  mfi.virtual_path = strdup(virtual_path);
+  mfi.directory_id = dir_id;
+
+  library_add_media(&mfi);
 
   free_mfi(&mfi, 1);
 
   return 0;
+
+ fail:
+  free_mfi(&mfi, 1);
+  return -1;
 }
 
 static int
-spotify_playlist_cleanupfiles()
+spotify_cleanup_files(void)
 {
   struct query_params qp;
   char *path;
@@ -570,7 +635,6 @@ spotify_playlist_cleanupfiles()
   if (ret < 0)
     {
       db_query_end(&qp);
-
       return -1;
     }
 
@@ -629,7 +693,7 @@ spotify_playlist_save(sp_playlist *pl)
 
   DPRINTF(E_LOG, L_SPOTIFY, "Saving playlist (%d tracks): '%s'\n", num_tracks, name);
 
-  /* Save playlist (playlists table) */
+  // Save playlist (playlists table)
   link = fptr_sp_link_create_from_playlist(pl);
   if (!link)
     {
@@ -703,7 +767,7 @@ spotify_playlist_save(sp_playlist *pl)
       pli->title = strdup(name);
       pli->path = strdup(url);
       pli->virtual_path = strdup(virtual_path);
-      pli->parent_id = g_base_plid;
+      pli->parent_id = spotify_base_plid;
       pli->directory_id = DIR_SPOTIFY;
 
       ret = db_pl_add(pli, &plid);
@@ -718,7 +782,7 @@ spotify_playlist_save(sp_playlist *pl)
 
   free_pli(pli, 0);
 
-  /* Save tracks and playlistitems (files and playlistitems table) */
+  // Save tracks and playlistitems (files and playlistitems table)
   db_transaction_begin();
   for (i = 0; i < num_tracks; i++)
     {
@@ -739,12 +803,78 @@ spotify_playlist_save(sp_playlist *pl)
 	}
     }
 
-  spotify_playlist_cleanupfiles();
+  spotify_cleanup_files();
   db_transaction_end();
 
   return plid;
 }
 
+// Registers a track with libspotify, which will make it start loading the track
+// metadata. When that is done metadata_updated() is called (but we won't be
+// told which track it was...). Note that this function will result in a ref
+// count on the sp_link, which the caller must decrease with sp_link_release.
+static enum command_state
+uri_register(void *arg, int *retval)
+{
+  sp_link *link;
+  sp_track *track;
+
+  char *uri = arg;
+
+  if (SP_CONNECTION_STATE_LOGGED_IN != fptr_sp_session_connectionstate(g_sess))
+    {
+      DPRINTF(E_LOG, L_SPOTIFY, "Can't register music, not connected and logged in to Spotify\n");
+      *retval = -1;
+      return COMMAND_END;
+    }
+
+  link = fptr_sp_link_create_from_string(uri);
+  if (!link)
+    {
+      DPRINTF(E_LOG, L_SPOTIFY, "Invalid Spotify link: '%s'\n", uri);
+      *retval = -1;
+      return COMMAND_END;
+    }
+
+  track = fptr_sp_link_as_track(link);
+  if (!track)
+    {
+      DPRINTF(E_LOG, L_SPOTIFY, "Invalid Spotify track: '%s'\n", uri);
+      *retval = -1;
+      return COMMAND_END;
+    }
+
+  *retval = 0;
+  return COMMAND_END;
+}
+
+static void
+webapi_playlist_updated(sp_playlist *pl)
+{
+  sp_link *link;
+  char url[1024];
+  int ret;
+
+  if (!scanning)
+    {
+      // Run playlist save in the library thread
+      link = fptr_sp_link_create_from_playlist(pl);
+      if (!link)
+        {
+	  DPRINTF(E_LOG, L_SPOTIFY, "Could not create link for playlist: '%s'\n", fptr_sp_playlist_name(pl));
+	  return;
+	}
+
+      ret = fptr_sp_link_as_string(link, url, sizeof(url));
+      if (ret == sizeof(url))
+        {
+	  DPRINTF(E_DBG, L_SPOTIFY, "Spotify link truncated: %s\n", url);
+	}
+      fptr_sp_link_release(link);
+
+      library_exec_async(webapi_pl_save, strdup(url));
+    }
+}
 
 /* --------------------------  PLAYLIST CALLBACKS  ------------------------- */
 /**
@@ -764,7 +894,14 @@ static void playlist_update_in_progress(sp_playlist *pl, bool done, void *userda
     {
       DPRINTF(E_DBG, L_SPOTIFY, "Playlist update (status %d): %s\n", done, fptr_sp_playlist_name(pl));
 
-      spotify_playlist_save(pl);
+      if (spotify_access_token_valid)
+	{
+	  webapi_playlist_updated(pl);
+	}
+      else
+	{
+	  spotify_playlist_save(pl);
+	}
     }
 }
 
@@ -772,7 +909,15 @@ static void playlist_metadata_updated(sp_playlist *pl, void *userdata)
 {
   DPRINTF(E_DBG, L_SPOTIFY, "Playlist metadata updated: %s\n", fptr_sp_playlist_name(pl));
 
-  spotify_playlist_save(pl);
+  if (spotify_access_token_valid)
+    {
+      //TODO Update disabled to prevent multiple triggering of updates e. g. on adding a playlist
+      //webapi_playlist_updated(pl);
+    }
+  else
+    {
+      spotify_playlist_save(pl);
+    }
 }
 
 /**
@@ -802,7 +947,39 @@ static void playlist_added(sp_playlistcontainer *pc, sp_playlist *pl,
 
   fptr_sp_playlist_add_callbacks(pl, &pl_callbacks, NULL);
 
-  spotify_playlist_save(pl);
+  if (spotify_access_token_valid)
+    {
+      webapi_playlist_updated(pl);
+    }
+  else
+    {
+      spotify_playlist_save(pl);
+    }
+}
+
+static int
+playlist_remove(const char *uri)
+{
+  struct playlist_info *pli;
+  int plid;
+
+  pli = db_pl_fetch_bypath(uri);
+
+  if (!pli)
+    {
+      DPRINTF(E_LOG, L_SPOTIFY, "Playlist '%s' not found, can't delete\n", uri);
+      return -1;
+    }
+
+  DPRINTF(E_LOG, L_SPOTIFY, "Removing playlist '%s' (%s)\n", pli->title, uri);
+
+  plid = pli->id;
+
+  free_pli(pli, 0);
+
+  db_spotify_pl_delete(plid);
+  spotify_cleanup_files();
+  return 0;
 }
 
 /**
@@ -818,10 +995,8 @@ static void playlist_added(sp_playlistcontainer *pc, sp_playlist *pl,
 static void
 playlist_removed(sp_playlistcontainer *pc, sp_playlist *pl, int position, void *userdata)
 {
-  struct playlist_info *pli;
   sp_link *link;
   char url[1024];
-  int plid;
   int ret;
 
   DPRINTF(E_INFO, L_SPOTIFY, "Playlist removed: %s\n", fptr_sp_playlist_name(pl));
@@ -842,20 +1017,16 @@ playlist_removed(sp_playlistcontainer *pc, sp_playlist *pl, int position, void *
     }
   fptr_sp_link_release(link);
 
-  pli = db_pl_fetch_bypath(url);
-
-  if (!pli)
+  if (spotify_access_token_valid)
     {
-      DPRINTF(E_DBG, L_SPOTIFY, "Playlist %s not found, can't delete\n", url);
-      return;
+      // Run playlist remove in the library thread
+      if (!scanning)
+	library_exec_async(webapi_pl_remove, strdup(url));
+    }
+  else
+    {
+      playlist_remove(url);
     }
-
-  plid = pli->id;
-
-  free_pli(pli, 0);
-
-  db_spotify_pl_delete(plid);
-  spotify_playlist_cleanupfiles();
 }
 
 /**
@@ -888,38 +1059,6 @@ static sp_playlistcontainer_callbacks pc_callbacks = {
 /* --------------------- INTERNAL PLAYBACK AND AUDIO ----------------------- */
 /*            Should only be called from within the spotify thread           */
 
-static void
-mk_reltime(struct timespec *ts, time_t sec)
-{
-#if _POSIX_TIMERS > 0
-  clock_gettime(CLOCK_REALTIME, ts);
-#else
-  struct timeval tv;
-  gettimeofday(&tv, NULL);
-  TIMEVAL_TO_TIMESPEC(&tv, ts);
-#endif
-  ts->tv_sec += sec;
-}
-
-static void
-audio_fifo_flush(void)
-{
-    audio_fifo_data_t *afd;
-
-    DPRINTF(E_DBG, L_SPOTIFY, "Flushing audio fifo\n");
-
-    pthread_mutex_lock(&g_audio_fifo->mutex);
-
-    while((afd = TAILQ_FIRST(&g_audio_fifo->q))) {
-	TAILQ_REMOVE(&g_audio_fifo->q, afd, link);
-	free(afd);
-    }
-
-    g_audio_fifo->qlen = 0;
-    g_audio_fifo->fullcount = 0;
-    pthread_mutex_unlock(&g_audio_fifo->mutex);
-}
-
 static enum command_state
 playback_setup(void *arg, int *retval)
 {
@@ -952,7 +1091,7 @@ playback_setup(void *arg, int *retval)
       *retval = -1;
       return COMMAND_END;
     }
-  
+
   err = fptr_sp_session_player_load(g_sess, track);
   if (SP_ERROR_OK != err)
     {
@@ -961,8 +1100,6 @@ playback_setup(void *arg, int *retval)
       return COMMAND_END;
     }
 
-  audio_fifo_flush();
-
   *retval = 0;
   return COMMAND_END;
 }
@@ -1028,6 +1165,7 @@ playback_stop(void *arg, int *retval)
 
   g_state = SPOTIFY_STATE_STOPPED;
 
+  evbuffer_drain(spotify_audio_buffer, evbuffer_get_length(spotify_audio_buffer));
 
   *retval = 0;
   return COMMAND_END;
@@ -1051,8 +1189,6 @@ playback_seek(void *arg, int *retval)
       return COMMAND_END;
     }
 
-  audio_fifo_flush();
-
   *retval = 0;
   return COMMAND_END;
 }
@@ -1074,88 +1210,10 @@ playback_eot(void *arg, int *retval)
 
   g_state = SPOTIFY_STATE_STOPPING;
 
-  *retval = 0;
-  return COMMAND_END;
-}
-
-static enum command_state
-audio_get(void *arg, int *retval)
-{
-  struct audio_get_param *audio;
-  struct timespec ts;
-  audio_fifo_data_t *afd;
-  int processed;
-  int timeout;
-  int ret;
-  int s;
-
-  audio = (struct audio_get_param *) arg;
-  afd = NULL;
-  processed = 0;
-
-  // If spotify was paused begin by resuming playback
-  if (g_state == SPOTIFY_STATE_PAUSED)
-    playback_play(NULL, retval);
-
-  pthread_mutex_lock(&g_audio_fifo->mutex);
-
-  while ((processed < audio->wanted) && (g_state != SPOTIFY_STATE_STOPPED))
-    {
-      // If track has ended and buffer is empty
-      if ((g_state == SPOTIFY_STATE_STOPPING) && (g_audio_fifo->qlen <= 0))
-	{
-	  DPRINTF(E_DBG, L_SPOTIFY, "Track finished\n");
-	  g_state = SPOTIFY_STATE_STOPPED;
-	  break;
-	}
-
-      // If buffer is empty, wait for audio, but use timed wait so we don't
-      // risk waiting forever (maybe the player stopped while we were waiting)
-      timeout = 0;
-      while ( !(afd = TAILQ_FIRST(&g_audio_fifo->q)) && 
-	       (g_state != SPOTIFY_STATE_STOPPED) &&
-	       (g_state != SPOTIFY_STATE_STOPPING) &&
-	       (timeout < SPOTIFY_TIMEOUT) )
-	{
-	  DPRINTF(E_DBG, L_SPOTIFY, "Waiting for audio\n");
-	  timeout += 5;
-	  mk_reltime(&ts, 5);
-	  pthread_cond_timedwait(&g_audio_fifo->cond, &g_audio_fifo->mutex, &ts);
-	}
-
-      if ((!afd) && (timeout >= SPOTIFY_TIMEOUT))
-	{
-	  DPRINTF(E_LOG, L_SPOTIFY, "Timeout waiting for audio (waited %d sec)\n", timeout);
-
-	  spotify_playback_stop_nonblock();
-	}
-
-      if (!afd)
-	break;
-
-      TAILQ_REMOVE(&g_audio_fifo->q, afd, link);
-      g_audio_fifo->qlen -= afd->nsamples;
-
-      s = afd->nsamples * sizeof(int16_t) * 2;
-  
-      ret = evbuffer_add(audio->evbuf, afd->samples, s);
-      free(afd);
-      afd = NULL;
-      if (ret < 0)
-	{
-	  DPRINTF(E_LOG, L_SPOTIFY, "Out of memory for evbuffer (tried to add %d bytes)\n", s);
-	  pthread_mutex_unlock(&g_audio_fifo->mutex);
-	  *retval = -1;
-	  return COMMAND_END;
-	}
-
-      processed += s;
-    }
-
-  pthread_mutex_unlock(&g_audio_fifo->mutex);
+  // TODO 1) This will block for a while, but perhaps ok?
+  input_write(spotify_audio_buffer, INPUT_FLAG_EOF);
 
-
-  *retval = processed;
+  *retval = 0;
   return COMMAND_END;
 }
 
@@ -1163,15 +1221,15 @@ static void
 artwork_loaded_cb(sp_image *image, void *userdata)
 {
   struct artwork_get_param *artwork;
-  
+
   artwork = userdata;
-  
-  pthread_mutex_lock(&artwork->mutex);
+
+  CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&artwork->mutex));
 
   artwork->is_loaded = 1;
 
-  pthread_cond_signal(&artwork->cond);
-  pthread_mutex_unlock(&artwork->mutex);
+  CHECK_ERR(L_SPOTIFY, pthread_cond_signal(&artwork->cond));
+  CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&artwork->mutex));
 }
 
 static enum command_state
@@ -1188,6 +1246,8 @@ artwork_get_bh(void *arg, int *retval)
   sp_image *image = artwork->image;
   char *path = artwork->path;
 
+  fptr_sp_image_remove_load_callback(image, artwork_loaded_cb, artwork);
+
   err = fptr_sp_image_error(image);
   if (err != SP_ERROR_OK)
     {
@@ -1345,11 +1405,8 @@ artwork_get(void *arg, int *retval)
 static void
 logged_in(sp_session *sess, sp_error error)
 {
-  cfg_t *spotify_cfg;
   sp_playlist *pl;
   sp_playlistcontainer *pc;
-  struct playlist_info pli;
-  int ret;
   int i;
 
   if (SP_ERROR_OK != error)
@@ -1358,32 +1415,17 @@ logged_in(sp_session *sess, sp_error error)
       return;
     }
 
-  DPRINTF(E_LOG, L_SPOTIFY, "Login to Spotify succeeded. Reloading playlists.\n");
+  DPRINTF(E_LOG, L_SPOTIFY, "Login to Spotify succeeded, reloading playlists\n");
+
+  if (!spotify_access_token_valid)
+    create_base_playlist();
 
   db_directory_enable_bypath("/spotify:");
 
   pl = fptr_sp_session_starred_create(sess);
   fptr_sp_playlist_add_callbacks(pl, &pl_callbacks, NULL);
 
-  spotify_cfg = cfg_getsec(cfg, "spotify");
-  if (! cfg_getbool(spotify_cfg, "base_playlist_disable"))
-    {
-      memset(&pli, 0, sizeof(struct playlist_info));
-      pli.title = "Spotify";
-      pli.type = PL_FOLDER;
-      pli.path = "spotify:playlistfolder";
-
-      ret = db_pl_add(&pli, &g_base_plid);
-      if (ret < 0)
-	{
-	  DPRINTF(E_LOG, L_SPOTIFY, "Error adding base playlist\n");
-	  return;
-	}
-    }
-  else
-    g_base_plid = 0;
-
-  pc = fptr_sp_session_playlistcontainer(sess);
+  pc = fptr_sp_session_playlistcontainer(sess);
 
   fptr_sp_playlistcontainer_add_callbacks(pc, &pc_callbacks, NULL);
 
@@ -1408,10 +1450,10 @@ logged_out(sp_session *sess)
 {
   DPRINTF(E_INFO, L_SPOTIFY, "Logout complete\n");
 
-  pthread_mutex_lock(&login_lck);
+  CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&login_lck));
 
-  pthread_cond_signal(&login_cond);
-  pthread_mutex_unlock(&login_lck);
+  CHECK_ERR(L_SPOTIFY, pthread_cond_signal(&login_cond));
+  CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&login_lck));
 }
 
 /**
@@ -1422,8 +1464,8 @@ logged_out(sp_session *sess)
 static int music_delivery(sp_session *sess, const sp_audioformat *format,
                           const void *frames, int num_frames)
 {
-  audio_fifo_data_t *afd;
-  size_t s;
+  size_t size;
+  int ret;
 
   /* No support for resampling right now */
   if ((format->sample_rate != 44100) || (format->channels != 2))
@@ -1433,44 +1475,26 @@ static int music_delivery(sp_session *sess, const sp_audioformat *format,
       return num_frames;
     }
 
+  // Audio discontinuity, e.g. seek
   if (num_frames == 0)
-    return 0; // Audio discontinuity, do nothing
-
-  pthread_mutex_lock(&g_audio_fifo->mutex);
-
-  /* Buffer three seconds of audio */
-  if (g_audio_fifo->qlen > (3 * format->sample_rate))
     {
-      // If the buffer has been full the last 300 times (~about a minute) we
-      // assume the player thread paused/died without telling us, so we signal pause
-      if (g_audio_fifo->fullcount < 300)
-	g_audio_fifo->fullcount++;
-      else
-	{
-	  DPRINTF(E_WARN, L_SPOTIFY, "Buffer full more than 300 times, pausing\n");
-	  spotify_playback_pause_nonblock();
-	  g_audio_fifo->fullcount = 0;
-	}
-
-      pthread_mutex_unlock(&g_audio_fifo->mutex);
-
+      evbuffer_drain(spotify_audio_buffer, evbuffer_get_length(spotify_audio_buffer));
       return 0;
     }
-  else
-    g_audio_fifo->fullcount = 0;
-
-  s = num_frames * sizeof(int16_t) * format->channels;
 
-  afd = malloc(sizeof(*afd) + s);
-  memcpy(afd->samples, frames, s);
+  size = num_frames * sizeof(int16_t) * format->channels;
 
-  afd->nsamples = num_frames;
-
-  TAILQ_INSERT_TAIL(&g_audio_fifo->q, afd, link);
-  g_audio_fifo->qlen += num_frames;
+  ret = evbuffer_add(spotify_audio_buffer, frames, size);
+  if (ret < 0)
+    {
+      DPRINTF(E_LOG, L_SPOTIFY, "Out of memory adding audio to buffer\n");
+      return num_frames;
+    }
 
-  pthread_cond_signal(&g_audio_fifo->cond);
-  pthread_mutex_unlock(&g_audio_fifo->mutex);
+  // The input buffer only accepts writing when it is approaching depletion, and
+  // because we use NONBLOCK it will just return if this is not the case. So in
+  // most cases no actual write is made and spotify_audio_buffer will just grow.
+  input_write(spotify_audio_buffer, INPUT_FLAG_NONBLOCK);
 
   return num_frames;
 }
@@ -1517,7 +1541,7 @@ static void connectionstate_updated(sp_session *session)
 {
   if (SP_CONNECTION_STATE_LOGGED_IN == fptr_sp_session_connectionstate(session))
     {
-      DPRINTF(E_LOG, L_SPOTIFY, "Connection to Spotify (re)established\n");
+      DPRINTF(E_LOG, L_SPOTIFY, "Connection to Spotify (re)established, reloading saved tracks\n");
     }
   else if (g_state == SPOTIFY_STATE_PLAYING)
     {
@@ -1643,16 +1667,16 @@ notify_cb(int fd, short what, void *arg)
 
 /* Thread: player */
 int
-spotify_playback_setup(struct media_file_info *mfi)
+spotify_playback_setup(const char *path)
 {
   sp_link *link;
 
   DPRINTF(E_DBG, L_SPOTIFY, "Playback setup request\n");
 
-  link = fptr_sp_link_create_from_string(mfi->path);
+  link = fptr_sp_link_create_from_string(path);
   if (!link)
     {
-      DPRINTF(E_LOG, L_SPOTIFY, "Playback setup failed, invalid Spotify link: %s\n", mfi->path);
+      DPRINTF(E_LOG, L_SPOTIFY, "Playback setup failed, invalid Spotify link: %s\n", path);
       return -1;
     }
 
@@ -1716,18 +1740,6 @@ spotify_playback_seek(int ms)
     return -1;
 }
 
-/* Thread: player */
-int
-spotify_audio_get(struct evbuffer *evbuf, int wanted)
-{
-  struct audio_get_param audio;
-
-  audio.evbuf  = evbuf;
-  audio.wanted = wanted;
-
-  return commands_exec_sync(cmdbase, audio_get, NULL, &audio);
-}
-
 /* Thread: httpd (artwork) and worker */
 int
 spotify_artwork_get(struct evbuffer *evbuf, char *path, int max_w, int max_h)
@@ -1741,138 +1753,97 @@ spotify_artwork_get(struct evbuffer *evbuf, char *path, int max_w, int max_h)
   artwork.max_w = max_w;
   artwork.max_h = max_h;
 
-  pthread_mutex_init(&artwork.mutex, NULL);
-  pthread_cond_init(&artwork.cond, NULL);
+  CHECK_ERR(L_SPOTIFY, mutex_init(&artwork.mutex));
+  CHECK_ERR(L_SPOTIFY, pthread_cond_init(&artwork.cond, NULL));
 
   ret = commands_exec_sync(cmdbase, artwork_get, NULL, &artwork);
-  
+
   // Artwork was not ready, wait for callback from libspotify
   if (ret == 0)
     {
-      pthread_mutex_lock(&artwork.mutex);
-      mk_reltime(&ts, SPOTIFY_ARTWORK_TIMEOUT);
-      if (!artwork.is_loaded)
-	pthread_cond_timedwait(&artwork.cond, &artwork.mutex, &ts);
-      pthread_mutex_unlock(&artwork.mutex);
+      CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&artwork.mutex));
+      ts = timespec_reltoabs(spotify_artwork_timeout);
+      while ((!artwork.is_loaded) && (ret != ETIMEDOUT))
+	CHECK_ERR_EXCEPT(L_SPOTIFY, pthread_cond_timedwait(&artwork.cond, &artwork.mutex, &ts), ret, ETIMEDOUT);
+      if (ret == ETIMEDOUT)
+	DPRINTF(E_LOG, L_SPOTIFY, "Timeout waiting for artwork from Spotify\n");
+      CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&artwork.mutex));
 
       ret = commands_exec_sync(cmdbase, artwork_get_bh, NULL, &artwork);
     }
-    
+
   return ret;
 }
 
-static int
-spotify_file_read(char *path, char **username, char **password)
+/* Thread: httpd */
+void
+spotify_oauth_interface(struct evbuffer *evbuf, const char *redirect_uri)
 {
-  FILE *fp;
-  char *u;
-  char *p;
-  char buf[256];
-  int len;
+  char *uri;
 
-  fp = fopen(path, "rb");
-  if (!fp)
+  uri = spotifywebapi_oauth_uri_get(redirect_uri);
+  if (!uri)
     {
-      DPRINTF(E_LOG, L_SPOTIFY, "Could not open Spotify credentials file %s: %s\n", path, strerror(errno));
-      return -1;
-    }
-
-  u = fgets(buf, sizeof(buf), fp);
-  if (!u)
-    {
-      DPRINTF(E_LOG, L_SPOTIFY, "Empty Spotify credentials file %s\n", path);
-
-      fclose(fp);
-      return -1;
+      DPRINTF(E_LOG, L_SPOTIFY, "Cannot display Spotify oath interface (http_form_uriencode() failed)\n");
+      return;
     }
 
-  len = strlen(u);
-  if (buf[len - 1] != '\n')
-    {
-      DPRINTF(E_LOG, L_SPOTIFY, "Invalid Spotify credentials file %s: username name too long or missing password\n", path);
+  evbuffer_add_printf(evbuf, "<a href=\"%s\">Click here to authorize forked-daapd with Spotify</a>\n", uri);
 
-      fclose(fp);
-      return -1;
-    }
+  free(uri);
+}
 
-  while (len)
-    {
-      if ((buf[len - 1] == '\r') || (buf[len - 1] == '\n'))
-	{
-	  buf[len - 1] = '\0';
-	  len--;
-	}
-      else
-	break;
-    }
+/* Thread: httpd */
+void
+spotify_oauth_callback(struct evbuffer *evbuf, struct evkeyvalq *param, const char *redirect_uri)
+{
+  const char *code;
+  const char *err;
+  int ret;
 
-  if (!len)
+  code = evhttp_find_header(param, "code");
+  if (!code)
     {
-      DPRINTF(E_LOG, L_SPOTIFY, "Invalid Spotify credentials file %s: empty line where username expected\n", path);
-
-      fclose(fp);
-      return -1;
+      evbuffer_add_printf(evbuf, "Error: Didn't receive a code from Spotify\n");
+      return;
     }
 
-  u = strdup(buf);
-  if (!u)
-    {
-      DPRINTF(E_LOG, L_SPOTIFY, "Out of memory for username while reading %s\n", path);
+  DPRINTF(E_DBG, L_SPOTIFY, "Received OAuth code: %s\n", code);
 
-      fclose(fp);
-      return -1;
-    }
+  evbuffer_add_printf(evbuf, "<p>Requesting access token from Spotify...\n");
 
-  p = fgets(buf, sizeof(buf), fp);
-  fclose(fp);
-  if (!p)
+  ret = spotifywebapi_token_get(code, redirect_uri, &err);
+  if (ret < 0)
     {
-      DPRINTF(E_LOG, L_SPOTIFY, "Invalid Spotify credentials file %s: no password\n", path);
-
-      free(u);
-      return -1;
+      evbuffer_add_printf(evbuf, "failed</p>\n<p>Error: %s</p>\n", err);
+      return;
     }
 
-  len = strlen(p);
-
-  while (len)
-    {
-      if ((buf[len - 1] == '\r') || (buf[len - 1] == '\n'))
-	{
-	  buf[len - 1] = '\0';
-	  len--;
-	}
-      else
-	break;
-    }
+  // Received a valid access token
+  spotify_access_token_valid = true;
 
-  p = strdup(buf);
-  if (!p)
-    {
-      DPRINTF(E_LOG, L_SPOTIFY, "Out of memory for password while reading %s\n", path);
+  // Trigger scan after successful access to spotifywebapi
+  library_exec_async(webapi_scan, NULL);
 
-      free(u);
-      return -1;
-    }
+  evbuffer_add_printf(evbuf, "ok, all done</p>\n");
 
-  DPRINTF(E_LOG, L_SPOTIFY, "Spotify credentials file OK, logging in with username %s\n", u);
+  return;
+}
 
-  *username = u;
-  *password = p;
+static void
+spotify_uri_register(const char *uri)
+{
+  char *tmp;
 
-  return 0;
+  tmp = strdup(uri);
+  commands_exec_async(cmdbase, uri_register, tmp);
 }
 
-/* Thread: filescanner */
+/* Thread: library */
 void
-spotify_login(char *path)
+spotify_login(char **arglist)
 {
   sp_error err;
-  char *username;
-  char *password;
-  int ret;
-
-  db_spotify_purge();
 
   if (!g_sess)
     {
@@ -1886,7 +1857,7 @@ spotify_login(char *path)
 
   if (SP_CONNECTION_STATE_LOGGED_IN == fptr_sp_session_connectionstate(g_sess))
     {
-      pthread_mutex_lock(&login_lck);
+      CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&login_lck));
 
       DPRINTF(E_LOG, L_SPOTIFY, "Logging out of Spotify (current state is %d)\n", g_state);
 
@@ -1896,27 +1867,22 @@ spotify_login(char *path)
       if (SP_ERROR_OK != err)
 	{
 	  DPRINTF(E_LOG, L_SPOTIFY, "Could not logout of Spotify: %s\n", fptr_sp_error_message(err));
-	  pthread_mutex_unlock(&login_lck);
+	  CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&login_lck));
 	  return;
 	}
 
-      pthread_cond_wait(&login_cond, &login_lck);
-      pthread_mutex_unlock(&login_lck);
+      CHECK_ERR(L_SPOTIFY, pthread_cond_wait(&login_cond, &login_lck));
+      CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&login_lck));
     }
 
-  DPRINTF(E_INFO, L_SPOTIFY, "Logging into Spotify\n");
-  if (path)
+  if (arglist)
     {
-      ret = spotify_file_read(path, &username, &password);
-      if (ret < 0)
-	return;
-
-      err = fptr_sp_session_login(g_sess, username, password, 1, NULL);
-      free(username);
-      free(password);
+      DPRINTF(E_LOG, L_SPOTIFY, "Spotify credentials file OK, logging in with username %s\n", arglist[0]);
+      err = fptr_sp_session_login(g_sess, arglist[0], arglist[1], 1, NULL);
     }
   else
     {
+      DPRINTF(E_INFO, L_SPOTIFY, "Relogin to Spotify\n");
       err = fptr_sp_session_relogin(g_sess);
     }
 
@@ -1927,6 +1893,472 @@ spotify_login(char *path)
     }
 }
 
+static void
+map_track_to_mfi(const struct spotify_track *track, struct media_file_info* mfi)
+{
+  mfi->title = safe_strdup(track->name);
+  mfi->album = safe_strdup(track->album);
+  mfi->artist = safe_strdup(track->artist);
+  mfi->album_artist = safe_strdup(track->album_artist);
+  mfi->disc = track->disc_number;
+  mfi->song_length = track->duration_ms;
+  mfi->track = track->track_number;
+  mfi->compilation = track->is_compilation;
+
+  mfi->artwork     = ARTWORK_SPOTIFY;
+  mfi->type        = strdup("spotify");
+  mfi->codectype   = strdup("wav");
+  mfi->description = strdup("Spotify audio");
+
+  mfi->path = strdup(track->uri);
+  mfi->fname = strdup(track->uri);
+}
+
+static void
+map_album_to_mfi(const struct spotify_album *album, struct media_file_info* mfi)
+{
+  mfi->album = safe_strdup(album->name);
+  mfi->album_artist = safe_strdup(album->artist);
+  mfi->genre = safe_strdup(album->genre);
+  mfi->compilation = album->is_compilation;
+  mfi->year = album->release_year;
+  mfi->time_modified = album->mtime;
+}
+
+/* Thread: library */
+static int
+scan_saved_albums()
+{
+  struct spotify_request request;
+  json_object *jsontracks;
+  int track_count;
+  struct spotify_album album;
+  struct spotify_track track;
+  struct media_file_info mfi;
+  char virtual_path[PATH_MAX];
+  int dir_id;
+  time_t stamp;
+  int id;
+  int i;
+  int count;
+  int ret;
+
+  count = 0;
+  memset(&request, 0, sizeof(struct spotify_request));
+
+  while (0 == spotifywebapi_request_next(&request, SPOTIFY_WEBAPI_SAVED_ALBUMS, false))
+    {
+      while (0 == spotifywebapi_saved_albums_fetch(&request, &jsontracks, &track_count, &album))
+	{
+	  DPRINTF(E_DBG, L_SPOTIFY, "Got saved album: '%s' - '%s' (%s) - track-count: %d\n",
+		  album.artist, album.name, album.uri, track_count);
+
+	  db_transaction_begin();
+
+	  dir_id = prepare_directories(album.artist, album.name);
+	  ret = 0;
+	  for (i = 0; i < track_count && ret == 0; i++)
+	    {
+	      ret = spotifywebapi_album_track_fetch(jsontracks, i, &track);
+	      if (ret == 0 && track.uri)
+		{
+		  db_file_stamp_bypath(track.uri, &stamp, &id);
+		  if (stamp && (stamp >= track.mtime))
+		    {
+		      db_file_ping(id);
+		    }
+		  else
+		    {
+		      memset(&mfi, 0, sizeof(struct media_file_info));
+
+		      mfi.id = id;
+
+		      map_track_to_mfi(&track, &mfi);
+		      map_album_to_mfi(&album, &mfi);
+
+		      mfi.data_kind = DATA_KIND_SPOTIFY;
+		      snprintf(virtual_path, PATH_MAX, "/spotify:/%s/%s/%s", mfi.album_artist, mfi.album, mfi.title);
+		      mfi.virtual_path = strdup(virtual_path);
+		      mfi.directory_id = dir_id;
+
+		      library_add_media(&mfi);
+
+		      free_mfi(&mfi, 1);
+		    }
+
+		  spotify_uri_register(track.uri);
+
+		  cache_artwork_ping(track.uri, album.mtime, 0);
+
+		  if (spotify_saved_plid)
+		    db_pl_add_item_bypath(spotify_saved_plid, track.uri);
+		}
+	    }
+
+	  db_transaction_end();
+
+	  count++;
+	  if (count >= request.total || (count % 10 == 0))
+	    DPRINTF(E_LOG, L_SPOTIFY, "Scanned %d of %d saved albums\n", count, request.total);
+	}
+    }
+
+  spotifywebapi_request_end(&request);
+
+  return 0;
+}
+
+/* Thread: library */
+static int
+scan_playlisttracks(struct spotify_playlist *playlist, int plid)
+{
+  cfg_t *spotify_cfg;
+  bool artist_override;
+  bool album_override;
+  struct spotify_request request;
+  struct spotify_track track;
+  struct media_file_info mfi;
+  char virtual_path[PATH_MAX];
+  int dir_id;
+  time_t stamp;
+  int id;
+
+  memset(&request, 0, sizeof(struct spotify_request));
+
+  spotify_cfg = cfg_getsec(cfg, "spotify");
+  artist_override = cfg_getbool(spotify_cfg, "artist_override");
+  album_override = cfg_getbool(spotify_cfg, "album_override");
+
+  while (0 == spotifywebapi_request_next(&request, playlist->tracks_href, true))
+    {
+      db_transaction_begin();
+
+//      DPRINTF(E_DBG, L_SPOTIFY, "Playlist tracks\n%s\n", request.response_body);
+      while (0 == spotifywebapi_playlisttracks_fetch(&request, &track))
+	{
+	  DPRINTF(E_DBG, L_SPOTIFY, "Got playlist track: '%s' (%s) \n", track.name, track.uri);
+
+	  if (!track.is_playable)
+	    {
+	      DPRINTF(E_LOG, L_SPOTIFY, "Track not available for playback: '%s' - '%s' (%s) (restrictions: %s)\n", track.artist, track.name, track.uri, track.restrictions);
+	      continue;
+	    }
+
+	  if (track.uri)
+	    {
+	      if (track.linked_from_uri)
+		DPRINTF(E_DBG, L_SPOTIFY, "Track '%s' (%s) linked from %s\n", track.name, track.uri, track.linked_from_uri);
+
+	      db_file_stamp_bypath(track.uri, &stamp, &id);
+	      if (stamp)
+		{
+		  db_file_ping(id);
+		}
+	      else
+		{
+		  memset(&mfi, 0, sizeof(struct media_file_info));
+
+		  mfi.id = id;
+
+		  dir_id = prepare_directories(track.album_artist, track.album);
+
+		  map_track_to_mfi(&track, &mfi);
+
+		  mfi.compilation = (track.is_compilation || artist_override);
+		  if (album_override)
+		    {
+		      free(mfi.album);
+		      mfi.album = strdup(playlist->name);
+		    }
+
+		  mfi.time_modified = time(NULL);
+		  mfi.data_kind = DATA_KIND_SPOTIFY;
+		  snprintf(virtual_path, PATH_MAX, "/spotify:/%s/%s/%s", mfi.album_artist, mfi.album, mfi.title);
+		  mfi.virtual_path = strdup(virtual_path);
+		  mfi.directory_id = dir_id;
+
+		  library_add_media(&mfi);
+		  free_mfi(&mfi, 1);
+		}
+
+	      spotify_uri_register(track.uri);
+	      cache_artwork_ping(track.uri, 1, 0);
+	      db_pl_add_item_bypath(plid, track.uri);
+	    }
+	}
+
+      db_transaction_end();
+    }
+
+  spotifywebapi_request_end(&request);
+
+  return 0;
+}
+
+/* Thread: library */
+static int
+scan_playlists()
+{
+  struct spotify_request request;
+  struct spotify_playlist playlist;
+  char virtual_path[PATH_MAX];
+  int plid;
+  int count;
+  int trackcount;
+
+  count = 0;
+  trackcount = 0;
+  memset(&request, 0, sizeof(struct spotify_request));
+
+  while (0 == spotifywebapi_request_next(&request, SPOTIFY_WEBAPI_SAVED_PLAYLISTS, false))
+    {
+      while (0 == spotifywebapi_playlists_fetch(&request, &playlist))
+	{
+	  DPRINTF(E_DBG, L_SPOTIFY, "Got playlist: '%s' with %d tracks (%s) \n", playlist.name, playlist.tracks_count, playlist.uri);
+
+	  if (!playlist.uri || !playlist.name || playlist.tracks_count == 0)
+	    {
+	      DPRINTF(E_LOG, L_SPOTIFY, "Ignoring playlist '%s' with %d tracks (%s)\n", playlist.name, playlist.tracks_count, playlist.uri);
+	      continue;
+	    }
+
+	  if (playlist.owner)
+	    {
+	      snprintf(virtual_path, PATH_MAX, "/spotify:/%s (%s)", playlist.name, playlist.owner);
+	    }
+	  else
+	    {
+	      snprintf(virtual_path, PATH_MAX, "/spotify:/%s", playlist.name);
+	    }
+
+	  db_transaction_begin();
+	  plid = library_add_playlist_info(playlist.uri, playlist.name, virtual_path, PL_PLAIN, spotify_base_plid, DIR_SPOTIFY);
+	  db_transaction_end();
+
+	  if (plid > 0)
+	    scan_playlisttracks(&playlist, plid);
+	  else
+	    DPRINTF(E_LOG, L_SPOTIFY, "Error adding playlist: '%s' (%s) \n", playlist.name, playlist.uri);
+
+	  count++;
+	  trackcount += playlist.tracks_count;
+	  DPRINTF(E_LOG, L_SPOTIFY, "Scanned %d of %d saved playlists (%d tracks)\n", count, request.total, trackcount);
+	}
+    }
+
+  spotifywebapi_request_end(&request);
+
+  return 0;
+}
+
+/* Thread: library */
+static int
+scan_playlist(const char *uri)
+{
+  struct spotify_request request;
+  struct spotify_playlist playlist;
+  char virtual_path[PATH_MAX];
+  int plid;
+
+  memset(&request, 0, sizeof(struct spotify_request));
+
+  if (0 == spotifywebapi_playlist_start(&request, uri, &playlist))
+    {
+      if (!playlist.uri)
+	{
+	  DPRINTF(E_LOG, L_SPOTIFY, "Got playlist with missing uri for path:: '%s'\n", uri);
+	}
+      else
+	{
+	  DPRINTF(E_LOG, L_SPOTIFY, "Saving playlist '%s' with %d tracks (%s) \n", playlist.name, playlist.tracks_count, playlist.uri);
+
+	  if (playlist.owner)
+	    {
+	      snprintf(virtual_path, PATH_MAX, "/spotify:/%s (%s)", playlist.name, playlist.owner);
+	    }
+	  else
+	    {
+	      snprintf(virtual_path, PATH_MAX, "/spotify:/%s", playlist.name);
+	    }
+
+	  db_transaction_begin();
+	  plid = library_add_playlist_info(playlist.uri, playlist.name, virtual_path, PL_PLAIN, spotify_base_plid, DIR_SPOTIFY);
+	  db_transaction_end();
+
+	  if (plid > 0)
+	    scan_playlisttracks(&playlist, plid);
+	  else
+	    DPRINTF(E_LOG, L_SPOTIFY, "Error adding playlist: '%s' (%s) \n", playlist.name, playlist.uri);
+	}
+    }
+
+  spotifywebapi_request_end(&request);
+
+  return 0;
+}
+
+static void
+create_saved_tracks_playlist()
+{
+  spotify_saved_plid = library_add_playlist_info("spotify:savedtracks", "Spotify Saved", "/spotify:/Spotify Saved", PL_PLAIN, spotify_base_plid, DIR_SPOTIFY);
+
+  if (spotify_saved_plid <= 0)
+    {
+      DPRINTF(E_LOG, L_SPOTIFY, "Error adding playlist for saved tracks\n");
+      spotify_saved_plid = 0;
+    }
+}
+
+/*
+ * Add or update playlist folder for all spotify playlists (if enabled in config)
+ */
+static void
+create_base_playlist()
+{
+  cfg_t *spotify_cfg;
+  int ret;
+
+  spotify_base_plid = 0;
+  spotify_cfg = cfg_getsec(cfg, "spotify");
+  if (!cfg_getbool(spotify_cfg, "base_playlist_disable"))
+    {
+      ret = library_add_playlist_info("spotify:playlistfolder", "Spotify", NULL, PL_FOLDER, 0, 0);
+      if (ret < 0)
+	DPRINTF(E_LOG, L_SPOTIFY, "Error adding base playlist\n");
+      else
+	spotify_base_plid = ret;
+    }
+}
+
+/* Thread: library */
+static int
+initscan()
+{
+  scanning = true;
+
+  /* Refresh access token for the spotify webapi */
+  spotify_access_token_valid = (0 == spotifywebapi_token_refresh());
+  if (!spotify_access_token_valid)
+    {
+      DPRINTF(E_LOG, L_SPOTIFY, "Spotify webapi token refresh failed. "
+	  "In order to use the web api, authorize forked-daapd to access "
+	  "your saved tracks by visiting http://forked-daapd.local:3689/oauth\n");
+
+      db_spotify_purge();
+    }
+
+  spotify_saved_plid = 0;
+
+  /*
+   * Login to spotify needs to be done before scanning tracks from the web api.
+   * (Scanned tracks need to be registered with libspotify for playback)
+   */
+  spotify_login(NULL);
+
+  /*
+   * Scan saved tracks from the web api
+   */
+  if (spotify_access_token_valid)
+    {
+      create_base_playlist();
+      create_saved_tracks_playlist();
+      scan_saved_albums();
+      scan_playlists();
+    }
+
+  scanning = false;
+
+  return 0;
+}
+
+/* Thread: library */
+static int
+rescan()
+{
+  scanning = true;
+
+  create_base_playlist();
+
+  /*
+   * Scan saved tracks from the web api
+   */
+  if (spotify_access_token_valid)
+    {
+      create_saved_tracks_playlist();
+      scan_saved_albums();
+      scan_playlists();
+    }
+  else
+    {
+      db_transaction_begin();
+      db_file_ping_bymatch("spotify:", 0);
+      db_pl_ping_bymatch("spotify:", 0);
+      db_directory_ping_bymatch("/spotify:");
+      db_transaction_end();
+    }
+
+  scanning = false;
+
+  return 0;
+}
+
+/* Thread: library */
+static int
+fullrescan()
+{
+  scanning = true;
+
+  create_base_playlist();
+
+  /*
+   * Scan saved tracks from the web api
+   */
+  if (spotify_access_token_valid)
+    {
+      create_saved_tracks_playlist();
+      scan_saved_albums();
+      scan_playlists();
+    }
+  else
+    {
+      spotify_login(NULL);
+    }
+
+  scanning = false;
+
+  return 0;
+}
+
+/* Thread: library */
+static enum command_state
+webapi_scan(void *arg, int *ret)
+{
+  db_spotify_purge();
+
+  *ret = rescan();
+  return COMMAND_END;
+}
+
+/* Thread: library */
+static enum command_state
+webapi_pl_save(void *arg, int *ret)
+{
+  const char *uri = arg;
+
+  *ret = scan_playlist(uri);
+  return COMMAND_END;
+}
+
+/* Thread: library */
+static enum command_state
+webapi_pl_remove(void *arg, int *ret)
+{
+  const char *uri = arg;
+
+  *ret = playlist_remove(uri);
+  return COMMAND_END;
+}
+
 /* Thread: main */
 int
 spotify_init(void)
@@ -1936,6 +2368,9 @@ spotify_init(void)
   sp_error err;
   int ret;
 
+  spotify_access_token_valid = false;
+  scanning = false;
+
   /* Initialize libspotify */
   g_libhandle = dlopen("libspotify.so", RTLD_LAZY);
   if (!g_libhandle)
@@ -1975,7 +2410,6 @@ spotify_init(void)
 
   event_add(g_notifyev, NULL);
 
-
   cmdbase = commands_base_new(evbase_spotify, exit_cb);
   if (!cmdbase)
     {
@@ -2013,20 +2447,12 @@ spotify_init(void)
 	break;
     }
 
-  /* Prepare audio buffer */
-  g_audio_fifo = (audio_fifo_t *)malloc(sizeof(audio_fifo_t));
-  if (!g_audio_fifo)
-    {
-      DPRINTF(E_LOG, L_SPOTIFY, "Out of memory for audio buffer\n");
-      goto audio_fifo_fail;
-    }
-  TAILQ_INIT(&g_audio_fifo->q);
-  g_audio_fifo->qlen = 0;
-  pthread_mutex_init(&g_audio_fifo->mutex, NULL);
-  pthread_cond_init(&g_audio_fifo->cond, NULL);
+  spotify_audio_buffer = evbuffer_new();
 
-  pthread_mutex_init(&login_lck, NULL);
-  pthread_cond_init(&login_cond, NULL);
+  CHECK_ERR(L_SPOTIFY, evbuffer_enable_locking(spotify_audio_buffer, NULL));
+
+  CHECK_ERR(L_SPOTIFY, mutex_init(&login_lck));
+  CHECK_ERR(L_SPOTIFY, pthread_cond_init(&login_cond, NULL));
 
   /* Spawn thread */
   ret = pthread_create(&tid_spotify, NULL, spotify, NULL);
@@ -2046,14 +2472,11 @@ spotify_init(void)
   return 0;
 
  thread_fail:
-  pthread_cond_destroy(&login_cond);
-  pthread_mutex_destroy(&login_lck);
+  CHECK_ERR(L_SPOTIFY, pthread_cond_destroy(&login_cond));
+  CHECK_ERR(L_SPOTIFY, pthread_mutex_destroy(&login_lck));
 
-  pthread_cond_destroy(&g_audio_fifo->cond);
-  pthread_mutex_destroy(&g_audio_fifo->mutex);
-  free(g_audio_fifo);  
+  evbuffer_free(spotify_audio_buffer);
 
- audio_fifo_fail:
   fptr_sp_session_release(g_sess);
   g_sess = NULL;
   
@@ -2110,14 +2533,23 @@ spotify_deinit(void)
   close(g_notify_pipe[1]);
 
   /* Destroy locks */
-  pthread_cond_destroy(&login_cond);
-  pthread_mutex_destroy(&login_lck);
+  CHECK_ERR(L_SPOTIFY, pthread_cond_destroy(&login_cond));
+  CHECK_ERR(L_SPOTIFY, pthread_mutex_destroy(&login_lck));
 
-  /* Clear audio fifo */
-  pthread_cond_destroy(&g_audio_fifo->cond);
-  pthread_mutex_destroy(&g_audio_fifo->mutex);
-  free(g_audio_fifo);
+  /* Free audio buffer */
+  evbuffer_free(spotify_audio_buffer);
 
   /* Release libspotify handle */
   dlclose(g_libhandle);
 }
+
+struct library_source spotifyscanner =
+{
+  .name = "spotifyscanner",
+  .disabled = 0,
+  .init = spotify_init,
+  .deinit = spotify_deinit,
+  .rescan = rescan,
+  .initscan = initscan,
+  .fullrescan = fullrescan,
+};
diff --git a/src/spotify.h b/src/spotify.h
index eb457a4..0d1f090 100644
--- a/src/spotify.h
+++ b/src/spotify.h
@@ -2,12 +2,12 @@
 #ifndef __SPOTIFY_H__
 #define __SPOTIFY_H__
 
-#include "db.h"
 #include <event2/event.h>
 #include <event2/buffer.h>
+#include <event2/http.h>
 
 int
-spotify_playback_setup(struct media_file_info *mfi);
+spotify_playback_setup(const char *path);
 
 int
 spotify_playback_play();
@@ -28,13 +28,16 @@ int
 spotify_playback_seek(int ms);
 
 int
-spotify_audio_get(struct evbuffer *evbuf, int wanted);
-
-int
 spotify_artwork_get(struct evbuffer *evbuf, char *path, int max_w, int max_h);
 
 void
-spotify_login(char *path);
+spotify_oauth_interface(struct evbuffer *evbuf, const char *redirect_uri);
+
+void
+spotify_oauth_callback(struct evbuffer *evbuf, struct evkeyvalq *param, const char *redirect_uri);
+
+void
+spotify_login(char **arglist);
 
 int
 spotify_init(void);
diff --git a/src/spotify_webapi.c b/src/spotify_webapi.c
new file mode 100644
index 0000000..3be9f56
--- /dev/null
+++ b/src/spotify_webapi.c
@@ -0,0 +1,816 @@
+/*
+ * Copyright (C) 2016 Espen Jürgensen <espenjurgensen at gmail.com>
+ * Copyright (C) 2016 Christian Meffert <christian.meffert at googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include "spotify_webapi.h"
+
+#include <event2/event.h>
+#include <json.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+
+#include "db.h"
+#include "http.h"
+#include "library.h"
+#include "logger.h"
+
+
+
+// Credentials for the web api
+static char *spotify_access_token;
+static char *spotify_refresh_token;
+static char *spotify_user_country;
+
+static int32_t expires_in = 3600;
+static time_t token_requested = 0;
+
+// Endpoints and credentials for the web api
+static const char *spotify_client_id     = "0e684a5422384114a8ae7ac020f01789";
+static const char *spotify_client_secret = "232af95f39014c9ba218285a5c11a239";
+static const char *spotify_auth_uri      = "https://accounts.spotify.com/authorize";
+static const char *spotify_token_uri     = "https://accounts.spotify.com/api/token";
+static const char *spotify_playlist_uri	 = "https://api.spotify.com/v1/users/%s/playlists/%s";
+static const char *spotify_me_uri        = "https://api.spotify.com/v1/me";
+
+
+/*--------------------- HELPERS FOR SPOTIFY WEB API -------------------------*/
+/*                 All the below is in the httpd thread                      */
+
+
+static void
+jparse_free(json_object* haystack)
+{
+  if (haystack)
+    {
+#ifdef HAVE_JSON_C_OLD
+      json_object_put(haystack);
+#else
+      if (json_object_put(haystack) != 1)
+        DPRINTF(E_LOG, L_SPOTIFY, "Memleak: JSON parser did not free object\n");
+#endif
+    }
+}
+
+static int
+jparse_array_from_obj(json_object *haystack, const char *key, json_object **needle)
+{
+  if (! (json_object_object_get_ex(haystack, key, needle) && json_object_get_type(*needle) == json_type_array) )
+    return -1;
+  else
+    return 0;
+}
+
+static const char *
+jparse_str_from_obj(json_object *haystack, const char *key)
+{
+  json_object *needle;
+
+  if (json_object_object_get_ex(haystack, key, &needle) && json_object_get_type(needle) == json_type_string)
+    return json_object_get_string(needle);
+  else
+    return NULL;
+}
+
+static int
+jparse_int_from_obj(json_object *haystack, const char *key)
+{
+  json_object *needle;
+
+  if (json_object_object_get_ex(haystack, key, &needle) && json_object_get_type(needle) == json_type_int)
+    return json_object_get_int(needle);
+  else
+    return 0;
+}
+
+static int
+jparse_bool_from_obj(json_object *haystack, const char *key)
+{
+  json_object *needle;
+
+  if (json_object_object_get_ex(haystack, key, &needle) && json_object_get_type(needle) == json_type_boolean)
+    return json_object_get_boolean(needle);
+  else
+    return false;
+}
+
+static time_t
+jparse_time_from_obj(json_object *haystack, const char *key)
+{
+  const char *tmp;
+  struct tm tp;
+  time_t parsed_time;
+
+  memset(&tp, 0, sizeof(struct tm));
+
+  tmp = jparse_str_from_obj(haystack, key);
+  if (!tmp)
+    return 0;
+
+  strptime(tmp, "%Y-%m-%dT%H:%M:%SZ", &tp);
+  parsed_time = mktime(&tp);
+  if (parsed_time < 0)
+    return 0;
+
+  return parsed_time;
+}
+
+static const char *
+jparse_str_from_array(json_object *array, int index, const char *key)
+{
+  json_object *item;
+  int count;
+
+  if (json_object_get_type(array) != json_type_array)
+    return NULL;
+
+  count = json_object_array_length(array);
+  if (count <= 0 || count <= index)
+    return NULL;
+
+  item = json_object_array_get_idx(array, index);
+  return jparse_str_from_obj(item, key);
+}
+
+static void
+free_http_client_ctx(struct http_client_ctx *ctx)
+{
+  if (!ctx)
+    return;
+
+  if (ctx->input_body)
+    evbuffer_free(ctx->input_body);
+  if (ctx->output_headers)
+    {
+      keyval_clear(ctx->output_headers);
+      free(ctx->output_headers);
+    }
+  free(ctx);
+}
+
+static int
+request_uri(struct spotify_request *request, const char *uri)
+{
+  char bearer_token[1024];
+  int ret;
+
+  memset(request, 0, sizeof(struct spotify_request));
+
+  if (0 > spotifywebapi_token_refresh())
+    {
+      return -1;
+    }
+
+  request->ctx = calloc(1, sizeof(struct http_client_ctx));
+  request->ctx->output_headers = calloc(1, sizeof(struct keyval));
+  request->ctx->input_body = evbuffer_new();
+  request->ctx->url = uri;
+
+  snprintf(bearer_token, sizeof(bearer_token), "Bearer %s", spotify_access_token);
+  if (keyval_add(request->ctx->output_headers, "Authorization", bearer_token) < 0)
+    {
+      DPRINTF(E_LOG, L_SPOTIFY, "Add bearer_token to keyval failed\n");
+      return -1;
+    }
+
+  ret = http_client_request(request->ctx);
+  if (ret < 0)
+    {
+      DPRINTF(E_LOG, L_SPOTIFY, "Request for saved tracks/albums failed\n");
+      return -1;
+    }
+
+  // 0-terminate for safety
+  evbuffer_add(request->ctx->input_body, "", 1);
+
+  request->response_body = (char *) evbuffer_pullup(request->ctx->input_body, -1);
+  if (!request->response_body || (strlen(request->response_body) == 0))
+    {
+      DPRINTF(E_LOG, L_SPOTIFY, "Request for saved tracks/albums failed, response was empty\n");
+      return -1;
+    }
+
+//  DPRINTF(E_DBG, L_SPOTIFY, "Wep api response for '%s'\n%s\n", uri, request->response_body);
+
+  request->haystack = json_tokener_parse(request->response_body);
+  if (!request->haystack)
+    {
+      DPRINTF(E_LOG, L_SPOTIFY, "JSON parser returned an error\n");
+      return -1;
+    }
+
+  DPRINTF(E_DBG, L_SPOTIFY, "Got response for '%s'\n", uri);
+  return 0;
+}
+
+void
+spotifywebapi_request_end(struct spotify_request *request)
+{
+  free_http_client_ctx(request->ctx);
+  jparse_free(request->haystack);
+}
+
+int
+spotifywebapi_request_next(struct spotify_request *request, const char *uri, bool append_market)
+{
+  char *next_uri;
+  int ret;
+
+  if (request->ctx && !request->next_uri)
+    {
+      // Reached end of paging requests, terminate loop
+      return -1;
+    }
+
+  if (!request->ctx)
+    {
+      // First paging request
+      if (append_market && spotify_user_country)
+	{
+	  if (strchr(uri, '?'))
+	    next_uri = safe_asprintf("%s&market=%s", uri, spotify_user_country);
+	  else
+	    next_uri = safe_asprintf("%s?market=%s", uri, spotify_user_country);
+	}
+      else
+	next_uri = strdup(uri);
+    }
+  else
+    {
+      // Next paging request
+      next_uri = strdup(request->next_uri);
+      spotifywebapi_request_end(request);
+    }
+
+  ret = request_uri(request, next_uri);
+  free(next_uri);
+
+  if (ret < 0)
+    return ret;
+
+  request->total = jparse_int_from_obj(request->haystack, "total");
+  request->next_uri = jparse_str_from_obj(request->haystack, "next");
+
+  if (jparse_array_from_obj(request->haystack, "items", &request->items) < 0)
+    {
+      DPRINTF(E_LOG, L_SPOTIFY, "No items in reply from Spotify. See:\n%s\n", request->response_body);
+      return -1;
+    }
+
+  request->count = json_object_array_length(request->items);
+
+  DPRINTF(E_DBG, L_SPOTIFY, "Got %d items\n", request->count);
+  return 0;
+}
+
+static void
+parse_metadata_track(json_object* jsontrack, struct spotify_track* track)
+{
+  json_object* jsonalbum;
+  json_object* jsonartists;
+  json_object* needle;
+
+  if (json_object_object_get_ex(jsontrack, "album", &jsonalbum))
+    {
+      track->album = jparse_str_from_obj(jsonalbum, "name");
+      if (json_object_object_get_ex(jsonalbum, "artists", &jsonartists))
+	{
+	  track->album_artist = jparse_str_from_array(jsonartists, 0, "name");
+	}
+    }
+  if (json_object_object_get_ex(jsontrack, "artists", &jsonartists))
+    {
+      track->artist = jparse_str_from_array(jsonartists, 0, "name");
+    }
+  track->disc_number = jparse_int_from_obj(jsontrack, "disc_number");
+  track->album_type = jparse_str_from_obj(jsonalbum, "album_type");
+  track->is_compilation = (track->album_type && 0 == strcmp(track->album_type, "compilation"));
+  track->duration_ms = jparse_int_from_obj(jsontrack, "duration_ms");
+  track->name = jparse_str_from_obj(jsontrack, "name");
+  track->track_number = jparse_int_from_obj(jsontrack, "track_number");
+  track->uri = jparse_str_from_obj(jsontrack, "uri");
+  track->id = jparse_str_from_obj(jsontrack, "id");
+
+  // "is_playable" is only returned for a request with a market parameter, default to true if it is not in the response
+  if (json_object_object_get_ex(jsontrack, "is_playable", NULL))
+    {
+      track->is_playable = jparse_bool_from_obj(jsontrack, "is_playable");
+      if (json_object_object_get_ex(jsontrack, "restrictions", &needle))
+	track->restrictions = json_object_to_json_string(needle);
+      if (json_object_object_get_ex(jsontrack, "linked_from", &needle))
+	track->linked_from_uri = jparse_str_from_obj(needle, "uri");
+    }
+  else
+    track->is_playable = true;
+}
+
+static int
+get_year_from_date(const char *date)
+{
+  char tmp[5];
+  uint32_t year = 0;
+
+  if (date && strlen(date) >= 4)
+    {
+      strncpy(tmp, date, sizeof(tmp));
+      tmp[4] = '\0';
+      safe_atou32(tmp, &year);
+    }
+
+  return year;
+}
+
+static void
+parse_metadata_album(json_object *jsonalbum, struct spotify_album *album)
+{
+  json_object* jsonartists;
+
+  if (json_object_object_get_ex(jsonalbum, "artists", &jsonartists))
+    {
+      album->artist = jparse_str_from_array(jsonartists, 0, "name");
+    }
+  album->name = jparse_str_from_obj(jsonalbum, "name");
+  album->uri = jparse_str_from_obj(jsonalbum, "uri");
+  album->id = jparse_str_from_obj(jsonalbum, "id");
+
+  album->album_type = jparse_str_from_obj(jsonalbum, "album_type");
+  album->is_compilation = (album->album_type && 0 == strcmp(album->album_type, "compilation"));
+
+  album->label = jparse_str_from_obj(jsonalbum, "label");
+
+  album->release_date = jparse_str_from_obj(jsonalbum, "release_date");
+  album->release_date_precision = jparse_str_from_obj(jsonalbum, "release_date_precision");
+  album->release_year = get_year_from_date(album->release_date);
+
+  // TODO Genre is an array of strings ('genres'), but it is always empty (https://github.com/spotify/web-api/issues/157)
+  //album->genre = jparse_str_from_obj(jsonalbum, "genre");
+}
+
+int
+spotifywebapi_saved_albums_fetch(struct spotify_request *request, json_object **jsontracks, int *track_count, struct spotify_album *album)
+{
+  json_object *jsonalbum;
+  json_object *item;
+  json_object *needle;
+
+  memset(album, 0, sizeof(struct spotify_album));
+  *track_count = 0;
+
+  if (request->index >= request->count)
+    {
+      return -1;
+    }
+
+  item = json_object_array_get_idx(request->items, request->index);
+  if (!(item && json_object_object_get_ex(item, "album", &jsonalbum)))
+    {
+      DPRINTF(E_LOG, L_SPOTIFY, "Unexpected JSON: Item %d did not have 'album'->'uri'\n", request->index);
+      request->index++;
+      return -1;
+    }
+
+  parse_metadata_album(jsonalbum, album);
+
+  album->added_at = jparse_str_from_obj(item, "added_at");
+  album->mtime = jparse_time_from_obj(item, "added_at");
+
+  if (json_object_object_get_ex(jsonalbum, "tracks", &needle))
+    {
+      if (jparse_array_from_obj(needle, "items", jsontracks) == 0)
+	{
+	  *track_count = json_object_array_length(*jsontracks);
+	}
+    }
+
+  request->index++;
+
+  return 0;
+}
+
+int
+spotifywebapi_album_track_fetch(json_object *jsontracks, int index, struct spotify_track *track)
+{
+  json_object *jsontrack;
+
+  memset(track, 0, sizeof(struct spotify_track));
+
+  jsontrack = json_object_array_get_idx(jsontracks, index);
+
+  if (!jsontrack)
+    {
+      return -1;
+    }
+
+  parse_metadata_track(jsontrack, track);
+
+  return 0;
+}
+
+static void
+parse_metadata_playlist(json_object *jsonplaylist, struct spotify_playlist *playlist)
+{
+  json_object *needle;
+
+  playlist->name = jparse_str_from_obj(jsonplaylist, "name");
+  playlist->uri = jparse_str_from_obj(jsonplaylist, "uri");
+  playlist->id = jparse_str_from_obj(jsonplaylist, "id");
+  playlist->href = jparse_str_from_obj(jsonplaylist, "href");
+
+  if (json_object_object_get_ex(jsonplaylist, "owner", &needle))
+    {
+      playlist->owner = jparse_str_from_obj(needle, "id");
+    }
+
+  if (json_object_object_get_ex(jsonplaylist, "tracks", &needle))
+    {
+      playlist->tracks_href = jparse_str_from_obj(needle, "href");
+      playlist->tracks_count = jparse_int_from_obj(needle, "total");
+    }
+}
+
+int
+spotifywebapi_playlists_fetch(struct spotify_request *request, struct spotify_playlist *playlist)
+{
+  json_object *jsonplaylist;
+
+  memset(playlist, 0, sizeof(struct spotify_playlist));
+
+  if (request->index >= request->count)
+    {
+      DPRINTF(E_DBG, L_SPOTIFY, "All playlists processed\n");
+      return -1;
+    }
+
+  jsonplaylist = json_object_array_get_idx(request->items, request->index);
+  if (!jsonplaylist)
+    {
+      DPRINTF(E_LOG, L_SPOTIFY, "Error fetching playlist at index '%d'\n", request->index);
+      return -1;
+    }
+
+  parse_metadata_playlist(jsonplaylist, playlist);
+  request->index++;
+
+  return 0;
+}
+
+/*
+ * Extracts the owner and the id from a spotify playlist uri
+ *
+ * Playlist-uri has the following format: spotify:user:[owner]:playlist:[id]
+ * Owner and plid must be freed by the caller.
+ */
+static int
+get_owner_plid_from_uri(const char *uri, char **owner, char **plid)
+{
+  char *ptr1;
+  char *ptr2;
+  char *tmp;
+  size_t len;
+
+  ptr1 = strchr(uri, ':');
+  if (!ptr1)
+    return -1;
+  ptr1++;
+  ptr1 = strchr(ptr1, ':');
+  if (!ptr1)
+    return -1;
+  ptr1++;
+  ptr2 = strchr(ptr1, ':');
+
+  len = ptr2 - ptr1;
+
+  tmp = malloc(sizeof(char) * (len + 1));
+  strncpy(tmp, ptr1, len);
+  tmp[len] = '\0';
+  *owner = tmp;
+
+  ptr2++;
+  ptr1 = strchr(ptr2, ':');
+  if (!ptr1)
+    {
+      free(tmp);
+      return -1;
+    }
+  ptr1++;
+  *plid = strdup(ptr1);
+
+  return 0;
+}
+
+int
+spotifywebapi_playlisttracks_fetch(struct spotify_request *request, struct spotify_track *track)
+{
+  json_object *item;
+  json_object *jsontrack;
+
+  memset(track, 0, sizeof(struct spotify_track));
+
+  if (request->index >= request->count)
+    {
+      return -1;
+    }
+
+  item = json_object_array_get_idx(request->items, request->index);
+  if (!(item && json_object_object_get_ex(item, "track", &jsontrack)))
+    {
+      DPRINTF(E_LOG, L_SPOTIFY, "Unexpected JSON: Item %d did not have 'track'->'uri'\n", request->index);
+      request->index++;
+      return -1;
+    }
+
+  parse_metadata_track(jsontrack, track);
+  track->added_at = jparse_str_from_obj(item, "added_at");
+  track->mtime = jparse_time_from_obj(item, "added_at");
+
+  request->index++;
+
+  return 0;
+}
+
+int
+spotifywebapi_playlist_start(struct spotify_request *request, const char *path, struct spotify_playlist *playlist)
+{
+  char uri[1024];
+  char *owner;
+  char *id;
+  int ret;
+
+  ret = get_owner_plid_from_uri(path, &owner, &id);
+  if (ret < 0)
+    {
+      DPRINTF(E_LOG, L_SPOTIFY, "Error extracting owner and id from playlist uri '%s'\n", path);
+      return -1;
+    }
+
+  ret = snprintf(uri, sizeof(uri), spotify_playlist_uri, owner, id);
+  if (ret < 0 || ret >= sizeof(uri))
+    {
+      DPRINTF(E_LOG, L_SPOTIFY, "Error creating playlist endpoint uri for playlist '%s'\n", path);
+      free(owner);
+      free(id);
+      return -1;
+    }
+
+  ret = request_uri(request, uri);
+  if (ret < 0)
+    {
+      free(owner);
+      free(id);
+      return -1;
+    }
+
+  request->haystack = json_tokener_parse(request->response_body);
+  parse_metadata_playlist(request->haystack, playlist);
+
+  free(owner);
+  free(id);
+  return 0;
+}
+
+static int
+request_user_country()
+{
+  struct spotify_request request;
+  int ret;
+
+  free(spotify_user_country);
+  spotify_user_country = NULL;
+
+  ret = request_uri(&request, spotify_me_uri);
+
+  if (ret < 0)
+    {
+      DPRINTF(E_LOG, L_SPOTIFY, "Failed to read user country\n");
+    }
+  else
+    {
+      spotify_user_country = safe_strdup(jparse_str_from_obj(request.haystack, "country"));
+      DPRINTF(E_DBG, L_SPOTIFY, "User country: '%s'\n", spotify_user_country);
+    }
+
+  spotifywebapi_request_end(&request);
+
+  return 0;
+}
+
+char *
+spotifywebapi_oauth_uri_get(const char *redirect_uri)
+{
+  struct keyval kv;
+  char *param;
+  char *uri;
+  int uri_len;
+  int ret;
+
+  uri = NULL;
+  memset(&kv, 0, sizeof(struct keyval));
+  ret = ( (keyval_add(&kv, "client_id", spotify_client_id) == 0) &&
+	  (keyval_add(&kv, "response_type", "code") == 0) &&
+	  (keyval_add(&kv, "redirect_uri", redirect_uri) == 0) &&
+	  (keyval_add(&kv, "scope", "user-read-private playlist-read-private user-library-read") == 0) &&
+	  (keyval_add(&kv, "show_dialog", "false") == 0) );
+  if (!ret)
+    {
+      DPRINTF(E_LOG, L_SPOTIFY, "Cannot display Spotify oath interface (error adding parameters to keyval)\n");
+      goto out_clear_kv;
+    }
+
+  param = http_form_urlencode(&kv);
+  if (param)
+    {
+      uri_len = strlen(spotify_auth_uri) + strlen(param) + 3;
+      uri = calloc(uri_len, sizeof(char));
+      snprintf(uri, uri_len, "%s/?%s", spotify_auth_uri, param);
+
+      free(param);
+    }
+
+ out_clear_kv:
+  keyval_clear(&kv);
+
+  return uri;
+}
+
+static int
+tokens_get(struct keyval *kv, const char **err)
+{
+  struct http_client_ctx ctx;
+  char *param;
+  char *body;
+  json_object *haystack;
+  const char *tmp;
+  int ret;
+
+  param = http_form_urlencode(kv);
+  if (!param)
+    {
+      *err = "http_form_uriencode() failed";
+      ret = -1;
+      goto out_clear_kv;
+    }
+
+  memset(&ctx, 0, sizeof(struct http_client_ctx));
+  ctx.url = (char *)spotify_token_uri;
+  ctx.output_body = param;
+  ctx.input_body = evbuffer_new();
+
+  ret = http_client_request(&ctx);
+  if (ret < 0)
+    {
+      *err = "Did not get a reply from Spotify";
+      goto out_free_input_body;
+    }
+
+  // 0-terminate for safety
+  evbuffer_add(ctx.input_body, "", 1);
+
+  body = (char *)evbuffer_pullup(ctx.input_body, -1);
+  if (!body || (strlen(body) == 0))
+    {
+      *err = "The reply from Spotify is empty or invalid";
+      ret = -1;
+      goto out_free_input_body;
+    }
+
+  DPRINTF(E_DBG, L_SPOTIFY, "Token reply: %s\n", body);
+
+  haystack = json_tokener_parse(body);
+  if (!haystack)
+    {
+      *err = "JSON parser returned an error";
+      ret = -1;
+      goto out_free_input_body;
+    }
+
+  free(spotify_access_token);
+  spotify_access_token = NULL;
+
+  tmp = jparse_str_from_obj(haystack, "access_token");
+  if (tmp)
+    spotify_access_token = strdup(tmp);
+
+  tmp = jparse_str_from_obj(haystack, "refresh_token");
+  if (tmp)
+    {
+      free(spotify_refresh_token);
+      spotify_refresh_token = strdup(tmp);
+    }
+
+  expires_in = jparse_int_from_obj(haystack, "expires_in");
+  if (expires_in == 0)
+    expires_in = 3600;
+
+  jparse_free(haystack);
+
+  if (!spotify_access_token)
+    {
+      DPRINTF(E_LOG, L_SPOTIFY, "Could not find access token in reply: %s\n", body);
+
+      *err = "Could not find access token in Spotify reply (see log)";
+      ret = -1;
+      goto out_free_input_body;
+    }
+
+  token_requested = time(NULL);
+
+  if (spotify_refresh_token)
+    db_admin_set("spotify_refresh_token", spotify_refresh_token);
+
+  request_user_country();
+
+  ret = 0;
+
+ out_free_input_body:
+  evbuffer_free(ctx.input_body);
+  free(param);
+ out_clear_kv:
+
+  return ret;
+}
+
+int
+spotifywebapi_token_get(const char *code, const char *redirect_uri, const char **err)
+{
+  struct keyval kv;
+  int ret;
+
+  *err = "";
+  memset(&kv, 0, sizeof(struct keyval));
+  ret = ( (keyval_add(&kv, "grant_type", "authorization_code") == 0) &&
+          (keyval_add(&kv, "code", code) == 0) &&
+          (keyval_add(&kv, "client_id", spotify_client_id) == 0) &&
+          (keyval_add(&kv, "client_secret", spotify_client_secret) == 0) &&
+          (keyval_add(&kv, "redirect_uri", redirect_uri) == 0) );
+
+  if (!ret)
+    {
+      *err = "Add parameters to keyval failed";
+      ret = -1;
+    }
+  else
+    ret = tokens_get(&kv, err);
+
+  keyval_clear(&kv);
+
+  return ret;
+}
+
+int
+spotifywebapi_token_refresh()
+{
+  struct keyval kv;
+  char *refresh_token;
+  const char *err;
+  int ret;
+
+  if (token_requested && difftime(time(NULL), token_requested) < expires_in)
+    {
+      DPRINTF(E_DBG, L_SPOTIFY, "Spotify token still valid\n");
+      return 0;
+    }
+
+  refresh_token = db_admin_get("spotify_refresh_token");
+  if (!refresh_token)
+    {
+      DPRINTF(E_LOG, L_SPOTIFY, "No spotify refresh token found\n");
+      return -1;
+    }
+
+  DPRINTF(E_DBG, L_SPOTIFY, "Spotify refresh-token: '%s'\n", refresh_token);
+
+  memset(&kv, 0, sizeof(struct keyval));
+  ret = ( (keyval_add(&kv, "grant_type", "refresh_token") == 0) &&
+	  (keyval_add(&kv, "client_id", spotify_client_id) == 0) &&
+	  (keyval_add(&kv, "client_secret", spotify_client_secret) == 0) &&
+          (keyval_add(&kv, "refresh_token", refresh_token) == 0) );
+  if (!ret)
+    {
+      DPRINTF(E_LOG, L_SPOTIFY, "Add parameters to keyval failed");
+      ret = -1;
+    }
+  else
+    ret = tokens_get(&kv, &err);
+
+  free(refresh_token);
+  keyval_clear(&kv);
+
+  return ret;
+}
+
diff --git a/src/spotify_webapi.h b/src/spotify_webapi.h
new file mode 100644
index 0000000..a195217
--- /dev/null
+++ b/src/spotify_webapi.h
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2016 Espen Jürgensen <espenjurgensen at gmail.com>
+ * Copyright (C) 2016 Christian Meffert <christian.meffert at googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef SRC_SPOTIFY_WEBAPI_H_
+#define SRC_SPOTIFY_WEBAPI_H_
+
+#include <event2/event.h>
+#include <json.h>
+#include <stdbool.h>
+
+#include "http.h"
+
+#define SPOTIFY_WEBAPI_SAVED_ALBUMS "https://api.spotify.com/v1/me/albums?limit=50"
+#define SPOTIFY_WEBAPI_SAVED_PLAYLISTS "https://api.spotify.com/v1/me/playlists?limit=50"
+
+struct spotify_album
+{
+  const char *added_at;
+  time_t mtime;
+
+  const char *album_type;
+  bool is_compilation;
+  const char *artist;
+  const char *genre;
+  const char *id;
+  const char *label;
+  const char *name;
+  const char *release_date;
+  const char *release_date_precision;
+  int release_year;
+  const char *uri;
+};
+
+struct spotify_track
+{
+  const char *added_at;
+  time_t mtime;
+
+  const char *album;
+  const char *album_artist;
+  const char *artist;
+  int disc_number;
+  const char *album_type;
+  bool is_compilation;
+  int duration_ms;
+  const char *id;
+  const char *name;
+  int track_number;
+  const char *uri;
+
+  bool is_playable;
+  const char *restrictions;
+  const char *linked_from_uri;
+};
+
+struct spotify_playlist
+{
+  const char *id;
+  const char *name;
+  const char *owner;
+  const char *uri;
+
+  const char *href;
+
+  const char *tracks_href;
+  int tracks_count;
+};
+
+struct spotify_request
+{
+  struct http_client_ctx *ctx;
+  char *response_body;
+  json_object *haystack;
+  json_object *items;
+  int count;
+  int total;
+  const char *next_uri;
+
+  int index;
+};
+
+char *
+spotifywebapi_oauth_uri_get(const char *redirect_uri);
+int
+spotifywebapi_token_get(const char *code, const char *redirect_uri, const char **err);
+int
+spotifywebapi_token_refresh();
+
+void
+spotifywebapi_request_end(struct spotify_request *request);
+int
+spotifywebapi_request_next(struct spotify_request *request, const char *uri, bool append_market);
+int
+spotifywebapi_saved_albums_fetch(struct spotify_request *request, json_object **jsontracks, int *track_count, struct spotify_album *album);
+int
+spotifywebapi_album_track_fetch(json_object *jsontracks, int index, struct spotify_track *track);
+int
+spotifywebapi_playlists_fetch(struct spotify_request *request, struct spotify_playlist* playlist);
+int
+spotifywebapi_playlisttracks_fetch(struct spotify_request *request, struct spotify_track *track);
+int
+spotifywebapi_playlist_start(struct spotify_request *request, const char *path, struct spotify_playlist *playlist);
+
+#endif /* SRC_SPOTIFY_WEBAPI_H_ */
diff --git a/src/transcode.c b/src/transcode.c
index b6ee21b..3d1de18 100644
--- a/src/transcode.c
+++ b/src/transcode.c
@@ -33,11 +33,7 @@
 #include <libavutil/time.h>
 #include <libavutil/pixdesc.h>
 
-#ifdef HAVE_LIBAVFILTER
-# include <libavfilter/avcodec.h>
-#else
-# include "ffmpeg-compat.h"
-#endif
+#include "ffmpeg-compat.h"
 
 #include "logger.h"
 #include "conffile.h"
@@ -79,6 +75,9 @@ struct decode_ctx {
   // Duration (used to make wav header)
   uint32_t duration;
 
+  // Data kind (used to determine if ICY metadata is relevant to look for)
+  enum data_kind data_kind;
+
   // Contains the most recent packet from av_read_frame
   // Used for resuming after seek and for freeing correctly
   // in transcode_decode()
@@ -402,7 +401,7 @@ encode_write_frame(struct encode_ctx *ctx, AVFrame *filt_frame, unsigned int str
   return ret;
 }
 
-#if defined(HAVE_LIBAV_BUFFERSRC_ADD_FRAME_FLAGS) && defined(HAVE_LIBAV_BUFFERSINK_GET_FRAME)
+#if HAVE_DECL_AV_BUFFERSRC_ADD_FRAME_FLAGS && HAVE_DECL_AV_BUFFERSINK_GET_FRAME
 static int
 filter_encode_write_frame(struct encode_ctx *ctx, AVFrame *frame, unsigned int stream_index)
 {
@@ -580,7 +579,7 @@ flush_encoder(struct encode_ctx *ctx, unsigned int stream_index)
 /* --------------------------- INPUT/OUTPUT INIT --------------------------- */
 
 static int
-open_input(struct decode_ctx *ctx, struct media_file_info *mfi, int decode_video)
+open_input(struct decode_ctx *ctx, const char *path, int decode_video)
 {
   AVDictionary *options;
   AVCodec *decoder;
@@ -597,10 +596,10 @@ open_input(struct decode_ctx *ctx, struct media_file_info *mfi, int decode_video
 
 # ifndef HAVE_FFMPEG
   // Without this, libav is slow to probe some internet streams, which leads to RAOP timeouts
-  if (mfi->data_kind == DATA_KIND_HTTP)
+  if (ctx->data_kind == DATA_KIND_HTTP)
     ctx->ifmt_ctx->probesize = 64000;
 # endif
-  if (mfi->data_kind == DATA_KIND_HTTP)
+  if (ctx->data_kind == DATA_KIND_HTTP)
     av_dict_set(&options, "icy", "1", 0);
 
   // TODO Newest versions of ffmpeg have timeout and reconnect options we should use
@@ -608,14 +607,14 @@ open_input(struct decode_ctx *ctx, struct media_file_info *mfi, int decode_video
   ctx->ifmt_ctx->interrupt_callback.opaque = ctx;
   ctx->timestamp = av_gettime();
 
-  ret = avformat_open_input(&ctx->ifmt_ctx, mfi->path, NULL, &options);
+  ret = avformat_open_input(&ctx->ifmt_ctx, path, NULL, &options);
 
   if (options)
     av_dict_free(&options);
 
   if (ret < 0)
     {
-      DPRINTF(E_LOG, L_XCODE, "Cannot open '%s': %s\n", mfi->path, err2str(ret));
+      DPRINTF(E_LOG, L_XCODE, "Cannot open '%s': %s\n", path, err2str(ret));
       return -1;
     }
 
@@ -628,7 +627,7 @@ open_input(struct decode_ctx *ctx, struct media_file_info *mfi, int decode_video
 
   if (ctx->ifmt_ctx->nb_streams > MAX_STREAMS)
     {
-      DPRINTF(E_LOG, L_XCODE, "File '%s' has too many streams (%u)\n", mfi->path, ctx->ifmt_ctx->nb_streams);
+      DPRINTF(E_LOG, L_XCODE, "File '%s' has too many streams (%u)\n", path, ctx->ifmt_ctx->nb_streams);
       goto out_fail;
     }
 
@@ -636,7 +635,7 @@ open_input(struct decode_ctx *ctx, struct media_file_info *mfi, int decode_video
   stream_index = av_find_best_stream(ctx->ifmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, &decoder, 0);
   if ((stream_index < 0) || (!decoder))
     {
-      DPRINTF(E_LOG, L_XCODE, "Did not find audio stream or suitable decoder for %s\n", mfi->path);
+      DPRINTF(E_LOG, L_XCODE, "Did not find audio stream or suitable decoder for %s\n", path);
       goto out_fail;
     }
 
@@ -664,7 +663,7 @@ open_input(struct decode_ctx *ctx, struct media_file_info *mfi, int decode_video
   stream_index = av_find_best_stream(ctx->ifmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &decoder, 0);
   if ((stream_index < 0) || (!decoder))
     {
-      DPRINTF(E_LOG, L_XCODE, "Did not find video stream or suitable decoder for '%s': %s\n", mfi->path, err2str(ret));
+      DPRINTF(E_LOG, L_XCODE, "Did not find video stream or suitable decoder for '%s': %s\n", path, err2str(ret));
       return 0;
     }
 
@@ -857,7 +856,7 @@ close_output(struct encode_ctx *ctx)
   avformat_free_context(ctx->ofmt_ctx);
 }
 
-#ifdef HAVE_LIBAV_GRAPH_PARSE_PTR
+#if HAVE_DECL_AVFILTER_GRAPH_PARSE_PTR
 static int
 open_filter(struct filter_ctx *filter_ctx, AVCodecContext *dec_ctx, AVCodecContext *enc_ctx, const char *filter_spec)
 {
@@ -922,7 +921,6 @@ open_filter(struct filter_ctx *filter_ctx, AVCodecContext *dec_ctx, AVCodecConte
       if (!buffersrc || !buffersink)
 	{
 	  DPRINTF(E_LOG, L_XCODE, "Filtering source or sink element not found\n");
-	  ret = AVERROR_UNKNOWN;
 	  goto out_fail;
 	}
 
@@ -1091,7 +1089,6 @@ open_filter(struct filter_ctx *filter_ctx, AVCodecContext *dec_ctx, AVCodecConte
       if (!buffersrc || !format || !buffersink)
 	{
 	  DPRINTF(E_LOG, L_XCODE, "Filtering source, format or sink element not found\n");
-	  ret = AVERROR_UNKNOWN;
 	  goto out_fail;
 	}
 
@@ -1232,7 +1229,7 @@ close_filters(struct encode_ctx *ctx)
 /*                                  Setup                                    */
 
 struct decode_ctx *
-transcode_decode_setup(struct media_file_info *mfi, int decode_video)
+transcode_decode_setup(enum data_kind data_kind, const char *path, uint32_t song_length, int decode_video)
 {
   struct decode_ctx *ctx;
 
@@ -1243,14 +1240,15 @@ transcode_decode_setup(struct media_file_info *mfi, int decode_video)
       return NULL;
     }
 
-  if (open_input(ctx, mfi, decode_video) < 0)
+  ctx->duration = song_length;
+  ctx->data_kind = data_kind;
+
+  if (open_input(ctx, path, decode_video) < 0)
     {
       free(ctx);
       return NULL;
     }
 
-  ctx->duration = mfi->song_length;
-
   av_init_packet(&ctx->packet);
 
   return ctx;
@@ -1281,7 +1279,8 @@ transcode_encode_setup(struct decode_ctx *src_ctx, enum transcode_profile profil
       return NULL;
     }
 
-  ctx->icy_interval = METADATA_ICY_INTERVAL * ctx->channels * ctx->byte_depth * ctx->sample_rate;
+  if (src_ctx->data_kind == DATA_KIND_HTTP)
+    ctx->icy_interval = METADATA_ICY_INTERVAL * ctx->channels * ctx->byte_depth * ctx->sample_rate;
 
   if (profile == XCODE_PCM16_HEADER)
     {
@@ -1293,7 +1292,7 @@ transcode_encode_setup(struct decode_ctx *src_ctx, enum transcode_profile profil
 }
 
 struct transcode_ctx *
-transcode_setup(struct media_file_info *mfi, enum transcode_profile profile, off_t *est_size)
+transcode_setup(enum data_kind data_kind, const char *path, uint32_t song_length, enum transcode_profile profile, off_t *est_size)
 {
   struct transcode_ctx *ctx;
 
@@ -1304,7 +1303,7 @@ transcode_setup(struct media_file_info *mfi, enum transcode_profile profile, off
       return NULL;
     }
 
-  ctx->decode_ctx = transcode_decode_setup(mfi, profile & XCODE_HAS_VIDEO);
+  ctx->decode_ctx = transcode_decode_setup(data_kind, path, song_length, profile & XCODE_HAS_VIDEO);
   if (!ctx->decode_ctx)
     {
       free(ctx);
@@ -1339,6 +1338,7 @@ transcode_decode_setup_raw(void)
   if (!ctx->ifmt_ctx)
     {
       DPRINTF(E_LOG, L_XCODE, "Out of memory for decode format ctx\n");
+      free(ctx);
       return NULL;
     }
 
@@ -1348,6 +1348,8 @@ transcode_decode_setup_raw(void)
   if (!ctx->audio_stream)
     {
       DPRINTF(E_LOG, L_XCODE, "Could not create stream with PCM16 decoder\n");
+      avformat_free_context(ctx->ifmt_ctx);
+      free(ctx);
       return NULL;
     }
 
@@ -1524,7 +1526,6 @@ transcode_decode(struct decoded_frame **decoded, struct decode_ctx *ctx)
 	  // with empty input until no more frames are returned
 	  DPRINTF(E_DBG, L_XCODE, "Could not read packet, will flush decoders\n");
 
-	  used = 1;
 	  got_frame = flush_decoder(frame, &in_stream, &stream_index, ctx);
 	  if (got_frame)
 	    break;
@@ -1573,18 +1574,21 @@ transcode_decode(struct decoded_frame **decoded, struct decode_ctx *ctx)
     }
   while (!got_frame);
 
-  // Return the decoded frame and stream index
-  *decoded = malloc(sizeof(struct decoded_frame));
-  if (!*decoded)
+  if (got_frame > 0)
     {
-      DPRINTF(E_LOG, L_XCODE, "Out of memory for decoded result\n");
+      // Return the decoded frame and stream index
+      *decoded = malloc(sizeof(struct decoded_frame));
+      if (!(*decoded))
+	{
+	  DPRINTF(E_LOG, L_XCODE, "Out of memory for decoded result\n");
 
-      av_frame_free(&frame);
-      return -1;
-    }
+	  av_frame_free(&frame);
+	  return -1;
+	}
 
-  (*decoded)->frame = frame;
-  (*decoded)->stream_index = stream_index;
+      (*decoded)->frame = frame;
+      (*decoded)->stream_index = stream_index;
+    }
 
   return got_frame;
 }
@@ -1630,6 +1634,8 @@ transcode(struct evbuffer *evbuf, int wanted, struct transcode_ctx *ctx, int *ic
   int processed;
   int ret;
 
+  *icy_timer = 0;
+
   processed = 0;
   while (processed < wanted)
     {
@@ -1646,7 +1652,8 @@ transcode(struct evbuffer *evbuf, int wanted, struct transcode_ctx *ctx, int *ic
     }
 
   ctx->encode_ctx->total_bytes += processed;
-  *icy_timer = (ctx->encode_ctx->total_bytes % ctx->encode_ctx->icy_interval < processed);
+  if (ctx->encode_ctx->icy_interval)
+    *icy_timer = (ctx->encode_ctx->total_bytes % ctx->encode_ctx->icy_interval < processed);
 
   return processed;
 }
@@ -1659,10 +1666,17 @@ transcode_raw2frame(uint8_t *data, size_t size)
   int ret;
 
   decoded = malloc(sizeof(struct decoded_frame));
+  if (!decoded)
+    {
+      DPRINTF(E_LOG, L_XCODE, "Out of memory for decoded struct\n");
+      return NULL;
+    }
+
   frame = av_frame_alloc();
-  if (!decoded || !frame)
+  if (!frame)
     {
-      DPRINTF(E_LOG, L_XCODE, "Out of memory for decoded struct or frame\n");
+      DPRINTF(E_LOG, L_XCODE, "Out of memory for frame\n");
+      free(decoded);
       return NULL;
     }
 
@@ -1682,6 +1696,7 @@ transcode_raw2frame(uint8_t *data, size_t size)
   if (ret < 0)
     {
       DPRINTF(E_LOG, L_XCODE, "Error filling frame with rawbuf: %s\n", err2str(ret));
+      transcode_decoded_free(decoded);
       return NULL;
     }
 
@@ -1776,6 +1791,10 @@ transcode_seek(struct transcode_ctx *ctx, int ms)
   got_pts = av_rescale_q(got_pts, in_stream->time_base, AV_TIME_BASE_Q);
   got_ms = got_pts / (AV_TIME_BASE / 1000);
 
+  // Since negative return would mean error, we disallow it here
+  if (got_ms < 0)
+    got_ms = 0;
+
   DPRINTF(E_DBG, L_XCODE, "Seek wanted %d ms, got %d ms\n", ms, got_ms);
 
   return got_ms;
@@ -1803,24 +1822,3 @@ transcode_metadata(struct transcode_ctx *ctx, int *changed)
   return m;
 }
 
-char *
-transcode_metadata_artwork_url(struct transcode_ctx *ctx)
-{
-  struct http_icy_metadata *m;
-  char *artwork_url;
-
-  if (!ctx->decode_ctx->ifmt_ctx || !ctx->decode_ctx->ifmt_ctx->filename)
-    return NULL;
-
-  artwork_url = NULL;
-
-  m = http_icy_metadata_get(ctx->decode_ctx->ifmt_ctx, 1);
-  if (m && m->artwork_url)
-    artwork_url = strdup(m->artwork_url);
-
-  if (m)
-    http_icy_metadata_free(m, 0);
-
-  return artwork_url;
-}
-
diff --git a/src/transcode.h b/src/transcode.h
index 0e1687a..3a8614f 100644
--- a/src/transcode.h
+++ b/src/transcode.h
@@ -28,13 +28,13 @@ struct decoded_frame;
 
 // Setting up
 struct decode_ctx *
-transcode_decode_setup(struct media_file_info *mfi, int decode_video);
+transcode_decode_setup(enum data_kind data_kind, const char *path, uint32_t song_length, int decode_video);
 
 struct encode_ctx *
 transcode_encode_setup(struct decode_ctx *src_ctx, enum transcode_profile profile, off_t *est_size);
 
 struct transcode_ctx *
-transcode_setup(struct media_file_info *mfi, enum transcode_profile profile, off_t *est_size);
+transcode_setup(enum data_kind data_kind, const char *path, uint32_t song_length, enum transcode_profile profile, off_t *est_size);
 
 struct decode_ctx *
 transcode_decode_setup_raw(void);
@@ -100,7 +100,4 @@ transcode_seek(struct transcode_ctx *ctx, int ms);
 struct http_icy_metadata *
 transcode_metadata(struct transcode_ctx *ctx, int *changed);
 
-char *
-transcode_metadata_artwork_url(struct transcode_ctx *ctx);
-
 #endif /* !__TRANSCODE_H__ */
diff --git a/src/worker.c b/src/worker.c
index a7cc9ee..20a7146 100644
--- a/src/worker.c
+++ b/src/worker.c
@@ -142,23 +142,27 @@ worker_execute(void (*cb)(void *), void *cb_arg, size_t arg_size, int delay)
 
   DPRINTF(E_DBG, L_MAIN, "Got worker execute request\n");
 
-  cmdarg = (struct worker_arg *)malloc(sizeof(struct worker_arg));
+  cmdarg = calloc(1, sizeof(struct worker_arg));
   if (!cmdarg)
     {
       DPRINTF(E_LOG, L_MAIN, "Could not allocate worker_arg\n");
       return;
     }
 
-  memset(cmdarg, 0, sizeof(struct worker_arg));
-
-  argcpy = malloc(arg_size);
-  if (!argcpy)
+  if (arg_size > 0)
     {
-      DPRINTF(E_LOG, L_MAIN, "Out of memory\n");
-      return;
+      argcpy = malloc(arg_size);
+      if (!argcpy)
+	{
+	  DPRINTF(E_LOG, L_MAIN, "Out of memory\n");
+	  free(cmdarg);
+	  return;
+	}
+
+      memcpy(argcpy, cb_arg, arg_size);
     }
-
-  memcpy(argcpy, cb_arg, arg_size);
+  else
+    argcpy = NULL;
 
   cmdarg->cb = cb;
   cmdarg->cb_arg = argcpy;

-- 
forked-daapd packaging



More information about the pkg-multimedia-commits mailing list