mirror of
https://github.com/owntone/owntone-server.git
synced 2024-12-24 22:25:56 -05:00
[mpd] add support for speaker (de)activation through 'outputs',
'disableoutput', 'enableoutput', 'toggleoutput' commands
This commit is contained in:
parent
5e2393479d
commit
2c0ce9cd56
382
src/mpd.c
382
src/mpd.c
@ -82,6 +82,41 @@ enum command_list_type
|
|||||||
COMMAND_LIST_NONE = 3
|
COMMAND_LIST_NONE = 3
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct output
|
||||||
|
{
|
||||||
|
unsigned short shortid;
|
||||||
|
uint64_t id;
|
||||||
|
char *name;
|
||||||
|
|
||||||
|
unsigned selected;
|
||||||
|
|
||||||
|
struct output *next;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct outputs
|
||||||
|
{
|
||||||
|
unsigned int count;
|
||||||
|
unsigned int active;
|
||||||
|
struct output *outputs;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
free_outputs(struct output *outputs)
|
||||||
|
{
|
||||||
|
struct output *temp;
|
||||||
|
struct output *next;
|
||||||
|
|
||||||
|
temp = outputs;
|
||||||
|
next = outputs ? outputs->next : NULL;
|
||||||
|
while (temp)
|
||||||
|
{
|
||||||
|
free(temp->name);
|
||||||
|
free(temp);
|
||||||
|
temp = next;
|
||||||
|
next = next ? next->next : NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
thread_exit(void)
|
thread_exit(void)
|
||||||
{
|
{
|
||||||
@ -2184,38 +2219,361 @@ mpd_command_rescan(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
* Callback function for the 'player_speaker_enumerate' function.
|
||||||
|
* Adds a new struct output to the given struct outputs in *arg for the given speaker (id, name, etc.).
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
outputs_enum_cb(uint64_t id, const char *name, int relvol, struct spk_flags flags, void *arg)
|
||||||
|
{
|
||||||
|
struct outputs *outputs;
|
||||||
|
struct output *output;
|
||||||
|
|
||||||
|
outputs = (struct outputs *)arg;
|
||||||
|
|
||||||
|
DPRINTF(E_DBG, L_MPD, "outputid: %" PRIu64 ", outputname: %s, outputenabled: %d\n",
|
||||||
|
id, name, flags.selected);
|
||||||
|
|
||||||
|
output = (struct output*)malloc(sizeof(struct output));
|
||||||
|
|
||||||
|
output->id = id;
|
||||||
|
output->shortid = (unsigned short) id;
|
||||||
|
output->name = strdup(name);
|
||||||
|
output->selected = flags.selected;
|
||||||
|
|
||||||
|
output->next = outputs->outputs;
|
||||||
|
outputs->outputs = output;
|
||||||
|
outputs->count++;
|
||||||
|
if (flags.selected)
|
||||||
|
outputs->active++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Command handler function for 'disableoutput'
|
||||||
|
* Expects argument argv[1] to be the id of the speaker to disable.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
mpd_command_disableoutput(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
|
||||||
|
{
|
||||||
|
struct outputs outputs;
|
||||||
|
struct output *output;
|
||||||
|
uint32_t num;
|
||||||
|
uint64_t *ids;
|
||||||
|
int nspk;
|
||||||
|
int i;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (argc < 2)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_MPD, "Missing argument for command 'disableoutput'\n");
|
||||||
|
ret = asprintf(errmsg, "Missing argument for command 'disableoutput'");
|
||||||
|
if (ret < 0)
|
||||||
|
DPRINTF(E_LOG, L_MPD, "Out of memory\n");
|
||||||
|
return ACK_ERROR_ARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = safe_atou32(argv[1], &num);
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_MPD, "Argument doesn't convert to integer: '%s'\n", argv[1]);
|
||||||
|
ret = asprintf(errmsg, "Argument doesn't convert to integer: '%s'", argv[1]);
|
||||||
|
if (ret < 0)
|
||||||
|
DPRINTF(E_LOG, L_MPD, "Out of memory\n");
|
||||||
|
return ACK_ERROR_ARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
outputs.count = 0;
|
||||||
|
outputs.active = 0;
|
||||||
|
outputs.outputs = NULL;
|
||||||
|
|
||||||
|
player_speaker_enumerate(outputs_enum_cb, &outputs);
|
||||||
|
|
||||||
|
nspk = outputs.active;
|
||||||
|
output = outputs.outputs;
|
||||||
|
while (output)
|
||||||
|
{
|
||||||
|
if (output->shortid == num && output->selected)
|
||||||
|
{
|
||||||
|
nspk--;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
output = output->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nspk == outputs.active)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_MPD, "No speaker to deactivate\n");
|
||||||
|
free_outputs(outputs.outputs);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ids = (uint64_t *)malloc((nspk + 1) * sizeof(uint64_t));
|
||||||
|
|
||||||
|
ids[0] = nspk;
|
||||||
|
|
||||||
|
i = 1;
|
||||||
|
output = outputs.outputs;
|
||||||
|
while (output)
|
||||||
|
{
|
||||||
|
if (output->shortid != num && output->selected)
|
||||||
|
{
|
||||||
|
ids[i] = output->id;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
output = output->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = player_speaker_set(ids);
|
||||||
|
|
||||||
|
free(ids);
|
||||||
|
free_outputs(outputs.outputs);
|
||||||
|
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_MPD, "Speaker deactivation failed: %d\n", num);
|
||||||
|
ret = asprintf(errmsg, "Speakers deactivation failed: %d", num);
|
||||||
|
|
||||||
|
if (ret < 0)
|
||||||
|
DPRINTF(E_LOG, L_MPD, "Out of memory\n");
|
||||||
|
return ACK_ERROR_UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Command handler function for 'enableoutput'
|
||||||
|
* Expects argument argv[1] to be the id of the speaker to enable.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
mpd_command_enableoutput(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
|
||||||
|
{
|
||||||
|
struct outputs outputs;
|
||||||
|
struct output *output;
|
||||||
|
uint32_t num;
|
||||||
|
uint64_t *ids;
|
||||||
|
int nspk;
|
||||||
|
int i;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (argc < 2)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_MPD, "Missing argument for command 'disableoutput'\n");
|
||||||
|
ret = asprintf(errmsg, "Missing argument for command 'disableoutput'");
|
||||||
|
if (ret < 0)
|
||||||
|
DPRINTF(E_LOG, L_MPD, "Out of memory\n");
|
||||||
|
return ACK_ERROR_ARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = safe_atou32(argv[1], &num);
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_MPD, "Argument doesn't convert to integer: '%s'\n", argv[1]);
|
||||||
|
ret = asprintf(errmsg, "Argument doesn't convert to integer: '%s'", argv[1]);
|
||||||
|
if (ret < 0)
|
||||||
|
DPRINTF(E_LOG, L_MPD, "Out of memory\n");
|
||||||
|
return ACK_ERROR_ARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
outputs.count = 0;
|
||||||
|
outputs.active = 0;
|
||||||
|
outputs.outputs = NULL;
|
||||||
|
|
||||||
|
player_speaker_enumerate(outputs_enum_cb, &outputs);
|
||||||
|
|
||||||
|
nspk = outputs.active;
|
||||||
|
output = outputs.outputs;
|
||||||
|
while (output)
|
||||||
|
{
|
||||||
|
if (output->shortid == num && !output->selected)
|
||||||
|
{
|
||||||
|
nspk++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
output = output->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nspk == outputs.active)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_MPD, "No speaker to activate\n");
|
||||||
|
free_outputs(outputs.outputs);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ids = (uint64_t *)malloc((nspk + 1) * sizeof(uint64_t));
|
||||||
|
|
||||||
|
ids[0] = nspk;
|
||||||
|
|
||||||
|
i = 1;
|
||||||
|
output = outputs.outputs;
|
||||||
|
while (output)
|
||||||
|
{
|
||||||
|
if (output->shortid == num || output->selected)
|
||||||
|
{
|
||||||
|
ids[i] = output->id;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
output = output->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = player_speaker_set(ids);
|
||||||
|
|
||||||
|
if (ids)
|
||||||
|
free(ids);
|
||||||
|
free_outputs(outputs.outputs);
|
||||||
|
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_MPD, "Speaker activation failed: %d\n", num);
|
||||||
|
ret = asprintf(errmsg, "Speakers activation failed: %d", num);
|
||||||
|
|
||||||
|
if (ret < 0)
|
||||||
|
DPRINTF(E_LOG, L_MPD, "Out of memory\n");
|
||||||
|
return ACK_ERROR_UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Command handler function for 'toggleoutput'
|
||||||
|
* Expects argument argv[1] to be the id of the speaker to enable/disable.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
mpd_command_toggleoutput(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
|
||||||
|
{
|
||||||
|
struct outputs outputs;
|
||||||
|
struct output *output;
|
||||||
|
uint32_t num;
|
||||||
|
uint64_t *ids;
|
||||||
|
int nspk;
|
||||||
|
int i;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (argc < 2)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_MPD, "Missing argument for command 'disableoutput'\n");
|
||||||
|
ret = asprintf(errmsg, "Missing argument for command 'disableoutput'");
|
||||||
|
if (ret < 0)
|
||||||
|
DPRINTF(E_LOG, L_MPD, "Out of memory\n");
|
||||||
|
return ACK_ERROR_ARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = safe_atou32(argv[1], &num);
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_MPD, "Argument doesn't convert to integer: '%s'\n", argv[1]);
|
||||||
|
ret = asprintf(errmsg, "Argument doesn't convert to integer: '%s'", argv[1]);
|
||||||
|
if (ret < 0)
|
||||||
|
DPRINTF(E_LOG, L_MPD, "Out of memory\n");
|
||||||
|
return ACK_ERROR_ARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
outputs.count = 0;
|
||||||
|
outputs.active = 0;
|
||||||
|
outputs.outputs = NULL;
|
||||||
|
|
||||||
|
player_speaker_enumerate(outputs_enum_cb, &outputs);
|
||||||
|
|
||||||
|
nspk = outputs.active;
|
||||||
|
output = outputs.outputs;
|
||||||
|
while (output)
|
||||||
|
{
|
||||||
|
if (output->shortid == num && !output->selected)
|
||||||
|
{
|
||||||
|
nspk++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (output->shortid == num && output->selected)
|
||||||
|
{
|
||||||
|
nspk--;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
output = output->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nspk == outputs.active)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_MPD, "No speaker to de/activate\n");
|
||||||
|
free_outputs(outputs.outputs);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ids = (uint64_t *)malloc((nspk + 1) * sizeof(uint64_t));
|
||||||
|
|
||||||
|
ids[0] = nspk;
|
||||||
|
|
||||||
|
i = 1;
|
||||||
|
output = outputs.outputs;
|
||||||
|
while (output)
|
||||||
|
{
|
||||||
|
if ((output->shortid == num && !output->selected)
|
||||||
|
|| (output->shortid != num && output->selected))
|
||||||
|
{
|
||||||
|
ids[i] = output->id;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
output = output->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = player_speaker_set(ids);
|
||||||
|
|
||||||
|
if (ids)
|
||||||
|
free(ids);
|
||||||
|
free_outputs(outputs.outputs);
|
||||||
|
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_MPD, "Speaker de/activation failed: %d\n", num);
|
||||||
|
ret = asprintf(errmsg, "Speakers de/activation failed: %d", num);
|
||||||
|
|
||||||
|
if (ret < 0)
|
||||||
|
DPRINTF(E_LOG, L_MPD, "Out of memory\n");
|
||||||
|
return ACK_ERROR_UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Callback function for the 'outputs' command.
|
||||||
|
* Gets called for each available speaker and prints the speaker information to the evbuffer given in *arg.
|
||||||
|
*
|
||||||
|
* Example output:
|
||||||
|
* outputid: 0
|
||||||
|
* outputname: Computer
|
||||||
|
* outputenabled: 1
|
||||||
|
*/
|
||||||
static void
|
static void
|
||||||
speaker_enum_cb(uint64_t id, const char *name, int relvol, struct spk_flags flags, void *arg)
|
speaker_enum_cb(uint64_t id, const char *name, int relvol, struct spk_flags flags, void *arg)
|
||||||
{
|
{
|
||||||
/ *
|
|
||||||
* outputid: 0
|
|
||||||
* outputname: My ALSA Device
|
|
||||||
* outputenabled: 0
|
|
||||||
* OK
|
|
||||||
* /
|
|
||||||
struct evbuffer *evbuf;
|
struct evbuffer *evbuf;
|
||||||
|
|
||||||
evbuf = (struct evbuffer *)arg;
|
evbuf = (struct evbuffer *)arg;
|
||||||
|
|
||||||
evbuffer_add_printf(evbuf,
|
evbuffer_add_printf(evbuf,
|
||||||
"outputid: %" PRIi64 "\n"
|
"outputid: %d\n"
|
||||||
"outputname: %s\n"
|
"outputname: %s\n"
|
||||||
"outputenabled: %d\n",
|
"outputenabled: %d\n",
|
||||||
id,
|
(unsigned short) id,
|
||||||
name,
|
name,
|
||||||
flags.selected);
|
flags.selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Command handler function for 'outputs'
|
||||||
|
* Returns a lists with the avaiable speakers.
|
||||||
|
*/
|
||||||
static int
|
static int
|
||||||
mpd_command_outputs(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
|
mpd_command_outputs(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
|
||||||
{
|
{
|
||||||
int ret;
|
|
||||||
|
|
||||||
player_speaker_enumerate(speaker_enum_cb, evbuf);
|
player_speaker_enumerate(speaker_enum_cb, evbuf);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
/*
|
/*
|
||||||
* Dummy function to handle commands that are not supported by forked-daapd and should
|
* Dummy function to handle commands that are not supported by forked-daapd and should
|
||||||
* not raise an error.
|
* not raise an error.
|
||||||
@ -2645,7 +3003,6 @@ static struct command mpd_handlers[] =
|
|||||||
/*
|
/*
|
||||||
* Audio output devices
|
* Audio output devices
|
||||||
*/
|
*/
|
||||||
/*
|
|
||||||
{
|
{
|
||||||
.mpdcommand = "disableoutput",
|
.mpdcommand = "disableoutput",
|
||||||
.handler = mpd_command_disableoutput
|
.handler = mpd_command_disableoutput
|
||||||
@ -2662,7 +3019,6 @@ static struct command mpd_handlers[] =
|
|||||||
.mpdcommand = "outputs",
|
.mpdcommand = "outputs",
|
||||||
.handler = mpd_command_outputs
|
.handler = mpd_command_outputs
|
||||||
},
|
},
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Reflection
|
* Reflection
|
||||||
|
Loading…
Reference in New Issue
Block a user