diff --git a/NEWS b/NEWS
index 5dfb6ca55..bcba64a8d 100644
--- a/NEWS
+++ b/NEWS
@@ -13,6 +13,10 @@ ver 0.22 (not yet released)
   - hdcd: new plugin based on FFmpeg's "af_hdcd" for HDCD playback
 
 ver 0.21.11 (not yet released)
+* input
+  - tidal: deprecated because Tidal has changed the protocol
+* decoder
+  - wildmidi: log error if library initialization fails
 * output
   - alsa, osx: fix distortions with DSD_U32 and DoP on 32 bit CPUs
 * protocol
diff --git a/doc/plugins.rst b/doc/plugins.rst
index 47bd61e0e..0568d208f 100644
--- a/doc/plugins.rst
+++ b/doc/plugins.rst
@@ -269,6 +269,11 @@ tidal
 
 Play songs from the commercial streaming service `Tidal <http://tidal.com/>`_. It plays URLs in the form tidal://track/ID, e.g.:
 
+.. warning::
+
+   This plugin is currently defunct because Tidal has changed the
+   protocol and decided not to share documentation.
+
 .. code-block:: none
 
     mpc add tidal://track/59727857
diff --git a/src/decoder/DecoderList.cxx b/src/decoder/DecoderList.cxx
index 4f78c2a9c..cf6d47958 100644
--- a/src/decoder/DecoderList.cxx
+++ b/src/decoder/DecoderList.cxx
@@ -20,6 +20,8 @@
 #include "config.h"
 #include "DecoderList.hxx"
 #include "DecoderPlugin.hxx"
+#include "PluginUnavailable.hxx"
+#include "Log.hxx"
 #include "config/Data.hxx"
 #include "config/Block.hxx"
 #include "plugins/AudiofileDecoderPlugin.hxx"
@@ -45,6 +47,7 @@
 #include "plugins/FluidsynthDecoderPlugin.hxx"
 #include "plugins/SidplayDecoderPlugin.hxx"
 #include "util/Macros.hxx"
+#include "util/RuntimeError.hxx"
 
 #include <string.h>
 
@@ -147,8 +150,17 @@ decoder_plugin_init_all(const ConfigData &config)
 		if (param != nullptr)
 			param->SetUsed();
 
-		if (plugin.Init(*param))
-			decoder_plugins_enabled[i] = true;
+		try {
+			if (plugin.Init(*param))
+				decoder_plugins_enabled[i] = true;
+		} catch (const PluginUnavailable &e) {
+			FormatError(e,
+				    "Decoder plugin '%s' is unavailable",
+				    plugin.name);
+		} catch (...) {
+			std::throw_with_nested(FormatRuntimeError("Failed to initialize decoder plugin '%s'",
+								  plugin.name));
+		}
 	}
 }
 
diff --git a/src/decoder/plugins/WildmidiDecoderPlugin.cxx b/src/decoder/plugins/WildmidiDecoderPlugin.cxx
index aff85b78c..021ac4eb1 100644
--- a/src/decoder/plugins/WildmidiDecoderPlugin.cxx
+++ b/src/decoder/plugins/WildmidiDecoderPlugin.cxx
@@ -20,18 +20,18 @@
 #include "WildmidiDecoderPlugin.hxx"
 #include "../DecoderAPI.hxx"
 #include "tag/Handler.hxx"
-#include "util/Domain.hxx"
+#include "util/ScopeExit.hxx"
+#include "util/StringFormat.hxx"
 #include "fs/AllocatedPath.hxx"
 #include "fs/FileSystem.hxx"
 #include "fs/Path.hxx"
 #include "Log.hxx"
+#include "PluginUnavailable.hxx"
 
 extern "C" {
 #include <wildmidi_lib.h>
 }
 
-static constexpr Domain wildmidi_domain("wildmidi");
-
 static constexpr AudioFormat wildmidi_audio_format{48000, SampleFormat::S16, 2};
 
 static bool
@@ -43,14 +43,27 @@ wildmidi_init(const ConfigBlock &block)
 
 	if (!FileExists(path)) {
 		const auto utf8 = path.ToUTF8();
-		FormatDebug(wildmidi_domain,
-			    "configuration file does not exist: %s",
-			    utf8.c_str());
-		return false;
+		throw PluginUnavailable(StringFormat<1024>("configuration file does not exist: %s",
+							   utf8.c_str()));
 	}
 
-	return WildMidi_Init(path.c_str(), wildmidi_audio_format.sample_rate,
-			     0) == 0;
+#ifdef LIBWILDMIDI_VERSION
+	/* WildMidi_ClearError() requires libwildmidi 0.4 */
+	WildMidi_ClearError();
+	AtScopeExit() { WildMidi_ClearError(); };
+#endif
+
+	if (WildMidi_Init(path.c_str(), wildmidi_audio_format.sample_rate,
+			  0) != 0) {
+#ifdef LIBWILDMIDI_VERSION
+		/* WildMidi_GetError() requires libwildmidi 0.4 */
+		throw PluginUnavailable(WildMidi_GetError());
+#else
+		throw PluginUnavailable("WildMidi_Init() failed");
+#endif
+	}
+
+	return true;
 }
 
 static void
diff --git a/src/input/plugins/TidalInputPlugin.cxx b/src/input/plugins/TidalInputPlugin.cxx
index f9b6f0fd1..7a47d6902 100644
--- a/src/input/plugins/TidalInputPlugin.cxx
+++ b/src/input/plugins/TidalInputPlugin.cxx
@@ -180,6 +180,8 @@ InitTidalInput(EventLoop &event_loop, const ConfigBlock &block)
 	if (password == nullptr)
 		throw PluginUnconfigured("No Tidal password configured");
 
+	FormatWarning(tidal_domain, "The Tidal input plugin is deprecated because Tidal has changed the protocol and doesn't share documentation");
+
 	tidal_audioquality = block.GetBlockValue("audioquality", "HIGH");
 
 	tidal_session = new TidalSessionManager(event_loop, base_url, token,