[player] Limit auto reconnection + add option for user override

With this commit auto reconnection per default will only be done for ATV4s and
HomePods. Reconnection is not always desirable, for instance if the device cuts
the connection because it is busy with something else, ref. issue #934.

The commit also adds an option to override auto reconnection, thus either
enabling it for other devices or disabling it for affected devices.
This commit is contained in:
ejurgensen 2020-07-23 00:39:29 +02:00
parent e8bdcd2365
commit a71444d3e1
5 changed files with 43 additions and 17 deletions

View File

@ -300,6 +300,11 @@ audio {
# present. The speaker will remain until restart of forked-daapd.
# permanent = false
# Some devices spuriously disconnect during playback, and based on the
# device type forked-daapd may attempt to reconnect. Setting this option
# overrides this so reconnecting is either always enabled or disabled.
# reconnect = false
# AirPlay password
# password = "s1kr3t"
#}

View File

@ -157,6 +157,7 @@ static cfg_opt_t sec_airplay[] =
CFG_INT("max_volume", 11, CFGF_NONE),
CFG_BOOL("exclude", cfg_false, CFGF_NONE),
CFG_BOOL("permanent", cfg_false, CFGF_NONE),
CFG_BOOL("reconnect", cfg_false, CFGF_NODEFAULT),
CFG_STR("password", NULL, CFGF_NONE),
CFG_END()
};

View File

@ -119,6 +119,7 @@ struct output_device
unsigned v6_disabled:1;
unsigned prevent_playback:1;
unsigned busy:1;
unsigned resurrect:1;
// Credentials if relevant
const char *password;

View File

@ -123,6 +123,7 @@ enum raop_devtype {
RAOP_DEV_APEX3_80211N,
RAOP_DEV_APPLETV,
RAOP_DEV_APPLETV4,
RAOP_DEV_HOMEPOD,
RAOP_DEV_OTHER,
};
@ -307,6 +308,7 @@ static const char *raop_devtype[] =
"AirPort Express 3 - 802.11n",
"AppleTV",
"AppleTV4",
"HomePod",
"Other",
};
@ -2124,10 +2126,9 @@ session_make(struct output_device *rd, int callback_id, bool only_probe)
rs->auth_quirk_itunes = 0;
break;
case RAOP_DEV_OTHER:
default:
rs->encrypt = re->encrypt;
rs->auth_quirk_itunes = 0;
break;
}
ret = session_connection_setup(rs, rd, AF_INET6);
@ -4408,7 +4409,10 @@ raop_verification_verify(struct raop_session *rs)
/* ------------------ RAOP devices discovery - mDNS callback ---------------- */
/* Thread: main (mdns) */
/* Examples of txt content:
* HomePod
["cn=0,1,2,3" "da=true" "et=0,3,5" "ft=0x4A7FCA00,0x56BD0" "sf=0x404" "md=0,1,2" "am=AudioAccessory1,1" "pk=1...f" "tp=UDP" "vn=65537" "vs=356.19" "ov=11.2.5" "vv=2"]
* Apple TV 2:
["sf=0x4" "am=AppleTV2,1" "vs=130.14" "vn=65537" "tp=UDP" "ss=16" "sr=4 4100" "sv=false" "pw=false" "md=0,1,2" "et=0,3,5" "da=true" "cn=0,1,2,3" "ch=2"]
["sf=0x4" "am=AppleTV2,1" "vs=105.5" "md=0,1,2" "tp=TCP,UDP" "vn=65537" "pw=false" "ss=16" "sr=44100" "da=true" "sv=false" "et=0,3" "cn=0,1" "ch=2" "txtvers=1"]
@ -4440,7 +4444,8 @@ raop_device_cb(const char *name, const char *type, const char *domain, const cha
{
struct output_device *rd;
struct raop_extra *re;
cfg_t *airplay;
cfg_t *devcfg;
cfg_opt_t *cfgopt;
const char *p;
char *at_name;
char *password;
@ -4470,14 +4475,14 @@ raop_device_cb(const char *name, const char *type, const char *domain, const cha
DPRINTF(E_DBG, L_RAOP, "Event for AirPlay device '%s' (port %d, id %" PRIx64 ")\n", at_name, port, id);
airplay = cfg_gettsec(cfg, "airplay", at_name);
if (airplay && cfg_getbool(airplay, "exclude"))
devcfg = cfg_gettsec(cfg, "airplay", at_name);
if (devcfg && cfg_getbool(devcfg, "exclude"))
{
DPRINTF(E_LOG, L_RAOP, "Excluding AirPlay device '%s' as set in config\n", at_name);
return;
}
if (airplay && cfg_getbool(airplay, "permanent") && (port < 0))
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);
@ -4559,9 +4564,8 @@ raop_device_cb(const char *name, const char *type, const char *domain, const cha
{
DPRINTF(E_LOG, L_RAOP, "AirPlay device '%s' is password-protected\n", at_name);
airplay = cfg_gettsec(cfg, "airplay", at_name);
if (airplay)
password = cfg_getstr(airplay, "password");
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);
@ -4610,9 +4614,19 @@ raop_device_cb(const char *name, const char *type, const char *domain, const cha
re->devtype = RAOP_DEV_APPLETV4; // Stream to ATV with tvOS 10 needs to be kept alive
else if (strncmp(p, "AppleTV", strlen("AppleTV")) == 0)
re->devtype = RAOP_DEV_APPLETV;
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);
// 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
cfgopt = devcfg ? cfg_getopt(devcfg, "reconnect") : NULL;
if (cfgopt && cfgopt->nvalues == 1)
rd->resurrect = cfg_opt_getnbool(cfgopt, 0);
else
rd->resurrect = (re->devtype == RAOP_DEV_APPLETV4) || (re->devtype == RAOP_DEV_HOMEPOD);
// Encrypt stream
p = keyval_get(txt, "ek");
if (p && (*p == '1'))

View File

@ -1466,22 +1466,26 @@ device_streaming_cb(struct output_device *device, enum output_device_state statu
{
DPRINTF(E_LOG, L_PLAYER, "Output device disappeared during streaming!\n");
}
else if (status == OUTPUT_STATE_FAILED && player_state == PLAY_PLAYING)
else if (status == OUTPUT_STATE_FAILED)
{
DPRINTF(E_LOG, L_PLAYER, "The %s device '%s' failed during playback - attempting reconnect in %d sec\n", device->type_name, device->name, PLAYER_SPEAKER_RESURRECT_TIME);
DPRINTF(E_WARN, L_PLAYER, "The %s device '%s' failed\n", device->type_name, device->name);
// The device can fail outside of playback, e.g. if it disconnects after a
// flush command
if (player_state != PLAY_PLAYING)
goto out;
if (outputs_sessions_count() == 0)
pb_suspend();
if (!device->resurrect)
goto out;
DPRINTF(E_LOG, L_PLAYER, "Attempting reconnection in %d sec to the %s device '%s'\n", PLAYER_SPEAKER_RESURRECT_TIME, device->type_name, device->name);
// TODO do this internally instead of through the worker
worker_execute(player_speaker_resurrect, &(device->id), sizeof(device->id), PLAYER_SPEAKER_RESURRECT_TIME);
}
else if (status == OUTPUT_STATE_FAILED)
{
// The device can fail outside of playback, e.g. if it disconnects after a
// flush command
DPRINTF(E_WARN, L_PLAYER, "The %s device '%s' failed\n", device->type_name, device->name);
}
else if (status == OUTPUT_STATE_STOPPED)
{
DPRINTF(E_INFO, L_PLAYER, "The %s device '%s' stopped\n", device->type_name, device->name);
@ -1492,6 +1496,7 @@ device_streaming_cb(struct output_device *device, enum output_device_state statu
outputs_device_cb_set(device, device_streaming_cb);
}
out:
// We don't do this in the other cb's because they are triggered by a command
// and thus the update should be done as part of the command completion (which
// can better determine which type of listener event to use)