diff --git a/src/mixer/plugins/PipeWireMixerPlugin.cxx b/src/mixer/plugins/PipeWireMixerPlugin.cxx
new file mode 100644
index 000000000..2a8bb3a6c
--- /dev/null
+++ b/src/mixer/plugins/PipeWireMixerPlugin.cxx
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2003-2021 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "PipeWireMixerPlugin.hxx"
+#include "mixer/MixerInternal.hxx"
+#include "mixer/Listener.hxx"
+#include "output/plugins/PipeWireOutputPlugin.hxx"
+
+#include <cmath>
+
+class PipeWireMixer final : public Mixer {
+	PipeWireOutput &output;
+
+	int volume = 100;
+
+public:
+	PipeWireMixer(PipeWireOutput &_output,
+		      MixerListener &_listener) noexcept
+		:Mixer(pipewire_mixer_plugin, _listener),
+		 output(_output)
+	{
+	}
+
+	PipeWireMixer(const PipeWireMixer &) = delete;
+	PipeWireMixer &operator=(const PipeWireMixer &) = delete;
+
+	void OnVolumeChanged(float new_volume) noexcept {
+		volume = std::lround(new_volume * 100.f);
+
+		listener.OnMixerVolumeChanged(*this, volume);
+	}
+
+	/* virtual methods from class Mixer */
+	void Open() override {
+	}
+
+	void Close() noexcept override {
+	}
+
+	int GetVolume() override;
+	void SetVolume(unsigned volume) override;
+};
+
+void
+pipewire_mixer_on_change(PipeWireMixer &pm, float new_volume) noexcept
+{
+	pm.OnVolumeChanged(new_volume);
+}
+
+int
+PipeWireMixer::GetVolume()
+{
+	return volume;
+}
+
+void
+PipeWireMixer::SetVolume(unsigned new_volume)
+{
+	pipewire_output_set_volume(output, float(new_volume) * 0.01f);
+	volume = new_volume;
+}
+
+static Mixer *
+pipewire_mixer_init([[maybe_unused]] EventLoop &event_loop, AudioOutput &ao,
+		    MixerListener &listener,
+		    const ConfigBlock &)
+{
+	auto &po = (PipeWireOutput &)ao;
+	return new PipeWireMixer(po, listener);
+}
+
+const MixerPlugin pipewire_mixer_plugin = {
+	pipewire_mixer_init,
+	true,
+};
diff --git a/src/mixer/plugins/PipeWireMixerPlugin.hxx b/src/mixer/plugins/PipeWireMixerPlugin.hxx
new file mode 100644
index 000000000..40c3af836
--- /dev/null
+++ b/src/mixer/plugins/PipeWireMixerPlugin.hxx
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2003-2021 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PIPEWIRE_MIXER_PLUGIN_HXX
+#define MPD_PIPEWIRE_MIXER_PLUGIN_HXX
+
+struct MixerPlugin;
+class PipeWireMixer;
+
+extern const MixerPlugin pipewire_mixer_plugin;
+
+void
+pipewire_mixer_on_change(PipeWireMixer &pm, float new_volume) noexcept;
+
+#endif
diff --git a/src/mixer/plugins/meson.build b/src/mixer/plugins/meson.build
index 6c23d6af0..7189ed2d8 100644
--- a/src/mixer/plugins/meson.build
+++ b/src/mixer/plugins/meson.build
@@ -22,6 +22,10 @@ if is_darwin
   mixer_plugins_sources += 'OSXMixerPlugin.cxx'
 endif
 
+if pipewire_dep.found()
+  mixer_plugins_sources += 'PipeWireMixerPlugin.cxx'
+endif
+
 if pulse_dep.found()
   mixer_plugins_sources += 'PulseMixerPlugin.cxx'
 endif
diff --git a/src/output/plugins/PipeWireOutputPlugin.cxx b/src/output/plugins/PipeWireOutputPlugin.cxx
index 957bcbaf9..d71505e6a 100644
--- a/src/output/plugins/PipeWireOutputPlugin.cxx
+++ b/src/output/plugins/PipeWireOutputPlugin.cxx
@@ -21,6 +21,7 @@
 #include "lib/pipewire/ThreadLoop.hxx"
 #include "../OutputAPI.hxx"
 #include "../Error.hxx"
+#include "mixer/plugins/PipeWireMixerPlugin.hxx"
 
 #ifdef __GNUC__
 #pragma GCC diagnostic push
@@ -32,6 +33,7 @@
 
 #include <pipewire/pipewire.h>
 #include <spa/param/audio/format-utils.h>
+#include <spa/param/props.h>
 
 #ifdef __GNUC__
 #pragma GCC diagnostic pop
@@ -56,7 +58,18 @@ class PipeWireOutput final : AudioOutput {
 
 	const uint32_t target_id;
 
+	float volume = 1.0;
+
 	bool disconnected;
+
+	/**
+	 * Shall the previously known volume be restored as soon as
+	 * PW_STREAM_STATE_STREAMING is reached?  This needs to be
+	 * done each time after the pw_stream got created, thus this
+	 * flag gets set by Open().
+	 */
+	bool restore_volume;
+
 	bool interrupted;
 	bool paused;
 	bool drained;
@@ -80,6 +93,8 @@ public:
 		return events;
 	}
 
+	void SetVolume(float volume);
+
 private:
 	void CheckThrowError() {
 		if (disconnected)
@@ -147,6 +162,20 @@ PipeWireOutput::PipeWireOutput(const ConfigBlock &block)
 {
 }
 
+void
+PipeWireOutput::SetVolume(float _volume)
+{
+	const PipeWire::ThreadLoopLock lock(thread_loop);
+
+	if (stream != nullptr && !restore_volume &&
+	    pw_stream_set_control(stream,
+				  SPA_PROP_volume, 1, &_volume,
+				  0) != 0)
+		throw std::runtime_error("pw_stream_set_control() failed");
+
+	volume = _volume;
+}
+
 void
 PipeWireOutput::Enable()
 {
@@ -282,6 +311,7 @@ void
 PipeWireOutput::Open(AudioFormat &audio_format)
 {
 	disconnected = false;
+	restore_volume = true;
 	paused = false;
 	drained = true;
 
@@ -292,6 +322,8 @@ PipeWireOutput::Open(AudioFormat &audio_format)
 				       PW_KEY_NODE_NAME, "mpd",
 				       nullptr);
 
+	const PipeWire::ThreadLoopLock lock(thread_loop);
+
 	stream = pw_stream_new_simple(pw_thread_loop_get_loop(thread_loop),
 				      "mpd",
 				      props,
@@ -317,7 +349,6 @@ PipeWireOutput::Open(AudioFormat &audio_format)
 	params[0] = spa_format_audio_raw_build(&pod_builder,
 					       SPA_PARAM_EnumFormat, &raw);
 
-	const PipeWire::ThreadLoopLock lock(thread_loop);
 	pw_stream_connect(stream,
 			  PW_DIRECTION_OUTPUT,
 			  target_id,
@@ -333,6 +364,7 @@ PipeWireOutput::Close() noexcept
 	{
 		const PipeWire::ThreadLoopLock lock(thread_loop);
 		pw_stream_destroy(stream);
+		stream = nullptr;
 	}
 
 	delete ring_buffer;
@@ -347,6 +379,15 @@ PipeWireOutput::StateChanged(enum pw_stream_state state,
 		state == PW_STREAM_STATE_UNCONNECTED;
 	if (!was_disconnected && disconnected)
 		pw_thread_loop_signal(thread_loop, false);
+
+	if (state == PW_STREAM_STATE_STREAMING && restore_volume) {
+		/* restore the last known volume after creating a new
+		   pw_stream */
+		restore_volume = false;
+		pw_stream_set_control(stream,
+				      SPA_PROP_volume, 1, &volume,
+				      0);
+	}
 }
 
 inline void
@@ -442,5 +483,11 @@ const struct AudioOutputPlugin pipewire_output_plugin = {
 	"pipewire",
 	nullptr,
 	&PipeWireOutput::Create,
-	nullptr,
+	&pipewire_mixer_plugin,
 };
+
+void
+pipewire_output_set_volume(PipeWireOutput &output, float volume)
+{
+	output.SetVolume(volume);
+}
diff --git a/src/output/plugins/PipeWireOutputPlugin.hxx b/src/output/plugins/PipeWireOutputPlugin.hxx
index cf942ec64..9e8cd2c23 100644
--- a/src/output/plugins/PipeWireOutputPlugin.hxx
+++ b/src/output/plugins/PipeWireOutputPlugin.hxx
@@ -20,6 +20,11 @@
 #ifndef MPD_PIPEWIRE_OUTPUT_PLUGIN_HXX
 #define MPD_PIPEWIRE_OUTPUT_PLUGIN_HXX
 
+class PipeWireOutput;
+
 extern const struct AudioOutputPlugin pipewire_output_plugin;
 
+void
+pipewire_output_set_volume(PipeWireOutput &output, float volume);
+
 #endif