From 6834806572a13b899218f167c7a78c2397376c8c Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Thu, 16 Jul 2026 20:49:42 +0200 Subject: [PATCH 1/3] output/Filtered: implement a MixerListener proxy Implemented as a settable pointer field, to allow class AudioOutputControl to implement another proxy. --- src/output/Filtered.cxx | 14 ++++++++++++++ src/output/Filtered.hxx | 23 +++++++++++++++-------- src/output/Init.cxx | 18 +++++++----------- src/output/MultipleOutputs.cxx | 6 ++---- 4 files changed, 38 insertions(+), 23 deletions(-) diff --git a/src/output/Filtered.cxx b/src/output/Filtered.cxx index ec820028f0..1e9d1d672e 100644 --- a/src/output/Filtered.cxx +++ b/src/output/Filtered.cxx @@ -201,3 +201,17 @@ FilteredAudioOutput::IteratePause() { return output->Pause(); } + +void +FilteredAudioOutput::OnMixerVolumeChanged(Mixer &_mixer, int volume) noexcept +{ + if (mixer_listener != nullptr) + mixer_listener->OnMixerVolumeChanged(_mixer, volume); +} + +void +FilteredAudioOutput::OnMixerChanged() noexcept +{ + if (mixer_listener != nullptr) + mixer_listener->OnMixerChanged(); +} diff --git a/src/output/Filtered.hxx b/src/output/Filtered.hxx index e9c4b27806..ac4666c6bf 100644 --- a/src/output/Filtered.hxx +++ b/src/output/Filtered.hxx @@ -1,9 +1,9 @@ // SPDX-License-Identifier: GPL-2.0-or-later // Copyright The Music Player Daemon Project -#ifndef MPD_FILTERED_AUDIO_OUTPUT_HXX -#define MPD_FILTERED_AUDIO_OUTPUT_HXX +#pragma once +#include "mixer/Listener.hxx" #include "pcm/AudioFormat.hxx" #include "filter/Observer.hxx" @@ -25,7 +25,7 @@ struct AudioOutputDefaults; struct ReplayGainConfig; struct Tag; -struct FilteredAudioOutput { +struct FilteredAudioOutput final : MixerListener { const char *const plugin_name; /** @@ -53,6 +53,12 @@ public: */ Mixer *mixer = nullptr; + /** + * If not nullptr, then all #MixerListener calls are proxied + * to this object. + */ + MixerListener *mixer_listener = nullptr; + /** * The configured audio format. */ @@ -127,7 +133,6 @@ public: void Setup(EventLoop &event_loop, const ReplayGainConfig &replay_gain_config, const MixerPlugin *mixer_plugin, - MixerListener &mixer_listener, const ConfigBlock &block, const AudioOutputDefaults &defaults); @@ -219,6 +224,11 @@ public: void EndPause() noexcept{ } + +private: + /* virtual methods from class MixerListener */ + void OnMixerVolumeChanged(Mixer &mixer, int volume) noexcept override; + void OnMixerChanged() noexcept override; }; /** @@ -229,7 +239,4 @@ audio_output_new(EventLoop &event_loop, EventLoop &rt_event_loop, const ReplayGainConfig &replay_gain_config, const ConfigBlock &block, const AudioOutputDefaults &defaults, - FilterFactory *filter_factory, - MixerListener &mixer_listener); - -#endif + FilterFactory *filter_factory); diff --git a/src/output/Init.cxx b/src/output/Init.cxx index 9c76321a63..859ef31e38 100644 --- a/src/output/Init.cxx +++ b/src/output/Init.cxx @@ -96,8 +96,7 @@ audio_output_load_mixer(EventLoop &event_loop, FilteredAudioOutput &ao, const ConfigBlock &block, const MixerType mixer_type, const MixerPlugin *plugin, - std::unique_ptr &filter_chain, - MixerListener &listener) + std::unique_ptr &filter_chain) { Mixer *mixer; @@ -107,7 +106,7 @@ audio_output_load_mixer(EventLoop &event_loop, FilteredAudioOutput &ao, case MixerType::NULL_: return mixer_new(event_loop, null_mixer_plugin, - *ao.output, listener, + *ao.output, ao, block); case MixerType::HARDWARE: @@ -115,12 +114,12 @@ audio_output_load_mixer(EventLoop &event_loop, FilteredAudioOutput &ao, return nullptr; return mixer_new(event_loop, *plugin, - *ao.output, listener, + *ao.output, ao, block); case MixerType::SOFTWARE: mixer = mixer_new(event_loop, software_mixer_plugin, - *ao.output, listener, + *ao.output, ao, ConfigBlock()); assert(mixer != nullptr); @@ -182,7 +181,6 @@ inline void FilteredAudioOutput::Setup(EventLoop &event_loop, const ReplayGainConfig &replay_gain_config, const MixerPlugin *mixer_plugin, - MixerListener &mixer_listener, const ConfigBlock &block, const AudioOutputDefaults &defaults) { @@ -219,8 +217,7 @@ FilteredAudioOutput::Setup(EventLoop &event_loop, mixer = audio_output_load_mixer(event_loop, *this, block, mixer_type, mixer_plugin, - prepared_filter, - mixer_listener); + prepared_filter); } catch (...) { FmtError(output_domain, "Failed to initialize hardware mixer for {:?}: {}", @@ -253,8 +250,7 @@ audio_output_new(EventLoop &normal_event_loop, EventLoop &rt_event_loop, const ReplayGainConfig &replay_gain_config, const ConfigBlock &block, const AudioOutputDefaults &defaults, - FilterFactory *filter_factory, - MixerListener &mixer_listener) + FilterFactory *filter_factory) { const AudioOutputPlugin *plugin; @@ -298,6 +294,6 @@ audio_output_new(EventLoop &normal_event_loop, EventLoop &rt_event_loop, filter_factory); f->Setup(event_loop, replay_gain_config, plugin->mixer_plugin, - mixer_listener, block, defaults); + block, defaults); return f; } diff --git a/src/output/MultipleOutputs.cxx b/src/output/MultipleOutputs.cxx index 6f2fe16f1a..7336da0ce7 100644 --- a/src/output/MultipleOutputs.cxx +++ b/src/output/MultipleOutputs.cxx @@ -37,15 +37,13 @@ MultipleOutputs::~MultipleOutputs() noexcept static std::unique_ptr LoadOutput(EventLoop &event_loop, EventLoop &rt_event_loop, const ReplayGainConfig &replay_gain_config, - MixerListener &mixer_listener, const ConfigBlock &block, const AudioOutputDefaults &defaults, FilterFactory *filter_factory) try { return audio_output_new(event_loop, rt_event_loop, replay_gain_config, block, defaults, - filter_factory, - mixer_listener); + filter_factory); } catch (...) { if (block.line > 0) std::throw_with_nested(FmtRuntimeError("Failed to configure output in line {}", @@ -64,8 +62,8 @@ LoadOutputControl(EventLoop &event_loop, EventLoop &rt_event_loop, { auto output = LoadOutput(event_loop, rt_event_loop, replay_gain_config, - mixer_listener, block, defaults, filter_factory); + output->mixer_listener = &mixer_listener; return std::make_unique(std::move(output), client, block); } From 5e1303714bba498d67919c01efef2ce9ab932eda Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Thu, 16 Jul 2026 20:31:04 +0200 Subject: [PATCH 2/3] output/Control: implement a MixerListener proxy Currently, the MixerListener is hard-coded to be the default partitions and mixer idle events to non-default partitions are bugged. --- src/output/Control.cxx | 20 ++++++++++++++++++++ src/output/Control.hxx | 20 +++++++++++++++----- src/output/MultipleOutputs.cxx | 6 ++---- 3 files changed, 37 insertions(+), 9 deletions(-) diff --git a/src/output/Control.cxx b/src/output/Control.cxx index 08690f80ff..7efea004a0 100644 --- a/src/output/Control.cxx +++ b/src/output/Control.cxx @@ -31,6 +31,8 @@ AudioOutputControl::AudioOutputControl(std::unique_ptr _out always_off(block.GetBlockValue("always_off", false)), enabled(block.GetBlockValue("enabled", true)) { + assert(output->mixer_listener == nullptr); + output->mixer_listener = this; } AudioOutputControl::~AudioOutputControl() noexcept @@ -426,3 +428,21 @@ AudioOutputControl::StopThread() noexcept assert(IsCommandFinished()); } + +void +AudioOutputControl::OnMixerVolumeChanged(Mixer &_mixer, int volume) noexcept +{ + const std::lock_guard lock{mutex}; + + if (mixer_listener != nullptr) + mixer_listener->OnMixerVolumeChanged(_mixer, volume); +} + +void +AudioOutputControl::OnMixerChanged() noexcept +{ + const std::lock_guard lock{mutex}; + + if (mixer_listener != nullptr) + mixer_listener->OnMixerChanged(); +} diff --git a/src/output/Control.hxx b/src/output/Control.hxx index e6f4f4d3c0..7c5dc97bfd 100644 --- a/src/output/Control.hxx +++ b/src/output/Control.hxx @@ -1,10 +1,10 @@ // SPDX-License-Identifier: GPL-2.0-or-later // Copyright The Music Player Daemon Project -#ifndef MPD_OUTPUT_CONTROL_HXX -#define MPD_OUTPUT_CONTROL_HXX +#pragma once #include "Source.hxx" +#include "mixer/Listener.hxx" #include "pcm/AudioFormat.hxx" #include "thread/Thread.hxx" #include "thread/Mutex.hxx" @@ -29,7 +29,7 @@ class AudioOutputClient; /** * Controller for an #AudioOutput and its output thread. */ -class AudioOutputControl { +class AudioOutputControl final : MixerListener { const std::unique_ptr output; /** @@ -39,6 +39,8 @@ class AudioOutputControl { */ const std::string name; + MixerListener *mixer_listener = nullptr; + /** * The PlayerControl object which "owns" this output. This * object is needed to signal command completion. @@ -310,6 +312,11 @@ public: return *client; } + void LockSetMixerListener(MixerListener &_mixer_listener) noexcept { + const std::lock_guard lock{mutex}; + mixer_listener = &_mixer_listener; + } + void SetClient(AudioOutputClient &_client) noexcept { assert(source_state == SourceState::CLOSED); @@ -655,6 +662,9 @@ private: * The OutputThread. */ void Task() noexcept; -}; -#endif +private: + /* virtual methods from class MixerListener */ + void OnMixerVolumeChanged(Mixer &mixer, int volume) noexcept override; + void OnMixerChanged() noexcept override; +}; diff --git a/src/output/MultipleOutputs.cxx b/src/output/MultipleOutputs.cxx index 7336da0ce7..7d411f3004 100644 --- a/src/output/MultipleOutputs.cxx +++ b/src/output/MultipleOutputs.cxx @@ -55,7 +55,6 @@ try { static std::unique_ptr LoadOutputControl(EventLoop &event_loop, EventLoop &rt_event_loop, const ReplayGainConfig &replay_gain_config, - MixerListener &mixer_listener, AudioOutputClient &client, const ConfigBlock &block, const AudioOutputDefaults &defaults, FilterFactory *filter_factory) @@ -63,7 +62,6 @@ LoadOutputControl(EventLoop &event_loop, EventLoop &rt_event_loop, auto output = LoadOutput(event_loop, rt_event_loop, replay_gain_config, block, defaults, filter_factory); - output->mixer_listener = &mixer_listener; return std::make_unique(std::move(output), client, block); } @@ -79,7 +77,6 @@ MultipleOutputs::Configure(EventLoop &event_loop, EventLoop &rt_event_loop, config.WithEach(ConfigBlockOption::AUDIO_OUTPUT, [&, this](const auto &block){ auto output = LoadOutputControl(event_loop, rt_event_loop, replay_gain_config, - mixer_listener, client, block, defaults, &filter_factory); if (HasName(output->GetName())) @@ -88,6 +85,7 @@ MultipleOutputs::Configure(EventLoop &event_loop, EventLoop &rt_event_loop, output->GetName()); outputs.emplace_back(std::move(output)); + outputs.back()->LockSetMixerListener(mixer_listener); }); if (outputs.empty()) { @@ -96,9 +94,9 @@ MultipleOutputs::Configure(EventLoop &event_loop, EventLoop &rt_event_loop, outputs.emplace_back(LoadOutputControl(event_loop, rt_event_loop, replay_gain_config, - mixer_listener, client, empty, defaults, nullptr)); + outputs.back()->LockSetMixerListener(mixer_listener); } } From d9f2c3666dfcf1483598e88416958445a6e4ccff Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Fri, 17 Jul 2026 15:44:09 +0200 Subject: [PATCH 3/3] output/MultipleOutputs: install new MixerListener on `moveoutput` This finally fixes "mixer" idle events on non-default partitions. Previously, the MixerListener always pointed to the initial (default) partition. --- NEWS | 2 ++ src/output/MultipleOutputs.cxx | 2 ++ 2 files changed, 4 insertions(+) diff --git a/NEWS b/NEWS index 798200e35c..7ff47bf7f1 100644 --- a/NEWS +++ b/NEWS @@ -16,6 +16,8 @@ ver 0.25 (not yet released) * output - alsa: use hardware pause if available - pipewire: add option "reconnect_stream" +* mixer + - fix mixer idle events on non-default partitions * tags - new tag "DiscSubtitle" * player diff --git a/src/output/MultipleOutputs.cxx b/src/output/MultipleOutputs.cxx index 7d411f3004..1753b11634 100644 --- a/src/output/MultipleOutputs.cxx +++ b/src/output/MultipleOutputs.cxx @@ -165,6 +165,7 @@ MultipleOutputs::ReplaceDummy(std::size_t idx, const std::lock_guard lock{mutex}; slot = std::move(src); + output.LockSetMixerListener(mixer_listener); output.SetClient(client); output.LockSetEnabled(enable); output.SetReplayGainMode(replay_gain_mode); @@ -183,6 +184,7 @@ MultipleOutputs::Add(std::unique_ptr &&src, } auto &output = *outputs.back(); + output.LockSetMixerListener(mixer_listener); output.SetClient(client); output.LockSetEnabled(enable); output.SetReplayGainMode(replay_gain_mode);