From cbf79769d396449cfc9e5555c7419cc3f117e8d4 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Sat, 8 Nov 2014 19:21:42 +0100
Subject: [PATCH 01/14] db/Count: include cleanup

---
 src/db/Count.cxx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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>

From b5ba94f1de06c621da937241eedfcfb100f26a09 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Sat, 8 Nov 2014 19:25:01 +0100
Subject: [PATCH 02/14] tag/Set: do AlbumArtist/Artist fallback only if
 AlbumArtist is not disabled

On "list albumartist", songs that have no AlbumArtist tag will use the
Artist tag.  However, if AlbumArtist is disabled via
"metadata_to_use", the TagBuilder::AddItem() call is ignored, and
PrintUniqueTag() attempts to print a nullptr string.

This commit fixes the problem by attempting the fallback only if
AlbumArtist is not disabled.
---
 NEWS            | 2 ++
 src/tag/Set.cxx | 2 ++
 2 files changed, 4 insertions(+)

diff --git a/NEWS b/NEWS
index 94ce8d4ba..f18e9ff0f 100644
--- a/NEWS
+++ b/NEWS
@@ -1,4 +1,6 @@
 ver 0.19.3 (not yet released)
+* protocol
+  - fix "(null)" result string to "list" when AlbumArtist is disabled
 * database
   - upnp: fix breakage due to malformed URIs
 * decoder
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);

From ff6f1655f0441f2fa8a1947bf242ea2cce6ba7a7 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Mon, 10 Nov 2014 08:45:19 +0100
Subject: [PATCH 03/14] input/curl: ignore ResponseBoundary() while seeking

While seeking, metadata must not be updated.  ResponseBoundary() was
added in MPD 0.19.1, but I forgot to add the IsSeeking() check there.
This caused the "seekable" flag to reset.
---
 NEWS                                  | 2 ++
 src/input/plugins/CurlInputPlugin.cxx | 4 ++++
 2 files changed, 6 insertions(+)

diff --git a/NEWS b/NEWS
index f18e9ff0f..3d28b44c8 100644
--- a/NEWS
+++ b/NEWS
@@ -3,6 +3,8 @@ ver 0.19.3 (not yet released)
   - 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
   - ffmpeg: support opus
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();

From 2e47cb12c4f1ee54f37ac2b72bfde23e864b3cbd Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Mon, 10 Nov 2014 09:00:38 +0100
Subject: [PATCH 04/14] test/FakeDecoderAPI: dump bit rate

---
 test/FakeDecoderAPI.cxx | 6 ++++++
 1 file changed, 6 insertions(+)

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;
 }

From 8ff0d99092acfe8e80eba9709c2e93727f9e47b9 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Mon, 10 Nov 2014 09:00:50 +0100
Subject: [PATCH 05/14] decoder/audiofile: fix bit rate calculation

---
 NEWS                                           | 1 +
 src/decoder/plugins/AudiofileDecoderPlugin.cxx | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/NEWS b/NEWS
index 3d28b44c8..933a980db 100644
--- a/NEWS
+++ b/NEWS
@@ -7,6 +7,7 @@ ver 0.19.3 (not yet released)
   - curl: another fix for redirected streams
 * decoder
   - audiofile: fix crash while playing streams
+  - audiofile: fix bit rate calculation
   - ffmpeg: support opus
 
 ver 0.19.2 (2014/11/02)
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);

From 8d036c4b7c3a09bd55bdc86a4fb7b5d525a805c2 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Mon, 10 Nov 2014 22:52:17 +0100
Subject: [PATCH 06/14] pcm/SoxrResampler: round output buffer size up

The old formula calculates the output buffer size with "regular"
rounding (to the nearest integer), however sometimes, that is
insufficient and the last sample cannot be resampled.  This causes
audible distortions.  By changing the formula to consider the worst
case (always round up), this problem is eliminated.
---
 NEWS                      | 1 +
 src/pcm/SoxrResampler.cxx | 3 ++-
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/NEWS b/NEWS
index 933a980db..b1cc2cf04 100644
--- a/NEWS
+++ b/NEWS
@@ -9,6 +9,7 @@ ver 0.19.3 (not yet released)
   - audiofile: fix crash while playing streams
   - audiofile: fix bit rate calculation
   - ffmpeg: support opus
+* fix distorted audio with soxr resampler
 
 ver 0.19.2 (2014/11/02)
 * input
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);
 

From 134cb6a0171192b7d621697f84196ce670a3ce21 Mon Sep 17 00:00:00 2001
From: Misty De Meo <mistydemeo@gmail.com>
Date: Mon, 10 Nov 2014 19:38:22 -0800
Subject: [PATCH 07/14] Main: fix compilation on OS X using non-Apple compilers

Commit d42c0f1dc5063d50a62817b63a1c2a4507c46071 added an OS X-specific
method of calling mpd_main_after_fork(), which uses Grand Central
Dispatch. Since this uses a block literal, it breaks compilation on
compilers which don't support the block extension, e.g. non-Apple
compilers. This affects users on older OS X releases with GCD (which
depend on older Clang releases, or Apple GCCs, which don't support the
C++11 features MPD needs); or which don't support GCD at all (10.5 and
lower).

This patch changes the #ifdef so that the non-GCD code is used
as it was on OS X before this patch if blocks aren't available, via
checking __BLOCKS__ macro.
---
 NEWS         | 1 +
 src/Main.cxx | 4 ++--
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/NEWS b/NEWS
index b1cc2cf04..52f4671e3 100644
--- a/NEWS
+++ b/NEWS
@@ -10,6 +10,7 @@ ver 0.19.3 (not yet released)
   - audiofile: fix bit rate calculation
   - ffmpeg: support opus
 * 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).

From 4a04f73434ed6953ae1403d6f1eb33a3807e01d7 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Tue, 11 Nov 2014 07:45:31 +0100
Subject: [PATCH 08/14] decoder/opus: add constexpr output_buffer_frames

---
 src/decoder/plugins/OpusDecoderPlugin.cxx | 18 ++++++++++--------
 1 file changed, 10 insertions(+), 8 deletions(-)

diff --git a/src/decoder/plugins/OpusDecoderPlugin.cxx b/src/decoder/plugins/OpusDecoderPlugin.cxx
index 97c81984c..9599f7bcf 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,7 +76,6 @@ class MPDOpusDecoder {
 
 	OpusDecoder *opus_decoder;
 	opus_int16 *output_buffer;
-	unsigned output_size;
 
 	bool os_initialized;
 	bool found_opus;
@@ -86,7 +91,7 @@ public:
 		       InputStream &_input_stream)
 		:decoder(_decoder), input_stream(_input_stream),
 		 opus_decoder(nullptr),
-		 output_buffer(nullptr), output_size(0),
+		 output_buffer(nullptr),
 		 os_initialized(false), found_opus(false) {}
 	~MPDOpusDecoder();
 
@@ -264,11 +269,8 @@ MPDOpusDecoder::HandleBOS(const ogg_packet &packet)
 			    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);
 }
@@ -304,7 +306,7 @@ 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));

From 466b6a23cdce42143a25f87ce2234e045e963bdf Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Tue, 11 Nov 2014 08:30:11 +0100
Subject: [PATCH 09/14] decoder/opus: eliminate flag "found_opus"

Check opus_decoder!=nullptr instead.
---
 src/decoder/plugins/OpusDecoderPlugin.cxx | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/src/decoder/plugins/OpusDecoderPlugin.cxx b/src/decoder/plugins/OpusDecoderPlugin.cxx
index 9599f7bcf..cf102a9b5 100644
--- a/src/decoder/plugins/OpusDecoderPlugin.cxx
+++ b/src/decoder/plugins/OpusDecoderPlugin.cxx
@@ -78,7 +78,6 @@ class MPDOpusDecoder {
 	opus_int16 *output_buffer;
 
 	bool os_initialized;
-	bool found_opus;
 
 	int opus_serialno;
 
@@ -92,7 +91,7 @@ public:
 		:decoder(_decoder), input_stream(_input_stream),
 		 opus_decoder(nullptr),
 		 output_buffer(nullptr),
-		 os_initialized(false), found_opus(false) {}
+		 os_initialized(false) {}
 	~MPDOpusDecoder();
 
 	bool ReadFirstPage(OggSyncState &oy);
@@ -168,7 +167,7 @@ MPDOpusDecoder::HandlePacket(const ogg_packet &packet)
 
 	if (packet.b_o_s)
 		return HandleBOS(packet);
-	else if (!found_opus)
+	else if (opus_decoder == nullptr)
 		return DecoderCommand::STOP;
 
 	if (IsOpusTags(packet))
@@ -230,7 +229,7 @@ MPDOpusDecoder::HandleBOS(const ogg_packet &packet)
 {
 	assert(packet.b_o_s);
 
-	if (found_opus || !IsOpusHead(packet))
+	if (opus_decoder != nullptr || !IsOpusHead(packet))
 		return DecoderCommand::STOP;
 
 	unsigned channels;
@@ -242,7 +241,6 @@ MPDOpusDecoder::HandleBOS(const ogg_packet &packet)
 	assert(output_buffer == nullptr);
 
 	opus_serialno = os.serialno;
-	found_opus = true;
 
 	/* TODO: parse attributes from the OpusHead (sample rate,
 	   channels, ...) */

From 7886a14b74f58896670bb8c0573646cd1a947d05 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Tue, 11 Nov 2014 11:18:51 +0100
Subject: [PATCH 10/14] decoder/opus: fix mistyped LoadEOSPacket() return value

---
 NEWS                                      | 1 +
 src/decoder/plugins/OpusDecoderPlugin.cxx | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/NEWS b/NEWS
index 52f4671e3..9f7cd74fa 100644
--- a/NEWS
+++ b/NEWS
@@ -9,6 +9,7 @@ ver 0.19.3 (not yet released)
   - audiofile: fix crash while playing streams
   - audiofile: fix bit rate calculation
   - ffmpeg: support opus
+  - opus: fix bogus duration on streams
 * fix distorted audio with soxr resampler
 * fix build failure on Mac OS X with non-Apple compilers
 
diff --git a/src/decoder/plugins/OpusDecoderPlugin.cxx b/src/decoder/plugins/OpusDecoderPlugin.cxx
index cf102a9b5..a4be8eb60 100644
--- a/src/decoder/plugins/OpusDecoderPlugin.cxx
+++ b/src/decoder/plugins/OpusDecoderPlugin.cxx
@@ -188,7 +188,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();
 

From 23465ad9855f4878fd7210bf076fed938a409b7d Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Tue, 11 Nov 2014 10:56:36 +0100
Subject: [PATCH 11/14] decoder/opus: improved error logging

---
 NEWS                                      |  1 +
 src/decoder/plugins/OpusDecoderPlugin.cxx | 15 +++++++++++----
 2 files changed, 12 insertions(+), 4 deletions(-)

diff --git a/NEWS b/NEWS
index 9f7cd74fa..8a6f33c1b 100644
--- a/NEWS
+++ b/NEWS
@@ -10,6 +10,7 @@ ver 0.19.3 (not yet released)
   - audiofile: fix bit rate calculation
   - ffmpeg: support opus
   - opus: fix bogus duration on streams
+  - opus: improved error logging
 * fix distorted audio with soxr resampler
 * fix build failure on Mac OS X with non-Apple compilers
 
diff --git a/src/decoder/plugins/OpusDecoderPlugin.cxx b/src/decoder/plugins/OpusDecoderPlugin.cxx
index a4be8eb60..c3ac96687 100644
--- a/src/decoder/plugins/OpusDecoderPlugin.cxx
+++ b/src/decoder/plugins/OpusDecoderPlugin.cxx
@@ -167,8 +167,10 @@ MPDOpusDecoder::HandlePacket(const ogg_packet &packet)
 
 	if (packet.b_o_s)
 		return HandleBOS(packet);
-	else if (opus_decoder == nullptr)
+	else if (opus_decoder == nullptr) {
+		LogDebug(opus_domain, "BOS packet expected");
 		return DecoderCommand::STOP;
+	}
 
 	if (IsOpusTags(packet))
 		return HandleTags(packet);
@@ -229,13 +231,17 @@ MPDOpusDecoder::HandleBOS(const ogg_packet &packet)
 {
 	assert(packet.b_o_s);
 
-	if (opus_decoder != nullptr || !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);
@@ -307,7 +313,8 @@ MPDOpusDecoder::HandleAudio(const ogg_packet &packet)
 				  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;
 	}
 

From ba6f2b0467d85e5d855c7bc9649a09cfe973e4b9 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Tue, 11 Nov 2014 08:36:22 +0100
Subject: [PATCH 12/14] decoder/opus: move code to HandleEOS()

---
 src/decoder/plugins/OpusDecoderPlugin.cxx | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/src/decoder/plugins/OpusDecoderPlugin.cxx b/src/decoder/plugins/OpusDecoderPlugin.cxx
index c3ac96687..30ce06708 100644
--- a/src/decoder/plugins/OpusDecoderPlugin.cxx
+++ b/src/decoder/plugins/OpusDecoderPlugin.cxx
@@ -100,6 +100,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);
 
@@ -163,7 +164,7 @@ 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);
@@ -279,6 +280,12 @@ MPDOpusDecoder::HandleBOS(const ogg_packet &packet)
 	return decoder_get_command(decoder);
 }
 
+inline DecoderCommand
+MPDOpusDecoder::HandleEOS()
+{
+	return DecoderCommand::STOP;
+}
+
 inline DecoderCommand
 MPDOpusDecoder::HandleTags(const ogg_packet &packet)
 {

From c98cb1d6f9704e1fefcc28938bcedb8a4d84a7e5 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Tue, 11 Nov 2014 07:41:01 +0100
Subject: [PATCH 13/14] decoder/opus: support chained streams

---
 NEWS                                      |  1 +
 src/decoder/plugins/OpusDecoderPlugin.cxx | 37 ++++++++++++++++++++++-
 2 files changed, 37 insertions(+), 1 deletion(-)

diff --git a/NEWS b/NEWS
index 8a6f33c1b..342e14eb6 100644
--- a/NEWS
+++ b/NEWS
@@ -10,6 +10,7 @@ ver 0.19.3 (not yet released)
   - 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
diff --git a/src/decoder/plugins/OpusDecoderPlugin.cxx b/src/decoder/plugins/OpusDecoderPlugin.cxx
index 30ce06708..b6b2e0465 100644
--- a/src/decoder/plugins/OpusDecoderPlugin.cxx
+++ b/src/decoder/plugins/OpusDecoderPlugin.cxx
@@ -77,6 +77,14 @@ class MPDOpusDecoder {
 	OpusDecoder *opus_decoder;
 	opus_int16 *output_buffer;
 
+	/**
+	 * 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;
 
 	int opus_serialno;
@@ -91,6 +99,7 @@ public:
 		:decoder(_decoder), input_stream(_input_stream),
 		 opus_decoder(nullptr),
 		 output_buffer(nullptr),
+		 previous_channels(0),
 		 os_initialized(false) {}
 	~MPDOpusDecoder();
 
@@ -245,7 +254,14 @@ MPDOpusDecoder::HandleBOS(const ogg_packet &packet)
 	}
 
 	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;
 
@@ -261,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
@@ -268,6 +291,7 @@ 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,
@@ -283,6 +307,17 @@ MPDOpusDecoder::HandleBOS(const ogg_packet &packet)
 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;
 }
 

From e5217e6ce97d4a0f972223f5eb6308e9b5e8df8f Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Tue, 11 Nov 2014 11:21:42 +0100
Subject: [PATCH 14/14] release v0.19.3

---
 NEWS | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/NEWS b/NEWS
index 342e14eb6..bf6bf11b6 100644
--- a/NEWS
+++ b/NEWS
@@ -1,4 +1,4 @@
-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