add "moveoutput" command

This commit is contained in:
Max Kellermann 2019-09-26 13:02:34 +02:00
parent ac126ede22
commit c16233fa74
9 changed files with 157 additions and 7 deletions

1
NEWS
View File

@ -3,6 +3,7 @@ ver 0.22 (not yet released)
- "findadd"/"searchadd"/"searchaddpl" support the "sort" and
"window" parameters
- add command "readpicture" to download embedded pictures
- command "moveoutput" moves an output between partitions
* tags
- new tags "Grouping" (for ID3 "TIT1"), "Work" and "Conductor"
* input

View File

@ -1258,6 +1258,9 @@ client is assigned to one partition at a time.
:command:`newpartition {NAME}`
Create a new partition.
:command:`moveoutput {OUTPUTNAME}`
Move an output to the current partition.
Audio output devices
====================

View File

@ -139,6 +139,7 @@ static constexpr struct command commands[] = {
#endif
{ "move", PERMISSION_CONTROL, 2, 2, handle_move },
{ "moveid", PERMISSION_CONTROL, 2, 2, handle_moveid },
{ "moveoutput", PERMISSION_ADMIN, 1, 1, handle_moveoutput },
{ "newpartition", PERMISSION_ADMIN, 1, 1, handle_newpartition },
{ "next", PERMISSION_CONTROL, 0, 0, handle_next },
{ "notcommands", PERMISSION_NONE, 0, 0, handle_not_commands },

View File

@ -22,6 +22,7 @@
#include "Instance.hxx"
#include "Partition.hxx"
#include "IdleFlags.hxx"
#include "output/Filtered.hxx"
#include "client/Client.hxx"
#include "client/Response.hxx"
#include "util/CharUtil.hxx"
@ -113,3 +114,44 @@ handle_newpartition(Client &client, Request request, Response &response)
return CommandResult::OK;
}
CommandResult
handle_moveoutput(Client &client, Request request, Response &response)
{
const char *output_name = request[0];
auto &dest_partition = client.GetPartition();
auto *existing_output = dest_partition.outputs.FindByName(output_name);
if (existing_output != nullptr && !existing_output->IsDummy())
/* this output is already in the specified partition,
so nothing needs to be done */
return CommandResult::OK;
/* find the partition which owns this output currently */
auto &instance = client.GetInstance();
for (auto &partition : instance.partitions) {
if (&partition == &dest_partition)
continue;
auto *output = partition.outputs.FindByName(output_name);
if (output == nullptr || output->IsDummy())
continue;
const bool was_enabled = output->IsEnabled();
if (existing_output != nullptr)
/* move the output back where it once was */
existing_output->ReplaceDummy(output->Steal(),
was_enabled);
else
/* add it to the output list */
dest_partition.outputs.Add(output->Steal(),
was_enabled);
instance.EmitIdle(IDLE_OUTPUT);
return CommandResult::OK;
}
response.Error(ACK_ERROR_NO_EXIST, "No such output");
return CommandResult::ERROR;
}

View File

@ -35,4 +35,7 @@ handle_listpartitions(Client &client, Request request, Response &response);
CommandResult
handle_newpartition(Client &client, Request request, Response &response);
CommandResult
handle_moveoutput(Client &client, Request request, Response &response);
#endif

View File

@ -19,6 +19,7 @@
#include "Control.hxx"
#include "Filtered.hxx"
#include "Client.hxx"
#include "mixer/MixerControl.hxx"
#include "config/Block.hxx"
#include "Log.hxx"
@ -31,7 +32,9 @@ static constexpr PeriodClock::Duration REOPEN_AFTER = std::chrono::seconds(10);
AudioOutputControl::AudioOutputControl(std::unique_ptr<FilteredAudioOutput> _output,
AudioOutputClient &_client) noexcept
:output(std::move(_output)), client(_client),
:output(std::move(_output)),
name(output->GetName()),
client(_client),
thread(BIND_THIS_METHOD(Task))
{
}
@ -49,40 +52,86 @@ AudioOutputControl::Configure(const ConfigBlock &block)
enabled = block.GetBlockValue("enabled", true);
}
std::unique_ptr<FilteredAudioOutput>
AudioOutputControl::Steal() noexcept
{
assert(!IsDummy());
/* close and disable the output */
{
std::unique_lock<Mutex> lock(mutex);
if (really_enabled && output->SupportsEnableDisable())
CommandWait(lock, Command::DISABLE);
enabled = really_enabled = false;
}
/* stop the thread */
StopThread();
/* now we can finally remove it */
const std::lock_guard<Mutex> protect(mutex);
return std::exchange(output, nullptr);
}
void
AudioOutputControl::ReplaceDummy(std::unique_ptr<FilteredAudioOutput> new_output,
bool _enabled) noexcept
{
assert(IsDummy());
assert(new_output);
{
const std::lock_guard<Mutex> protect(mutex);
output = std::move(new_output);
enabled = _enabled;
}
client.ApplyEnabled();
}
const char *
AudioOutputControl::GetName() const noexcept
{
return output->GetName();
return name.c_str();
}
const char *
AudioOutputControl::GetPluginName() const noexcept
{
return output->GetPluginName();
return output ? output->GetPluginName() : "dummy";
}
const char *
AudioOutputControl::GetLogName() const noexcept
{
assert(!IsDummy());
return output->GetLogName();
}
Mixer *
AudioOutputControl::GetMixer() const noexcept
{
return output->mixer;
return output ? output->mixer : nullptr;
}
const std::map<std::string, std::string>
AudioOutputControl::GetAttributes() const noexcept
{
return output->GetAttributes();
return output
? output->GetAttributes()
: std::map<std::string, std::string>{};
}
void
AudioOutputControl::SetAttribute(std::string &&name, std::string &&value)
AudioOutputControl::SetAttribute(std::string &&attribute_name,
std::string &&value)
{
output->SetAttribute(std::move(name), std::move(value));
if (!output)
throw std::runtime_error("Cannot set attribute on dummy output");
output->SetAttribute(std::move(attribute_name), std::move(value));
}
bool
@ -137,6 +186,9 @@ AudioOutputControl::LockCommandWait(Command cmd) noexcept
void
AudioOutputControl::EnableAsync()
{
if (!output)
return;
if (!thread.IsDefined()) {
if (!output->SupportsEnableDisable()) {
/* don't bother to start the thread now if the
@ -155,6 +207,9 @@ AudioOutputControl::EnableAsync()
void
AudioOutputControl::DisableAsync() noexcept
{
if (!output)
return;
if (!thread.IsDefined()) {
if (!output->SupportsEnableDisable())
really_enabled = false;
@ -234,6 +289,9 @@ AudioOutputControl::CloseWait(std::unique_lock<Mutex> &lock) noexcept
{
assert(allow_play);
if (IsDummy())
return;
if (output->mixer != nullptr)
mixer_auto_close(output->mixer);

View File

@ -49,6 +49,13 @@ class AudioOutputClient;
class AudioOutputControl {
std::unique_ptr<FilteredAudioOutput> output;
/**
* A copy of FilteredAudioOutput::name which we need just in
* case this is a "dummy" output (output==nullptr) because
* this output has been moved to another partitioncommands.
*/
const std::string name;
/**
* The PlayerControl object which "owns" this output. This
* object is needed to signal command completion.
@ -256,6 +263,10 @@ public:
gcc_pure
Mixer *GetMixer() const noexcept;
bool IsDummy() const noexcept {
return !output;
}
/**
* Caller must lock the mutex.
*/
@ -294,6 +305,14 @@ public:
return last_error;
}
/**
* Detach and return the #FilteredAudioOutput instance and,
* replacing it here with a "dummy" object.
*/
std::unique_ptr<FilteredAudioOutput> Steal() noexcept;
void ReplaceDummy(std::unique_ptr<FilteredAudioOutput> new_output,
bool _enabled) noexcept;
void StartThread();
/**

View File

@ -18,6 +18,7 @@
*/
#include "MultipleOutputs.hxx"
#include "Client.hxx"
#include "Filtered.hxx"
#include "Defaults.hxx"
#include "MusicPipe.hxx"
@ -142,6 +143,21 @@ MultipleOutputs::FindByName(const char *name) noexcept
return nullptr;
}
void
MultipleOutputs::Add(std::unique_ptr<FilteredAudioOutput> output,
bool enable) noexcept
{
auto &client = GetAnyClient();
// TODO: this operation needs to be protected with a mutex
outputs.emplace_back(std::make_unique<AudioOutputControl>(std::move(output),
client));
outputs.back()->LockSetEnabled(enable);
client.ApplyEnabled();
}
void
MultipleOutputs::EnableDisable()
{

View File

@ -119,6 +119,9 @@ public:
return FindByName(name) != nullptr;
}
void Add(std::unique_ptr<FilteredAudioOutput> output,
bool enable) noexcept;
void SetReplayGainMode(ReplayGainMode mode) noexcept;
/**
@ -153,6 +156,10 @@ public:
void SetSoftwareVolume(unsigned volume) noexcept;
private:
AudioOutputClient &GetAnyClient() noexcept {
return outputs.front()->GetClient();
}
/**
* Was Open() called successfully?
*