diff --git a/NEWS b/NEWS
index 67f7b647f..a04b255ee 100644
--- a/NEWS
+++ b/NEWS
@@ -4,12 +4,22 @@ ver 0.20 (not yet released)
 * output
   - pulse: set channel map to WAVE-EX
 
-ver 0.19.3 (not yet released)
+ver 0.19.3 (2014/11/11)
+* protocol
+  - fix "(null)" result string to "list" when AlbumArtist is disabled
 * database
   - upnp: fix breakage due to malformed URIs
+* input
+  - curl: another fix for redirected streams
 * decoder
   - audiofile: fix crash while playing streams
+  - audiofile: fix bit rate calculation
   - ffmpeg: support opus
+  - opus: fix bogus duration on streams
+  - opus: support chained streams
+  - opus: improved error logging
+* fix distorted audio with soxr resampler
+* fix build failure on Mac OS X with non-Apple compilers
 
 ver 0.19.2 (2014/11/02)
 * input
diff --git a/src/Main.cxx b/src/Main.cxx
index 2719c05e0..26d4e7ae4 100644
--- a/src/Main.cxx
+++ b/src/Main.cxx
@@ -114,7 +114,7 @@
 #include <ws2tcpip.h>
 #endif
 
-#ifdef __APPLE__
+#ifdef __BLOCKS__
 #include <dispatch/dispatch.h>
 #endif
 
@@ -517,7 +517,7 @@ int mpd_main(int argc, char *argv[])
 	daemonize_begin(options.daemon);
 #endif
 
-#ifdef __APPLE__
+#ifdef __BLOCKS__
 	/* Runs the OS X native event loop in the main thread, and runs
 	   the rest of mpd_main on a new thread. This lets CoreAudio receive
 	   route change notifications (e.g. plugging or unplugging headphones).
diff --git a/src/db/Count.cxx b/src/db/Count.cxx
index b58ac53e9..e5e244a6a 100644
--- a/src/db/Count.cxx
+++ b/src/db/Count.cxx
@@ -23,7 +23,7 @@
 #include "Interface.hxx"
 #include "client/Client.hxx"
 #include "LightSong.hxx"
-#include "tag/Set.hxx"
+#include "tag/Tag.hxx"
 
 #include <functional>
 #include <map>
diff --git a/src/decoder/plugins/AudiofileDecoderPlugin.cxx b/src/decoder/plugins/AudiofileDecoderPlugin.cxx
index 4b347817e..a0ef71e49 100644
--- a/src/decoder/plugins/AudiofileDecoderPlugin.cxx
+++ b/src/decoder/plugins/AudiofileDecoderPlugin.cxx
@@ -209,7 +209,7 @@ audiofile_stream_decode(Decoder &decoder, InputStream &is)
 	const auto total_time = audiofile_get_duration(fh);
 
 	const uint16_t kbit_rate = (uint16_t)
-		(is.GetSize() * uint64_t(8000) / total_time.ToMS());
+		(is.GetSize() * uint64_t(8) / total_time.ToMS());
 
 	const unsigned frame_size = (unsigned)
 		afGetVirtualFrameSize(fh, AF_DEFAULT_TRACK, true);
diff --git a/src/decoder/plugins/OpusDecoderPlugin.cxx b/src/decoder/plugins/OpusDecoderPlugin.cxx
index 97c81984c..b6b2e0465 100644
--- a/src/decoder/plugins/OpusDecoderPlugin.cxx
+++ b/src/decoder/plugins/OpusDecoderPlugin.cxx
@@ -40,6 +40,12 @@
 
 static constexpr opus_int32 opus_sample_rate = 48000;
 
+/**
+ * Allocate an output buffer for 16 bit PCM samples big enough to hold
+ * a quarter second, larger than 120ms required by libopus.
+ */
+static constexpr unsigned opus_output_buffer_frames = opus_sample_rate / 4;
+
 gcc_pure
 static bool
 IsOpusHead(const ogg_packet &packet)
@@ -70,10 +76,16 @@ class MPDOpusDecoder {
 
 	OpusDecoder *opus_decoder;
 	opus_int16 *output_buffer;
-	unsigned output_size;
+
+	/**
+	 * If non-zero, then a previous Opus stream has been found
+	 * already with this number of channels.  If opus_decoder is
+	 * nullptr, then its end-of-stream packet has been found
+	 * already.
+	 */
+	unsigned previous_channels;
 
 	bool os_initialized;
-	bool found_opus;
 
 	int opus_serialno;
 
@@ -86,8 +98,9 @@ public:
 		       InputStream &_input_stream)
 		:decoder(_decoder), input_stream(_input_stream),
 		 opus_decoder(nullptr),
-		 output_buffer(nullptr), output_size(0),
-		 os_initialized(false), found_opus(false) {}
+		 output_buffer(nullptr),
+		 previous_channels(0),
+		 os_initialized(false) {}
 	~MPDOpusDecoder();
 
 	bool ReadFirstPage(OggSyncState &oy);
@@ -96,6 +109,7 @@ public:
 	DecoderCommand HandlePackets();
 	DecoderCommand HandlePacket(const ogg_packet &packet);
 	DecoderCommand HandleBOS(const ogg_packet &packet);
+	DecoderCommand HandleEOS();
 	DecoderCommand HandleTags(const ogg_packet &packet);
 	DecoderCommand HandleAudio(const ogg_packet &packet);
 
@@ -159,12 +173,14 @@ inline DecoderCommand
 MPDOpusDecoder::HandlePacket(const ogg_packet &packet)
 {
 	if (packet.e_o_s)
-		return DecoderCommand::STOP;
+		return HandleEOS();
 
 	if (packet.b_o_s)
 		return HandleBOS(packet);
-	else if (!found_opus)
+	else if (opus_decoder == nullptr) {
+		LogDebug(opus_domain, "BOS packet expected");
 		return DecoderCommand::STOP;
+	}
 
 	if (IsOpusTags(packet))
 		return HandleTags(packet);
@@ -184,7 +200,7 @@ LoadEOSPacket(InputStream &is, Decoder *decoder, int serialno,
 		/* we do this for local files only, because seeking
 		   around remote files is expensive and not worth the
 		   troubl */
-		return -1;
+		return false;
 
 	const auto old_offset = is.GetOffset();
 
@@ -225,19 +241,29 @@ MPDOpusDecoder::HandleBOS(const ogg_packet &packet)
 {
 	assert(packet.b_o_s);
 
-	if (found_opus || !IsOpusHead(packet))
+	if (opus_decoder != nullptr || !IsOpusHead(packet)) {
+		LogDebug(opus_domain, "BOS packet must be OpusHead");
 		return DecoderCommand::STOP;
+	}
 
 	unsigned channels;
 	if (!ScanOpusHeader(packet.packet, packet.bytes, channels) ||
-	    !audio_valid_channel_count(channels))
+	    !audio_valid_channel_count(channels)) {
+		LogDebug(opus_domain, "Malformed BOS packet");
 		return DecoderCommand::STOP;
+	}
 
 	assert(opus_decoder == nullptr);
-	assert(output_buffer == nullptr);
+	assert((previous_channels == 0) == (output_buffer == nullptr));
+
+	if (previous_channels != 0 && channels != previous_channels) {
+		FormatWarning(opus_domain,
+			      "Next stream has different channels (%u -> %u)",
+			      previous_channels, channels);
+		return DecoderCommand::STOP;
+	}
 
 	opus_serialno = os.serialno;
-	found_opus = true;
 
 	/* TODO: parse attributes from the OpusHead (sample rate,
 	   channels, ...) */
@@ -251,6 +277,13 @@ MPDOpusDecoder::HandleBOS(const ogg_packet &packet)
 		return DecoderCommand::STOP;
 	}
 
+	if (previous_channels != 0) {
+		/* decoder was already initialized by the previous
+		   stream; skip the rest of this method */
+		LogDebug(opus_domain, "Found another stream");
+		return decoder_get_command(decoder);
+	}
+
 	eos_granulepos = LoadEOSGranulePos(input_stream, &decoder,
 					   opus_serialno);
 	const auto duration = eos_granulepos >= 0
@@ -258,21 +291,36 @@ MPDOpusDecoder::HandleBOS(const ogg_packet &packet)
 						      opus_sample_rate)
 		: SignedSongTime::Negative();
 
+	previous_channels = channels;
 	const AudioFormat audio_format(opus_sample_rate,
 				       SampleFormat::S16, channels);
 	decoder_initialized(decoder, audio_format,
 			    eos_granulepos > 0, duration);
 	frame_size = audio_format.GetFrameSize();
 
-	/* allocate an output buffer for 16 bit PCM samples big enough
-	   to hold a quarter second, larger than 120ms required by
-	   libopus */
-	output_size = audio_format.sample_rate / 4;
-	output_buffer = new opus_int16[output_size * audio_format.channels];
+	output_buffer = new opus_int16[opus_output_buffer_frames
+				       * audio_format.channels];
 
 	return decoder_get_command(decoder);
 }
 
+inline DecoderCommand
+MPDOpusDecoder::HandleEOS()
+{
+	if (eos_granulepos < 0 && previous_channels != 0) {
+		/* allow chaining of (unseekable) streams */
+		assert(opus_decoder != nullptr);
+		assert(output_buffer != nullptr);
+
+		opus_decoder_destroy(opus_decoder);
+		opus_decoder = nullptr;
+
+		return decoder_get_command(decoder);
+	}
+
+	return DecoderCommand::STOP;
+}
+
 inline DecoderCommand
 MPDOpusDecoder::HandleTags(const ogg_packet &packet)
 {
@@ -304,10 +352,11 @@ MPDOpusDecoder::HandleAudio(const ogg_packet &packet)
 	int nframes = opus_decode(opus_decoder,
 				  (const unsigned char*)packet.packet,
 				  packet.bytes,
-				  output_buffer, output_size,
+				  output_buffer, opus_output_buffer_frames,
 				  0);
 	if (nframes < 0) {
-		LogError(opus_domain, opus_strerror(nframes));
+		FormatError(opus_domain, "libopus error: %s",
+			    opus_strerror(nframes));
 		return DecoderCommand::STOP;
 	}
 
diff --git a/src/input/plugins/CurlInputPlugin.cxx b/src/input/plugins/CurlInputPlugin.cxx
index 1e1a46108..abb7e312c 100644
--- a/src/input/plugins/CurlInputPlugin.cxx
+++ b/src/input/plugins/CurlInputPlugin.cxx
@@ -610,6 +610,10 @@ CurlInputStream::ResponseBoundary()
 	/* undo all effects of HeaderReceived() because the previous
 	   response was not applicable for this stream */
 
+	if (IsSeekPending())
+		/* don't update metadata while seeking */
+		return;
+
 	seekable = false;
 	size = UNKNOWN_SIZE;
 	ClearMimeType();
diff --git a/src/pcm/SoxrResampler.cxx b/src/pcm/SoxrResampler.cxx
index 56b9760d5..b9d6fc099 100644
--- a/src/pcm/SoxrResampler.cxx
+++ b/src/pcm/SoxrResampler.cxx
@@ -147,7 +147,8 @@ SoxrPcmResampler::Resample(ConstBuffer<void> src, Error &error)
 
 	const size_t n_frames = src.size / frame_size;
 
-	const size_t o_frames = size_t(n_frames * ratio + 0.5);
+	/* always round up: worst case output buffer size */
+	const size_t o_frames = size_t(n_frames * ratio) + 1;
 
 	float *output_buffer = (float *)buffer.Get(o_frames * frame_size);
 
diff --git a/src/tag/Set.cxx b/src/tag/Set.cxx
index 47a8423bf..6a55a450f 100644
--- a/src/tag/Set.cxx
+++ b/src/tag/Set.cxx
@@ -19,6 +19,7 @@
 
 #include "Set.hxx"
 #include "TagBuilder.hxx"
+#include "TagSettings.h"
 
 #include <assert.h>
 
@@ -109,6 +110,7 @@ TagSet::InsertUnique(const Tag &tag,
 
 	if (!CheckUnique(type, tag, type, group_mask) &&
 	    (type != TAG_ALBUM_ARTIST ||
+	     ignore_tag_items[TAG_ALBUM_ARTIST] ||
 	     /* fall back to "Artist" if no "AlbumArtist" was found */
 	     !CheckUnique(type, tag, TAG_ARTIST, group_mask)))
 		InsertUnique(tag, type, nullptr, group_mask);
diff --git a/test/FakeDecoderAPI.cxx b/test/FakeDecoderAPI.cxx
index dcc78125b..3c7453a4f 100644
--- a/test/FakeDecoderAPI.cxx
+++ b/test/FakeDecoderAPI.cxx
@@ -132,6 +132,12 @@ decoder_data(gcc_unused Decoder &decoder,
 	     const void *data, size_t datalen,
 	     gcc_unused uint16_t kbit_rate)
 {
+	static uint16_t prev_kbit_rate;
+	if (kbit_rate != prev_kbit_rate) {
+		prev_kbit_rate = kbit_rate;
+		fprintf(stderr, "%u kbit/s\n", kbit_rate);
+	}
+
 	gcc_unused ssize_t nbytes = write(1, data, datalen);
 	return DecoderCommand::NONE;
 }