[Pkg-mpd-commits] [pkg-mpd] 02/05: Imported Upstream version 0.18.12

Florian Schlichting fsfs at moszumanska.debian.org
Sun Aug 3 21:54:59 UTC 2014


This is an automated email from the git hooks/post-receive script.

fsfs pushed a commit to branch master
in repository pkg-mpd.

commit 3832f02c93f0f5e53aadf8c6921e5ed2a8627766
Author: Florian Schlichting <fsfs at debian.org>
Date:   Sun Aug 3 22:33:39 2014 +0200

    Imported Upstream version 0.18.12
---
 Makefile.am                                  |   3 +
 Makefile.in                                  |  98 +++++++------
 NEWS                                         |  14 ++
 configure                                    |  20 +--
 configure.ac                                 |   2 +-
 doc/doxygen.conf                             |   2 +-
 src/{DatabaseSelection.cxx => BulkEdit.hxx}  |  38 ++---
 src/DatabaseSelection.cxx                    |  12 ++
 src/DatabaseSelection.hxx                    |   9 ++
 src/DecoderAPI.cxx                           |  34 +++++
 src/DecoderAPI.hxx                           |  19 +++
 src/DecoderBuffer.cxx                        |  18 +++
 src/DecoderBuffer.hxx                        |  18 +++
 src/Playlist.cxx                             |  10 +-
 src/Playlist.hxx                             |  19 ++-
 src/PlaylistControl.cxx                      |   2 +-
 src/PlaylistEdit.cxx                         |  35 +++++
 src/SongFilter.cxx                           |  10 ++
 src/SongFilter.hxx                           |  11 ++
 src/command/DatabaseCommands.cxx             |   3 +
 src/command/PlaylistCommands.cxx             |   3 +
 src/command/QueueCommands.cxx                |   8 +-
 src/db/ProxyDatabasePlugin.cxx               |  27 +++-
 src/decoder/AudiofileDecoderPlugin.cxx       |  46 +++++--
 src/decoder/DsdLib.cxx                       |  10 +-
 src/decoder/DsdLib.hxx                       |   4 -
 src/decoder/DsdiffDecoderPlugin.cxx          |  46 +++----
 src/decoder/DsfDecoderPlugin.cxx             |  11 +-
 src/decoder/FaadDecoderPlugin.cxx            | 199 ++++++++++++++-------------
 src/decoder/MadDecoderPlugin.cxx             |  29 +---
 src/decoder/OpusDecoderPlugin.cxx            |   1 +
 src/decoder/SndfileDecoderPlugin.cxx         |  55 ++++----
 src/output/HttpdClient.cxx                   |   1 +
 src/playlist/PlsPlaylistPlugin.cxx           |   1 +
 test/{run_decoder.cxx => FakeDecoderAPI.cxx} | 155 ++++++---------------
 test/dump_playlist.cxx                       |  83 -----------
 test/read_tags.cxx                           |  75 +---------
 test/run_decoder.cxx                         |  34 +++++
 38 files changed, 601 insertions(+), 564 deletions(-)

diff --git a/Makefile.am b/Makefile.am
index c7cf631..25352e3 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -181,6 +181,7 @@ src_mpd_SOURCES = \
 	src/PlaylistInfo.hxx \
 	src/PlaylistDatabase.cxx src/PlaylistDatabase.hxx \
 	src/PlaylistUpdate.cxx \
+	src/BulkEdit.hxx \
 	src/IdTable.hxx \
 	src/Queue.cxx src/Queue.hxx \
 	src/QueuePrint.cxx src/QueuePrint.hxx \
@@ -1218,6 +1219,7 @@ test_dump_playlist_LDADD = \
 	libpcm.a \
 	$(GLIB_LIBS)
 test_dump_playlist_SOURCES = test/dump_playlist.cxx \
+	test/FakeDecoderAPI.cxx \
 	$(DECODER_SRC) \
 	src/Log.cxx \
 	src/IOThread.cxx \
@@ -1271,6 +1273,7 @@ test_read_tags_LDADD = \
 	libutil.a \
 	$(GLIB_LIBS)
 test_read_tags_SOURCES = test/read_tags.cxx \
+	test/FakeDecoderAPI.cxx \
 	src/Log.cxx \
 	src/IOThread.cxx \
 	src/ReplayGainInfo.cxx \
diff --git a/Makefile.in b/Makefile.in
index 998d0f2..4d22168 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -1021,21 +1021,22 @@ am__src_mpd_SOURCES_DIST = src/check.h src/gerror.h \
 	src/PlaylistVector.cxx src/PlaylistVector.hxx \
 	src/PlaylistInfo.hxx src/PlaylistDatabase.cxx \
 	src/PlaylistDatabase.hxx src/PlaylistUpdate.cxx \
-	src/IdTable.hxx src/Queue.cxx src/Queue.hxx src/QueuePrint.cxx \
-	src/QueuePrint.hxx src/QueueSave.cxx src/QueueSave.hxx \
-	src/ReplayGainConfig.cxx src/ReplayGainConfig.hxx \
-	src/ReplayGainInfo.cxx src/ReplayGainInfo.hxx \
-	src/SignalHandlers.cxx src/SignalHandlers.hxx src/Song.cxx \
-	src/Song.hxx src/SongUpdate.cxx src/SongPrint.cxx \
-	src/SongPrint.hxx src/SongSave.cxx src/SongSave.hxx \
-	src/SongSort.cxx src/SongSort.hxx src/StateFile.cxx \
-	src/StateFile.hxx src/Stats.cxx src/Stats.hxx src/TagPrint.cxx \
-	src/TagPrint.hxx src/TagSave.cxx src/TagSave.hxx \
-	src/TagFile.cxx src/TagFile.hxx src/TextFile.cxx \
-	src/TextFile.hxx src/TextInputStream.cxx src/Volume.cxx \
-	src/Volume.hxx src/SongFilter.cxx src/SongFilter.hxx \
-	src/SongPointer.hxx src/PlaylistFile.cxx src/PlaylistFile.hxx \
-	src/Timer.cxx src/DespotifyUtils.cxx src/DespotifyUtils.hxx \
+	src/BulkEdit.hxx src/IdTable.hxx src/Queue.cxx src/Queue.hxx \
+	src/QueuePrint.cxx src/QueuePrint.hxx src/QueueSave.cxx \
+	src/QueueSave.hxx src/ReplayGainConfig.cxx \
+	src/ReplayGainConfig.hxx src/ReplayGainInfo.cxx \
+	src/ReplayGainInfo.hxx src/SignalHandlers.cxx \
+	src/SignalHandlers.hxx src/Song.cxx src/Song.hxx \
+	src/SongUpdate.cxx src/SongPrint.cxx src/SongPrint.hxx \
+	src/SongSave.cxx src/SongSave.hxx src/SongSort.cxx \
+	src/SongSort.hxx src/StateFile.cxx src/StateFile.hxx \
+	src/Stats.cxx src/Stats.hxx src/TagPrint.cxx src/TagPrint.hxx \
+	src/TagSave.cxx src/TagSave.hxx src/TagFile.cxx \
+	src/TagFile.hxx src/TextFile.cxx src/TextFile.hxx \
+	src/TextInputStream.cxx src/Volume.cxx src/Volume.hxx \
+	src/SongFilter.cxx src/SongFilter.hxx src/SongPointer.hxx \
+	src/PlaylistFile.cxx src/PlaylistFile.hxx src/Timer.cxx \
+	src/DespotifyUtils.cxx src/DespotifyUtils.hxx \
 	src/InotifyDomain.cxx src/InotifyDomain.hxx \
 	src/InotifySource.cxx src/InotifySource.hxx \
 	src/InotifyQueue.cxx src/InotifyQueue.hxx \
@@ -1275,16 +1276,18 @@ test_DumpDatabase_OBJECTS = $(am_test_DumpDatabase_OBJECTS)
 @ENABLE_TEST_TRUE@	libconf.a libutil.a libsystem.a libfs.a \
 @ENABLE_TEST_TRUE@	$(am__DEPENDENCIES_1)
 am__test_dump_playlist_SOURCES_DIST = test/dump_playlist.cxx \
-	src/Log.cxx src/IOThread.cxx src/Song.cxx src/TagSave.cxx \
-	src/TagFile.cxx src/CheckAudioFormat.cxx \
-	src/TextInputStream.cxx src/cue/CueParser.cxx \
-	src/cue/CueParser.hxx src/ReplayGainInfo.cxx \
-	src/decoder/FlacMetadata.cxx src/DespotifyUtils.cxx
+	test/FakeDecoderAPI.cxx src/Log.cxx src/IOThread.cxx \
+	src/Song.cxx src/TagSave.cxx src/TagFile.cxx \
+	src/CheckAudioFormat.cxx src/TextInputStream.cxx \
+	src/cue/CueParser.cxx src/cue/CueParser.hxx \
+	src/ReplayGainInfo.cxx src/decoder/FlacMetadata.cxx \
+	src/DespotifyUtils.cxx
 @ENABLE_TEST_TRUE@@HAVE_FLAC_TRUE at am__objects_70 = src/ReplayGainInfo.$(OBJEXT) \
 @ENABLE_TEST_TRUE@@HAVE_FLAC_TRUE@	src/decoder/FlacMetadata.$(OBJEXT)
 @ENABLE_DESPOTIFY_TRUE@@ENABLE_TEST_TRUE at am__objects_71 = src/DespotifyUtils.$(OBJEXT)
 @ENABLE_TEST_TRUE at am_test_dump_playlist_OBJECTS =  \
 @ENABLE_TEST_TRUE@	test/dump_playlist.$(OBJEXT) \
+ at ENABLE_TEST_TRUE@	test/FakeDecoderAPI.$(OBJEXT) \
 @ENABLE_TEST_TRUE@	$(am__objects_60) src/Log.$(OBJEXT) \
 @ENABLE_TEST_TRUE@	src/IOThread.$(OBJEXT) src/Song.$(OBJEXT) \
 @ENABLE_TEST_TRUE@	src/TagSave.$(OBJEXT) src/TagFile.$(OBJEXT) \
@@ -1342,12 +1345,14 @@ test_read_mixer_OBJECTS = $(am_test_read_mixer_OBJECTS)
 @ENABLE_TEST_TRUE@	libmixer_plugins.a $(am__DEPENDENCIES_10) \
 @ENABLE_TEST_TRUE@	libconf.a libutil.a libevent.a libsystem.a \
 @ENABLE_TEST_TRUE@	libfs.a $(am__DEPENDENCIES_1)
-am__test_read_tags_SOURCES_DIST = test/read_tags.cxx src/Log.cxx \
-	src/IOThread.cxx src/ReplayGainInfo.cxx \
-	src/CheckAudioFormat.cxx src/DespotifyUtils.cxx
+am__test_read_tags_SOURCES_DIST = test/read_tags.cxx \
+	test/FakeDecoderAPI.cxx src/Log.cxx src/IOThread.cxx \
+	src/ReplayGainInfo.cxx src/CheckAudioFormat.cxx \
+	src/DespotifyUtils.cxx
 @ENABLE_TEST_TRUE at am_test_read_tags_OBJECTS =  \
- at ENABLE_TEST_TRUE@	test/read_tags.$(OBJEXT) src/Log.$(OBJEXT) \
- at ENABLE_TEST_TRUE@	src/IOThread.$(OBJEXT) \
+ at ENABLE_TEST_TRUE@	test/read_tags.$(OBJEXT) \
+ at ENABLE_TEST_TRUE@	test/FakeDecoderAPI.$(OBJEXT) \
+ at ENABLE_TEST_TRUE@	src/Log.$(OBJEXT) src/IOThread.$(OBJEXT) \
 @ENABLE_TEST_TRUE@	src/ReplayGainInfo.$(OBJEXT) \
 @ENABLE_TEST_TRUE@	src/CheckAudioFormat.$(OBJEXT) \
 @ENABLE_TEST_TRUE@	$(am__objects_60) $(am__objects_71)
@@ -2341,21 +2346,22 @@ src_mpd_SOURCES = $(mpd_headers) $(DECODER_SRC) $(OUTPUT_API_SRC) \
 	src/PlaylistVector.cxx src/PlaylistVector.hxx \
 	src/PlaylistInfo.hxx src/PlaylistDatabase.cxx \
 	src/PlaylistDatabase.hxx src/PlaylistUpdate.cxx \
-	src/IdTable.hxx src/Queue.cxx src/Queue.hxx src/QueuePrint.cxx \
-	src/QueuePrint.hxx src/QueueSave.cxx src/QueueSave.hxx \
-	src/ReplayGainConfig.cxx src/ReplayGainConfig.hxx \
-	src/ReplayGainInfo.cxx src/ReplayGainInfo.hxx \
-	src/SignalHandlers.cxx src/SignalHandlers.hxx src/Song.cxx \
-	src/Song.hxx src/SongUpdate.cxx src/SongPrint.cxx \
-	src/SongPrint.hxx src/SongSave.cxx src/SongSave.hxx \
-	src/SongSort.cxx src/SongSort.hxx src/StateFile.cxx \
-	src/StateFile.hxx src/Stats.cxx src/Stats.hxx src/TagPrint.cxx \
-	src/TagPrint.hxx src/TagSave.cxx src/TagSave.hxx \
-	src/TagFile.cxx src/TagFile.hxx src/TextFile.cxx \
-	src/TextFile.hxx src/TextInputStream.cxx src/Volume.cxx \
-	src/Volume.hxx src/SongFilter.cxx src/SongFilter.hxx \
-	src/SongPointer.hxx src/PlaylistFile.cxx src/PlaylistFile.hxx \
-	src/Timer.cxx $(am__append_1) $(am__append_2) $(am__append_3) \
+	src/BulkEdit.hxx src/IdTable.hxx src/Queue.cxx src/Queue.hxx \
+	src/QueuePrint.cxx src/QueuePrint.hxx src/QueueSave.cxx \
+	src/QueueSave.hxx src/ReplayGainConfig.cxx \
+	src/ReplayGainConfig.hxx src/ReplayGainInfo.cxx \
+	src/ReplayGainInfo.hxx src/SignalHandlers.cxx \
+	src/SignalHandlers.hxx src/Song.cxx src/Song.hxx \
+	src/SongUpdate.cxx src/SongPrint.cxx src/SongPrint.hxx \
+	src/SongSave.cxx src/SongSave.hxx src/SongSort.cxx \
+	src/SongSort.hxx src/StateFile.cxx src/StateFile.hxx \
+	src/Stats.cxx src/Stats.hxx src/TagPrint.cxx src/TagPrint.hxx \
+	src/TagSave.cxx src/TagSave.hxx src/TagFile.cxx \
+	src/TagFile.hxx src/TextFile.cxx src/TextFile.hxx \
+	src/TextInputStream.cxx src/Volume.cxx src/Volume.hxx \
+	src/SongFilter.cxx src/SongFilter.hxx src/SongPointer.hxx \
+	src/PlaylistFile.cxx src/PlaylistFile.hxx src/Timer.cxx \
+	$(am__append_1) $(am__append_2) $(am__append_3) \
 	$(am__append_7) $(am__append_40) $(am__append_41) \
 	$(am__append_42)
 @HAVE_WINDOWS_TRUE at noinst_DATA = src/win/mpd_win32_rc.rc
@@ -2894,8 +2900,9 @@ SPARSE_SRC = $(addprefix $(top_srcdir)/,$(filter %.c,$(src_mpd_SOURCES)))
 @ENABLE_TEST_TRUE@	$(GLIB_LIBS)
 
 @ENABLE_TEST_TRUE at test_dump_playlist_SOURCES = test/dump_playlist.cxx \
- at ENABLE_TEST_TRUE@	$(DECODER_SRC) src/Log.cxx src/IOThread.cxx \
- at ENABLE_TEST_TRUE@	src/Song.cxx src/TagSave.cxx src/TagFile.cxx \
+ at ENABLE_TEST_TRUE@	test/FakeDecoderAPI.cxx $(DECODER_SRC) \
+ at ENABLE_TEST_TRUE@	src/Log.cxx src/IOThread.cxx src/Song.cxx \
+ at ENABLE_TEST_TRUE@	src/TagSave.cxx src/TagFile.cxx \
 @ENABLE_TEST_TRUE@	src/CheckAudioFormat.cxx \
 @ENABLE_TEST_TRUE@	src/TextInputStream.cxx \
 @ENABLE_TEST_TRUE@	src/cue/CueParser.cxx src/cue/CueParser.hxx \
@@ -2935,8 +2942,8 @@ SPARSE_SRC = $(addprefix $(top_srcdir)/,$(filter %.c,$(src_mpd_SOURCES)))
 @ENABLE_TEST_TRUE@	$(GLIB_LIBS)
 
 @ENABLE_TEST_TRUE at test_read_tags_SOURCES = test/read_tags.cxx \
- at ENABLE_TEST_TRUE@	src/Log.cxx src/IOThread.cxx \
- at ENABLE_TEST_TRUE@	src/ReplayGainInfo.cxx \
+ at ENABLE_TEST_TRUE@	test/FakeDecoderAPI.cxx src/Log.cxx \
+ at ENABLE_TEST_TRUE@	src/IOThread.cxx src/ReplayGainInfo.cxx \
 @ENABLE_TEST_TRUE@	src/CheckAudioFormat.cxx $(DECODER_SRC) \
 @ENABLE_TEST_TRUE@	$(am__append_78)
 @ENABLE_TEST_TRUE@@HAVE_ID3TAG_TRUE at test_dump_rva2_LDADD = \
@@ -4409,6 +4416,8 @@ test/DumpDatabase$(EXEEXT): $(test_DumpDatabase_OBJECTS) $(test_DumpDatabase_DEP
 	$(AM_V_CXXLD)$(CXXLINK) $(test_DumpDatabase_OBJECTS) $(test_DumpDatabase_LDADD) $(LIBS)
 test/dump_playlist.$(OBJEXT): test/$(am__dirstamp) \
 	test/$(DEPDIR)/$(am__dirstamp)
+test/FakeDecoderAPI.$(OBJEXT): test/$(am__dirstamp) \
+	test/$(DEPDIR)/$(am__dirstamp)
 src/IOThread.$(OBJEXT): src/$(am__dirstamp) \
 	src/$(DEPDIR)/$(am__dirstamp)
 src/TagFile.$(OBJEXT): src/$(am__dirstamp) \
@@ -5089,6 +5098,7 @@ distclean-compile:
 @AMDEP_TRUE@@am__include@ @am__quote at src/util/$(DEPDIR)/growing_fifo.Po at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at src/util/$(DEPDIR)/list_sort.Po at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at test/$(DEPDIR)/DumpDatabase.Po at am__quote@
+ at AMDEP_TRUE@@am__include@ @am__quote at test/$(DEPDIR)/FakeDecoderAPI.Po at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at test/$(DEPDIR)/FakeReplayGainConfig.Po at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at test/$(DEPDIR)/ShutdownHandler.Po at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at test/$(DEPDIR)/dump_playlist.Po at am__quote@
diff --git a/NEWS b/NEWS
index d8e9016..8cc6cdd 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,17 @@
+ver 0.18.12 (2014/07/30)
+* database
+  - proxy: fix build failure with libmpdclient 2.2
+  - proxy: fix add/search and other commands with libmpdclient < 2.9
+* decoder
+  - audiofile: improve responsiveness
+  - audiofile: fix WAV stream playback
+  - dsdiff, dsf: fix stream playback
+  - dsdiff: fix metadata parser bug (uninitialized variables)
+  - faad: estimate song duration for remote files
+  - sndfile: improve responsiveness
+* randomize next song when enabling "random" mode while not playing
+* randomize next song when adding to single-song queue
+
 ver 0.18.11 (2014/05/12)
 * decoder
   - opus: fix missing song length on high-latency files
diff --git a/configure b/configure
index 6079fe2..44cc171 100755
--- a/configure
+++ b/configure
@@ -1,6 +1,6 @@
 #! /bin/sh
 # Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.69 for mpd 0.18.11.
+# Generated by GNU Autoconf 2.69 for mpd 0.18.12.
 #
 # Report bugs to <mpd-devel at musicpd.org>.
 #
@@ -580,8 +580,8 @@ MAKEFLAGS=
 # Identity of this package.
 PACKAGE_NAME='mpd'
 PACKAGE_TARNAME='mpd'
-PACKAGE_VERSION='0.18.11'
-PACKAGE_STRING='mpd 0.18.11'
+PACKAGE_VERSION='0.18.12'
+PACKAGE_STRING='mpd 0.18.12'
 PACKAGE_BUGREPORT='mpd-devel at musicpd.org'
 PACKAGE_URL=''
 
@@ -1673,7 +1673,7 @@ if test "$ac_init_help" = "long"; then
   # Omit some internal or obsolete options to make the list less imposing.
   # This message is too long to be a string in the A/UX 3.1 sh.
   cat <<_ACEOF
-\`configure' configures mpd 0.18.11 to adapt to many kinds of systems.
+\`configure' configures mpd 0.18.12 to adapt to many kinds of systems.
 
 Usage: $0 [OPTION]... [VAR=VALUE]...
 
@@ -1743,7 +1743,7 @@ fi
 
 if test -n "$ac_init_help"; then
   case $ac_init_help in
-     short | recursive ) echo "Configuration of mpd 0.18.11:";;
+     short | recursive ) echo "Configuration of mpd 0.18.12:";;
    esac
   cat <<\_ACEOF
 
@@ -2041,7 +2041,7 @@ fi
 test -n "$ac_init_help" && exit $ac_status
 if $ac_init_version; then
   cat <<\_ACEOF
-mpd configure 0.18.11
+mpd configure 0.18.12
 generated by GNU Autoconf 2.69
 
 Copyright (C) 2012 Free Software Foundation, Inc.
@@ -2494,7 +2494,7 @@ cat >config.log <<_ACEOF
 This file contains any messages produced by compilers while
 running configure, to aid debugging if configure makes a mistake.
 
-It was created by mpd $as_me 0.18.11, which was
+It was created by mpd $as_me 0.18.12, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   $ $0 $@
@@ -3364,7 +3364,7 @@ fi
 
 # Define the identity of the package.
  PACKAGE='mpd'
- VERSION='0.18.11'
+ VERSION='0.18.12'
 
 
 cat >>confdefs.h <<_ACEOF
@@ -18461,7 +18461,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
 # report actual input values of CONFIG_FILES etc. instead of their
 # values after options handling.
 ac_log="
-This file was extended by mpd $as_me 0.18.11, which was
+This file was extended by mpd $as_me 0.18.12, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   CONFIG_FILES    = $CONFIG_FILES
@@ -18527,7 +18527,7 @@ _ACEOF
 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
 ac_cs_version="\\
-mpd config.status 0.18.11
+mpd config.status 0.18.12
 configured by $0, generated by GNU Autoconf 2.69,
   with options \\"\$ac_cs_config\\"
 
diff --git a/configure.ac b/configure.ac
index 48933c4..36f785f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,6 +1,6 @@
 AC_PREREQ(2.60)
 
-AC_INIT(mpd, 0.18.11, mpd-devel at musicpd.org)
+AC_INIT(mpd, 0.18.12, mpd-devel at musicpd.org)
 
 VERSION_MAJOR=0
 VERSION_MINOR=18
diff --git a/doc/doxygen.conf b/doc/doxygen.conf
index ba4e372..81bd1ec 100644
--- a/doc/doxygen.conf
+++ b/doc/doxygen.conf
@@ -31,7 +31,7 @@ PROJECT_NAME           = MPD
 # This could be handy for archiving the generated documentation or
 # if some version control system is used.
 
-PROJECT_NUMBER = 0.18.11
+PROJECT_NUMBER = 0.18.12
 
 # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
 # base path where the generated documentation will be put.
diff --git a/src/DatabaseSelection.cxx b/src/BulkEdit.hxx
similarity index 57%
copy from src/DatabaseSelection.cxx
copy to src/BulkEdit.hxx
index 96018cb..422dc4f 100644
--- a/src/DatabaseSelection.cxx
+++ b/src/BulkEdit.hxx
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
  * http://www.musicpd.org
  *
  * This program is free software; you can redistribute it and/or modify
@@ -17,21 +17,25 @@
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 
-#include "DatabaseSelection.hxx"
-#include "SongFilter.hxx"
+#ifndef MPD_BULK_EDIT_HXX
+#define MPD_BULK_EDIT_HXX
 
-DatabaseSelection::DatabaseSelection(const char *_uri, bool _recursive,
-				     const SongFilter *_filter)
-	:uri(_uri), recursive(_recursive), filter(_filter)
-{
-	/* optimization: if the caller didn't specify a base URI, pick
-	   the one from SongFilter */
-	if (uri.empty() && filter != nullptr)
-		uri = filter->GetBase();
-}
+#include "Partition.hxx"
 
-bool
-DatabaseSelection::Match(const Song &song) const
-{
-	return filter == nullptr || filter->Match(song);
-}
+/**
+ * Begin a "bulk edit" and commit it automatically.
+ */
+class ScopeBulkEdit {
+	Partition &partition;
+
+public:
+	ScopeBulkEdit(Partition &_partition):partition(_partition) {
+		partition.playlist.BeginBulk();
+	}
+
+	~ScopeBulkEdit() {
+		partition.playlist.CommitBulk(partition.pc);
+	}
+};
+
+#endif
diff --git a/src/DatabaseSelection.cxx b/src/DatabaseSelection.cxx
index 96018cb..ffe5f7d 100644
--- a/src/DatabaseSelection.cxx
+++ b/src/DatabaseSelection.cxx
@@ -31,6 +31,18 @@ DatabaseSelection::DatabaseSelection(const char *_uri, bool _recursive,
 }
 
 bool
+DatabaseSelection::IsEmpty() const
+{
+	return uri.empty() && (filter == nullptr || filter->IsEmpty());
+}
+
+bool
+DatabaseSelection::HasOtherThanBase() const
+{
+	return filter != nullptr && filter->HasOtherThanBase();
+}
+
+bool
 DatabaseSelection::Match(const Song &song) const
 {
 	return filter == nullptr || filter->Match(song);
diff --git a/src/DatabaseSelection.hxx b/src/DatabaseSelection.hxx
index 6c1a1dc..4825c73 100644
--- a/src/DatabaseSelection.hxx
+++ b/src/DatabaseSelection.hxx
@@ -45,6 +45,15 @@ struct DatabaseSelection {
 			  const SongFilter *_filter=nullptr);
 
 	gcc_pure
+	bool IsEmpty() const;
+
+	/**
+	 * Does this selection contain constraints other than "base"?
+	 */
+	gcc_pure
+	bool HasOtherThanBase() const;
+
+	gcc_pure
 	bool Match(const Song &song) const;
 };
 
diff --git a/src/DecoderAPI.cxx b/src/DecoderAPI.cxx
index 4fea02b..e4122d6 100644
--- a/src/DecoderAPI.cxx
+++ b/src/DecoderAPI.cxx
@@ -292,6 +292,40 @@ decoder_read(Decoder *decoder,
 	return nbytes;
 }
 
+bool
+decoder_read_full(Decoder *decoder, InputStream &is,
+		  void *_buffer, size_t size)
+{
+	uint8_t *buffer = (uint8_t *)_buffer;
+
+	while (size > 0) {
+		size_t nbytes = decoder_read(decoder, is, buffer, size);
+		if (nbytes == 0)
+			return false;
+
+		buffer += nbytes;
+		size -= nbytes;
+	}
+
+	return true;
+}
+
+bool
+decoder_skip(Decoder *decoder, InputStream &is, size_t size)
+{
+	while (size > 0) {
+		char buffer[1024];
+		size_t nbytes = decoder_read(decoder, is, buffer,
+					     std::min(sizeof(buffer), size));
+		if (nbytes == 0)
+			return false;
+
+		size -= nbytes;
+	}
+
+	return true;
+}
+
 void
 decoder_timestamp(Decoder &decoder, double t)
 {
diff --git a/src/DecoderAPI.hxx b/src/DecoderAPI.hxx
index 2ee4248..a9da763 100644
--- a/src/DecoderAPI.hxx
+++ b/src/DecoderAPI.hxx
@@ -113,6 +113,25 @@ decoder_read(Decoder &decoder, InputStream &is,
 }
 
 /**
+ * Blocking read from the input stream.  Attempts to fill the buffer
+ * completely; there is no partial result.
+ *
+ * @return true on success, false on error or command or not enough
+ * data
+ */
+bool
+decoder_read_full(Decoder *decoder, InputStream &is,
+		  void *buffer, size_t size);
+
+/**
+ * Skip data on the #InputStream.
+ *
+ * @return true on success, false on error or command
+ */
+bool
+decoder_skip(Decoder *decoder, InputStream &is, size_t size);
+
+/**
  * Sets the time stamp for the next data chunk [seconds].  The MPD
  * core automatically counts it up, and a decoder plugin only needs to
  * use this function if it thinks that adding to the time stamp based
diff --git a/src/DecoderBuffer.cxx b/src/DecoderBuffer.cxx
index 6aad53c..4a5125b 100644
--- a/src/DecoderBuffer.cxx
+++ b/src/DecoderBuffer.cxx
@@ -70,6 +70,12 @@ decoder_buffer_free(DecoderBuffer *buffer)
 	g_free(buffer);
 }
 
+const InputStream &
+decoder_buffer_get_stream(const DecoderBuffer *buffer)
+{
+	return *buffer->is;
+}
+
 bool
 decoder_buffer_is_empty(const DecoderBuffer *buffer)
 {
@@ -82,6 +88,12 @@ decoder_buffer_is_full(const DecoderBuffer *buffer)
 	return buffer->consumed == 0 && buffer->length == buffer->size;
 }
 
+void
+decoder_buffer_clear(DecoderBuffer *buffer)
+{
+	buffer->length = buffer->consumed = 0;
+}
+
 static void
 decoder_buffer_shift(DecoderBuffer *buffer)
 {
@@ -118,6 +130,12 @@ decoder_buffer_fill(DecoderBuffer *buffer)
 	return true;
 }
 
+size_t
+decoder_buffer_available(const DecoderBuffer *buffer)
+{
+	return buffer->length - buffer->consumed;;
+}
+
 const void *
 decoder_buffer_read(const DecoderBuffer *buffer, size_t *length_r)
 {
diff --git a/src/DecoderBuffer.hxx b/src/DecoderBuffer.hxx
index 92cc31a..65c6e0d 100644
--- a/src/DecoderBuffer.hxx
+++ b/src/DecoderBuffer.hxx
@@ -20,6 +20,8 @@
 #ifndef MPD_DECODER_BUFFER_HXX
 #define MPD_DECODER_BUFFER_HXX
 
+#include "Compiler.h"
+
 #include <stddef.h>
 
 /**
@@ -50,12 +52,21 @@ decoder_buffer_new(Decoder *decoder, InputStream &is,
 void
 decoder_buffer_free(DecoderBuffer *buffer);
 
+gcc_pure
+const InputStream &
+decoder_buffer_get_stream(const DecoderBuffer *buffer);
+
+gcc_pure
 bool
 decoder_buffer_is_empty(const DecoderBuffer *buffer);
 
+gcc_pure
 bool
 decoder_buffer_is_full(const DecoderBuffer *buffer);
 
+void
+decoder_buffer_clear(DecoderBuffer *buffer);
+
 /**
  * Read data from the input_stream and append it to the buffer.
  *
@@ -67,6 +78,13 @@ bool
 decoder_buffer_fill(DecoderBuffer *buffer);
 
 /**
+ * How many bytes are stored in the buffer?
+ */
+gcc_pure
+size_t
+decoder_buffer_available(const DecoderBuffer *buffer);
+
+/**
  * Reads data from the buffer.  This data is not yet consumed, you
  * have to call decoder_buffer_consume() to do that.  The returned
  * buffer becomes invalid after a decoder_buffer_fill() or a
diff --git a/src/Playlist.cxx b/src/Playlist.cxx
index 8d9ab24..526f352 100644
--- a/src/Playlist.cxx
+++ b/src/Playlist.cxx
@@ -103,6 +103,12 @@ playlist::UpdateQueuedSong(PlayerControl &pc, const Song *prev)
 	if (!playing)
 		return;
 
+	if (prev == nullptr && bulk_edit)
+		/* postponed until CommitBulk() to avoid always
+		   queueing the first song that is being added (in
+		   random mode) */
+		return;
+
 	assert(!queue.IsEmpty());
 	assert((queued < 0) == (prev == nullptr));
 
@@ -294,7 +300,9 @@ playlist::SetRandom(PlayerControl &pc, bool status)
 	if (queue.random) {
 		/* shuffle the queue order, but preserve current */
 
-		const int current_position = GetCurrentPosition();
+		const int current_position = playing
+			? GetCurrentPosition()
+			: -1;
 
 		queue.ShuffleOrder();
 
diff --git a/src/Playlist.hxx b/src/Playlist.hxx
index 7d7e9b1..b660ecb 100644
--- a/src/Playlist.hxx
+++ b/src/Playlist.hxx
@@ -46,6 +46,18 @@ struct playlist {
 	bool stop_on_error;
 
 	/**
+	 * If true, then a bulk edit has been initiated by
+	 * BeginBulk(), and UpdateQueuedSong() and OnModified() will
+	 * be postponed until CommitBulk()
+	 */
+	bool bulk_edit;
+
+	/**
+	 * Has the queue been modified during bulk edit mode?
+	 */
+	bool bulk_modified;
+
+	/**
 	 * Number of errors since playback was started.  If this
 	 * number exceeds the length of the playlist, MPD gives up,
 	 * because all songs have been tried.
@@ -69,7 +81,9 @@ struct playlist {
 	int queued;
 
 	playlist(unsigned max_length)
-		:queue(max_length), playing(false), current(-1), queued(-1) {
+		:queue(max_length), playing(false),
+		 bulk_edit(false),
+		 current(-1), queued(-1) {
 	}
 
 	~playlist() {
@@ -126,6 +140,9 @@ protected:
 	void UpdateQueuedSong(PlayerControl &pc, const Song *prev);
 
 public:
+	void BeginBulk();
+	void CommitBulk(PlayerControl &pc);
+
 	void Clear(PlayerControl &pc);
 
 	/**
diff --git a/src/PlaylistControl.cxx b/src/PlaylistControl.cxx
index 2dbd75d..58971a4 100644
--- a/src/PlaylistControl.cxx
+++ b/src/PlaylistControl.cxx
@@ -153,7 +153,7 @@ playlist::PlayNext(PlayerControl &pc)
 			queue.ShuffleOrder();
 
 			/* note that current and queued are
-			   now invalid, but playlist_play_order() will
+			   now invalid, but PlayOrder() will
 			   discard them anyway */
 		}
 
diff --git a/src/PlaylistEdit.cxx b/src/PlaylistEdit.cxx
index 3eea249..0dffd3d 100644
--- a/src/PlaylistEdit.cxx
+++ b/src/PlaylistEdit.cxx
@@ -40,6 +40,12 @@
 void
 playlist::OnModified()
 {
+	if (bulk_edit) {
+		/* postponed to CommitBulk() */
+		bulk_modified = true;
+		return;
+	}
+
 	queue.IncrementVersion();
 
 	idle_add(IDLE_PLAYLIST);
@@ -56,6 +62,35 @@ playlist::Clear(PlayerControl &pc)
 	OnModified();
 }
 
+void
+playlist::BeginBulk()
+{
+	assert(!bulk_edit);
+
+	bulk_edit = true;
+	bulk_modified = false;
+}
+
+void
+playlist::CommitBulk(PlayerControl &pc)
+{
+	assert(bulk_edit);
+
+	bulk_edit = false;
+	if (!bulk_modified)
+		return;
+
+	if (queued < 0)
+		/* if no song was queued, UpdateQueuedSong() is being
+		   ignored in "bulk" edit mode; now that we have
+		   shuffled all new songs, we can pick a random one
+		   (instead of always picking the first one that was
+		   added) */
+		UpdateQueuedSong(pc, nullptr);
+
+	OnModified();
+}
+
 PlaylistResult
 playlist::AppendFile(PlayerControl &pc,
 		     const char *path_utf8, unsigned *added_id)
diff --git a/src/SongFilter.cxx b/src/SongFilter.cxx
index 49c966b..235dfe7 100644
--- a/src/SongFilter.cxx
+++ b/src/SongFilter.cxx
@@ -203,6 +203,16 @@ SongFilter::Match(const Song &song) const
 	return true;
 }
 
+bool
+SongFilter::HasOtherThanBase() const
+{
+	for (const auto &i : items)
+		if (i.GetTag() != LOCATE_TAG_BASE_TYPE)
+			return true;
+
+	return false;
+}
+
 std::string
 SongFilter::GetBase() const
 {
diff --git a/src/SongFilter.hxx b/src/SongFilter.hxx
index b15127c..8c46ed5 100644
--- a/src/SongFilter.hxx
+++ b/src/SongFilter.hxx
@@ -109,6 +109,11 @@ public:
 		return items;
 	}
 
+	gcc_pure
+	bool IsEmpty() const {
+		return items.empty();
+	}
+
 	/**
 	 * Is there at least one item with "fold case" enabled?
 	 */
@@ -122,6 +127,12 @@ public:
 	}
 
 	/**
+	 * Does this filter contain constraints other than "base"?
+	 */
+	gcc_pure
+	bool HasOtherThanBase() const;
+
+	/**
 	 * Returns the "base" specification (if there is one) or an
 	 * empty string.
 	 */
diff --git a/src/command/DatabaseCommands.cxx b/src/command/DatabaseCommands.cxx
index b86cbda..a7d2467 100644
--- a/src/command/DatabaseCommands.cxx
+++ b/src/command/DatabaseCommands.cxx
@@ -30,6 +30,7 @@
 #include "util/Error.hxx"
 #include "SongFilter.hxx"
 #include "protocol/Result.hxx"
+#include "BulkEdit.hxx"
 
 #include <assert.h>
 #include <string.h>
@@ -92,6 +93,8 @@ handle_match_add(Client &client, int argc, char *argv[], bool fold_case)
 		return CommandResult::ERROR;
 	}
 
+	const ScopeBulkEdit bulk_edit(client.partition);
+
 	const DatabaseSelection selection("", true, &filter);
 	Error error;
 	return AddFromDatabase(client.partition, selection, error)
diff --git a/src/command/PlaylistCommands.cxx b/src/command/PlaylistCommands.cxx
index d178fa0..c444129 100644
--- a/src/command/PlaylistCommands.cxx
+++ b/src/command/PlaylistCommands.cxx
@@ -26,6 +26,7 @@
 #include "PlaylistFile.hxx"
 #include "PlaylistVector.hxx"
 #include "PlaylistQueue.hxx"
+#include "BulkEdit.hxx"
 #include "TimePrint.hxx"
 #include "Client.hxx"
 #include "protocol/ArgParser.hxx"
@@ -67,6 +68,8 @@ handle_load(Client &client, int argc, char *argv[])
 	} else if (!check_range(client, &start_index, &end_index, argv[2]))
 		return CommandResult::ERROR;
 
+	const ScopeBulkEdit bulk_edit(client.partition);
+
 	const PlaylistResult result =
 		playlist_open_into_queue(argv[1],
 					 start_index, end_index,
diff --git a/src/command/QueueCommands.cxx b/src/command/QueueCommands.cxx
index a21eb75..a987e1b 100644
--- a/src/command/QueueCommands.cxx
+++ b/src/command/QueueCommands.cxx
@@ -28,6 +28,7 @@
 #include "ClientFile.hxx"
 #include "Client.hxx"
 #include "Partition.hxx"
+#include "BulkEdit.hxx"
 #include "protocol/ArgParser.hxx"
 #include "protocol/Result.hxx"
 #include "ls.hxx"
@@ -43,7 +44,6 @@ CommandResult
 handle_add(Client &client, gcc_unused int argc, char *argv[])
 {
 	char *uri = argv[1];
-	PlaylistResult result;
 
 	if (memcmp(uri, "file:///", 8) == 0) {
 		const char *path_utf8 = uri + 7;
@@ -59,7 +59,7 @@ handle_add(Client &client, gcc_unused int argc, char *argv[])
 		if (!client_allow_file(client, path_fs, error))
 			return print_error(client, error);
 
-		result = client.partition.AppendFile(path_utf8);
+		auto result = client.partition.AppendFile(path_utf8);
 		return print_playlist_result(client, result);
 	}
 
@@ -70,10 +70,12 @@ handle_add(Client &client, gcc_unused int argc, char *argv[])
 			return CommandResult::ERROR;
 		}
 
-		result = client.partition.AppendURI(uri);
+		auto result = client.partition.AppendURI(uri);
 		return print_playlist_result(client, result);
 	}
 
+	const ScopeBulkEdit bulk_edit(client.partition);
+
 	const DatabaseSelection selection(uri, true);
 	Error error;
 	return AddFromDatabase(client.partition, selection, error)
diff --git a/src/db/ProxyDatabasePlugin.cxx b/src/db/ProxyDatabasePlugin.cxx
index 00b5d44..cb1bcdc 100644
--- a/src/db/ProxyDatabasePlugin.cxx
+++ b/src/db/ProxyDatabasePlugin.cxx
@@ -398,8 +398,13 @@ Convert(const struct mpd_song *song)
 	Song *s = Song::NewDetached(mpd_song_get_uri(song));
 
 	s->mtime = mpd_song_get_last_modified(song);
+
+#if LIBMPDCLIENT_CHECK_VERSION(2,3,0)
 	s->start_ms = mpd_song_get_start(song) * 1000;
 	s->end_ms = mpd_song_get_end(song) * 1000;
+#else
+	s->start_ms = s->end_ms = 0;
+#endif
 
 	TagBuilder tag;
 	tag.SetTime(mpd_song_get_duration(song));
@@ -561,6 +566,23 @@ SearchSongs(struct mpd_connection *connection,
 	return result && CheckError(connection, error);
 }
 
+/**
+ * Check whether we can use the "base" constraint.  Requires
+ * libmpdclient 2.9 and MPD 0.18.
+ */
+gcc_pure
+static bool
+ServerSupportsSearchBase(const struct mpd_connection *connection)
+{
+#if LIBMPDCLIENT_CHECK_VERSION(2,9,0)
+	return mpd_connection_cmp_server_version(connection, 0, 18, 0) >= 0;
+#else
+	(void)connection;
+
+	return false;
+#endif
+}
+
 bool
 ProxyDatabase::Visit(const DatabaseSelection &selection,
 		     VisitDirectory visit_directory,
@@ -572,7 +594,10 @@ ProxyDatabase::Visit(const DatabaseSelection &selection,
 	if (!const_cast<ProxyDatabase *>(this)->EnsureConnected(error))
 		return nullptr;
 
-	if (!visit_directory && !visit_playlist && selection.recursive)
+	if (!visit_directory && !visit_playlist && selection.recursive &&
+	    (ServerSupportsSearchBase(connection)
+	     ? !selection.IsEmpty()
+	     : selection.HasOtherThanBase()))
 		/* this optimized code path can only be used under
 		   certain conditions */
 		return ::SearchSongs(connection, selection, visit_song, error);
diff --git a/src/decoder/AudiofileDecoderPlugin.cxx b/src/decoder/AudiofileDecoderPlugin.cxx
index ab3557e..9f097f9 100644
--- a/src/decoder/AudiofileDecoderPlugin.cxx
+++ b/src/decoder/AudiofileDecoderPlugin.cxx
@@ -31,12 +31,27 @@
 #include <af_vfs.h>
 
 #include <assert.h>
+#include <stdio.h>
 
 /* pick 1020 since its devisible for 8,16,24, and 32-bit audio */
 #define CHUNK_SIZE		1020
 
 static constexpr Domain audiofile_domain("audiofile");
 
+struct AudioFileInputStream {
+	Decoder *const decoder;
+	InputStream &is;
+
+	size_t Read(void *buffer, size_t size) {
+		/* libaudiofile does not like partial reads at all,
+		   and will abort playback; therefore always force full
+		   reads */
+		return decoder_read_full(decoder, is, buffer, size)
+			? size
+			: 0;
+	}
+};
+
 static int audiofile_get_duration(const char *file)
 {
 	int total_time;
@@ -54,29 +69,26 @@ static int audiofile_get_duration(const char *file)
 static ssize_t
 audiofile_file_read(AFvirtualfile *vfile, void *data, size_t length)
 {
-	InputStream &is = *(InputStream *)vfile->closure;
+	AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure;
 
-	Error error;
-	size_t nbytes = is.LockRead(data, length, error);
-	if (nbytes == 0 && error.IsDefined()) {
-		LogError(error);
-		return -1;
-	}
-
-	return nbytes;
+	return afis.Read(data, length);
 }
 
 static AFfileoffset
 audiofile_file_length(AFvirtualfile *vfile)
 {
-	InputStream &is = *(InputStream *)vfile->closure;
+	AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure;
+	InputStream &is = afis.is;
+
 	return is.GetSize();
 }
 
 static AFfileoffset
 audiofile_file_tell(AFvirtualfile *vfile)
 {
-	InputStream &is = *(InputStream *)vfile->closure;
+	AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure;
+	InputStream &is = afis.is;
+
 	return is.GetOffset();
 }
 
@@ -91,11 +103,14 @@ audiofile_file_destroy(AFvirtualfile *vfile)
 static AFfileoffset
 audiofile_file_seek(AFvirtualfile *vfile, AFfileoffset offset, int is_relative)
 {
-	InputStream &is = *(InputStream *)vfile->closure;
+	AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure;
+	InputStream &is = afis.is;
+
 	int whence = (is_relative ? SEEK_CUR : SEEK_SET);
 
 	Error error;
 	if (is.LockSeek(offset, whence, error)) {
+		LogError(error, "Seek failed");
 		return is.GetOffset();
 	} else {
 		return -1;
@@ -103,10 +118,10 @@ audiofile_file_seek(AFvirtualfile *vfile, AFfileoffset offset, int is_relative)
 }
 
 static AFvirtualfile *
-setup_virtual_fops(InputStream &stream)
+setup_virtual_fops(AudioFileInputStream &afis)
 {
 	AFvirtualfile *vf = new AFvirtualfile();
-	vf->closure = &stream;
+	vf->closure = &afis;
 	vf->write = nullptr;
 	vf->read    = audiofile_file_read;
 	vf->length  = audiofile_file_length;
@@ -173,7 +188,8 @@ audiofile_stream_decode(Decoder &decoder, InputStream &is)
 		return;
 	}
 
-	vf = setup_virtual_fops(is);
+	AudioFileInputStream afis{&decoder, is};
+	vf = setup_virtual_fops(afis);
 
 	af_fp = afOpenVirtualFile(vf, "r", nullptr);
 	if (af_fp == AF_NULL_FILEHANDLE) {
diff --git a/src/decoder/DsdLib.cxx b/src/decoder/DsdLib.cxx
index 67cc7e9..eafedda 100644
--- a/src/decoder/DsdLib.cxx
+++ b/src/decoder/DsdLib.cxx
@@ -49,14 +49,6 @@ DsdId::Equals(const char *s) const
 	return memcmp(value, s, sizeof(value)) == 0;
 }
 
-bool
-dsdlib_read(Decoder *decoder, InputStream &is,
-	    void *data, size_t length)
-{
-	size_t nbytes = decoder_read(decoder, is, data, length);
-	return nbytes == length;
-}
-
 /**
  * Skip the #input_stream to the specified offset.
  */
@@ -149,7 +141,7 @@ dsdlib_tag_id3(InputStream &is,
 	id3_byte_t *dsdid3data;
 	dsdid3data = dsdid3;
 
-	if (!dsdlib_read(nullptr, is, dsdid3data, count))
+	if (!decoder_read_full(nullptr, is, dsdid3data, count))
 		return;
 
 	id3_tag = id3_tag_parse(dsdid3data, count);
diff --git a/src/decoder/DsdLib.hxx b/src/decoder/DsdLib.hxx
index 53160cf..5c61271 100644
--- a/src/decoder/DsdLib.hxx
+++ b/src/decoder/DsdLib.hxx
@@ -59,10 +59,6 @@ public:
 };
 
 bool
-dsdlib_read(Decoder *decoder, InputStream &is,
-	    void *data, size_t length);
-
-bool
 dsdlib_skip_to(Decoder *decoder, InputStream &is,
 	       int64_t offset);
 
diff --git a/src/decoder/DsdiffDecoderPlugin.cxx b/src/decoder/DsdiffDecoderPlugin.cxx
index a3c0149..60b2e76 100644
--- a/src/decoder/DsdiffDecoderPlugin.cxx
+++ b/src/decoder/DsdiffDecoderPlugin.cxx
@@ -93,14 +93,14 @@ static bool
 dsdiff_read_id(Decoder *decoder, InputStream &is,
 	       DsdId *id)
 {
-	return dsdlib_read(decoder, is, id, sizeof(*id));
+	return decoder_read_full(decoder, is, id, sizeof(*id));
 }
 
 static bool
 dsdiff_read_chunk_header(Decoder *decoder, InputStream &is,
 			 DsdiffChunkHeader *header)
 {
-	return dsdlib_read(decoder, is, header, sizeof(*header));
+	return decoder_read_full(decoder, is, header, sizeof(*header));
 }
 
 static bool
@@ -112,8 +112,7 @@ dsdiff_read_payload(Decoder *decoder, InputStream &is,
 	if (size != (uint64_t)length)
 		return false;
 
-	size_t nbytes = decoder_read(decoder, is, data, length);
-	return nbytes == length;
+	return decoder_read_full(decoder, is, data, length);
 }
 
 /**
@@ -145,8 +144,8 @@ dsdiff_read_prop_snd(Decoder *decoder, InputStream &is,
 		} else if (header.id.Equals("CHNL")) {
 			uint16_t channels;
 			if (header.GetSize() < sizeof(channels) ||
-			    !dsdlib_read(decoder, is,
-					 &channels, sizeof(channels)) ||
+			    !decoder_read_full(decoder, is,
+					       &channels, sizeof(channels)) ||
 			    !dsdlib_skip_to(decoder, is, chunk_end_offset))
 				return false;
 
@@ -154,8 +153,8 @@ dsdiff_read_prop_snd(Decoder *decoder, InputStream &is,
 		} else if (header.id.Equals("CMPR")) {
 			DsdId type;
 			if (header.GetSize() < sizeof(type) ||
-			    !dsdlib_read(decoder, is,
-					 &type, sizeof(type)) ||
+			    !decoder_read_full(decoder, is,
+					       &type, sizeof(type)) ||
 			    !dsdlib_skip_to(decoder, is, chunk_end_offset))
 				return false;
 
@@ -208,7 +207,7 @@ dsdiff_handle_native_tag(InputStream &is,
 
 	struct dsdiff_native_tag metatag;
 
-	if (!dsdlib_read(nullptr, is, &metatag, sizeof(metatag)))
+	if (!decoder_read_full(nullptr, is, &metatag, sizeof(metatag)))
 		return;
 
 	uint32_t length = FromBE32(metatag.size);
@@ -221,7 +220,7 @@ dsdiff_handle_native_tag(InputStream &is,
 	char *label;
 	label = string;
 
-	if (!dsdlib_read(nullptr, is, label, (size_t)length))
+	if (!decoder_read_full(nullptr, is, label, (size_t)length))
 		return;
 
 	string[length] = '\0';
@@ -251,15 +250,17 @@ dsdiff_read_metadata_extra(Decoder *decoder, InputStream &is,
 	if (!dsdiff_read_chunk_header(decoder, is, chunk_header))
 		return false;
 
+	metadata->diar_offset = 0;
+	metadata->diti_offset = 0;
+
 #ifdef HAVE_ID3TAG
-	metadata->id3_size = 0;
+	metadata->id3_offset = 0;
 #endif
 
 	/* Now process all the remaining chunk headers in the stream
 	   and record their position and size */
 
-	const auto size = is.GetSize();
-	while (is.GetOffset() < size) {
+	do {
 		uint64_t chunk_size = chunk_header->GetSize();
 
 		/* DIIN chunk, is directly followed by other chunks  */
@@ -285,16 +286,11 @@ dsdiff_read_metadata_extra(Decoder *decoder, InputStream &is,
 			metadata->id3_size = chunk_size;
 		}
 #endif
-		if (chunk_size != 0) {
-			if (!dsdlib_skip(decoder, is, chunk_size))
-				break;
-		}
 
-		if (is.GetOffset() < size) {
-			if (!dsdiff_read_chunk_header(decoder, is, chunk_header))
-				return false;
-		}
-	}
+		if (!dsdlib_skip(decoder, is, chunk_size))
+			break;
+	} while (dsdiff_read_chunk_header(decoder, is, chunk_header));
+
 	/* done processing chunk headers, process tags if any */
 
 #ifdef HAVE_ID3TAG
@@ -328,7 +324,7 @@ dsdiff_read_metadata(Decoder *decoder, InputStream &is,
 		     DsdiffChunkHeader *chunk_header)
 {
 	DsdiffHeader header;
-	if (!dsdlib_read(decoder, is, &header, sizeof(header)) ||
+	if (!decoder_read_full(decoder, is, &header, sizeof(header)) ||
 	    !header.id.Equals("FRM8") ||
 	    !header.format.Equals("DSD "))
 		return false;
@@ -391,10 +387,10 @@ dsdiff_decode_chunk(Decoder &decoder, InputStream &is,
 			now_size = now_frames * frame_size;
 		}
 
-		size_t nbytes = decoder_read(decoder, is, buffer, now_size);
-		if (nbytes != now_size)
+		if (!decoder_read_full(&decoder, is, buffer, now_size))
 			return false;
 
+		const size_t nbytes = now_size;
 		chunk_size -= nbytes;
 
 		if (lsbitfirst)
diff --git a/src/decoder/DsfDecoderPlugin.cxx b/src/decoder/DsfDecoderPlugin.cxx
index 5ef94e6..ad5483c 100644
--- a/src/decoder/DsfDecoderPlugin.cxx
+++ b/src/decoder/DsfDecoderPlugin.cxx
@@ -103,7 +103,7 @@ dsf_read_metadata(Decoder *decoder, InputStream &is,
 		  DsfMetaData *metadata)
 {
 	DsfHeader dsf_header;
-	if (!dsdlib_read(decoder, is, &dsf_header, sizeof(dsf_header)) ||
+	if (!decoder_read_full(decoder, is, &dsf_header, sizeof(dsf_header)) ||
 	    !dsf_header.id.Equals("DSD "))
 		return false;
 
@@ -117,7 +117,8 @@ dsf_read_metadata(Decoder *decoder, InputStream &is,
 
 	/* read the 'fmt ' chunk of the DSF file */
 	DsfFmtChunk dsf_fmt_chunk;
-	if (!dsdlib_read(decoder, is, &dsf_fmt_chunk, sizeof(dsf_fmt_chunk)) ||
+	if (!decoder_read_full(decoder, is,
+			       &dsf_fmt_chunk, sizeof(dsf_fmt_chunk)) ||
 	    !dsf_fmt_chunk.id.Equals("fmt "))
 		return false;
 
@@ -143,7 +144,7 @@ dsf_read_metadata(Decoder *decoder, InputStream &is,
 
 	/* read the 'data' chunk of the DSF file */
 	DsfDataChunk data_chunk;
-	if (!dsdlib_read(decoder, is, &data_chunk, sizeof(data_chunk)) ||
+	if (!decoder_read_full(decoder, is, &data_chunk, sizeof(data_chunk)) ||
 	    !data_chunk.id.Equals("data"))
 		return false;
 
@@ -247,10 +248,10 @@ dsf_decode_chunk(Decoder &decoder, InputStream &is,
 			now_size = now_frames * frame_size;
 		}
 
-		size_t nbytes = decoder_read(&decoder, is, buffer, now_size);
-		if (nbytes != now_size)
+		if (!decoder_read_full(&decoder, is, buffer, now_size))
 			return false;
 
+		const size_t nbytes = now_size;
 		chunk_size -= nbytes;
 
 		if (bitreverse)
diff --git a/src/decoder/FaadDecoderPlugin.cxx b/src/decoder/FaadDecoderPlugin.cxx
index 9fd2016..b446ac5 100644
--- a/src/decoder/FaadDecoderPlugin.cxx
+++ b/src/decoder/FaadDecoderPlugin.cxx
@@ -34,8 +34,6 @@
 #include <string.h>
 #include <unistd.h>
 
-#define AAC_MAX_CHANNELS	6
-
 static const unsigned adts_sample_rates[] =
     { 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050,
 	16000, 12000, 11025, 8000, 7350, 0, 0, 0
@@ -66,16 +64,13 @@ adts_check_frame(const unsigned char *data)
 static size_t
 adts_find_frame(DecoderBuffer *buffer)
 {
-	size_t length, frame_length;
-	bool ret;
-
 	while (true) {
+		size_t length;
 		const uint8_t *data = (const uint8_t *)
 			decoder_buffer_read(buffer, &length);
 		if (data == nullptr || length < 8) {
 			/* not enough data yet */
-			ret = decoder_buffer_fill(buffer);
-			if (!ret)
+			if (!decoder_buffer_fill(buffer))
 				/* failed */
 				return 0;
 
@@ -86,7 +81,7 @@ adts_find_frame(DecoderBuffer *buffer)
 		const uint8_t *p = (const uint8_t *)memchr(data, 0xff, length);
 		if (p == nullptr) {
 			/* no marker - discard the buffer */
-			decoder_buffer_consume(buffer, length);
+			decoder_buffer_clear(buffer);
 			continue;
 		}
 
@@ -97,7 +92,7 @@ adts_find_frame(DecoderBuffer *buffer)
 		}
 
 		/* is it a frame? */
-		frame_length = adts_check_frame(data);
+		const size_t frame_length = adts_check_frame(data);
 		if (frame_length == 0) {
 			/* it's just some random 0xff byte; discard it
 			   and continue searching */
@@ -109,15 +104,11 @@ adts_find_frame(DecoderBuffer *buffer)
 			/* available buffer size is smaller than the
 			   frame will be - attempt to read more
 			   data */
-			ret = decoder_buffer_fill(buffer);
-			if (!ret) {
+			if (!decoder_buffer_fill(buffer)) {
 				/* not enough data; discard this frame
 				   to prevent a possible buffer
 				   overflow */
-				data = (const uint8_t *)
-					decoder_buffer_read(buffer, &length);
-				if (data != nullptr)
-					decoder_buffer_consume(buffer, length);
+				decoder_buffer_clear(buffer);
 			}
 
 			continue;
@@ -131,13 +122,18 @@ adts_find_frame(DecoderBuffer *buffer)
 static float
 adts_song_duration(DecoderBuffer *buffer)
 {
-	unsigned int frames, frame_length;
+	const InputStream &is = decoder_buffer_get_stream(buffer);
+	const bool estimate = !is.CheapSeeking();
+	const auto file_size = is.GetSize();
+	if (estimate && file_size <= 0)
+		return -1;
+
 	unsigned sample_rate = 0;
-	float frames_per_second;
 
 	/* Read all frames to ensure correct time and bitrate */
+	unsigned frames;
 	for (frames = 0;; frames++) {
-		frame_length = adts_find_frame(buffer);
+		const unsigned frame_length = adts_find_frame(buffer);
 		if (frame_length == 0)
 			break;
 
@@ -150,36 +146,52 @@ adts_song_duration(DecoderBuffer *buffer)
 			assert(frame_length <= buffer_length);
 
 			sample_rate = adts_sample_rates[(data[2] & 0x3c) >> 2];
+			if (sample_rate == 0)
+				break;
 		}
 
 		decoder_buffer_consume(buffer, frame_length);
+
+		if (estimate && frames == 128) {
+			/* if this is a remote file, don't slurp the
+			   whole file just for checking the song
+			   duration; instead, stop after some time and
+			   extrapolate the song duration from what we
+			   have until now */
+
+			const auto offset = is.GetOffset()
+				- decoder_buffer_available(buffer);
+			if (offset <= 0)
+				return -1;
+
+			frames = (frames * file_size) / offset;
+			break;
+		}
 	}
 
-	frames_per_second = (float)sample_rate / 1024.0;
-	if (frames_per_second <= 0)
+	if (sample_rate == 0)
 		return -1;
 
+	float frames_per_second = (float)sample_rate / 1024.0;
+	assert(frames_per_second > 0);
+
 	return (float)frames / frames_per_second;
 }
 
 static float
 faad_song_duration(DecoderBuffer *buffer, InputStream &is)
 {
-	size_t fileread;
-	size_t tagsize;
-	size_t length;
-	bool success;
-
 	const auto size = is.GetSize();
-	fileread = size >= 0 ? size : 0;
+	const size_t fileread = size >= 0 ? size : 0;
 
 	decoder_buffer_fill(buffer);
+	size_t length;
 	const uint8_t *data = (const uint8_t *)
 		decoder_buffer_read(buffer, &length);
 	if (data == nullptr)
 		return -1;
 
-	tagsize = 0;
+	size_t tagsize = 0;
 	if (length >= 10 && !memcmp(data, "ID3", 3)) {
 		/* skip the ID3 tag */
 
@@ -188,7 +200,7 @@ faad_song_duration(DecoderBuffer *buffer, InputStream &is)
 
 		tagsize += 10;
 
-		success = decoder_buffer_skip(buffer, tagsize) &&
+		const bool success = decoder_buffer_skip(buffer, tagsize) &&
 			decoder_buffer_fill(buffer);
 		if (!success)
 			return -1;
@@ -198,22 +210,20 @@ faad_song_duration(DecoderBuffer *buffer, InputStream &is)
 			return -1;
 	}
 
-	if (is.IsSeekable() && length >= 2 &&
-	    data[0] == 0xFF && ((data[1] & 0xF6) == 0xF0)) {
+	if (length >= 8 && adts_check_frame(data) > 0) {
 		/* obtain the duration from the ADTS header */
+
+		if (!is.IsSeekable())
+			return -1;
+
 		float song_length = adts_song_duration(buffer);
 
 		is.LockSeek(tagsize, SEEK_SET, IgnoreError());
-
-		data = (const uint8_t *)decoder_buffer_read(buffer, &length);
-		if (data != nullptr)
-			decoder_buffer_consume(buffer, length);
-		decoder_buffer_fill(buffer);
+		decoder_buffer_clear(buffer);
 
 		return song_length;
 	} else if (length >= 5 && memcmp(data, "ADIF", 4) == 0) {
 		/* obtain the duration from the ADIF header */
-		unsigned bit_rate;
 		size_t skip_size = (data[4] & 0x80) ? 9 : 0;
 
 		if (8 + skip_size > length)
@@ -221,7 +231,7 @@ faad_song_duration(DecoderBuffer *buffer, InputStream &is)
 			   header */
 			return -1;
 
-		bit_rate = ((data[4 + skip_size] & 0x0F) << 19) |
+		unsigned bit_rate = ((data[4 + skip_size] & 0x0F) << 19) |
 			(data[5 + skip_size] << 11) |
 			(data[6 + skip_size] << 3) |
 			(data[7 + skip_size] & 0xE0);
@@ -234,6 +244,21 @@ faad_song_duration(DecoderBuffer *buffer, InputStream &is)
 		return -1;
 }
 
+static NeAACDecHandle
+faad_decoder_new()
+{
+	const NeAACDecHandle decoder = NeAACDecOpen();
+
+	NeAACDecConfigurationPtr config =
+		NeAACDecGetCurrentConfiguration(decoder);
+	config->outputFormat = FAAD_FMT_16BIT;
+	config->downMatrix = 1;
+	config->dontUpSampleImplicitSBR = 0;
+	NeAACDecSetConfiguration(decoder, config);
+
+	return decoder;
+}
+
 /**
  * Wrapper for NeAACDecInit() which works around some API
  * inconsistencies in libfaad.
@@ -242,17 +267,6 @@ static bool
 faad_decoder_init(NeAACDecHandle decoder, DecoderBuffer *buffer,
 		  AudioFormat &audio_format, Error &error)
 {
-	int32_t nbytes;
-	uint32_t sample_rate;
-	uint8_t channels;
-#ifdef HAVE_FAAD_LONG
-	/* neaacdec.h declares all arguments as "unsigned long", but
-	   internally expects uint32_t pointers.  To avoid gcc
-	   warnings, use this workaround. */
-	unsigned long *sample_rate_p = (unsigned long *)(void *)&sample_rate;
-#else
-	uint32_t *sample_rate_p = &sample_rate;
-#endif
 
 	size_t length;
 	const unsigned char *data = (const unsigned char *)
@@ -262,11 +276,21 @@ faad_decoder_init(NeAACDecHandle decoder, DecoderBuffer *buffer,
 		return false;
 	}
 
-	nbytes = NeAACDecInit(decoder,
-			      /* deconst hack, libfaad requires this */
-			      const_cast<unsigned char *>(data),
-			     length,
-			     sample_rate_p, &channels);
+	uint8_t channels;
+	uint32_t sample_rate;
+#ifdef HAVE_FAAD_LONG
+	/* neaacdec.h declares all arguments as "unsigned long", but
+	   internally expects uint32_t pointers.  To avoid gcc
+	   warnings, use this workaround. */
+	unsigned long *sample_rate_p = (unsigned long *)(void *)&sample_rate;
+#else
+	uint32_t *sample_rate_p = &sample_rate;
+#endif
+	long nbytes = NeAACDecInit(decoder,
+				   /* deconst hack, libfaad requires this */
+				   const_cast<unsigned char *>(data),
+				   length,
+				   sample_rate_p, &channels);
 	if (nbytes < 0) {
 		error.Set(faad_decoder_domain, "Not an AAC stream");
 		return false;
@@ -306,29 +330,21 @@ faad_decoder_decode(NeAACDecHandle decoder, DecoderBuffer *buffer,
 static float
 faad_get_file_time_float(InputStream &is)
 {
-	DecoderBuffer *buffer;
-	float length;
+	DecoderBuffer *const buffer =
+		decoder_buffer_new(nullptr, is,
+				   FAAD_MIN_STREAMSIZE * MAX_CHANNELS);
 
-	buffer = decoder_buffer_new(nullptr, is,
-				    FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS);
-	length = faad_song_duration(buffer, is);
+	float length = faad_song_duration(buffer, is);
 
 	if (length < 0) {
-		bool ret;
 		AudioFormat audio_format;
 
-		NeAACDecHandle decoder = NeAACDecOpen();
-
-		NeAACDecConfigurationPtr config =
-			NeAACDecGetCurrentConfiguration(decoder);
-		config->outputFormat = FAAD_FMT_16BIT;
-		NeAACDecSetConfiguration(decoder, config);
+		NeAACDecHandle decoder = faad_decoder_new();
 
 		decoder_buffer_fill(buffer);
 
-		ret = faad_decoder_init(decoder, buffer, audio_format,
-					IgnoreError());
-		if (ret)
+		if (faad_decoder_init(decoder, buffer, audio_format,
+				      IgnoreError()))
 			length = 0;
 
 		NeAACDecClose(decoder);
@@ -347,38 +363,25 @@ faad_get_file_time_float(InputStream &is)
 static int
 faad_get_file_time(InputStream &is)
 {
-	int file_time = -1;
-	float length;
-
-	if ((length = faad_get_file_time_float(is)) >= 0)
-		file_time = length + 0.5;
+	float length = faad_get_file_time_float(is);
+	if (length < 0)
+		return -1;
 
-	return file_time;
+	return int(length + 0.5);
 }
 
 static void
 faad_stream_decode(Decoder &mpd_decoder, InputStream &is)
 {
-	float total_time = 0;
-	AudioFormat audio_format;
-	bool ret;
-	uint16_t bit_rate = 0;
-	DecoderBuffer *buffer;
+	DecoderBuffer *const buffer =
+		decoder_buffer_new(&mpd_decoder, is,
+				   FAAD_MIN_STREAMSIZE * MAX_CHANNELS);
 
-	buffer = decoder_buffer_new(&mpd_decoder, is,
-				    FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS);
-	total_time = faad_song_duration(buffer, is);
+	const float total_time = faad_song_duration(buffer, is);
 
 	/* create the libfaad decoder */
 
-	NeAACDecHandle decoder = NeAACDecOpen();
-
-	NeAACDecConfigurationPtr config =
-		NeAACDecGetCurrentConfiguration(decoder);
-	config->outputFormat = FAAD_FMT_16BIT;
-	config->downMatrix = 1;
-	config->dontUpSampleImplicitSBR = 0;
-	NeAACDecSetConfiguration(decoder, config);
+	const NeAACDecHandle decoder = faad_decoder_new();
 
 	while (!decoder_buffer_is_full(buffer) && !is.LockIsEOF() &&
 	       decoder_get_command(mpd_decoder) == DecoderCommand::NONE) {
@@ -389,8 +392,8 @@ faad_stream_decode(Decoder &mpd_decoder, InputStream &is)
 	/* initialize it */
 
 	Error error;
-	ret = faad_decoder_init(decoder, buffer, audio_format, error);
-	if (!ret) {
+	AudioFormat audio_format;
+	if (!faad_decoder_init(decoder, buffer, audio_format, error)) {
 		LogError(error);
 		NeAACDecClose(decoder);
 		decoder_buffer_free(buffer);
@@ -404,21 +407,20 @@ faad_stream_decode(Decoder &mpd_decoder, InputStream &is)
 	/* the decoder loop */
 
 	DecoderCommand cmd;
+	uint16_t bit_rate = 0;
 	do {
-		size_t frame_size;
-		const void *decoded;
-		NeAACDecFrameInfo frame_info;
-
 		/* find the next frame */
 
-		frame_size = adts_find_frame(buffer);
+		const size_t frame_size = adts_find_frame(buffer);
 		if (frame_size == 0)
 			/* end of file */
 			break;
 
 		/* decode it */
 
-		decoded = faad_decoder_decode(decoder, buffer, &frame_info);
+		NeAACDecFrameInfo frame_info;
+		const void *const decoded =
+			faad_decoder_decode(decoder, buffer, &frame_info);
 
 		if (frame_info.error > 0) {
 			FormatWarning(faad_decoder_domain,
@@ -470,7 +472,6 @@ faad_scan_stream(InputStream &is,
 		 const struct tag_handler *handler, void *handler_ctx)
 {
 	int file_time = faad_get_file_time(is);
-
 	if (file_time < 0)
 		return false;
 
diff --git a/src/decoder/MadDecoderPlugin.cxx b/src/decoder/MadDecoderPlugin.cxx
index 9dd86c5..6f619b3 100644
--- a/src/decoder/MadDecoderPlugin.cxx
+++ b/src/decoder/MadDecoderPlugin.cxx
@@ -353,18 +353,8 @@ MadDecoder::ParseId3(size_t tagsize, Tag **mpd_tag)
 		memcpy(allocated, stream.this_frame, count);
 		mad_stream_skip(&(stream), count);
 
-		while (count < tagsize) {
-			size_t len;
-
-			len = decoder_read(decoder, input_stream,
-					   allocated + count, tagsize - count);
-			if (len == 0)
-				break;
-			else
-				count += len;
-		}
-
-		if (count != tagsize) {
+		if (!decoder_read_full(decoder, input_stream,
+				       allocated + count, tagsize - count)) {
 			LogDebug(mad_domain, "error parsing ID3 tag");
 			g_free(allocated);
 			return;
@@ -413,20 +403,7 @@ MadDecoder::ParseId3(size_t tagsize, Tag **mpd_tag)
 		mad_stream_skip(&stream, tagsize);
 	} else {
 		mad_stream_skip(&stream, count);
-
-		while (count < tagsize) {
-			size_t len = tagsize - count;
-			char ignored[1024];
-			if (len > sizeof(ignored))
-				len = sizeof(ignored);
-
-			len = decoder_read(decoder, input_stream,
-					   ignored, len);
-			if (len == 0)
-				break;
-			else
-				count += len;
-		}
+		decoder_skip(decoder, input_stream, tagsize - count);
 	}
 #endif
 }
diff --git a/src/decoder/OpusDecoderPlugin.cxx b/src/decoder/OpusDecoderPlugin.cxx
index f3d7b34..01ea368 100644
--- a/src/decoder/OpusDecoderPlugin.cxx
+++ b/src/decoder/OpusDecoderPlugin.cxx
@@ -40,6 +40,7 @@
 #include <glib.h>
 
 #include <string.h>
+#include <stdio.h>
 
 static constexpr opus_int32 opus_sample_rate = 48000;
 
diff --git a/src/decoder/SndfileDecoderPlugin.cxx b/src/decoder/SndfileDecoderPlugin.cxx
index 77b1329..bcdf6d7 100644
--- a/src/decoder/SndfileDecoderPlugin.cxx
+++ b/src/decoder/SndfileDecoderPlugin.cxx
@@ -31,10 +31,24 @@
 
 static constexpr Domain sndfile_domain("sndfile");
 
+struct SndfileInputStream {
+	Decoder *const decoder;
+	InputStream &is;
+
+	size_t Read(void *buffer, size_t size) {
+		/* libsndfile chokes on partial reads; therefore
+		   always force full reads */
+		return decoder_read_full(decoder, is, buffer, size)
+			? size
+			: 0;
+	}
+};
+
 static sf_count_t
 sndfile_vio_get_filelen(void *user_data)
 {
-	const InputStream &is = *(const InputStream *)user_data;
+	SndfileInputStream &sis = *(SndfileInputStream *)user_data;
+	const InputStream &is = sis.is;
 
 	return is.GetSize();
 }
@@ -42,10 +56,14 @@ sndfile_vio_get_filelen(void *user_data)
 static sf_count_t
 sndfile_vio_seek(sf_count_t offset, int whence, void *user_data)
 {
-	InputStream &is = *(InputStream *)user_data;
+	SndfileInputStream &sis = *(SndfileInputStream *)user_data;
+	InputStream &is = sis.is;
 
-	if (!is.LockSeek(offset, whence, IgnoreError()))
+	Error error;
+	if (!is.LockSeek(offset, whence, error)) {
+		LogError(error, "Seek failed");
 		return -1;
+	}
 
 	return is.GetOffset();
 }
@@ -53,30 +71,9 @@ sndfile_vio_seek(sf_count_t offset, int whence, void *user_data)
 static sf_count_t
 sndfile_vio_read(void *ptr, sf_count_t count, void *user_data)
 {
-	InputStream &is = *(InputStream *)user_data;
-
-	sf_count_t total_bytes = 0;
-	Error error;
-
-	/* this loop is necessary because libsndfile chokes on partial
-	   reads */
-
-	do {
-		size_t nbytes = is.LockRead((char *)ptr + total_bytes,
-					    count - total_bytes, error);
-		if (nbytes == 0) {
-			if (error.IsDefined()) {
-				LogError(error);
-				return -1;
-			}
-
-			break;
-		}
-
-		total_bytes += nbytes;
-	} while (total_bytes < count);
+	SndfileInputStream &sis = *(SndfileInputStream *)user_data;
 
-	return total_bytes;
+	return sis.Read(ptr, count);
 }
 
 static sf_count_t
@@ -91,7 +88,8 @@ sndfile_vio_write(gcc_unused const void *ptr,
 static sf_count_t
 sndfile_vio_tell(void *user_data)
 {
-	const InputStream &is = *(const InputStream *)user_data;
+	SndfileInputStream &sis = *(SndfileInputStream *)user_data;
+	const InputStream &is = sis.is;
 
 	return is.GetOffset();
 }
@@ -137,7 +135,8 @@ sndfile_stream_decode(Decoder &decoder, InputStream &is)
 
 	info.format = 0;
 
-	sf = sf_open_virtual(&vio, SFM_READ, &info, &is);
+	SndfileInputStream sis{&decoder, is};
+	sf = sf_open_virtual(&vio, SFM_READ, &info, &sis);
 	if (sf == nullptr) {
 		LogWarning(sndfile_domain, "sf_open_virtual() failed");
 		return;
diff --git a/src/output/HttpdClient.cxx b/src/output/HttpdClient.cxx
index 8e13fda..dc33705 100644
--- a/src/output/HttpdClient.cxx
+++ b/src/output/HttpdClient.cxx
@@ -30,6 +30,7 @@
 
 #include <assert.h>
 #include <string.h>
+#include <stdio.h>
 
 HttpdClient::~HttpdClient()
 {
diff --git a/src/playlist/PlsPlaylistPlugin.cxx b/src/playlist/PlsPlaylistPlugin.cxx
index 99be3ad..7b5c882 100644
--- a/src/playlist/PlsPlaylistPlugin.cxx
+++ b/src/playlist/PlsPlaylistPlugin.cxx
@@ -31,6 +31,7 @@
 #include <glib.h>
 
 #include <string>
+#include <stdio.h>
 
 static constexpr Domain pls_domain("pls");
 
diff --git a/test/run_decoder.cxx b/test/FakeDecoderAPI.cxx
similarity index 50%
copy from test/run_decoder.cxx
copy to test/FakeDecoderAPI.cxx
index 2f7330a..ca09ca9 100644
--- a/test/run_decoder.cxx
+++ b/test/FakeDecoderAPI.cxx
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * Copyright (C) 2003-2012 The Music Player Daemon Project
  * http://www.musicpd.org
  *
  * This program is free software; you can redistribute it and/or modify
@@ -18,57 +18,21 @@
  */
 
 #include "config.h"
-#include "IOThread.hxx"
-#include "DecoderList.hxx"
 #include "DecoderAPI.hxx"
-#include "InputInit.hxx"
 #include "InputStream.hxx"
-#include "AudioFormat.hxx"
 #include "util/Error.hxx"
-#include "thread/Cond.hxx"
-#include "Log.hxx"
-#include "stdbin.h"
+#include "Compiler.h"
 
 #include <glib.h>
 
-#include <assert.h>
 #include <unistd.h>
-#include <stdlib.h>
-
-static void
-my_log_func(const gchar *log_domain, gcc_unused GLogLevelFlags log_level,
-	    const gchar *message, gcc_unused gpointer user_data)
-{
-	if (log_domain != NULL)
-		g_printerr("%s: %s\n", log_domain, message);
-	else
-		g_printerr("%s\n", message);
-}
-
-struct Decoder {
-	const char *uri;
-
-	const struct DecoderPlugin *plugin;
-
-	bool initialized;
-};
 
 void
-decoder_initialized(Decoder &decoder,
-		    const AudioFormat audio_format,
+decoder_initialized(gcc_unused Decoder &decoder,
+		    gcc_unused const AudioFormat audio_format,
 		    gcc_unused bool seekable,
-		    float duration)
+		    gcc_unused float total_time)
 {
-	struct audio_format_string af_string;
-
-	assert(!decoder.initialized);
-	assert(audio_format.IsValid());
-
-	g_printerr("audio_format=%s duration=%f\n",
-		   audio_format_to_string(audio_format, &af_string),
-		   duration);
-
-	decoder.initialized = true;
 }
 
 DecoderCommand
@@ -101,6 +65,40 @@ decoder_read(gcc_unused Decoder *decoder,
 	return is.LockRead(buffer, length, IgnoreError());
 }
 
+bool
+decoder_read_full(Decoder *decoder, InputStream &is,
+		  void *_buffer, size_t size)
+{
+	uint8_t *buffer = (uint8_t *)_buffer;
+
+	while (size > 0) {
+		size_t nbytes = decoder_read(decoder, is, buffer, size);
+		if (nbytes == 0)
+			return false;
+
+		buffer += nbytes;
+		size -= nbytes;
+	}
+
+	return true;
+}
+
+bool
+decoder_skip(Decoder *decoder, InputStream &is, size_t size)
+{
+	while (size > 0) {
+		char buffer[1024];
+		size_t nbytes = decoder_read(decoder, is, buffer,
+					     std::min(sizeof(buffer), size));
+		if (nbytes == 0)
+			return false;
+
+		size -= nbytes;
+	}
+
+	return true;
+}
+
 void
 decoder_timestamp(gcc_unused Decoder &decoder,
 		  gcc_unused double t)
@@ -144,78 +142,3 @@ void
 decoder_mixramp(gcc_unused Decoder &decoder, gcc_unused MixRampInfo &&mix_ramp)
 {
 }
-
-int main(int argc, char **argv)
-{
-	const char *decoder_name;
-
-	if (argc != 3) {
-		g_printerr("Usage: run_decoder DECODER URI >OUT\n");
-		return 1;
-	}
-
-	Decoder decoder;
-	decoder_name = argv[1];
-	decoder.uri = argv[2];
-
-#if !GLIB_CHECK_VERSION(2,32,0)
-	g_thread_init(NULL);
-#endif
-
-	g_log_set_default_handler(my_log_func, NULL);
-
-	io_thread_init();
-	io_thread_start();
-
-	Error error;
-	if (!input_stream_global_init(error)) {
-		LogError(error);
-		return 2;
-	}
-
-	decoder_plugin_init_all();
-
-	decoder.plugin = decoder_plugin_from_name(decoder_name);
-	if (decoder.plugin == NULL) {
-		g_printerr("No such decoder: %s\n", decoder_name);
-		return 1;
-	}
-
-	decoder.initialized = false;
-
-	if (decoder.plugin->file_decode != NULL) {
-		decoder.plugin->FileDecode(decoder, decoder.uri);
-	} else if (decoder.plugin->stream_decode != NULL) {
-		Mutex mutex;
-		Cond cond;
-
-		InputStream *is =
-			InputStream::Open(decoder.uri, mutex, cond, error);
-		if (is == NULL) {
-			if (error.IsDefined())
-				LogError(error);
-			else
-				g_printerr("InputStream::Open() failed\n");
-
-			return 1;
-		}
-
-		decoder.plugin->StreamDecode(decoder, *is);
-
-		is->Close();
-	} else {
-		g_printerr("Decoder plugin is not usable\n");
-		return 1;
-	}
-
-	decoder_plugin_deinit_all();
-	input_stream_global_finish();
-	io_thread_deinit();
-
-	if (!decoder.initialized) {
-		g_printerr("Decoding failed\n");
-		return 1;
-	}
-
-	return 0;
-}
diff --git a/test/dump_playlist.cxx b/test/dump_playlist.cxx
index d115629..4ac0298 100644
--- a/test/dump_playlist.cxx
+++ b/test/dump_playlist.cxx
@@ -24,7 +24,6 @@
 #include "Directory.hxx"
 #include "InputStream.hxx"
 #include "ConfigGlobal.hxx"
-#include "DecoderAPI.hxx"
 #include "DecoderList.hxx"
 #include "InputInit.hxx"
 #include "IOThread.hxx"
@@ -53,88 +52,6 @@ my_log_func(const gchar *log_domain, gcc_unused GLogLevelFlags log_level,
 		g_printerr("%s\n", message);
 }
 
-void
-decoder_initialized(gcc_unused Decoder &decoder,
-		    gcc_unused const AudioFormat audio_format,
-		    gcc_unused bool seekable,
-		    gcc_unused float total_time)
-{
-}
-
-DecoderCommand
-decoder_get_command(gcc_unused Decoder &decoder)
-{
-	return DecoderCommand::NONE;
-}
-
-void
-decoder_command_finished(gcc_unused Decoder &decoder)
-{
-}
-
-double
-decoder_seek_where(gcc_unused Decoder &decoder)
-{
-	return 1.0;
-}
-
-void
-decoder_seek_error(gcc_unused Decoder &decoder)
-{
-}
-
-size_t
-decoder_read(gcc_unused Decoder *decoder,
-	     InputStream &is,
-	     void *buffer, size_t length)
-{
-	return is.LockRead(buffer, length, IgnoreError());
-}
-
-void
-decoder_timestamp(gcc_unused Decoder &decoder,
-		  gcc_unused double t)
-{
-}
-
-DecoderCommand
-decoder_data(gcc_unused Decoder &decoder,
-	     gcc_unused InputStream *is,
-	     const void *data, size_t datalen,
-	     gcc_unused uint16_t kbit_rate)
-{
-	gcc_unused ssize_t nbytes = write(1, data, datalen);
-	return DecoderCommand::NONE;
-}
-
-DecoderCommand
-decoder_tag(gcc_unused Decoder &decoder,
-	    gcc_unused InputStream *is,
-	    gcc_unused Tag &&tag)
-{
-	return DecoderCommand::NONE;
-}
-
-void
-decoder_replay_gain(gcc_unused Decoder &decoder,
-		    const ReplayGainInfo *rgi)
-{
-	const ReplayGainTuple *tuple = &rgi->tuples[REPLAY_GAIN_ALBUM];
-	if (tuple->IsDefined())
-		g_printerr("replay_gain[album]: gain=%f peak=%f\n",
-			   tuple->gain, tuple->peak);
-
-	tuple = &rgi->tuples[REPLAY_GAIN_TRACK];
-	if (tuple->IsDefined())
-		g_printerr("replay_gain[track]: gain=%f peak=%f\n",
-			   tuple->gain, tuple->peak);
-}
-
-void
-decoder_mixramp(gcc_unused Decoder &decoder, gcc_unused MixRampInfo &&mix_ramp)
-{
-}
-
 int main(int argc, char **argv)
 {
 	const char *uri;
diff --git a/test/read_tags.cxx b/test/read_tags.cxx
index 90f1424..52b2561 100644
--- a/test/read_tags.cxx
+++ b/test/read_tags.cxx
@@ -20,7 +20,7 @@
 #include "config.h"
 #include "IOThread.hxx"
 #include "DecoderList.hxx"
-#include "DecoderAPI.hxx"
+#include "DecoderPlugin.hxx"
 #include "InputInit.hxx"
 #include "InputStream.hxx"
 #include "AudioFormat.hxx"
@@ -42,79 +42,6 @@
 #include <locale.h>
 #endif
 
-void
-decoder_initialized(gcc_unused Decoder &decoder,
-		    gcc_unused const AudioFormat audio_format,
-		    gcc_unused bool seekable,
-		    gcc_unused float total_time)
-{
-}
-
-DecoderCommand
-decoder_get_command(gcc_unused Decoder &decoder)
-{
-	return DecoderCommand::NONE;
-}
-
-void
-decoder_command_finished(gcc_unused Decoder &decoder)
-{
-}
-
-double
-decoder_seek_where(gcc_unused Decoder &decoder)
-{
-	return 1.0;
-}
-
-void
-decoder_seek_error(gcc_unused Decoder &decoder)
-{
-}
-
-size_t
-decoder_read(gcc_unused Decoder *decoder,
-	     InputStream &is,
-	     void *buffer, size_t length)
-{
-	return is.LockRead(buffer, length, IgnoreError());
-}
-
-void
-decoder_timestamp(gcc_unused Decoder &decoder,
-		  gcc_unused double t)
-{
-}
-
-DecoderCommand
-decoder_data(gcc_unused Decoder &decoder,
-	     gcc_unused InputStream *is,
-	     const void *data, size_t datalen,
-	     gcc_unused uint16_t kbit_rate)
-{
-	gcc_unused ssize_t nbytes = write(1, data, datalen);
-	return DecoderCommand::NONE;
-}
-
-DecoderCommand
-decoder_tag(gcc_unused Decoder &decoder,
-	    gcc_unused InputStream *is,
-	    gcc_unused Tag &&tag)
-{
-	return DecoderCommand::NONE;
-}
-
-void
-decoder_replay_gain(gcc_unused Decoder &decoder,
-		    gcc_unused const ReplayGainInfo *replay_gain_info)
-{
-}
-
-void
-decoder_mixramp(gcc_unused Decoder &decoder, gcc_unused MixRampInfo &&mix_ramp)
-{
-}
-
 static bool empty = true;
 
 static void
diff --git a/test/run_decoder.cxx b/test/run_decoder.cxx
index 2f7330a..7db8dde 100644
--- a/test/run_decoder.cxx
+++ b/test/run_decoder.cxx
@@ -101,6 +101,40 @@ decoder_read(gcc_unused Decoder *decoder,
 	return is.LockRead(buffer, length, IgnoreError());
 }
 
+bool
+decoder_read_full(Decoder *decoder, InputStream &is,
+		  void *_buffer, size_t size)
+{
+	uint8_t *buffer = (uint8_t *)_buffer;
+
+	while (size > 0) {
+		size_t nbytes = decoder_read(decoder, is, buffer, size);
+		if (nbytes == 0)
+			return false;
+
+		buffer += nbytes;
+		size -= nbytes;
+	}
+
+	return true;
+}
+
+bool
+decoder_skip(Decoder *decoder, InputStream &is, size_t size)
+{
+	while (size > 0) {
+		char buffer[1024];
+		size_t nbytes = decoder_read(decoder, is, buffer,
+					     std::min(sizeof(buffer), size));
+		if (nbytes == 0)
+			return false;
+
+		size -= nbytes;
+	}
+
+	return true;
+}
+
 void
 decoder_timestamp(gcc_unused Decoder &decoder,
 		  gcc_unused double t)

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-mpd/pkg-mpd.git



More information about the Pkg-mpd-commits mailing list