add "moveoutput" command
This commit is contained in:
parent
ac126ede22
commit
c16233fa74
1
NEWS
1
NEWS
|
@ -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
|
||||
|
|
|
@ -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
|
||||
====================
|
||||
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
/**
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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?
|
||||
*
|
||||
|
|
Loading…
Reference in New Issue