diff --git a/NEWS b/NEWS
index f4d1d1fd5..7a87dd67f 100644
--- a/NEWS
+++ b/NEWS
@@ -32,6 +32,7 @@ ver 0.24 (not yet released)
- alsa: limit ALSA buffer time to 2 seconds
- alsa: set up a channel map
- alsa: support the alsa-lib 1.2.11 API
+ - alsa: add option "close_on_pause"
- curl: add "connect_timeout" configuration
* decoder
- ffmpeg: require FFmpeg 4.0 or later
diff --git a/doc/plugins.rst b/doc/plugins.rst
index 9e4d0d7fd..a8599ea79 100644
--- a/doc/plugins.rst
+++ b/doc/plugins.rst
@@ -896,6 +896,11 @@ The `Advanced Linux Sound Architecture (ALSA) `_ p
Example: "96000:16:* 192000:24:* dsd64:*=dop *:dsd:*".
+ * - **close_on_pause yes|no**
+ - Close the ALSA device while playback is paused? This defaults
+ to *yes* because this allows other applications to use the
+ device while MPD is paused.
+
The according hardware mixer plugin understands the following settings:
.. list-table::
diff --git a/doc/user.rst b/doc/user.rst
index 9738182b1..7406ad777 100644
--- a/doc/user.rst
+++ b/doc/user.rst
@@ -532,7 +532,8 @@ The following table lists the audio_output options valid for all plugins:
- If set to yes, then :program:`MPD` attempts to keep this audio
output always open. Instead of closing at the end
of playback, it puts the device in "pause" mode. This works
- only with output plugins that suport "pause" mode.
+ only with output plugins that suport "pause" mode (see
+ :ref:`ALSA option "close_on_pause" `).
This may be useful for streaming servers, when you don't want
to disconnect all listeners even when playback is accidentally
stopped.
diff --git a/src/output/plugins/AlsaOutputPlugin.cxx b/src/output/plugins/AlsaOutputPlugin.cxx
index 4c64e75cd..f6bb22432 100644
--- a/src/output/plugins/AlsaOutputPlugin.cxx
+++ b/src/output/plugins/AlsaOutputPlugin.cxx
@@ -33,6 +33,7 @@
#include
+#include
#include
#include
@@ -222,6 +223,15 @@ class AlsaOutput final
*/
bool interrupted;
+ /**
+ * Close the ALSA PCM while playback is paused? This defaults
+ * to true because this allows other applications to use the
+ * PCM while MPD is paused.
+ */
+ const bool close_on_pause;
+
+ std::atomic_bool paused;
+
public:
AlsaOutput(EventLoop &loop, const ConfigBlock &block);
@@ -261,6 +271,7 @@ private:
void Close() noexcept override;
void Interrupt() noexcept override;
+ std::chrono::steady_clock::duration Delay() const noexcept override;
std::size_t Play(std::span src) override;
void Drain() override;
@@ -414,7 +425,7 @@ GetAlsaOpenMode(const ConfigBlock &block)
}
AlsaOutput::AlsaOutput(EventLoop &_loop, const ConfigBlock &block)
- :AudioOutput(FLAG_ENABLE_DISABLE),
+ :AudioOutput(FLAG_ENABLE_DISABLE|FLAG_PAUSE),
MultiSocketMonitor(_loop),
defer_invalidate_sockets(_loop, BIND_THIS_METHOD(InvalidateSockets)),
silence_timer(_loop, BIND_THIS_METHOD(OnSilenceTimer)),
@@ -422,16 +433,16 @@ AlsaOutput::AlsaOutput(EventLoop &_loop, const ConfigBlock &block)
buffer_time(block.GetPositiveValue("buffer_time",
MPD_ALSA_BUFFER_TIME_US)),
period_time(block.GetPositiveValue("period_time", 0U)),
- mode(GetAlsaOpenMode(block))
+ mode(GetAlsaOpenMode(block)),
#ifdef ENABLE_DSD
-,
dop_setting(block.GetBlockValue("dop", false) ||
/* legacy name from MPD 0.18 and older: */
block.GetBlockValue("dsd_usb", false)),
stop_dsd_silence(block.GetBlockValue("stop_dsd_silence", false)),
thesycon_dsd_workaround(block.GetBlockValue("thesycon_dsd_workaround",
- false))
+ false)),
#endif
+ close_on_pause(block.GetBlockValue("close_on_pause", true))
{
const char *allowed_formats_string =
block.GetBlockValue("allowed_formats", nullptr);
@@ -752,6 +763,8 @@ Play_44_1_Silence(snd_pcm_t *pcm)
void
AlsaOutput::Open(AudioFormat &audio_format)
{
+ paused = false;
+
#ifdef ENABLE_DSD
bool dop;
#endif
@@ -866,6 +879,15 @@ AlsaOutput::Interrupt() noexcept
cond.notify_one();
}
+std::chrono::steady_clock::duration
+AlsaOutput::Delay() const noexcept
+{
+ if (paused)
+ return std::chrono::hours{1};
+
+ return AudioOutput::Delay();
+}
+
inline int
AlsaOutput::Recover(int err) noexcept
{
@@ -1119,9 +1141,13 @@ AlsaOutput::Pause() noexcept
std::lock_guard lock{mutex};
interrupted = false;
- /* not implemented - this override exists only to reset the
- "interrupted" flag */
- return false;
+ if (close_on_pause)
+ return false;
+
+ // TODO use snd_pcm_pause()?
+
+ paused = true;
+ return true;
}
void
@@ -1188,6 +1214,8 @@ AlsaOutput::Play(std::span src)
assert(!src.empty());
assert(src.size() % in_frame_size == 0);
+ paused = false;
+
const size_t max_frames = LockWaitWriteAvailable();
const size_t max_size = max_frames * in_frame_size;
if (src.size() > max_size)