mirror of
https://github.com/owntone/owntone-server.git
synced 2024-12-28 08:05:56 -05:00
[raop] Listen for _airplay._tcp and make it a condition for device removal (issue #496)
This commit is contained in:
parent
18acb73fdb
commit
1e051407d5
@ -159,8 +159,11 @@ struct raop_extra
|
|||||||
{
|
{
|
||||||
enum raop_devtype devtype;
|
enum raop_devtype devtype;
|
||||||
|
|
||||||
|
// "ek" param in the mdns announcement
|
||||||
bool encrypt;
|
bool encrypt;
|
||||||
|
// "md" param in the mdns announcement
|
||||||
bool wants_metadata;
|
bool wants_metadata;
|
||||||
|
// Part of the "et" param in the mdns announcement
|
||||||
bool supports_auth_setup;
|
bool supports_auth_setup;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -236,6 +239,15 @@ struct raop_metadata
|
|||||||
struct raop_metadata *next;
|
struct raop_metadata *next;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct raop_airplay_announcement
|
||||||
|
{
|
||||||
|
uint64_t id;
|
||||||
|
char *name;
|
||||||
|
int family;
|
||||||
|
|
||||||
|
struct raop_airplay_announcement *next;
|
||||||
|
};
|
||||||
|
|
||||||
struct raop_service
|
struct raop_service
|
||||||
{
|
{
|
||||||
int fd;
|
int fd;
|
||||||
@ -339,6 +351,9 @@ static struct timeval keep_alive_tv = { 30, 0 };
|
|||||||
/* Sessions */
|
/* Sessions */
|
||||||
static struct raop_session *sessions;
|
static struct raop_session *sessions;
|
||||||
|
|
||||||
|
/* _airplay._tcp announcements */
|
||||||
|
static struct raop_airplay_announcement *raop_airplay_announcements;
|
||||||
|
|
||||||
|
|
||||||
/* ALAC bits writer - big endian
|
/* ALAC bits writer - big endian
|
||||||
* p outgoing buffer pointer
|
* p outgoing buffer pointer
|
||||||
@ -4557,6 +4572,7 @@ raop_device_cb(const char *name, const char *type, const char *domain, const cha
|
|||||||
{
|
{
|
||||||
struct output_device *rd;
|
struct output_device *rd;
|
||||||
struct raop_extra *re;
|
struct raop_extra *re;
|
||||||
|
struct raop_airplay_announcement *an;
|
||||||
cfg_t *airplay;
|
cfg_t *airplay;
|
||||||
const char *p;
|
const char *p;
|
||||||
char *at_name;
|
char *at_name;
|
||||||
@ -4592,12 +4608,6 @@ raop_device_cb(const char *name, const char *type, const char *domain, const cha
|
|||||||
{
|
{
|
||||||
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", at_name);
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (airplay && cfg_getbool(airplay, "permanent") && (port < 0))
|
|
||||||
{
|
|
||||||
DPRINTF(E_INFO, L_RAOP, "AirPlay device '%s' disappeared, but set as permanent in config\n", at_name);
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4612,7 +4622,21 @@ raop_device_cb(const char *name, const char *type, const char *domain, const cha
|
|||||||
|
|
||||||
if (port < 0)
|
if (port < 0)
|
||||||
{
|
{
|
||||||
/* Device stopped advertising */
|
// Device stopped advertising, but let's see if we really should remove it
|
||||||
|
if (airplay && cfg_getbool(airplay, "permanent"))
|
||||||
|
{
|
||||||
|
DPRINTF(E_INFO, L_RAOP, "AirPlay device '%s' disappeared, but set as permanent in config\n", at_name);
|
||||||
|
goto free_rd;
|
||||||
|
}
|
||||||
|
for (an = raop_airplay_announcements; an; an = an->next)
|
||||||
|
{
|
||||||
|
if (!(id == an->id && family == an->family))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
DPRINTF(E_DBG, L_RAOP, "Removal of AirPlay device '%s' deferred for _airplay._tcp announcement\n", at_name);
|
||||||
|
goto free_rd;
|
||||||
|
}
|
||||||
|
|
||||||
switch (family)
|
switch (family)
|
||||||
{
|
{
|
||||||
case AF_INET:
|
case AF_INET:
|
||||||
@ -4631,7 +4655,7 @@ raop_device_cb(const char *name, const char *type, const char *domain, const cha
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Protocol */
|
// Protocol
|
||||||
p = keyval_get(txt, "tp");
|
p = keyval_get(txt, "tp");
|
||||||
if (!p)
|
if (!p)
|
||||||
{
|
{
|
||||||
@ -4654,7 +4678,7 @@ raop_device_cb(const char *name, const char *type, const char *domain, const cha
|
|||||||
goto free_rd;
|
goto free_rd;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Password protection */
|
// Password protection
|
||||||
password = NULL;
|
password = NULL;
|
||||||
p = keyval_get(txt, "pw");
|
p = keyval_get(txt, "pw");
|
||||||
if (!p)
|
if (!p)
|
||||||
@ -4686,7 +4710,7 @@ raop_device_cb(const char *name, const char *type, const char *domain, const cha
|
|||||||
|
|
||||||
rd->password = password;
|
rd->password = password;
|
||||||
|
|
||||||
/* Device verification */
|
// Device verification
|
||||||
p = keyval_get(txt, "sf");
|
p = keyval_get(txt, "sf");
|
||||||
if (p && (safe_hextou64(p, &sf) == 0))
|
if (p && (safe_hextou64(p, &sf) == 0))
|
||||||
{
|
{
|
||||||
@ -4696,7 +4720,7 @@ raop_device_cb(const char *name, const char *type, const char *domain, const cha
|
|||||||
// Note: device_add() in player.c will get the auth key from the db if available
|
// Note: device_add() in player.c will get the auth key from the db if available
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Device type */
|
// Device type
|
||||||
re->devtype = RAOP_DEV_OTHER;
|
re->devtype = RAOP_DEV_OTHER;
|
||||||
p = keyval_get(txt, "am");
|
p = keyval_get(txt, "am");
|
||||||
|
|
||||||
@ -4713,14 +4737,14 @@ raop_device_cb(const char *name, const char *type, const char *domain, const cha
|
|||||||
else if (*p == '\0')
|
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", at_name);
|
||||||
|
|
||||||
/* Encrypt stream */
|
// Encrypt stream
|
||||||
p = keyval_get(txt, "ek");
|
p = keyval_get(txt, "ek");
|
||||||
if (p && (*p == '1'))
|
if (p && (*p == '1'))
|
||||||
re->encrypt = 1;
|
re->encrypt = 1;
|
||||||
else
|
else
|
||||||
re->encrypt = 0;
|
re->encrypt = 0;
|
||||||
|
|
||||||
/* Metadata support */
|
// Metadata support
|
||||||
p = keyval_get(txt, "md");
|
p = keyval_get(txt, "md");
|
||||||
if (p && (*p != '\0'))
|
if (p && (*p != '\0'))
|
||||||
re->wants_metadata = 1;
|
re->wants_metadata = 1;
|
||||||
@ -4776,6 +4800,103 @@ raop_device_cb(const char *name, const char *type, const char *domain, const cha
|
|||||||
outputs_device_free(rd);
|
outputs_device_free(rd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Here is the deal with browsing for _airplay._tcp (even though we mainly use
|
||||||
|
// _raop._tcp): Some devices (Apple HomePod) stop announcing _raop._tcp records
|
||||||
|
// despite being online. They will, however, keep announcing _airplay._tcp, so
|
||||||
|
// we make it a condition that _airplay._tcp also times out before removing a
|
||||||
|
// device.
|
||||||
|
static void
|
||||||
|
raop_device_airplay_cb(const char *name, const char *type, const char *domain, const char *hostname, int family, const char *address, int port, struct keyval *txt)
|
||||||
|
{
|
||||||
|
struct raop_airplay_announcement *an;
|
||||||
|
struct raop_airplay_announcement *prev;
|
||||||
|
const char *p;
|
||||||
|
char deviceid[13];
|
||||||
|
char raopname[128];
|
||||||
|
int i;
|
||||||
|
uint64_t id;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
DPRINTF(E_DBG, L_RAOP, "_airplay._tcp event from '%s' (address '%s', port %d, family %d)\n", name, address, port, family);
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
for (an = raop_airplay_announcements; an; an = an->next)
|
||||||
|
{
|
||||||
|
snprintf(raopname, sizeof(raopname), "%" PRIX64 "@%s", an->id, an->name);
|
||||||
|
DPRINTF(E_DBG, L_RAOP, "Dump of _airplay._tcp announcements in list: '%s' (family %d)\n", raopname, an->family);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Device is gone (note, depending on order it might already have been removed)
|
||||||
|
if (port < 0)
|
||||||
|
{
|
||||||
|
prev = NULL;
|
||||||
|
for (an = raop_airplay_announcements; an; an = an->next)
|
||||||
|
{
|
||||||
|
if (strcmp(name, an->name) == 0 && family == an->family)
|
||||||
|
break;
|
||||||
|
|
||||||
|
prev = an;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!an)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Remove from list so raop_device_cb will not find it
|
||||||
|
if (!prev)
|
||||||
|
raop_airplay_announcements = an->next;
|
||||||
|
else
|
||||||
|
prev->next = an->next;
|
||||||
|
|
||||||
|
snprintf(raopname, sizeof(raopname), "%" PRIX64 "@%s", an->id, an->name);
|
||||||
|
raop_device_cb(raopname, type, domain, hostname, family, address, port, txt);
|
||||||
|
|
||||||
|
free(an->name);
|
||||||
|
free(an);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find device's ID
|
||||||
|
p = keyval_get(txt, "deviceid");
|
||||||
|
if (!p)
|
||||||
|
{
|
||||||
|
DPRINTF(E_WARN, L_RAOP, "_airplay._tcp event from '%s' without deviceid in txt field\n", name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converts p="AB:CD:EF:01:02:03" -> deviceid="ABCDEF0123456"
|
||||||
|
for (i = 0; (*p != '\0') && (i < sizeof(deviceid)); p++)
|
||||||
|
{
|
||||||
|
if (*p == ':')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
deviceid[i] = *p;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
deviceid[sizeof(deviceid) - 1] = '\0';
|
||||||
|
|
||||||
|
ret = safe_hextou64(deviceid, &id);
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_RAOP, "Could not read AirPlay device ID ('%s') from '%s'\n", deviceid, name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we already have the announcement registered we are done
|
||||||
|
for (an = raop_airplay_announcements; an; an = an->next)
|
||||||
|
{
|
||||||
|
if (id == an->id && family == an->family)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
an = calloc(1, sizeof(struct raop_airplay_announcement));
|
||||||
|
an->id = id;
|
||||||
|
an->name = strdup(name);
|
||||||
|
an->family = family;
|
||||||
|
an->next = raop_airplay_announcements;
|
||||||
|
raop_airplay_announcements = an;
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
raop_device_start_generic(struct output_device *rd, output_status_cb cb, uint64_t rtptime, bool only_probe)
|
raop_device_start_generic(struct output_device *rd, output_status_cb cb, uint64_t rtptime, bool only_probe)
|
||||||
{
|
{
|
||||||
@ -5034,11 +5155,18 @@ raop_init(void)
|
|||||||
ret = mdns_browse("_raop._tcp", family, raop_device_cb, MDNS_CONNECTION_TEST);
|
ret = mdns_browse("_raop._tcp", family, raop_device_cb, MDNS_CONNECTION_TEST);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_RAOP, "Could not add mDNS browser for AirPlay devices\n");
|
DPRINTF(E_LOG, L_RAOP, "Could not add mDNS browser for AirPlay devices (_raop._tcp)\n");
|
||||||
|
|
||||||
goto out_stop_control;
|
goto out_stop_control;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ret = mdns_browse("_airplay._tcp", family, raop_device_airplay_cb, 0);
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_RAOP, "Could not add mDNS browser for AirPlay devices (_airplay._tcp)\n");
|
||||||
|
|
||||||
|
goto out_stop_control;
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user