[outputs] Add nickname option for Airplay/Chromecast speakers (issue #1145)

This commit is contained in:
ejurgensen 2020-12-14 23:47:21 +01:00
parent 76cd982201
commit 14756c0d59
5 changed files with 71 additions and 69 deletions

View File

@ -261,8 +261,7 @@ audio {
# README about ALSA for details. Note that these settings will override the ALSA
# settings in the "audio" section above.
#alsa "card name" {
# Name - used in the speaker list in Remote
# If not set, the card name will be used
# Name used in the speaker list. If not set, the card name will be used.
# nickname = "Computer"
# Mixer channel to use for volume control
@ -312,6 +311,9 @@ audio {
# AirPlay password
# password = "s1kr3t"
# Name used in the speaker list, overrides name from the device
# nickname = "My speaker name"
#}
# Chromecast settings
@ -324,6 +326,9 @@ audio {
# Enable this option to exclude a particular device from the speaker
# list
# exclude = false
# Name used in the speaker list, overrides name from the device
# nickname = "My speaker name"
#}
# Spotify settings (only have effect if Spotify enabled - see README/INSTALL)

View File

@ -160,6 +160,7 @@ static cfg_opt_t sec_airplay[] =
CFG_BOOL("permanent", cfg_false, CFGF_NONE),
CFG_BOOL("reconnect", cfg_false, CFGF_NODEFAULT),
CFG_STR("password", NULL, CFGF_NONE),
CFG_STR("nickname", NULL, CFGF_NONE),
CFG_END()
};
@ -169,6 +170,7 @@ static cfg_opt_t sec_chromecast[] =
CFG_INT("max_volume", 11, CFGF_NONE),
CFG_BOOL("exclude", cfg_false, CFGF_NONE),
CFG_INT("offset_ms", 0, CFGF_NONE),
CFG_STR("nickname", NULL, CFGF_NONE),
CFG_END()
};

View File

@ -128,6 +128,7 @@ struct output_device
// Device volume
int volume;
int relvol;
int max_volume;
// Quality of audio output
struct media_quality quality;

View File

@ -1822,7 +1822,7 @@ cast_device_cb(const char *name, const char *type, const char *domain, const cha
{
struct output_device *device;
const char *friendly_name;
cfg_t *chromecast;
cfg_t *devcfg;
uint32_t id;
id = djb_hash(name, strlen(name));
@ -1838,12 +1838,16 @@ cast_device_cb(const char *name, const char *type, const char *domain, const cha
DPRINTF(E_DBG, L_CAST, "Event for Chromecast device '%s' (port %d, id %" PRIu32 ")\n", name, port, id);
chromecast = cfg_gettsec(cfg, "chromecast", name);
if (chromecast && cfg_getbool(chromecast, "exclude"))
devcfg = cfg_gettsec(cfg, "chromecast", name);
if (devcfg && cfg_getbool(devcfg, "exclude"))
{
DPRINTF(E_LOG, L_CAST, "Excluding Chromecast device '%s' as set in config\n", name);
return;
}
if (devcfg && cfg_getstr(devcfg, "nickname"))
{
name = cfg_getstr(devcfg, "nickname");
}
device = calloc(1, sizeof(struct output_device));
if (!device)
@ -1876,6 +1880,14 @@ cast_device_cb(const char *name, const char *type, const char *domain, const cha
return;
}
// Max volume
device->max_volume = devcfg ? cfg_getint(devcfg, "max_volume") : CAST_CONFIG_MAX_VOLUME;
if ((device->max_volume < 1) || (device->max_volume > CAST_CONFIG_MAX_VOLUME))
{
DPRINTF(E_LOG, L_CAST, "Config has bad max_volume (%d) for device '%s', using default instead\n", device->max_volume, name);
device->max_volume = CAST_CONFIG_MAX_VOLUME;
}
DPRINTF(E_INFO, L_CAST, "Adding Chromecast device '%s'\n", name);
device->advertised = 1;
@ -2261,22 +2273,12 @@ static int
cast_device_volume_set(struct output_device *device, int callback_id)
{
struct cast_session *cs = device->session;
cfg_t *cast_cfg;
int max_volume;
int ret;
if (!cs || !(cs->state & CAST_STATE_F_APP_READY))
return 0;
cast_cfg = cfg_gettsec(cfg, "chromecast", device->name);
max_volume = cast_cfg ? cfg_getint(cast_cfg, "max_volume") : CAST_CONFIG_MAX_VOLUME;
if ((max_volume < 1) || (max_volume > CAST_CONFIG_MAX_VOLUME))
{
DPRINTF(E_LOG, L_CAST, "Config has bad max_volume (%d) for device '%s', using default instead\n", max_volume, device->name);
max_volume = CAST_CONFIG_MAX_VOLUME;
}
cs->volume = ((float)max_volume * (float)device->volume * 1.0) / (100.0 * CAST_CONFIG_MAX_VOLUME);
cs->volume = ((float)device->max_volume * (float)device->volume * 1.0) / (100.0 * CAST_CONFIG_MAX_VOLUME);
ret = cast_msg_send(cs, SET_VOLUME, cast_cb_volume);
if (ret < 0)

View File

@ -2488,31 +2488,16 @@ raop_metadata_send(struct output_metadata *metadata)
/* ------------------------------ Volume handling --------------------------- */
static float
raop_volume_from_pct(int volume, char *name)
raop_volume_from_pct(int volume, struct output_device *device)
{
float raop_volume;
cfg_t *airplay;
int max_volume;
max_volume = RAOP_CONFIG_MAX_VOLUME;
airplay = cfg_gettsec(cfg, "airplay", 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, name);
max_volume = RAOP_CONFIG_MAX_VOLUME;
}
/* RAOP volume
* -144.0 is off
* 0 - 100 maps to -30.0 - 0
*/
if (volume > 0 && volume <= 100)
raop_volume = -30.0 + ((float)max_volume * (float)volume * 30.0) / (100.0 * RAOP_CONFIG_MAX_VOLUME);
raop_volume = -30.0 + ((float)device->max_volume * (float)volume * 30.0) / (100.0 * RAOP_CONFIG_MAX_VOLUME);
else
raop_volume = -144.0;
@ -2520,11 +2505,9 @@ raop_volume_from_pct(int volume, char *name)
}
static int
raop_volume_to_pct(struct output_device *rd, const char *volume)
raop_volume_to_pct(struct output_device *device, const char *volume)
{
float raop_volume;
cfg_t *airplay;
int max_volume;
raop_volume = atof(volume);
@ -2535,21 +2518,9 @@ raop_volume_to_pct(struct output_device *rd, const char *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);
return (int)(100.0 * (raop_volume / 30.0 + 1.0) * RAOP_CONFIG_MAX_VOLUME / (float)device->max_volume);
else
return 0;
}
@ -2557,10 +2528,19 @@ raop_volume_to_pct(struct output_device *rd, const char *volume)
static int
raop_set_volume_internal(struct raop_session *rs, int volume, evrtsp_req_cb cb)
{
struct output_device *device;
struct evbuffer *evbuf;
float raop_volume;
int ret;
device = outputs_device_get(rs->device_id);
if (!device)
{
DPRINTF(E_LOG, L_RAOP, "Could not set volume, device with id %" PRIu64 " not in out list\n", rs->device_id);
return -1;
}
evbuf = evbuffer_new();
if (!evbuf)
{
@ -2569,7 +2549,7 @@ raop_set_volume_internal(struct raop_session *rs, int volume, evrtsp_req_cb cb)
return -1;
}
raop_volume = raop_volume_from_pct(volume, rs->devname);
raop_volume = raop_volume_from_pct(volume, device);
/* Don't let locales get in the way here */
/* We use -%d and -(int)raop_volume so -0.3 won't become 0.3 */
@ -4438,7 +4418,7 @@ raop_device_cb(const char *name, const char *type, const char *domain, const cha
cfg_t *devcfg;
cfg_opt_t *cfgopt;
const char *p;
char *at_name;
const char *device_name;
char *password;
char *s;
char *token;
@ -4455,36 +4435,40 @@ raop_device_cb(const char *name, const char *type, const char *domain, const cha
return;
}
at_name = strchr(name, '@');
if (!at_name)
device_name = strchr(name, '@');
if (!device_name)
{
DPRINTF(E_LOG, L_RAOP, "Could not extract AirPlay device name ('%s')\n", name);
return;
}
at_name++;
device_name++;
DPRINTF(E_DBG, L_RAOP, "Event for AirPlay device '%s' (port %d, id %" PRIx64 ")\n", at_name, port, id);
DPRINTF(E_DBG, L_RAOP, "Event for AirPlay device '%s' (port %d, id %" PRIx64 ")\n", device_name, port, id);
devcfg = cfg_gettsec(cfg, "airplay", at_name);
devcfg = cfg_gettsec(cfg, "airplay", device_name);
if (devcfg && cfg_getbool(devcfg, "exclude"))
{
DPRINTF(E_LOG, L_RAOP, "Excluding AirPlay device '%s' as set in config\n", at_name);
DPRINTF(E_LOG, L_RAOP, "Excluding AirPlay device '%s' as set in config\n", device_name);
return;
}
if (devcfg && cfg_getbool(devcfg, "permanent") && (port < 0))
{
DPRINTF(E_INFO, L_RAOP, "AirPlay device '%s' disappeared, but set as permanent in config\n", at_name);
DPRINTF(E_INFO, L_RAOP, "AirPlay device '%s' disappeared, but set as permanent in config\n", device_name);
return;
}
if (devcfg && cfg_getstr(devcfg, "nickname"))
{
device_name = cfg_getstr(devcfg, "nickname");
}
CHECK_NULL(L_RAOP, rd = calloc(1, sizeof(struct output_device)));
CHECK_NULL(L_RAOP, re = calloc(1, sizeof(struct raop_extra)));
rd->id = id;
rd->name = strdup(at_name);
rd->name = strdup(device_name);
rd->type = OUTPUT_TYPE_RAOP;
rd->type_name = outputs_name(rd->type);
rd->extra_device_info = re;
@ -4514,21 +4498,21 @@ raop_device_cb(const char *name, const char *type, const char *domain, const cha
p = keyval_get(txt, "tp");
if (!p)
{
DPRINTF(E_LOG, L_RAOP, "AirPlay '%s': no tp field in TXT record!\n", at_name);
DPRINTF(E_LOG, L_RAOP, "AirPlay '%s': no tp field in TXT record!\n", device_name);
goto free_rd;
}
if (*p == '\0')
{
DPRINTF(E_LOG, L_RAOP, "AirPlay '%s': tp has no value\n", at_name);
DPRINTF(E_LOG, L_RAOP, "AirPlay '%s': tp has no value\n", device_name);
goto free_rd;
}
if (!strstr(p, "UDP"))
{
DPRINTF(E_LOG, L_RAOP, "AirPlay '%s': device does not support AirTunes v2 (tp=%s), discarding\n", at_name, p);
DPRINTF(E_LOG, L_RAOP, "AirPlay '%s': device does not support AirTunes v2 (tp=%s), discarding\n", device_name, p);
goto free_rd;
}
@ -4542,7 +4526,7 @@ raop_device_cb(const char *name, const char *type, const char *domain, const cha
}
else if (*p == '\0')
{
DPRINTF(E_LOG, L_RAOP, "AirPlay '%s': pw has no value\n", at_name);
DPRINTF(E_LOG, L_RAOP, "AirPlay '%s': pw has no value\n", device_name);
goto free_rd;
}
@ -4553,13 +4537,13 @@ raop_device_cb(const char *name, const char *type, const char *domain, const cha
if (rd->has_password)
{
DPRINTF(E_LOG, L_RAOP, "AirPlay device '%s' is password-protected\n", at_name);
DPRINTF(E_LOG, L_RAOP, "AirPlay device '%s' is password-protected\n", device_name);
if (devcfg)
password = cfg_getstr(devcfg, "password");
if (!password)
DPRINTF(E_LOG, L_RAOP, "No password given in config for AirPlay device '%s'\n", at_name);
DPRINTF(E_LOG, L_RAOP, "No password given in config for AirPlay device '%s'\n", device_name);
}
rd->password = password;
@ -4591,6 +4575,14 @@ raop_device_cb(const char *name, const char *type, const char *domain, const cha
if (!quality_is_equal(&rd->quality, &raop_quality_default))
DPRINTF(E_LOG, L_RAOP, "Device '%s' requested non-default audio quality (%d/%d/%d)\n", rd->name, rd->quality.sample_rate, rd->quality.bits_per_sample, rd->quality.channels);
// Max volume
rd->max_volume = devcfg ? cfg_getint(devcfg, "max_volume") : RAOP_CONFIG_MAX_VOLUME;
if ((rd->max_volume < 1) || (rd->max_volume > RAOP_CONFIG_MAX_VOLUME))
{
DPRINTF(E_LOG, L_RAOP, "Config has bad max_volume (%d) for device '%s', using default instead\n", rd->max_volume, device_name);
rd->max_volume = RAOP_CONFIG_MAX_VOLUME;
}
// Device type
re->devtype = RAOP_DEV_OTHER;
p = keyval_get(txt, "am");
@ -4608,7 +4600,7 @@ raop_device_cb(const char *name, const char *type, const char *domain, const cha
else if (strncmp(p, "AudioAccessory", strlen("AudioAccessory")) == 0)
re->devtype = RAOP_DEV_HOMEPOD;
else if (*p == '\0')
DPRINTF(E_LOG, L_RAOP, "AirPlay device '%s': am has no value\n", at_name);
DPRINTF(E_LOG, L_RAOP, "AirPlay device '%s': am has no value\n", device_name);
// If the user didn't set any reconnect setting we enable for Apple TV and
// HomePods due to https://github.com/ejurgensen/forked-daapd/issues/734
@ -4665,18 +4657,18 @@ raop_device_cb(const char *name, const char *type, const char *domain, const cha
rd->v4_address = strdup(address);
rd->v4_port = port;
DPRINTF(E_INFO, L_RAOP, "Adding AirPlay device '%s': password: %u, verification: %u, encrypt: %u, authsetup: %u, metadata: %u, type %s, address %s:%d\n",
at_name, rd->has_password, rd->requires_auth, re->encrypt, re->supports_auth_setup, re->wanted_metadata, raop_devtype[re->devtype], address, port);
device_name, rd->has_password, rd->requires_auth, re->encrypt, re->supports_auth_setup, re->wanted_metadata, raop_devtype[re->devtype], address, port);
break;
case AF_INET6:
rd->v6_address = strdup(address);
rd->v6_port = port;
DPRINTF(E_INFO, L_RAOP, "Adding AirPlay device '%s': password: %u, verification: %u, encrypt: %u, authsetup: %u, metadata: %u, type %s, address [%s]:%d\n",
at_name, rd->has_password, rd->requires_auth, re->encrypt, re->supports_auth_setup, re->wanted_metadata, raop_devtype[re->devtype], address, port);
device_name, rd->has_password, rd->requires_auth, re->encrypt, re->supports_auth_setup, re->wanted_metadata, raop_devtype[re->devtype], address, port);
break;
default:
DPRINTF(E_LOG, L_RAOP, "Error: AirPlay device '%s' has neither ipv4 og ipv6 address\n", at_name);
DPRINTF(E_LOG, L_RAOP, "Error: AirPlay device '%s' has neither ipv4 og ipv6 address\n", device_name);
goto free_rd;
}