[raop/dacp/player] Support for Airplay speakers to register volume updates

Implements Active-Remote, which is sent to the speaker, so it can use this
to tell us who it is when it makes dacp request with a device-volume update.
This commit is contained in:
ejurgensen 2018-05-27 01:42:39 +02:00
parent 646bf37f17
commit b6e1269cf2
6 changed files with 154 additions and 35 deletions

View File

@ -59,7 +59,7 @@ struct dacp_update_request {
};
typedef void (*dacp_propget)(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item);
typedef void (*dacp_propset)(const char *value, struct evkeyvalq *query);
typedef void (*dacp_propset)(const char *value, struct httpd_request *hreq);
struct dacp_prop_map {
char *desc;
@ -108,17 +108,17 @@ dacp_propget_extendedmediakind(struct evbuffer *evbuf, struct player_status *sta
static void
dacp_propset_volume(const char *value, struct evkeyvalq *query);
dacp_propset_volume(const char *value, struct httpd_request *hreq);
static void
dacp_propset_devicevolume(const char *value, struct evkeyvalq *query);
dacp_propset_devicevolume(const char *value, struct httpd_request *hreq);
static void
dacp_propset_playingtime(const char *value, struct evkeyvalq *query);
dacp_propset_playingtime(const char *value, struct httpd_request *hreq);
static void
dacp_propset_shufflestate(const char *value, struct evkeyvalq *query);
dacp_propset_shufflestate(const char *value, struct httpd_request *hreq);
static void
dacp_propset_repeatstate(const char *value, struct evkeyvalq *query);
dacp_propset_repeatstate(const char *value, struct httpd_request *hreq);
static void
dacp_propset_userrating(const char *value, struct evkeyvalq *query);
dacp_propset_userrating(const char *value, struct httpd_request *hreq);
/* gperf static hash, dacp_prop.gperf */
@ -746,7 +746,7 @@ dacp_playstatus_update_handler(short event_mask)
int ret;
// Only send status update on player change events
if (!(event_mask & LISTENER_PLAYER))
if (!(event_mask & (LISTENER_PLAYER | LISTENER_VOLUME)))
return;
#ifdef HAVE_EVENTFD
@ -903,7 +903,7 @@ dacp_propget_extendedmediakind(struct evbuffer *evbuf, struct player_status *sta
/* Properties setters */
static void
dacp_propset_volume(const char *value, struct evkeyvalq *query)
dacp_propset_volume(const char *value, struct httpd_request *hreq)
{
const char *param;
uint64_t id;
@ -918,7 +918,7 @@ dacp_propset_volume(const char *value, struct evkeyvalq *query)
return;
}
param = evhttp_find_header(query, "speaker-id");
param = evhttp_find_header(hreq->query, "speaker-id");
if (param)
{
ret = safe_atou64(param, &id);
@ -933,7 +933,7 @@ dacp_propset_volume(const char *value, struct evkeyvalq *query)
return;
}
param = evhttp_find_header(query, "include-speaker-id");
param = evhttp_find_header(hreq->query, "include-speaker-id");
if (param)
{
ret = safe_atou64(param, &id);
@ -952,33 +952,26 @@ dacp_propset_volume(const char *value, struct evkeyvalq *query)
}
static void
dacp_propset_devicevolume(const char *value, struct evkeyvalq *query)
dacp_propset_devicevolume(const char *value, struct httpd_request *hreq)
{
float raop_volume;
int volume;
struct evkeyvalq *headers;
const char *remote;
uint32_t id;
raop_volume = atof(value);
headers = evhttp_request_get_input_headers(hreq->req);
remote = evhttp_find_header(headers, "Active-Remote");
// Basic sanity check, don't want to set to max volume on invalid volume
if (raop_volume == 0.0 && value[0] != '0')
if (!headers || !remote || (safe_atou32(remote, &id) < 0))
{
DPRINTF(E_LOG, L_DACP, "dmcp.device-volume is invalid: %s\n", value);
DPRINTF(E_LOG, L_DACP, "Request for setting device-volume has invalid Active-Remote: '%s'\n", remote);
return;
}
// RAOP volume: -144.0 is off, -30.0 - 0 maps to 0 - 100
if (raop_volume > -30.0 && raop_volume <= 0.0)
volume = (int)(100.0 * raop_volume / 30.0 + 100.0);
else
volume = 0;
DPRINTF(E_DBG, L_DACP, "Propset volume to %s, new volume is %d\n", value, volume);
// TODO: How to find the id so we can call something like player_volume_speaker_info(id, volume);
player_volume_byactiveremote(id, value);
}
static void
dacp_propset_playingtime(const char *value, struct evkeyvalq *query)
dacp_propset_playingtime(const char *value, struct httpd_request *hreq)
{
struct timeval tv;
int ret;
@ -997,7 +990,7 @@ dacp_propset_playingtime(const char *value, struct evkeyvalq *query)
}
static void
dacp_propset_shufflestate(const char *value, struct evkeyvalq *query)
dacp_propset_shufflestate(const char *value, struct httpd_request *hreq)
{
int enable;
int ret;
@ -1014,7 +1007,7 @@ dacp_propset_shufflestate(const char *value, struct evkeyvalq *query)
}
static void
dacp_propset_repeatstate(const char *value, struct evkeyvalq *query)
dacp_propset_repeatstate(const char *value, struct httpd_request *hreq)
{
int mode;
int ret;
@ -1031,7 +1024,7 @@ dacp_propset_repeatstate(const char *value, struct evkeyvalq *query)
}
static void
dacp_propset_userrating(const char *value, struct evkeyvalq *query)
dacp_propset_userrating(const char *value, struct httpd_request *hreq)
{
const char *param;
uint32_t itemid;
@ -1046,9 +1039,9 @@ dacp_propset_userrating(const char *value, struct evkeyvalq *query)
return;
}
param = evhttp_find_header(query, "item-spec"); // Remote
param = evhttp_find_header(hreq->query, "item-spec"); // Remote
if (!param)
param = evhttp_find_header(query, "song-spec"); // Retune
param = evhttp_find_header(hreq->query, "song-spec"); // Retune
if (!param)
{
@ -2459,7 +2452,7 @@ dacp_reply_setproperty(struct httpd_request *hreq)
}
if (dpm->propset)
dpm->propset(param->value, hreq->query);
dpm->propset(param->value, hreq);
else
DPRINTF(E_WARN, L_DACP, "No setter method for DACP property %s\n", dpm->desc);
}
@ -2785,7 +2778,7 @@ dacp_init(void)
return -1;
}
listener_add(dacp_playstatus_update_handler, LISTENER_PLAYER);
listener_add(dacp_playstatus_update_handler, LISTENER_PLAYER | LISTENER_VOLUME);
return 0;

View File

@ -132,6 +132,18 @@ outputs_device_volume_set(struct output_device *device, output_status_cb cb)
return -1;
}
int
outputs_device_volume_to_pct(struct output_device *device, const char *volume)
{
if (outputs[device->type]->disabled)
return -1;
if (outputs[device->type]->device_volume_to_pct)
return outputs[device->type]->device_volume_to_pct(device, volume);
else
return -1;
}
void
outputs_playback_start(uint64_t next_pkt, struct timespec *ts)
{

View File

@ -179,6 +179,9 @@ struct output_definition
// Set the volume and call back
int (*device_volume_set)(struct output_device *device, output_status_cb cb);
// Convert device internal representation of volume to our pct scale
int (*device_volume_to_pct)(struct output_device *device, const char *volume);
// Start/stop playback on devices that were started
void (*playback_start)(uint64_t next_pkt, struct timespec *ts);
void (*playback_stop)(void);
@ -217,6 +220,9 @@ outputs_device_free(struct output_device *device);
int
outputs_device_volume_set(struct output_device *device, output_status_cb cb);
int
outputs_device_volume_to_pct(struct output_device *device, const char *value);
void
outputs_playback_start(uint64_t next_pkt, struct timespec *ts);

View File

@ -1197,6 +1197,11 @@ raop_add_headers(struct raop_session *rs, struct evrtsp_request *req, enum evrts
evrtsp_add_header(req->output_headers, "Client-Instance", buf);
evrtsp_add_header(req->output_headers, "DACP-ID", buf);
// We set Active-Remote as 32 bit unsigned decimal, as at least my device
// can't handle any larger. Must be aligned with volume_byactiveremote().
snprintf(buf, sizeof(buf), "%" PRIu32, (uint32_t)rs->device->id);
evrtsp_add_header(req->output_headers, "Active-Remote", buf);
if (rs->session)
evrtsp_add_header(req->output_headers, "Session", rs->session);
@ -2402,6 +2407,41 @@ raop_volume_from_pct(int volume, char *name)
return raop_volume;
}
static int
raop_volume_to_pct(struct output_device *rd, const char *volume)
{
float raop_volume;
cfg_t *airplay;
int max_volume;
raop_volume = atof(volume);
// Basic sanity check
if (raop_volume == 0.0 && volume[0] != '0')
{
DPRINTF(E_LOG, L_RAOP, "RAOP device volume is invalid: '%s'\n", volume);
return -1;
}
max_volume = RAOP_CONFIG_MAX_VOLUME;
airplay = cfg_gettsec(cfg, "airplay", rd->name);
if (airplay)
max_volume = cfg_getint(airplay, "max_volume");
if ((max_volume < 1) || (max_volume > RAOP_CONFIG_MAX_VOLUME))
{
DPRINTF(E_LOG, L_RAOP, "Config has bad max_volume (%d) for device '%s', using default instead\n", max_volume, rd->name);
max_volume = RAOP_CONFIG_MAX_VOLUME;
}
// RAOP volume: -144.0 is off, -30.0 - 0 scaled by max_volume maps to 0 - 100
if (raop_volume > -30.0 && raop_volume <= 0.0)
return (int)(100.0 * (raop_volume / 30.0 + 1.0) * RAOP_CONFIG_MAX_VOLUME / (float)max_volume);
else
return 0;
}
static int
raop_set_volume_internal(struct raop_session *rs, int volume, evrtsp_req_cb cb)
{
@ -4905,6 +4945,7 @@ struct output_definition output_raop =
.device_probe = raop_device_probe,
.device_free_extra = raop_device_free_extra,
.device_volume_set = raop_set_volume_one,
.device_volume_to_pct = raop_volume_to_pct,
.playback_start = raop_playback_start,
.playback_stop = raop_playback_stop,
.write = raop_v2_write,

View File

@ -129,6 +129,11 @@ struct volume_param {
uint64_t spk_id;
};
struct activeremote_param {
uint32_t activeremote;
const char *value;
};
struct spk_enum
{
spk_enum_cb cb;
@ -2720,6 +2725,52 @@ volume_setabs_speaker(void *arg, int *retval)
return COMMAND_END;
}
// Just updates internal volume params (does not make actual requests to the speaker)
static enum command_state
volume_byactiveremote(void *arg, int *retval)
{
struct activeremote_param *ar_param = arg;
struct output_device *device;
uint32_t activeremote;
int volume;
*retval = 0;
activeremote = ar_param->activeremote;
for (device = dev_list; device; device = device->next)
{
if ((uint32_t)device->id == activeremote)
break;
}
if (!device)
{
DPRINTF(E_LOG, L_DACP, "Could not find speaker with Active-Remote id %d\n", activeremote);
*retval = -1;
return COMMAND_END;
}
volume = outputs_device_volume_to_pct(device, ar_param->value); // Only converts
if (volume < 0)
{
DPRINTF(E_LOG, L_DACP, "Could not parse volume given by Active-Remote id %d\n", activeremote);
*retval = -1;
return COMMAND_END;
}
device->volume = volume;
volume_master_find();
#ifdef DEBUG_RELVOL
DPRINTF(E_DBG, L_PLAYER, "*** %s: abs %d rel %d\n", device->name, device->volume, device->relvol);
#endif
listener_notify(LISTENER_VOLUME);
return COMMAND_END;
}
static enum command_state
repeat_set(void *arg, int *retval)
{
@ -3099,6 +3150,19 @@ player_volume_setabs_speaker(uint64_t id, int vol)
return ret;
}
int
player_volume_byactiveremote(uint32_t activeremote, const char *value)
{
struct activeremote_param ar_param;
int ret;
ar_param.activeremote = activeremote;
ar_param.value = value;
ret = commands_exec_sync(cmdbase, volume_byactiveremote, NULL, &ar_param);
return ret;
}
int
player_repeat_set(enum repeat_mode mode)
{

View File

@ -137,6 +137,9 @@ player_volume_setrel_speaker(uint64_t id, int relvol);
int
player_volume_setabs_speaker(uint64_t id, int vol);
int
player_volume_byactiveremote(uint32_t activeremote, const char *value);
int
player_repeat_set(enum repeat_mode mode);