mirror of
https://github.com/owntone/owntone-server.git
synced 2025-02-25 04:19:15 -05:00
[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:
parent
646bf37f17
commit
b6e1269cf2
@ -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_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 {
|
struct dacp_prop_map {
|
||||||
char *desc;
|
char *desc;
|
||||||
@ -108,17 +108,17 @@ dacp_propget_extendedmediakind(struct evbuffer *evbuf, struct player_status *sta
|
|||||||
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
dacp_propset_volume(const char *value, struct evkeyvalq *query);
|
dacp_propset_volume(const char *value, struct httpd_request *hreq);
|
||||||
static void
|
static void
|
||||||
dacp_propset_devicevolume(const char *value, struct evkeyvalq *query);
|
dacp_propset_devicevolume(const char *value, struct httpd_request *hreq);
|
||||||
static void
|
static void
|
||||||
dacp_propset_playingtime(const char *value, struct evkeyvalq *query);
|
dacp_propset_playingtime(const char *value, struct httpd_request *hreq);
|
||||||
static void
|
static void
|
||||||
dacp_propset_shufflestate(const char *value, struct evkeyvalq *query);
|
dacp_propset_shufflestate(const char *value, struct httpd_request *hreq);
|
||||||
static void
|
static void
|
||||||
dacp_propset_repeatstate(const char *value, struct evkeyvalq *query);
|
dacp_propset_repeatstate(const char *value, struct httpd_request *hreq);
|
||||||
static void
|
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 */
|
/* gperf static hash, dacp_prop.gperf */
|
||||||
@ -746,7 +746,7 @@ dacp_playstatus_update_handler(short event_mask)
|
|||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
// Only send status update on player change events
|
// Only send status update on player change events
|
||||||
if (!(event_mask & LISTENER_PLAYER))
|
if (!(event_mask & (LISTENER_PLAYER | LISTENER_VOLUME)))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
#ifdef HAVE_EVENTFD
|
#ifdef HAVE_EVENTFD
|
||||||
@ -903,7 +903,7 @@ dacp_propget_extendedmediakind(struct evbuffer *evbuf, struct player_status *sta
|
|||||||
|
|
||||||
/* Properties setters */
|
/* Properties setters */
|
||||||
static void
|
static void
|
||||||
dacp_propset_volume(const char *value, struct evkeyvalq *query)
|
dacp_propset_volume(const char *value, struct httpd_request *hreq)
|
||||||
{
|
{
|
||||||
const char *param;
|
const char *param;
|
||||||
uint64_t id;
|
uint64_t id;
|
||||||
@ -918,7 +918,7 @@ dacp_propset_volume(const char *value, struct evkeyvalq *query)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
param = evhttp_find_header(query, "speaker-id");
|
param = evhttp_find_header(hreq->query, "speaker-id");
|
||||||
if (param)
|
if (param)
|
||||||
{
|
{
|
||||||
ret = safe_atou64(param, &id);
|
ret = safe_atou64(param, &id);
|
||||||
@ -933,7 +933,7 @@ dacp_propset_volume(const char *value, struct evkeyvalq *query)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
param = evhttp_find_header(query, "include-speaker-id");
|
param = evhttp_find_header(hreq->query, "include-speaker-id");
|
||||||
if (param)
|
if (param)
|
||||||
{
|
{
|
||||||
ret = safe_atou64(param, &id);
|
ret = safe_atou64(param, &id);
|
||||||
@ -952,33 +952,26 @@ dacp_propset_volume(const char *value, struct evkeyvalq *query)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
dacp_propset_devicevolume(const char *value, struct evkeyvalq *query)
|
dacp_propset_devicevolume(const char *value, struct httpd_request *hreq)
|
||||||
{
|
{
|
||||||
float raop_volume;
|
struct evkeyvalq *headers;
|
||||||
int volume;
|
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 (!headers || !remote || (safe_atou32(remote, &id) < 0))
|
||||||
if (raop_volume == 0.0 && value[0] != '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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// RAOP volume: -144.0 is off, -30.0 - 0 maps to 0 - 100
|
player_volume_byactiveremote(id, value);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
dacp_propset_playingtime(const char *value, struct evkeyvalq *query)
|
dacp_propset_playingtime(const char *value, struct httpd_request *hreq)
|
||||||
{
|
{
|
||||||
struct timeval tv;
|
struct timeval tv;
|
||||||
int ret;
|
int ret;
|
||||||
@ -997,7 +990,7 @@ dacp_propset_playingtime(const char *value, struct evkeyvalq *query)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
dacp_propset_shufflestate(const char *value, struct evkeyvalq *query)
|
dacp_propset_shufflestate(const char *value, struct httpd_request *hreq)
|
||||||
{
|
{
|
||||||
int enable;
|
int enable;
|
||||||
int ret;
|
int ret;
|
||||||
@ -1014,7 +1007,7 @@ dacp_propset_shufflestate(const char *value, struct evkeyvalq *query)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
dacp_propset_repeatstate(const char *value, struct evkeyvalq *query)
|
dacp_propset_repeatstate(const char *value, struct httpd_request *hreq)
|
||||||
{
|
{
|
||||||
int mode;
|
int mode;
|
||||||
int ret;
|
int ret;
|
||||||
@ -1031,7 +1024,7 @@ dacp_propset_repeatstate(const char *value, struct evkeyvalq *query)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
dacp_propset_userrating(const char *value, struct evkeyvalq *query)
|
dacp_propset_userrating(const char *value, struct httpd_request *hreq)
|
||||||
{
|
{
|
||||||
const char *param;
|
const char *param;
|
||||||
uint32_t itemid;
|
uint32_t itemid;
|
||||||
@ -1046,9 +1039,9 @@ dacp_propset_userrating(const char *value, struct evkeyvalq *query)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
param = evhttp_find_header(query, "item-spec"); // Remote
|
param = evhttp_find_header(hreq->query, "item-spec"); // Remote
|
||||||
if (!param)
|
if (!param)
|
||||||
param = evhttp_find_header(query, "song-spec"); // Retune
|
param = evhttp_find_header(hreq->query, "song-spec"); // Retune
|
||||||
|
|
||||||
if (!param)
|
if (!param)
|
||||||
{
|
{
|
||||||
@ -2459,7 +2452,7 @@ dacp_reply_setproperty(struct httpd_request *hreq)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (dpm->propset)
|
if (dpm->propset)
|
||||||
dpm->propset(param->value, hreq->query);
|
dpm->propset(param->value, hreq);
|
||||||
else
|
else
|
||||||
DPRINTF(E_WARN, L_DACP, "No setter method for DACP property %s\n", dpm->desc);
|
DPRINTF(E_WARN, L_DACP, "No setter method for DACP property %s\n", dpm->desc);
|
||||||
}
|
}
|
||||||
@ -2785,7 +2778,7 @@ dacp_init(void)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
listener_add(dacp_playstatus_update_handler, LISTENER_PLAYER);
|
listener_add(dacp_playstatus_update_handler, LISTENER_PLAYER | LISTENER_VOLUME);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
@ -132,6 +132,18 @@ outputs_device_volume_set(struct output_device *device, output_status_cb cb)
|
|||||||
return -1;
|
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
|
void
|
||||||
outputs_playback_start(uint64_t next_pkt, struct timespec *ts)
|
outputs_playback_start(uint64_t next_pkt, struct timespec *ts)
|
||||||
{
|
{
|
||||||
|
@ -179,6 +179,9 @@ struct output_definition
|
|||||||
// Set the volume and call back
|
// Set the volume and call back
|
||||||
int (*device_volume_set)(struct output_device *device, output_status_cb cb);
|
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
|
// Start/stop playback on devices that were started
|
||||||
void (*playback_start)(uint64_t next_pkt, struct timespec *ts);
|
void (*playback_start)(uint64_t next_pkt, struct timespec *ts);
|
||||||
void (*playback_stop)(void);
|
void (*playback_stop)(void);
|
||||||
@ -217,6 +220,9 @@ outputs_device_free(struct output_device *device);
|
|||||||
int
|
int
|
||||||
outputs_device_volume_set(struct output_device *device, output_status_cb cb);
|
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
|
void
|
||||||
outputs_playback_start(uint64_t next_pkt, struct timespec *ts);
|
outputs_playback_start(uint64_t next_pkt, struct timespec *ts);
|
||||||
|
|
||||||
|
@ -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, "Client-Instance", buf);
|
||||||
evrtsp_add_header(req->output_headers, "DACP-ID", 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)
|
if (rs->session)
|
||||||
evrtsp_add_header(req->output_headers, "Session", 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;
|
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
|
static int
|
||||||
raop_set_volume_internal(struct raop_session *rs, int volume, evrtsp_req_cb cb)
|
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_probe = raop_device_probe,
|
||||||
.device_free_extra = raop_device_free_extra,
|
.device_free_extra = raop_device_free_extra,
|
||||||
.device_volume_set = raop_set_volume_one,
|
.device_volume_set = raop_set_volume_one,
|
||||||
|
.device_volume_to_pct = raop_volume_to_pct,
|
||||||
.playback_start = raop_playback_start,
|
.playback_start = raop_playback_start,
|
||||||
.playback_stop = raop_playback_stop,
|
.playback_stop = raop_playback_stop,
|
||||||
.write = raop_v2_write,
|
.write = raop_v2_write,
|
||||||
|
64
src/player.c
64
src/player.c
@ -129,6 +129,11 @@ struct volume_param {
|
|||||||
uint64_t spk_id;
|
uint64_t spk_id;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct activeremote_param {
|
||||||
|
uint32_t activeremote;
|
||||||
|
const char *value;
|
||||||
|
};
|
||||||
|
|
||||||
struct spk_enum
|
struct spk_enum
|
||||||
{
|
{
|
||||||
spk_enum_cb cb;
|
spk_enum_cb cb;
|
||||||
@ -2720,6 +2725,52 @@ volume_setabs_speaker(void *arg, int *retval)
|
|||||||
return COMMAND_END;
|
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
|
static enum command_state
|
||||||
repeat_set(void *arg, int *retval)
|
repeat_set(void *arg, int *retval)
|
||||||
{
|
{
|
||||||
@ -3099,6 +3150,19 @@ player_volume_setabs_speaker(uint64_t id, int vol)
|
|||||||
return ret;
|
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
|
int
|
||||||
player_repeat_set(enum repeat_mode mode)
|
player_repeat_set(enum repeat_mode mode)
|
||||||
{
|
{
|
||||||
|
@ -137,6 +137,9 @@ player_volume_setrel_speaker(uint64_t id, int relvol);
|
|||||||
int
|
int
|
||||||
player_volume_setabs_speaker(uint64_t id, int vol);
|
player_volume_setabs_speaker(uint64_t id, int vol);
|
||||||
|
|
||||||
|
int
|
||||||
|
player_volume_byactiveremote(uint32_t activeremote, const char *value);
|
||||||
|
|
||||||
int
|
int
|
||||||
player_repeat_set(enum repeat_mode mode);
|
player_repeat_set(enum repeat_mode mode);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user