output/Control: Support always_off outputs that are never used for playback

Add an `always_off` option to outputs that causes them to never start
playback even if they're enabled.

This allows placeholder `null` outputs to be defined for the purpose
of having an external client react to the enabled state without the
side effects of real outputs. Like an external mixer, the client can
perform some action when an output is enabled.

Normally `null` outputs can be used for playback so it's possible for
MPD to continue playback silently if a problem occurs with all the real
outputs (or there are none enabled).
This commit is contained in:
Simon Arlott 2023-04-20 21:18:17 +01:00
parent 90c3fe22f5
commit ab196f7afc
No known key found for this signature in database
GPG Key ID: DF001BFD83E75990
4 changed files with 24 additions and 1 deletions

View File

@ -517,6 +517,11 @@ The following table lists the audio_output options valid for all plugins:
- If set to no, then :program:`MPD` will not send tags to this output. This is only useful for output plugins that can receive tags, for example the httpd output plugin.
* - **always_on yes|no**
- If set to yes, then :program:`MPD` attempts to keep this audio output always open. This may be useful for streaming servers, when you don't want to disconnect all listeners even when playback is accidentally stopped.
* - **always_off yes|no**
- If set to yes, then :program:`MPD` never uses this audio output for
playback even if it's enabled. This can be used with the null output
plugin to create placeholder outputs for other software to react to
the enabled state without affecting playback.
* - **mixer_type hardware|software|null|none**
- Specifies which mixer should be used for this audio output: the
hardware mixer (available for ALSA :ref:`alsa_plugin`, OSS

View File

@ -25,6 +25,7 @@ AudioOutputControl::AudioOutputControl(std::unique_ptr<FilteredAudioOutput> _out
thread(BIND_THIS_METHOD(Task)),
tags(block.GetBlockValue("tags", true)),
always_on(block.GetBlockValue("always_on", false)),
always_off(block.GetBlockValue("always_off", false)),
enabled(block.GetBlockValue("enabled", true))
{
}
@ -36,7 +37,8 @@ AudioOutputControl::AudioOutputControl(AudioOutputControl &&src,
client(_client),
thread(BIND_THIS_METHOD(Task)),
tags(src.tags),
always_on(src.always_on)
always_on(src.always_on),
always_off(src.always_off)
{
}
@ -176,6 +178,9 @@ AudioOutputControl::EnableAsync()
if (!output)
return;
if (always_off)
return;
if (!thread.IsDefined()) {
if (!output->SupportsEnableDisable()) {
/* don't bother to start the thread now if the

View File

@ -143,6 +143,11 @@ class AudioOutputControl {
*/
const bool always_on;
/**
* Should this output never play anything, even when enabled?
*/
const bool always_off;
/**
* Has the user enabled this device?
*/
@ -274,6 +279,10 @@ public:
return !output;
}
bool AlwaysOff() const noexcept {
return always_off;
}
/**
* Caller must lock the mutex.
*/

View File

@ -215,6 +215,10 @@ MultipleOutputs::Open(const AudioFormat audio_format)
for (const auto &ao : outputs) {
const std::scoped_lock<Mutex> lock(ao->mutex);
/* can't play on this device even if it's enabled */
if (ao->AlwaysOff())
continue;
if (ao->IsEnabled())
enabled = true;