From ffe8653d9ea2400d0d1e320a9afbc78d7bee8918 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Sun, 24 Jan 2016 01:14:07 +0100 Subject: [PATCH] [chromecast] Remove RAOP specifics from player.c, add generic output interface --- src/Makefile.am | 5 +- src/cast.h | 58 -- src/outputs.c | 354 ++++++++++ src/outputs.h | 233 +++++++ src/{ => outputs}/cast.c | 86 +++ src/{ => outputs}/cast_channel.pb-c.c | 0 src/{ => outputs}/cast_channel.pb-c.h | 0 src/{ => outputs}/raop.c | 476 ++++++++++--- src/player.c | 948 ++++++++------------------ src/player.h | 5 + src/raop.h | 139 ---- 11 files changed, 1382 insertions(+), 922 deletions(-) delete mode 100644 src/cast.h create mode 100644 src/outputs.c create mode 100644 src/outputs.h rename src/{ => outputs}/cast.c (85%) rename src/{ => outputs}/cast_channel.pb-c.c (100%) rename src/{ => outputs}/cast_channel.pb-c.h (100%) rename src/{ => outputs}/raop.c (88%) delete mode 100644 src/raop.h diff --git a/src/Makefile.am b/src/Makefile.am index bd67efaa..5a0a4e00 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -14,7 +14,7 @@ LASTFM_SRC=lastfm.c lastfm.h endif if COND_CHROMECAST -CHROMECAST_SRC=cast.h cast.c cast_channel.pb-c.h cast_channel.pb-c.c +CHROMECAST_SRC=outputs/cast.c outputs/cast_channel.pb-c.h outputs/cast_channel.pb-c.c endif if COND_MPD @@ -103,9 +103,10 @@ forked_daapd_SOURCES = main.c \ queue.c queue.h \ worker.c worker.h \ $(ALSA_SRC) $(OSS4_SRC) \ + outputs.h outputs.c \ laudio_dummy.c \ laudio.c laudio.h \ - raop.c raop.h \ + outputs/raop.c \ evrtsp/rtsp.c evrtp/evrtsp.h evrtsp/rtsp-internal.h evrtsp/log.h \ $(SPOTIFY_SRC) \ $(LASTFM_SRC) \ diff --git a/src/cast.h b/src/cast.h deleted file mode 100644 index ce95deab..00000000 --- a/src/cast.h +++ /dev/null @@ -1,58 +0,0 @@ - -#ifndef __CAST_H__ -#define __CAST_H__ - -#include "raop.h" - -int -cast_device_start(struct raop_device *rd, raop_status_cb cb); - -/*void -raop_metadata_purge(void); - -void -raop_metadata_prune(uint64_t rtptime); - -struct raop_metadata * -raop_metadata_prepare(int id); - -void -raop_metadata_send(struct raop_metadata *rmd, uint64_t rtptime, uint64_t offset, int startup); - -int -raop_device_probe(struct raop_device *rd, raop_status_cb cb); - -int -raop_device_start(struct raop_device *rd, raop_status_cb cb, uint64_t rtptime); - -void -raop_device_stop(struct raop_session *rs); - -void -raop_playback_start(uint64_t next_pkt, struct timespec *ts); - -void -raop_playback_stop(void); - -int -raop_set_volume_one(struct raop_session *rs, int volume, raop_status_cb cb); - -int -raop_flush(raop_status_cb cb, uint64_t rtptime); - - -void -raop_set_status_cb(struct raop_session *rs, raop_status_cb cb); - - -void -raop_v2_write(uint8_t *buf, uint64_t rtptime); -*/ - -int -cast_init(void); - -void -cast_deinit(void); - -#endif /* !__CAST_H__ */ diff --git a/src/outputs.c b/src/outputs.c new file mode 100644 index 00000000..05f8caef --- /dev/null +++ b/src/outputs.c @@ -0,0 +1,354 @@ +/* + * Copyright (C) 2016 Espen Jürgensen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "logger.h" +#include "outputs.h" + +/*#ifdef ALSA +extern audio_output output_alsa; +#endif +#ifdef OSS4 +extern audio_output output_oss4; +#endif +extern struct output_definition output_dummy;*/ +extern struct output_definition output_raop; +#ifdef CHROMECAST +extern struct output_definition output_cast; +#endif + +// Must be in sync with enum output_types +static struct output_definition *outputs[] = { +/*#ifdef ALSA + &output_alsa, +#endif +#ifdef OSS4 + &output_oss4, +#endif + &output_dummy,*/ + &output_raop, +#ifdef CHROMECAST + &output_cast, +#endif + NULL +}; + +int +outputs_device_start(struct output_device *device, output_status_cb cb, uint64_t rtptime) +{ + if (outputs[device->type]->disabled) + return -1; + + if (outputs[device->type]->device_start) + return outputs[device->type]->device_start(device, cb, rtptime); + else + return -1; +} + +void +outputs_device_stop(struct output_session *session) +{ + if (outputs[session->type]->disabled) + return; + + if (outputs[session->type]->device_stop) + outputs[session->type]->device_stop(session); +} + +int +outputs_device_probe(struct output_device *device, output_status_cb cb) +{ + if (outputs[device->type]->disabled) + return -1; + + if (outputs[device->type]->device_probe) + return outputs[device->type]->device_probe(device, cb); + else + return -1; +} + +void +outputs_device_free(struct output_device *device) +{ + if (outputs[device->type]->disabled) + DPRINTF(E_LOG, L_PLAYER, "BUG! Freeing device from a disabled output?\n"); + + if (outputs[device->type]->device_free_extra) + outputs[device->type]->device_free_extra(device); + + if (device->name) + free(device->name); + + if (device->v4_address) + free(device->v4_address); + + if (device->v6_address) + free(device->v6_address); + + if (device->session) + DPRINTF(E_LOG, L_PLAYER, "BUG! Freeing device with active session?\n"); + + free(device); +} + +int +outputs_device_volume_set(struct output_device *device, output_status_cb cb) +{ + if (outputs[device->type]->disabled) + return -1; + + if (outputs[device->type]->device_volume_set) + return outputs[device->type]->device_volume_set(device, cb); + else + return -1; +} + +void +outputs_playback_start(uint64_t next_pkt, struct timespec *ts) +{ + int i; + + for (i = 0; outputs[i]; i++) + { + if (outputs[i]->disabled) + continue; + + if (outputs[i]->playback_start) + outputs[i]->playback_start(next_pkt, ts); + } +} + +void +outputs_playback_stop(void) +{ + int i; + + for (i = 0; outputs[i]; i++) + { + if (outputs[i]->disabled) + continue; + + if (outputs[i]->playback_stop) + outputs[i]->playback_stop(); + } +} + +void +outputs_write(uint8_t *buf, uint64_t rtptime) +{ + int i; + + for (i = 0; outputs[i]; i++) + { + if (outputs[i]->disabled) + continue; + + if (outputs[i]->write) + outputs[i]->write(buf, rtptime); + } +} + +// TODO output of this makes little sense +int +outputs_flush(output_status_cb cb, uint64_t rtptime) +{ + int ret; + int i; + + ret = 0; + for (i = 0; outputs[i]; i++) + { + if (outputs[i]->disabled) + continue; + + if (outputs[i]->flush) + ret = outputs[i]->flush(cb, rtptime); + } + + return ret; +} + +void +outputs_status_cb(struct output_session *session, output_status_cb cb) +{ + if (outputs[session->type]->disabled) + return; + + if (outputs[session->type]->status_cb) + outputs[session->type]->status_cb(session, cb); +} + +struct output_metadata * +outputs_metadata_prepare(int id) +{ + struct output_metadata *omd; + struct output_metadata *new; + void *metadata; + int i; + + omd = NULL; + for (i = 0; outputs[i]; i++) + { + if (outputs[i]->disabled) + continue; + + if (!outputs[i]->metadata_prepare) + continue; + + metadata = outputs[i]->metadata_prepare(id); + if (!metadata) + continue; + + new = calloc(1, sizeof(struct output_metadata)); + if (!new) + return omd; + + if (omd) + new->next = omd; + omd = new; + omd->type = i; + omd->metadata = metadata; + } + + return omd; +} + +void +outputs_metadata_send(struct output_metadata *omd, uint64_t rtptime, uint64_t offset, int startup) +{ + struct output_metadata *ptr; + int i; + + for (i = 0; outputs[i]; i++) + { + if (outputs[i]->disabled) + continue; + + if (!outputs[i]->metadata_send) + continue; + + // Go through linked list to find appropriate metadata for type + for (ptr = omd; ptr; ptr = ptr->next) + if (ptr->type == i) + break; + + if (!ptr) + continue; + + outputs[i]->metadata_send(ptr->metadata, rtptime, offset, startup); + } +} + +void +outputs_metadata_purge(void) +{ + int i; + + for (i = 0; outputs[i]; i++) + { + if (outputs[i]->disabled) + continue; + + if (outputs[i]->metadata_purge) + outputs[i]->metadata_purge(); + } +} + +void +outputs_metadata_prune(uint64_t rtptime) +{ + int i; + + for (i = 0; outputs[i]; i++) + { + if (outputs[i]->disabled) + continue; + + if (outputs[i]->metadata_prune) + outputs[i]->metadata_prune(rtptime); + } +} + +void +outputs_metadata_free(struct output_metadata *omd) +{ + struct output_metadata *ptr; + + if (!omd) + return; + + for (ptr = omd; omd; ptr = omd) + { + omd = ptr->next; + free(ptr); + } +} + + +const char * +outputs_name(enum output_types type) +{ + return outputs[type]->name; +} + +int +outputs_init(void) +{ + int no_output; + int ret; + int i; + + no_output = 1; + for (i = 0; outputs[i]; i++) + { + ret = outputs[i]->init(); + if (ret < 0) + outputs[i]->disabled = 1; + else + no_output = 0; + } + + if (no_output) + return -1; + + return 0; +} + +void +outputs_deinit(void) +{ + int i; + + for (i = 0; outputs[i]; i++) + { + if (!outputs[i]->disabled) + outputs[i]->deinit(); + } +} + diff --git a/src/outputs.h b/src/outputs.h new file mode 100644 index 00000000..5a93b7ae --- /dev/null +++ b/src/outputs.h @@ -0,0 +1,233 @@ + +#ifndef __OUTPUTS_H__ +#define __OUTPUTS_H__ + +/* Outputs is a generic interface between the player and a media output method, + * like for instance AirPlay (raop) or ALSA. The purpose of the interface is to + * make it easier to add new outputs without messing too much with the player or + * existing output methods. + * + * An output method will have a general type, and it will be able to detect + * supported devices that are available for output. A device will be typically + * be something like an AirPlay speaker. + * + * When a device is started the output backend will typically create a session. + * This session is only passed around as an opaque object in this interface. + * + */ + +enum output_types +{ +/* OUTPUT_TYPE_ALSA, + OUTPUT_TYPE_OSS, + OUTPUT_TYPE_DUMMY,*/ + OUTPUT_TYPE_RAOP, +#ifdef CHROMECAST + OUTPUT_TYPE_CAST, +#endif +}; + +/* Output session state */ + +/* Session is starting up */ +#define OUTPUT_STATE_F_STARTUP (1 << 15) +/* Streaming is up (connection established) */ +#define OUTPUT_STATE_F_CONNECTED (1 << 16) +enum output_device_state +{ + OUTPUT_STATE_STOPPED = 0, + + /* Session startup */ + OUTPUT_STATE_OPTIONS = OUTPUT_STATE_F_STARTUP | 0x01, + OUTPUT_STATE_ANNOUNCE = OUTPUT_STATE_F_STARTUP | 0x02, + OUTPUT_STATE_SETUP = OUTPUT_STATE_F_STARTUP | 0x03, + OUTPUT_STATE_RECORD = OUTPUT_STATE_F_STARTUP | 0x04, + + /* Session established + * - streaming ready (RECORD sent and acked, connection established) + * - commands (SET_PARAMETER) are possible + */ + OUTPUT_STATE_CONNECTED = OUTPUT_STATE_F_CONNECTED, + + /* Audio data is being sent */ + OUTPUT_STATE_STREAMING = OUTPUT_STATE_F_CONNECTED | 0x01, + + /* Session is failed, couldn't startup or error occurred */ + OUTPUT_STATE_FAILED = -1, + + /* Password issue: unknown password or bad password */ + OUTPUT_STATE_PASSWORD = -2, +}; + +/* Linked list of device info used by the player for each device + */ +struct output_device +{ + // Device id + uint64_t id; + + // Name of the device, e.g. "Living Room" + char *name; + + // Type of the device, will be used to determine which output backend to call + enum output_types type; + + // Type of output (string) + const char *type_name; + + // Misc device flags + unsigned selected:1; + unsigned advertised:1; + unsigned has_password:1; + unsigned has_video:1; + + // Password if relevant + const char *password; + + // Device volume + int volume; + int relvol; + + // Address + char *v4_address; + char *v6_address; + short v4_port; + short v6_port; + + // Opaque pointers to device and session data + void *extra_device_info; + struct output_session *session; + + struct output_device *next; +}; + +// Except for the type, sessions are opaque outside of the output backend +struct output_session +{ + enum output_types type; + void *session; +}; + +// Linked list of metadata prepared by each output backend +struct output_metadata +{ + enum output_types type; + void *metadata; + struct output_metadata *next; +}; + +typedef void (*output_status_cb)(struct output_device *device, struct output_session *session, enum output_device_state status); + +struct output_definition +{ + // Name of the output + const char *name; + + // Type of output + enum output_types type; + + // Priority to give this output when autoselecting an output, 1 is highest + // 1 = highest priority, 0 = don't autoselect + // TODO Not implemented yet + int priority; + + // Set to 1 if the output initialization failed + int disabled; + + // Initialization function called during startup + // Output must call device_cb when an output device becomes available/unavailable + int (*init)(void); + + // Deinitialization function called at shutdown + void (*deinit)(void); + + // Prepare a playback session on device and call back + int (*device_start)(struct output_device *device, output_status_cb cb, uint64_t rtptime); + + // Close a session prepared by device_start + void (*device_stop)(struct output_session *session); + + // Test the connection to a device and call back + int (*device_probe)(struct output_device *device, output_status_cb cb); + + // Free the private device data + void (*device_free_extra)(struct output_device *device); + + // Set the volume and call back + int (*device_volume_set)(struct output_device *device, output_status_cb cb); + + // Start/stop playback on devices that were started + void (*playback_start)(uint64_t next_pkt, struct timespec *ts); + void (*playback_stop)(void); + + // Write stream data to the output devices + void (*write)(uint8_t *buf, uint64_t rtptime); + + // Flush all sessions + int (*flush)(output_status_cb cb, uint64_t rtptime); + + // Change the call back associated with a session + void (*status_cb)(struct output_session *session, output_status_cb cb); + + // Metadata + void *(*metadata_prepare)(int id); + void (*metadata_send)(void *metadata, uint64_t rtptime, uint64_t offset, int startup); + void (*metadata_purge)(void); + void (*metadata_prune)(uint64_t rtptime); +}; + +int +outputs_device_start(struct output_device *device, output_status_cb cb, uint64_t rtptime); + +void +outputs_device_stop(struct output_session *session); + +int +outputs_device_probe(struct output_device *device, output_status_cb cb); + +void +outputs_device_free(struct output_device *device); + +int +outputs_device_volume_set(struct output_device *device, output_status_cb cb); + +void +outputs_playback_start(uint64_t next_pkt, struct timespec *ts); + +void +outputs_playback_stop(void); + +void +outputs_write(uint8_t *buf, uint64_t rtptime); + +int +outputs_flush(output_status_cb cb, uint64_t rtptime); + +void +outputs_status_cb(struct output_session *session, output_status_cb cb); + +struct output_metadata * +outputs_metadata_prepare(int id); + +void +outputs_metadata_send(struct output_metadata *omd, uint64_t rtptime, uint64_t offset, int startup); + +void +outputs_metadata_purge(void); + +void +outputs_metadata_prune(uint64_t rtptime); + +void +outputs_metadata_free(struct output_metadata *omd); + +const char * +outputs_name(enum output_types type); + +int +outputs_init(void); + +void +outputs_deinit(void); + +#endif /* !__OUTPUTS_H__ */ diff --git a/src/cast.c b/src/outputs/cast.c similarity index 85% rename from src/cast.c rename to src/outputs/cast.c index d186b910..b345fc81 100644 --- a/src/cast.c +++ b/src/outputs/cast.c @@ -392,6 +392,81 @@ cast_session_make(struct raop_device *rd, int family, raop_status_cb cb) return NULL; } +static void +cast_device_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_device *d; + const char *p; + uint32_t id; + + p = keyval_get(txt, "id"); + if (p) + id = djb_hash(p, strlen(p)); + else + id = 0; + + if (!id) + { + DPRINTF(E_LOG, L_PLAYER, "Could not extract ChromeCast device ID (%s)\n", name); + + return; + } + + DPRINTF(E_DBG, L_PLAYER, "Event for Chromecast device %s (port %d, id %" PRIu32 ")\n", name, port, id); + + d = calloc(1, sizeof(struct raop_device)); + if (!d) + { + DPRINTF(E_LOG, L_PLAYER, "Out of memory for new Chromecast device\n"); + + return; + } + + d->id = id; + d->name = strdup(name); + + if (port < 0) + { + /* Device stopped advertising */ + switch (family) + { + case AF_INET: + d->v4_port = 1; + break; + + case AF_INET6: + d->v6_port = 1; + break; + } + + player_device_remove(d); + + return; + } + + /* Device type */ + d->devtype = RAOP_DEV_CHROMECAST; + + DPRINTF(E_INFO, L_PLAYER, "Adding Chromecast device %s\n", name); + + d->advertised = 1; + + switch (family) + { + case AF_INET: + d->v4_address = strdup(address); + d->v4_port = port; + break; + + case AF_INET6: + d->v6_address = strdup(address); + d->v6_port = port; + break; + } + + player_device_add(d); +} + int cast_device_start(struct raop_device *rd, raop_status_cb cb) { @@ -435,8 +510,19 @@ cast_device_start(struct raop_device *rd, raop_status_cb cb) int cast_init(void) { + int mdns_flags; int ret; + mdns_flags = MDNS_WANT_V4 | MDNS_WANT_V6 | MDNS_WANT_V6LL; + + ret = mdns_browse("_googlecast._tcp", mdns_flags, cast_device_cb); + if (ret < 0) + { + DPRINTF(E_LOG, L_PLAYER, "Could not add mDNS browser for Chromecast devices\n"); + + goto mdns_browse_cast_fail; + } + // TODO Setting the cert file may not be required if ( ((ret = gnutls_global_init()) != GNUTLS_E_SUCCESS) || ((ret = gnutls_certificate_allocate_credentials(&tls_credentials)) != GNUTLS_E_SUCCESS) || diff --git a/src/cast_channel.pb-c.c b/src/outputs/cast_channel.pb-c.c similarity index 100% rename from src/cast_channel.pb-c.c rename to src/outputs/cast_channel.pb-c.c diff --git a/src/cast_channel.pb-c.h b/src/outputs/cast_channel.pb-c.h similarity index 100% rename from src/cast_channel.pb-c.h rename to src/outputs/cast_channel.pb-c.h diff --git a/src/raop.c b/src/outputs/raop.c similarity index 88% rename from src/raop.c rename to src/outputs/raop.c index 53230a4e..2aa35977 100644 --- a/src/raop.c +++ b/src/outputs/raop.c @@ -56,6 +56,7 @@ #include #include +#include #include #include @@ -64,12 +65,13 @@ #include "evrtsp/evrtsp.h" #include "conffile.h" #include "logger.h" +#include "mdns.h" #include "misc.h" #include "player.h" #include "db.h" #include "artwork.h" #include "dmap_common.h" -#include "raop.h" +#include "outputs.h" #ifndef MIN # define MIN(a, b) ((a < b) ? a : b) @@ -88,6 +90,14 @@ /* This is an arbitrary value which just needs to be kept in sync with the config */ #define RAOP_CONFIG_MAX_VOLUME 11 +union sockaddr_all +{ + struct sockaddr_in sin; + struct sockaddr_in6 sin6; + struct sockaddr sa; + struct sockaddr_storage ss; +}; + struct raop_v2_packet { uint8_t clear[AIRTUNES_V2_PKT_LEN]; @@ -99,11 +109,28 @@ struct raop_v2_packet struct raop_v2_packet *next; }; +enum raop_devtype { + RAOP_DEV_APEX1_80211G, + RAOP_DEV_APEX2_80211N, + RAOP_DEV_APEX3_80211N, + RAOP_DEV_APPLETV, + RAOP_DEV_OTHER, +}; + +// Info about the device, which is not required by the player, only internally +struct raop_extra +{ + enum raop_devtype devtype; + + unsigned encrypt:1; + unsigned wants_metadata:1; +}; + struct raop_session { struct evrtsp_connection *ctrl; - enum raop_session_state state; + enum output_device_state state; unsigned req_has_auth:1; unsigned encrypt:1; unsigned auth_quirk_itunes:1; @@ -127,8 +154,9 @@ struct raop_session uint64_t start_rtptime; /* Do not dereference - only passed to the status cb */ - struct raop_device *dev; - raop_status_cb status_cb; + struct output_device *device; + struct output_session *output_session; + output_status_cb status_cb; /* AirTunes v2 */ unsigned short server_port; @@ -202,6 +230,15 @@ static const uint8_t raop_rsa_pubkey[] = static const uint8_t raop_rsa_exp[] = "\x01\x00\x01"; +/* Keep in sync with enum raop_devtype */ +static const char *raop_devtype[] = +{ + "AirPort Express 1 - 802.11g", + "AirPort Express 2 - 802.11n", + "AirPort Express 3 - 802.11n", + "AppleTV", + "Other", +}; /* From player.c */ extern struct event_base *evbase_player; @@ -713,7 +750,7 @@ raop_metadata_free(struct raop_metadata *rmd) free(rmd); } -void +static void raop_metadata_purge(void) { struct raop_metadata *rmd; @@ -728,7 +765,7 @@ raop_metadata_purge(void) metadata_tail = NULL; } -void +static void raop_metadata_prune(uint64_t rtptime) { struct raop_metadata *rmd; @@ -748,7 +785,7 @@ raop_metadata_prune(uint64_t rtptime) } /* Thread: worker */ -struct raop_metadata * +static void * raop_metadata_prepare(int id) { struct query_params qp; @@ -1135,7 +1172,7 @@ raop_add_headers(struct raop_session *rs, struct evrtsp_request *req, enum evrts DPRINTF(E_LOG, L_RAOP, "Could not add Authorization header\n"); if (ret == -2) - rs->state = RAOP_PASSWORD; + rs->state = OUTPUT_STATE_PASSWORD; return -1; } @@ -1652,6 +1689,8 @@ raop_session_free(struct raop_session *rs) if (rs->devname) free(rs->devname); + free(rs->output_session); + free(rs); } @@ -1698,9 +1737,9 @@ static void raop_session_failure(struct raop_session *rs) { /* Session failed, let our user know */ - if (rs->state != RAOP_PASSWORD) - rs->state = RAOP_FAILED; - rs->status_cb(rs->dev, rs, rs->state); + if (rs->state != OUTPUT_STATE_PASSWORD) + rs->state = OUTPUT_STATE_FAILED; + rs->status_cb(rs->device, rs->output_session, rs->state); raop_session_cleanup(rs); } @@ -1727,21 +1766,25 @@ raop_rtsp_close_cb(struct evrtsp_connection *evcon, void *arg) DPRINTF(E_LOG, L_RAOP, "ApEx %s closed RTSP connection\n", rs->devname); - rs->state = RAOP_FAILED; + rs->state = OUTPUT_STATE_FAILED; evutil_timerclear(&tv); evtimer_add(rs->deferredev, &tv); } static struct raop_session * -raop_session_make(struct raop_device *rd, int family, raop_status_cb cb) +raop_session_make(struct output_device *rd, int family, output_status_cb cb) { + struct output_session *os; struct raop_session *rs; + struct raop_extra *re; char *address; char *intf; unsigned short port; int ret; + re = rd->extra_device_info; + switch (family) { case AF_INET: @@ -1765,27 +1808,30 @@ raop_session_make(struct raop_device *rd, int family, raop_status_cb cb) return NULL; } - rs = (struct raop_session *)malloc(sizeof(struct raop_session)); - if (!rs) + os = calloc(1, sizeof(struct output_session)); + rs = calloc(1, sizeof(struct raop_session)); + if (!os || !rs) { DPRINTF(E_LOG, L_RAOP, "Out of memory for RAOP session\n"); return NULL; } - memset(rs, 0, sizeof(struct raop_session)); + os->session = rs; + os->type = rd->type; - rs->state = RAOP_STOPPED; + rs->output_session = os; + rs->state = OUTPUT_STATE_STOPPED; rs->reqs_in_flight = 0; rs->cseq = 1; - rs->dev = rd; + rs->device = rd; rs->status_cb = cb; rs->server_fd = -1; rs->password = rd->password; - rs->wants_metadata = rd->wants_metadata; + rs->wants_metadata = re->wants_metadata; - switch (rd->devtype) + switch (re->devtype) { case RAOP_DEV_APEX1_80211G: rs->encrypt = 1; @@ -1808,7 +1854,7 @@ raop_session_make(struct raop_device *rd, int family, raop_status_cb cb) break; case RAOP_DEV_OTHER: - rs->encrypt = rd->encrypt; + rs->encrypt = re->encrypt; rs->auth_quirk_itunes = 0; break; } @@ -2156,13 +2202,15 @@ raop_metadata_startup_send(struct raop_session *rs) } } -void -raop_metadata_send(struct raop_metadata *rmd, uint64_t rtptime, uint64_t offset, int startup) +static void +raop_metadata_send(void *metadata, uint64_t rtptime, uint64_t offset, int startup) { + struct raop_metadata *rmd; struct raop_session *rs; uint32_t delay; int ret; + rmd = metadata; rmd->start += rtptime; rmd->end += rtptime; @@ -2177,7 +2225,7 @@ raop_metadata_send(struct raop_metadata *rmd, uint64_t rtptime, uint64_t offset, for (rs = sessions; rs; rs = rs->next) { - if (!(rs->state & RAOP_F_CONNECTED)) + if (!(rs->state & OUTPUT_STATE_F_CONNECTED)) continue; if (!rs->wants_metadata) @@ -2269,7 +2317,7 @@ raop_set_volume_internal(struct raop_session *rs, int volume, evrtsp_req_cb cb) static void raop_cb_set_volume(struct evrtsp_request *req, void *arg) { - raop_status_cb status_cb; + output_status_cb status_cb; struct raop_session *rs; int ret; @@ -2294,7 +2342,7 @@ raop_cb_set_volume(struct evrtsp_request *req, void *arg) /* Let our user know */ status_cb = rs->status_cb; rs->status_cb = NULL; - status_cb(rs->dev, rs, rs->state); + status_cb(rs->device, rs->output_session, rs->state); if (!rs->reqs_in_flight) evrtsp_connection_set_closecb(rs->ctrl, raop_rtsp_close_cb, rs); @@ -2306,15 +2354,21 @@ raop_cb_set_volume(struct evrtsp_request *req, void *arg) } /* Volume in [0 - 100] */ -int -raop_set_volume_one(struct raop_session *rs, int volume, raop_status_cb cb) +static int +raop_set_volume_one(struct output_device *rd, output_status_cb cb) { + struct raop_session *rs; int ret; - if (!(rs->state & RAOP_F_CONNECTED)) + if (!rd->session || !rd->session->session) return 0; - ret = raop_set_volume_internal(rs, volume, raop_cb_set_volume); + rs = rd->session->session; + + if (!(rs->state & OUTPUT_STATE_F_CONNECTED)) + return 0; + + ret = raop_set_volume_internal(rs, rd->volume, raop_cb_set_volume); if (ret < 0) { raop_session_failure(rs); @@ -2330,7 +2384,7 @@ raop_set_volume_one(struct raop_session *rs, int volume, raop_status_cb cb) static void raop_cb_flush(struct evrtsp_request *req, void *arg) { - raop_status_cb status_cb; + output_status_cb status_cb; struct raop_session *rs; int ret; @@ -2352,12 +2406,12 @@ raop_cb_flush(struct evrtsp_request *req, void *arg) if (ret < 0) goto error; - rs->state = RAOP_CONNECTED; + rs->state = OUTPUT_STATE_CONNECTED; /* Let our user know */ status_cb = rs->status_cb; rs->status_cb = NULL; - status_cb(rs->dev, rs, rs->state); + status_cb(rs->device, rs->output_session, rs->state); if (!rs->reqs_in_flight) evrtsp_connection_set_closecb(rs->ctrl, raop_rtsp_close_cb, rs); @@ -2368,6 +2422,10 @@ raop_cb_flush(struct evrtsp_request *req, void *arg) raop_session_failure(rs); } +// Forward +static void +raop_device_stop(struct output_session *session); + static void raop_flush_timer_cb(int fd, short what, void *arg) { @@ -2377,15 +2435,15 @@ raop_flush_timer_cb(int fd, short what, void *arg) for (rs = sessions; rs; rs = rs->next) { - if (!(rs->state & RAOP_F_CONNECTED)) + if (!(rs->state & OUTPUT_STATE_F_CONNECTED)) continue; - raop_device_stop(rs); + raop_device_stop(rs->output_session); } } -int -raop_flush(raop_status_cb cb, uint64_t rtptime) +static int +raop_flush(output_status_cb cb, uint64_t rtptime) { struct timeval tv; struct raop_session *rs; @@ -2395,7 +2453,7 @@ raop_flush(raop_status_cb cb, uint64_t rtptime) pending = 0; for (rs = sessions; rs; rs = rs->next) { - if (rs->state != RAOP_STREAMING) + if (rs->state != OUTPUT_STATE_STREAMING) continue; ret = raop_send_req_flush(rs, rtptime, raop_cb_flush); @@ -2767,7 +2825,7 @@ raop_v2_control_send_sync(uint64_t next_pkt, struct timespec *init) for (rs = sessions; rs; rs = rs->next) { - if (rs->state != RAOP_STREAMING) + if (rs->state != OUTPUT_STATE_STREAMING) continue; switch (rs->sa.ss.ss_family) @@ -3198,7 +3256,11 @@ raop_v2_send_packet(struct raop_session *rs, struct raop_v2_packet *pkt) return 0; } -void +// Forward +static void +raop_playback_stop(void); + +static void raop_v2_write(uint8_t *buf, uint64_t rtptime) { struct raop_v2_packet *pkt; @@ -3223,7 +3285,7 @@ raop_v2_write(uint8_t *buf, uint64_t rtptime) for (rs = sessions; rs; rs = rs->next) { - if (rs->state != RAOP_STREAMING) + if (rs->state != OUTPUT_STATE_STREAMING) continue; raop_v2_send_packet(rs, pkt); @@ -3341,9 +3403,9 @@ raop_v2_stream_open(struct raop_session *rs) * playback is in progress. */ if (sync_counter != 0) - rs->state = RAOP_STREAMING; + rs->state = OUTPUT_STATE_STREAMING; else - rs->state = RAOP_CONNECTED; + rs->state = OUTPUT_STATE_CONNECTED; return 0; @@ -3369,7 +3431,7 @@ raop_startup_cancel(struct raop_session *rs) static void raop_cb_startup_volume(struct evrtsp_request *req, void *arg) { - raop_status_cb status_cb; + output_status_cb status_cb; struct raop_session *rs; int ret; @@ -3405,7 +3467,7 @@ raop_cb_startup_volume(struct evrtsp_request *req, void *arg) status_cb = rs->status_cb; rs->status_cb = NULL; - status_cb(rs->dev, rs, rs->state); + status_cb(rs->device, rs->output_session, rs->state); if (!rs->reqs_in_flight) evrtsp_connection_set_closecb(rs->ctrl, raop_rtsp_close_cb, rs); @@ -3448,7 +3510,7 @@ raop_cb_startup_record(struct evrtsp_request *req, void *arg) else DPRINTF(E_DBG, L_RAOP, "RAOP audio latency is %s\n", param); - rs->state = RAOP_RECORD; + rs->state = OUTPUT_STATE_RECORD; /* Set initial volume */ raop_set_volume_internal(rs, rs->volume, raop_cb_startup_volume); @@ -3596,7 +3658,7 @@ raop_cb_startup_setup(struct evrtsp_request *req, void *arg) DPRINTF(E_DBG, L_RAOP, "Negotiated AirTunes v2 UDP streaming session %s; ports s=%u c=%u t=%u\n", rs->session, rs->server_port, rs->control_port, rs->timing_port); - rs->state = RAOP_SETUP; + rs->state = OUTPUT_STATE_SETUP; /* Send RECORD */ ret = raop_send_req_record(rs, raop_cb_startup_record); @@ -3633,7 +3695,7 @@ raop_cb_startup_announce(struct evrtsp_request *req, void *arg) if (ret < 0) goto cleanup; - rs->state = RAOP_ANNOUNCE; + rs->state = OUTPUT_STATE_ANNOUNCE; /* Send SETUP */ ret = raop_send_req_setup(rs, raop_cb_startup_setup); @@ -3676,7 +3738,7 @@ raop_cb_startup_options(struct evrtsp_request *req, void *arg) { DPRINTF(E_LOG, L_RAOP, "Bad password for device %s\n", rs->devname); - rs->state = RAOP_PASSWORD; + rs->state = OUTPUT_STATE_PASSWORD; goto cleanup; } @@ -3695,7 +3757,7 @@ raop_cb_startup_options(struct evrtsp_request *req, void *arg) return; } - rs->state = RAOP_OPTIONS; + rs->state = OUTPUT_STATE_OPTIONS; /* Send ANNOUNCE */ ret = raop_send_req_announce(rs, raop_cb_startup_announce); @@ -3712,7 +3774,7 @@ raop_cb_startup_options(struct evrtsp_request *req, void *arg) static void raop_cb_shutdown_teardown(struct evrtsp_request *req, void *arg) { - raop_status_cb status_cb; + output_status_cb status_cb; struct raop_session *rs; int ret; @@ -3734,13 +3796,13 @@ raop_cb_shutdown_teardown(struct evrtsp_request *req, void *arg) if (ret < 0) goto error; - rs->state = RAOP_STOPPED; + rs->state = OUTPUT_STATE_STOPPED; /* Session shut down, tell our user */ status_cb = rs->status_cb; rs->status_cb = NULL; - status_cb(rs->dev, rs, rs->state); + status_cb(rs->device, rs->output_session, rs->state); raop_session_cleanup(rs); @@ -3753,7 +3815,7 @@ raop_cb_shutdown_teardown(struct evrtsp_request *req, void *arg) static void raop_cb_probe_options(struct evrtsp_request *req, void *arg) { - raop_status_cb status_cb; + output_status_cb status_cb; struct raop_session *rs; int ret; @@ -3781,7 +3843,7 @@ raop_cb_probe_options(struct evrtsp_request *req, void *arg) { DPRINTF(E_LOG, L_RAOP, "Bad password for device %s\n", rs->devname); - rs->state = RAOP_PASSWORD; + rs->state = OUTPUT_STATE_PASSWORD; goto cleanup; } @@ -3800,13 +3862,13 @@ raop_cb_probe_options(struct evrtsp_request *req, void *arg) return; } - rs->state = RAOP_OPTIONS; + rs->state = OUTPUT_STATE_OPTIONS; /* Device probed successfully, tell our user */ status_cb = rs->status_cb; rs->status_cb = NULL; - status_cb(rs->dev, rs, rs->state); + status_cb(rs->device, rs->output_session, rs->state); /* We're not going further with this session */ raop_session_cleanup(rs); @@ -3818,8 +3880,213 @@ raop_cb_probe_options(struct evrtsp_request *req, void *arg) } -int -raop_device_probe(struct raop_device *rd, raop_status_cb cb) +/* RAOP devices discovery - mDNS callback */ +/* Thread: main (mdns) */ +/* Examples of txt content: + * 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"] + * Apple TV 3: + ["vv=2" "vs=200.54" "vn=65537" "tp=UDP" "sf=0x44" "pk=8...f" "am=AppleTV3,1" "md=0,1,2" "ft=0x5A7FFFF7,0xE" "et=0,3,5" "da=true" "cn=0,1,2,3"] + * Sony STR-DN1040: + ["fv=s9327.1090.0" "am=STR-DN1040" "vs=141.9" "vn=65537" "tp=UDP" "ss=16" "sr=44100" "sv=false" "pw=false" "md=0,2" "ft=0x44F0A00" "et=0,4" "da=true" "cn=0,1" "ch=2" "txtvers=1"] + * AirFoil: + ["rastx=iafs" "sm=false" "raver=3.5.3.0" "ek=1" "md=0,1,2" "ramach=Win32NT.6" "et=0,1" "cn=0,1" "sr=44100" "ss=16" "raAudioFormats=ALAC" "raflakyzeroconf=true" "pw=false" "rast=afs" "vn=3" "sv=false" "txtvers=1" "ch=2" "tp=UDP"] + * Xbmc 13: + ["am=Xbmc,1" "md=0,1,2" "vs=130.14" "da=true" "vn=3" "pw=false" "sr=44100" "ss=16" "sm=false" "tp=UDP" "sv=false" "et=0,1" "ek=1" "ch=2" "cn=0,1" "txtvers=1"] + * Shairport (abrasive/1.0): + ["pw=false" "txtvers=1" "vn=3" "sr=44100" "ss=16" "ch=2" "cn=0,1" "et=0,1" "ek=1" "sm=false" "tp=UDP"] + * JB2: + ["fv=95.8947" "am=JB2 Gen" "vs=103.2" "tp=UDP" "vn=65537" "pw=false" "s s=16" "sr=44100" "da=true" "sv=false" "et=0,4" "cn=0,1" "ch=2" "txtvers=1"] + * Airport Express 802.11g (Gen 1): + ["tp=TCP,UDP" "sm=false" "sv=false" "ek=1" "et=0,1" "cn=0,1" "ch=2" "ss=16" "sr=44100" "pw=false" "vn=3" "txtvers=1"] + * Airport Express 802.11n: + 802.11n Gen 2 model (firmware 7.6.4): "am=Airport4,107", "et=0,1" + 802.11n Gen 3 model (firmware 7.6.4): "am=Airport10,115", "et=0,4" + */ +static void +raop_device_cb(const char *name, const char *type, const char *domain, const char *hostname, int family, const char *address, int port, struct keyval *txt) +{ + struct output_device *rd; + struct raop_extra *re; + cfg_t *airplay; + const char *p; + char *at_name; + char *password; + uint64_t id; + int ret; + + ret = safe_hextou64(name, &id); + if (ret < 0) + { + DPRINTF(E_LOG, L_RAOP, "Could not extract AirPlay device ID (%s)\n", name); + + return; + } + + at_name = strchr(name, '@'); + if (!at_name) + { + DPRINTF(E_LOG, L_RAOP, "Could not extract AirPlay device name (%s)\n", name); + + return; + } + at_name++; + + DPRINTF(E_DBG, L_RAOP, "Event for AirPlay device %s (port %d, id %" PRIx64 ")\n", at_name, port, id); + + rd = calloc(1, sizeof(struct output_device)); + re = calloc(1, sizeof(struct raop_extra)); + if (!rd || !re) + { + DPRINTF(E_LOG, L_RAOP, "Out of memory for new AirPlay device\n"); + + return; + } + + rd->id = id; + rd->name = strdup(at_name); + rd->type = OUTPUT_TYPE_RAOP; + rd->type_name = outputs_name(rd->type); + rd->extra_device_info = re; + + if (port < 0) + { + /* Device stopped advertising */ + switch (family) + { + case AF_INET: + rd->v4_port = 1; + break; + + case AF_INET6: + rd->v6_port = 1; + break; + } + + ret = player_device_remove(rd); + if (ret < 0) + goto free_rd; + + return; + } + + /* Protocol */ + p = keyval_get(txt, "tp"); + if (!p) + { + DPRINTF(E_LOG, L_RAOP, "AirPlay %s: no tp field in TXT record!\n", name); + + goto free_rd; + } + + if (*p == '\0') + { + DPRINTF(E_LOG, L_RAOP, "AirPlay %s: tp has no value\n", 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", name, p); + + goto free_rd; + } + + /* Password protection */ + password = NULL; + p = keyval_get(txt, "pw"); + if (!p) + { + DPRINTF(E_INFO, L_RAOP, "AirPlay %s: no pw field in TXT record, assuming no password protection\n", name); + + rd->has_password = 0; + } + else if (*p == '\0') + { + DPRINTF(E_LOG, L_RAOP, "AirPlay %s: pw has no value\n", name); + + goto free_rd; + } + else + { + rd->has_password = (strcmp(p, "false") != 0); + } + + if (rd->has_password) + { + DPRINTF(E_LOG, L_RAOP, "AirPlay device %s is password-protected\n", name); + + airplay = cfg_gettsec(cfg, "airplay", at_name); + if (airplay) + password = cfg_getstr(airplay, "password"); + + if (!password) + DPRINTF(E_LOG, L_RAOP, "No password given in config for AirPlay device %s\n", name); + } + + rd->password = password; + + /* Device type */ + re->devtype = RAOP_DEV_OTHER; + p = keyval_get(txt, "am"); + + if (!p) + re->devtype = RAOP_DEV_APEX1_80211G; // First generation AirPort Express + else if (strncmp(p, "AirPort4", strlen("AirPort4")) == 0) + re->devtype = RAOP_DEV_APEX2_80211N; // Second generation + else if (strncmp(p, "AirPort", strlen("AirPort")) == 0) + re->devtype = RAOP_DEV_APEX3_80211N; // Third generation and newer + else if (strncmp(p, "AppleTV", strlen("AppleTV")) == 0) + re->devtype = RAOP_DEV_APPLETV; + else if (*p == '\0') + DPRINTF(E_LOG, L_RAOP, "AirPlay %s: am has no value\n", name); + + /* Encrypt stream */ + p = keyval_get(txt, "ek"); + if (p && (*p == '1')) + re->encrypt = 1; + else + re->encrypt = 0; + + /* Metadata support */ + p = keyval_get(txt, "md"); + if (p && (*p != '\0')) + re->wants_metadata = 1; + else + re->wants_metadata = 0; + + DPRINTF(E_INFO, L_RAOP, "Adding AirPlay device %s: password: %u, encrypt: %u, metadata: %u, type %s\n", + name, rd->has_password, re->encrypt, re->wants_metadata, raop_devtype[re->devtype]); + + rd->advertised = 1; + + switch (family) + { + case AF_INET: + rd->v4_address = strdup(address); + rd->v4_port = port; + break; + + case AF_INET6: + rd->v6_address = strdup(address); + rd->v6_port = port; + break; + } + + ret = player_device_add(rd); + if (ret < 0) + goto free_rd; + + return; + + free_rd: + outputs_device_free(rd); +} + +static int +raop_device_probe(struct output_device *rd, output_status_cb cb) { struct raop_session *rs; int ret; @@ -3858,8 +4125,8 @@ raop_device_probe(struct raop_device *rd, raop_status_cb cb) return 0; } -int -raop_device_start(struct raop_device *rd, raop_status_cb cb, uint64_t rtptime) +static int +raop_device_start(struct output_device *rd, output_status_cb cb, uint64_t rtptime) { struct raop_session *rs; int ret; @@ -3903,17 +4170,25 @@ raop_device_start(struct raop_device *rd, raop_status_cb cb, uint64_t rtptime) return 0; } -void -raop_device_stop(struct raop_session *rs) +static void +raop_device_stop(struct output_session *session) { - if (!(rs->state & RAOP_F_CONNECTED)) + struct raop_session *rs = session->session; + + if (!(rs->state & OUTPUT_STATE_F_CONNECTED)) raop_session_cleanup(rs); else raop_send_req_teardown(rs, raop_cb_shutdown_teardown); } -void +static void +raop_device_free_extra(struct output_device *device) +{ + free(device->extra_device_info); +} + +static void raop_playback_start(uint64_t next_pkt, struct timespec *ts) { struct raop_session *rs; @@ -3924,15 +4199,15 @@ raop_playback_start(uint64_t next_pkt, struct timespec *ts) for (rs = sessions; rs; rs = rs->next) { - if (rs->state == RAOP_CONNECTED) - rs->state = RAOP_STREAMING; + if (rs->state == OUTPUT_STATE_CONNECTED) + rs->state = OUTPUT_STATE_STREAMING; } /* Send initial playback sync */ raop_v2_control_send_sync(next_pkt, ts); } -void +static void raop_playback_stop(void) { struct raop_session *rs; @@ -3946,20 +4221,23 @@ raop_playback_stop(void) } } -void -raop_set_status_cb(struct raop_session *rs, raop_status_cb cb) +static void +raop_set_status_cb(struct output_session *session, output_status_cb cb) { + struct raop_session *rs = session->session; + rs->status_cb = cb; } - -int -raop_init(int *v6enabled) +static int +raop_init(void) { char ebuf[64]; char *ptr; char *libname; gpg_error_t gc_err; + int v6enabled; + int mdns_flags; int ret; timing_4svc.fd = -1; @@ -4048,7 +4326,9 @@ raop_init(int *v6enabled) goto out_free_b64_iv; } - ret = raop_v2_timing_start(*v6enabled); + v6enabled = cfg_getbool(cfg_getsec(cfg, "general"), "ipv6"); + + ret = raop_v2_timing_start(v6enabled); if (ret < 0) { DPRINTF(E_LOG, L_RAOP, "AirTunes v2 time synchronization failed to start\n"); @@ -4056,7 +4336,7 @@ raop_init(int *v6enabled) goto out_free_flush_timer; } - ret = raop_v2_control_start(*v6enabled); + ret = raop_v2_control_start(v6enabled); if (ret < 0) { DPRINTF(E_LOG, L_RAOP, "AirTunes v2 playback synchronization failed to start\n"); @@ -4064,11 +4344,27 @@ raop_init(int *v6enabled) goto out_stop_timing; } - if (*v6enabled) - *v6enabled = !((timing_6svc.fd < 0) || (control_6svc.fd < 0)); + if (v6enabled) + v6enabled = !((timing_6svc.fd < 0) || (control_6svc.fd < 0)); + + if (v6enabled) + mdns_flags = MDNS_WANT_V4 | MDNS_WANT_V6 | MDNS_WANT_V6LL; + else + mdns_flags = MDNS_WANT_V4; + + ret = mdns_browse("_raop._tcp", mdns_flags, raop_device_cb); + if (ret < 0) + { + DPRINTF(E_LOG, L_RAOP, "Could not add mDNS browser for AirPlay devices\n"); + + goto out_stop_control; + } + return 0; + out_stop_control: + raop_v2_control_stop(); out_stop_timing: raop_v2_timing_stop(); out_free_flush_timer: @@ -4083,7 +4379,7 @@ raop_init(int *v6enabled) return -1; } -void +static void raop_deinit(void) { struct raop_session *rs; @@ -4105,3 +4401,27 @@ raop_deinit(void) free(raop_aes_key_b64); free(raop_aes_iv_b64); } + +struct output_definition output_raop = +{ + .name = "AirPlay", + .type = OUTPUT_TYPE_RAOP, + .priority = 1, + .disabled = 0, + .init = raop_init, + .deinit = raop_deinit, + .device_start = raop_device_start, + .device_stop = raop_device_stop, + .device_probe = raop_device_probe, + .device_free_extra = raop_device_free_extra, + .device_volume_set = raop_set_volume_one, + .playback_start = raop_playback_start, + .playback_stop = raop_playback_stop, + .write = raop_v2_write, + .flush = raop_flush, + .status_cb = raop_set_status_cb, + .metadata_prepare = raop_metadata_prepare, + .metadata_send = raop_metadata_send, + .metadata_purge = raop_metadata_purge, + .metadata_prune = raop_metadata_prune, +}; diff --git a/src/player.c b/src/player.c index 4fc152ea..f90d6763 100644 --- a/src/player.c +++ b/src/player.c @@ -1,5 +1,6 @@ /* * Copyright (C) 2010-2011 Julien BLACHE + * Copyright (C) 2016 Espen Jürgensen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -44,7 +45,6 @@ #include "db.h" #include "logger.h" -#include "mdns.h" #include "conffile.h" #include "misc.h" #include "player.h" @@ -52,11 +52,8 @@ #include "listener.h" /* Audio outputs */ -#include "raop.h" +#include "outputs.h" #include "laudio.h" -#ifdef CHROMECAST -# include "cast.h" -#endif /* Audio inputs */ #include "transcode.h" @@ -196,7 +193,7 @@ struct player_metadata uint64_t offset; int startup; - struct raop_metadata *rmd; + struct output_metadata *omd; }; struct player_command @@ -213,12 +210,12 @@ struct player_command struct volume_param vol_param; void *noarg; struct spk_enum *spk_enum; - struct raop_device *rd; + struct output_device *device; struct player_status *status; struct player_source *ps; struct player_metadata *pmd; uint32_t *id_ptr; - uint64_t *raop_ids; + uint64_t *device_ids; enum repeat_mode mode; uint32_t id; int intval; @@ -235,18 +232,6 @@ struct player_command int raop_pending; }; -/* Keep in sync with enum raop_devtype */ -static const char *raop_devtype[] = - { - "AirPort Express 1 - 802.11g", - "AirPort Express 2 - 802.11n", - "AirPort Express 3 - 802.11n", - "AppleTV", - "Chromecast", - "Other", - }; - - struct event_base *evbase_player; static int exit_pipe[2]; @@ -290,16 +275,16 @@ static uint64_t pb_pos; /* Stream position (packets) */ static uint64_t last_rtptime; -/* AirPlay devices */ +/* Output devices */ static int dev_autoselect; //TODO [player] Is this still necessary? -static struct raop_device *dev_list; +static struct output_device *dev_list; /* Output status */ static enum laudio_state laudio_status; static int laudio_selected; static int laudio_volume; static int laudio_relvol; -static int raop_sessions; +static int output_sessions; static int streaming_selected; static player_streaming_cb streaming_write; @@ -396,24 +381,24 @@ vol_to_rel(int volume) static void volume_master_update(int newvol) { - struct raop_device *rd; + struct output_device *device; master_volume = newvol; if (laudio_selected) laudio_relvol = vol_to_rel(laudio_volume); - for (rd = dev_list; rd; rd = rd->next) + for (device = dev_list; device; device = device->next) { - if (rd->selected) - rd->relvol = vol_to_rel(rd->volume); + if (device->selected) + device->relvol = vol_to_rel(device->volume); } } static void volume_master_find(void) { - struct raop_device *rd; + struct output_device *device; int newmaster; newmaster = -1; @@ -421,10 +406,10 @@ volume_master_find(void) if (laudio_selected) newmaster = laudio_volume; - for (rd = dev_list; rd; rd = rd->next) + for (device = dev_list; device; device = device->next) { - if (rd->selected && (rd->volume > newmaster)) - newmaster = rd->volume; + if (device->selected && (device->volume > newmaster)) + newmaster = device->volume; } volume_master_update(newmaster); @@ -449,19 +434,19 @@ speaker_select_laudio(void) } static void -speaker_select_raop(struct raop_device *rd) +speaker_select_output(struct output_device *device) { - rd->selected = 1; + device->selected = 1; - if (rd->volume > master_volume) + if (device->volume > master_volume) { if (player_state == PLAY_STOPPED || master_volume == -1) - volume_master_update(rd->volume); + volume_master_update(device->volume); else - rd->volume = master_volume; + device->volume = master_volume; } - rd->relvol = vol_to_rel(rd->volume); + device->relvol = vol_to_rel(device->volume); } static void @@ -474,11 +459,11 @@ speaker_deselect_laudio(void) } static void -speaker_deselect_raop(struct raop_device *rd) +speaker_deselect_output(struct output_device *device) { - rd->selected = 0; + device->selected = 0; - if (rd->volume == master_volume) + if (device->volume == master_volume) volume_master_find(); } @@ -664,7 +649,7 @@ player_laudio_status_cb(enum laudio_state status) laudio_close(); - if (raop_sessions == 0) + if (output_sessions == 0) playback_abort(); speaker_deselect_laudio(); @@ -698,18 +683,20 @@ scrobble_cb(void *arg) /* Callback from the worker thread * This prepares metadata in the worker thread, since especially the artwork - * retrieval may take some time. raop_metadata_prepare() is thread safe. The - * sending, however, must be done in the player thread. + * retrieval may take some time. outputs_metadata_prepare() must be thread safe. + * The sending must be done in the player thread. */ static void metadata_prepare_cb(void *arg) { struct player_metadata *pmd = arg; - pmd->rmd = raop_metadata_prepare(pmd->id); + pmd->omd = outputs_metadata_prepare(pmd->id); - if (pmd->rmd) + if (pmd->omd) player_metadata_send(pmd); + + outputs_metadata_free(pmd->omd); } /* Callback from the worker thread (async operation as it may block) */ @@ -727,13 +714,13 @@ update_icy_cb(void *arg) static void metadata_prune(uint64_t pos) { - raop_metadata_prune(pos); + outputs_metadata_prune(pos); } static void metadata_purge(void) { - raop_metadata_purge(); + outputs_metadata_purge(); } static void @@ -782,7 +769,7 @@ metadata_check_icy(void) /* Defer the database update to the worker thread */ worker_execute(update_icy_cb, metadata, sizeof(struct http_icy_metadata), 0); - /* Triggers preparing and sending RAOP metadata */ + /* Triggers preparing and sending output metadata */ metadata_trigger(0); /* Only free the struct, the content must be preserved for update_icy_cb */ @@ -1612,8 +1599,8 @@ playback_write(void) if (laudio_status & LAUDIO_F_STARTED) laudio_write(rawbuf, last_rtptime); - if (raop_sessions > 0) - raop_v2_write(rawbuf, last_rtptime); + if (output_sessions > 0) + outputs_write(rawbuf, last_rtptime); } static void @@ -1692,146 +1679,131 @@ player_playback_cb(int fd, short what, void *arg) pb_timer_last = next_tick; } -static void -device_free(struct raop_device *dev) -{ - free(dev->name); - - if (dev->v4_address) - free(dev->v4_address); - - if (dev->v6_address) - free(dev->v6_address); - - free(dev); -} - /* Helpers */ static void -device_remove(struct raop_device *dev) +device_remove(struct output_device *remove) { - struct raop_device *rd; - struct raop_device *prev; + struct output_device *device; + struct output_device *prev; int ret; prev = NULL; - for (rd = dev_list; rd; rd = rd->next) + for (device = dev_list; device; device = device->next) { - if (rd == dev) + if (device == remove) break; - prev = rd; + prev = device; } - if (!rd) + if (!device) return; - DPRINTF(E_DBG, L_PLAYER, "Removing AirPlay device %s; stopped advertising\n", dev->name); + DPRINTF(E_DBG, L_PLAYER, "Removing %s device '%s'; stopped advertising\n", remove->type_name, remove->name); /* Make sure device isn't selected anymore */ - if (dev->selected) - speaker_deselect_raop(dev); + if (device->selected) + speaker_deselect_output(remove); /* Save device volume */ - ret = db_speaker_save(dev->id, 0, dev->volume); + ret = db_speaker_save(remove->id, 0, remove->volume); if (ret < 0) - DPRINTF(E_LOG, L_PLAYER, "Could not save state for speaker %s\n", dev->name); + DPRINTF(E_LOG, L_PLAYER, "Could not save state for %s device '%s'\n", remove->type_name, remove->name); if (!prev) - dev_list = dev->next; + dev_list = remove->next; else - prev->next = dev->next; + prev->next = remove->next; - device_free(dev); + outputs_device_free(remove); } static int -device_check(struct raop_device *dev) +device_check(struct output_device *check) { - struct raop_device *rd; + struct output_device *device; - for (rd = dev_list; rd; rd = rd->next) + for (device = dev_list; device; device = device->next) { - if (rd == dev) + if (device == check) break; } - return (rd) ? 0 : -1; + return (device) ? 0 : -1; } static int device_add(struct player_command *cmd) { - struct raop_device *dev; - struct raop_device *rd; + struct output_device *add; + struct output_device *device; int selected; int ret; - dev = cmd->arg.rd; + add = cmd->arg.device; - for (rd = dev_list; rd; rd = rd->next) + for (device = dev_list; device; device = device->next) { - if (rd->id == dev->id) + if (device->id == add->id) break; } /* New device */ - if (!rd) + if (!device) { - rd = dev; + device = add; - ret = db_speaker_get(rd->id, &selected, &rd->volume); + ret = db_speaker_get(device->id, &selected, &device->volume); if (ret < 0) { selected = 0; - rd->volume = (master_volume >= 0) ? master_volume : PLAYER_DEFAULT_VOLUME; + device->volume = (master_volume >= 0) ? master_volume : PLAYER_DEFAULT_VOLUME; } if (dev_autoselect && selected) - speaker_select_raop(rd); + speaker_select_output(device); - rd->next = dev_list; - dev_list = rd; + device->next = dev_list; + dev_list = device; } + // Update to a device already in the list else { - rd->advertised = 1; + device->advertised = 1; - if (dev->v4_address) + if (add->v4_address) { - if (rd->v4_address) - free(rd->v4_address); + if (device->v4_address) + free(device->v4_address); - rd->v4_address = dev->v4_address; - rd->v4_port = dev->v4_port; + device->v4_address = add->v4_address; + device->v4_port = add->v4_port; /* Address is ours now */ - dev->v4_address = NULL; + add->v4_address = NULL; } - if (dev->v6_address) + if (add->v6_address) { - if (rd->v6_address) - free(rd->v6_address); + if (device->v6_address) + free(device->v6_address); - rd->v6_address = dev->v6_address; - rd->v6_port = dev->v6_port; + device->v6_address = add->v6_address; + device->v6_port = add->v6_port; /* Address is ours now */ - dev->v6_address = NULL; + add->v6_address = NULL; } - if (rd->name) - free(rd->name); - rd->name = dev->name; - dev->name = NULL; + if (device->name) + free(device->name); + device->name = add->name; + add->name = NULL; - rd->devtype = dev->devtype; + device->has_password = add->has_password; + device->password = add->password; - rd->has_password = dev->has_password; - rd->password = dev->password; - - device_free(dev); + outputs_device_free(add); } return 0; @@ -1840,49 +1812,49 @@ device_add(struct player_command *cmd) static int device_remove_family(struct player_command *cmd) { - struct raop_device *dev; - struct raop_device *rd; + struct output_device *remove; + struct output_device *device; - dev = cmd->arg.rd; + remove = cmd->arg.device; - for (rd = dev_list; rd; rd = rd->next) + for (device = dev_list; device; device = device->next) { - if (rd->id == dev->id) + if (device->id == remove->id) break; } - if (!rd) + if (!device) { - DPRINTF(E_WARN, L_PLAYER, "AirPlay device %s stopped advertising, but not in our list\n", dev->name); + DPRINTF(E_WARN, L_PLAYER, "The %s device '%s' stopped advertising, but not in our list\n", remove->type_name, remove->name); - device_free(dev); + outputs_device_free(remove); return 0; } /* v{4,6}_port non-zero indicates the address family stopped advertising */ - if (dev->v4_port && rd->v4_address) + if (remove->v4_port && device->v4_address) { - free(rd->v4_address); - rd->v4_address = NULL; - rd->v4_port = 0; + free(device->v4_address); + device->v4_address = NULL; + device->v4_port = 0; } - if (dev->v6_port && rd->v6_address) + if (remove->v6_port && device->v6_address) { - free(rd->v6_address); - rd->v6_address = NULL; - rd->v6_port = 0; + free(device->v6_address); + device->v6_address = NULL; + device->v6_port = 0; } - if (!rd->v4_address && !rd->v6_address) + if (!device->v4_address && !device->v6_address) { - rd->advertised = 0; + device->advertised = 0; - if (!rd->session) - device_remove(rd); + if (!device->session) + device_remove(device); } - device_free(dev); + outputs_device_free(remove); return 0; } @@ -1900,69 +1872,69 @@ metadata_send(struct player_command *cmd) if ((pmd->rtptime == 0) && (pmd->startup)) pmd->rtptime = last_rtptime + AIRTUNES_V2_PACKET_SAMPLES; - raop_metadata_send(pmd->rmd, pmd->rtptime, pmd->offset, pmd->startup); + outputs_metadata_send(pmd->omd, pmd->rtptime, pmd->offset, pmd->startup); return 0; } -/* RAOP callbacks executed in the player thread */ +/* Output device callbacks executed in the player thread */ static void -device_streaming_cb(struct raop_device *dev, struct raop_session *rs, enum raop_session_state status) +device_streaming_cb(struct output_device *device, struct output_session *session, enum output_device_state status) { int ret; - if (status == RAOP_FAILED) + if (status == OUTPUT_STATE_FAILED) { - raop_sessions--; + output_sessions--; - ret = device_check(dev); + ret = device_check(device); if (ret < 0) { - DPRINTF(E_WARN, L_PLAYER, "AirPlay device disappeared during streaming!\n"); + DPRINTF(E_WARN, L_PLAYER, "Output device disappeared during streaming!\n"); return; } - DPRINTF(E_LOG, L_PLAYER, "AirPlay device %s FAILED\n", dev->name); + DPRINTF(E_LOG, L_PLAYER, "The %s device '%s' FAILED\n", device->type_name, device->name); if (player_state == PLAY_PLAYING) - speaker_deselect_raop(dev); + speaker_deselect_output(device); - dev->session = NULL; + device->session = NULL; - if (!dev->advertised) - device_remove(dev); + if (!device->advertised) + device_remove(device); } - else if (status == RAOP_STOPPED) + else if (status == OUTPUT_STATE_STOPPED) { - raop_sessions--; + output_sessions--; - ret = device_check(dev); + ret = device_check(device); if (ret < 0) { - DPRINTF(E_WARN, L_PLAYER, "AirPlay device disappeared during streaming!\n"); + DPRINTF(E_WARN, L_PLAYER, "Output device disappeared during streaming!\n"); return; } - DPRINTF(E_INFO, L_PLAYER, "AirPlay device %s stopped\n", dev->name); + DPRINTF(E_INFO, L_PLAYER, "The %s device '%s' stopped\n", device->type_name, device->name); - dev->session = NULL; + device->session = NULL; - if (!dev->advertised) - device_remove(dev); + if (!device->advertised) + device_remove(device); } } static void -device_command_cb(struct raop_device *dev, struct raop_session *rs, enum raop_session_state status) +device_command_cb(struct output_device *device, struct output_session *session, enum output_device_state status) { cur_cmd->raop_pending--; - raop_set_status_cb(rs, device_streaming_cb); + outputs_status_cb(session, device_streaming_cb); - if (status == RAOP_FAILED) - device_streaming_cb(dev, rs, status); + if (status == OUTPUT_STATE_FAILED) + device_streaming_cb(device, session, status); if (cur_cmd->raop_pending == 0) { @@ -1976,29 +1948,29 @@ device_command_cb(struct raop_device *dev, struct raop_session *rs, enum raop_se } static void -device_shutdown_cb(struct raop_device *dev, struct raop_session *rs, enum raop_session_state status) +device_shutdown_cb(struct output_device *device, struct output_session *session, enum output_device_state status) { int ret; cur_cmd->raop_pending--; - if (raop_sessions) - raop_sessions--; + if (output_sessions) + output_sessions--; - ret = device_check(dev); + ret = device_check(device); if (ret < 0) { - DPRINTF(E_WARN, L_PLAYER, "AirPlay device disappeared before shutdown completion!\n"); + DPRINTF(E_WARN, L_PLAYER, "Output device disappeared before shutdown completion!\n"); if (cur_cmd->ret != -2) cur_cmd->ret = -1; goto out; } - dev->session = NULL; + device->session = NULL; - if (!dev->advertised) - device_remove(dev); + if (!device->advertised) + device_remove(device); out: if (cur_cmd->raop_pending == 0) @@ -2012,59 +1984,59 @@ device_shutdown_cb(struct raop_device *dev, struct raop_session *rs, enum raop_s } static void -device_lost_cb(struct raop_device *dev, struct raop_session *rs, enum raop_session_state status) +device_lost_cb(struct output_device *device, struct output_session *session, enum output_device_state status) { /* We lost that device during startup for some reason, not much we can do here */ - if (status == RAOP_FAILED) + if (status == OUTPUT_STATE_FAILED) DPRINTF(E_WARN, L_PLAYER, "Failed to stop lost device\n"); else DPRINTF(E_INFO, L_PLAYER, "Lost device stopped properly\n"); } static void -device_activate_cb(struct raop_device *dev, struct raop_session *rs, enum raop_session_state status) +device_activate_cb(struct output_device *device, struct output_session *session, enum output_device_state status) { struct timespec ts; int ret; cur_cmd->raop_pending--; - ret = device_check(dev); + ret = device_check(device); if (ret < 0) { - DPRINTF(E_WARN, L_PLAYER, "AirPlay device disappeared during startup!\n"); + DPRINTF(E_WARN, L_PLAYER, "Output device disappeared during startup!\n"); - raop_set_status_cb(rs, device_lost_cb); - raop_device_stop(rs); + outputs_status_cb(session, device_lost_cb); + outputs_device_stop(session); if (cur_cmd->ret != -2) cur_cmd->ret = -1; goto out; } - if (status == RAOP_PASSWORD) + if (status == OUTPUT_STATE_PASSWORD) { - status = RAOP_FAILED; + status = OUTPUT_STATE_FAILED; cur_cmd->ret = -2; } - if (status == RAOP_FAILED) + if (status == OUTPUT_STATE_FAILED) { - speaker_deselect_raop(dev); + speaker_deselect_output(device); - if (!dev->advertised) - device_remove(dev); + if (!device->advertised) + device_remove(device); if (cur_cmd->ret != -2) cur_cmd->ret = -1; goto out; } - dev->session = rs; + device->session = session; - raop_sessions++; + output_sessions++; - if ((player_state == PLAY_PLAYING) && (raop_sessions == 1)) + if ((player_state == PLAY_PLAYING) && (output_sessions == 1)) { ret = clock_gettime_with_res(CLOCK_MONOTONIC, &ts, &timer_res); if (ret < 0) @@ -2076,10 +2048,10 @@ device_activate_cb(struct raop_device *dev, struct raop_session *rs, enum raop_s ts.tv_nsec = pb_timer_last.tv_nsec; } - raop_playback_start(last_rtptime + AIRTUNES_V2_PACKET_SAMPLES, &ts); + outputs_playback_start(last_rtptime + AIRTUNES_V2_PACKET_SAMPLES, &ts); } - raop_set_status_cb(rs, device_streaming_cb); + outputs_status_cb(session, device_streaming_cb); out: if (cur_cmd->raop_pending == 0) @@ -2094,34 +2066,34 @@ device_activate_cb(struct raop_device *dev, struct raop_session *rs, enum raop_s } static void -device_probe_cb(struct raop_device *dev, struct raop_session *rs, enum raop_session_state status) +device_probe_cb(struct output_device *device, struct output_session *session, enum output_device_state status) { int ret; cur_cmd->raop_pending--; - ret = device_check(dev); + ret = device_check(device); if (ret < 0) { - DPRINTF(E_WARN, L_PLAYER, "AirPlay device disappeared during probe!\n"); + DPRINTF(E_WARN, L_PLAYER, "Output device disappeared during probe!\n"); if (cur_cmd->ret != -2) cur_cmd->ret = -1; goto out; } - if (status == RAOP_PASSWORD) + if (status == OUTPUT_STATE_PASSWORD) { - status = RAOP_FAILED; + status = OUTPUT_STATE_FAILED; cur_cmd->ret = -2; } - if (status == RAOP_FAILED) + if (status == OUTPUT_STATE_FAILED) { - speaker_deselect_raop(dev); + speaker_deselect_output(device); - if (!dev->advertised) - device_remove(dev); + if (!device->advertised) + device_remove(device); if (cur_cmd->ret != -2) cur_cmd->ret = -1; @@ -2141,37 +2113,37 @@ device_probe_cb(struct raop_device *dev, struct raop_session *rs, enum raop_sess } static void -device_restart_cb(struct raop_device *dev, struct raop_session *rs, enum raop_session_state status) +device_restart_cb(struct output_device *device, struct output_session *session, enum output_device_state status) { int ret; cur_cmd->raop_pending--; - ret = device_check(dev); + ret = device_check(device); if (ret < 0) { - DPRINTF(E_WARN, L_PLAYER, "AirPlay device disappeared during restart!\n"); + DPRINTF(E_WARN, L_PLAYER, "Output device disappeared during restart!\n"); - raop_set_status_cb(rs, device_lost_cb); - raop_device_stop(rs); + outputs_status_cb(session, device_lost_cb); + outputs_device_stop(session); goto out; } - if (status == RAOP_FAILED) + if (status == OUTPUT_STATE_FAILED) { - speaker_deselect_raop(dev); + speaker_deselect_output(device); - if (!dev->advertised) - device_remove(dev); + if (!device->advertised) + device_remove(device); goto out; } - dev->session = rs; + device->session = session; - raop_sessions++; - raop_set_status_cb(rs, device_streaming_cb); + output_sessions++; + outputs_status_cb(session, device_streaming_cb); out: if (cur_cmd->raop_pending == 0) @@ -2182,7 +2154,6 @@ device_restart_cb(struct raop_device *dev, struct raop_session *rs, enum raop_se } } - /* Internal abort routine */ static void playback_abort(void) @@ -2190,8 +2161,8 @@ playback_abort(void) if (laudio_status != LAUDIO_CLOSED) laudio_close(); - if (raop_sessions > 0) - raop_playback_stop(); + if (output_sessions > 0) + outputs_playback_stop(); pb_timer_stop(); @@ -2365,7 +2336,8 @@ playback_stop(struct player_command *cmd) * full stop just yet; this saves time when restarting, which is nicer * for the user. */ - cmd->raop_pending = raop_flush(device_command_cb, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES); +//TODO Flush will give wrong result for raop_pending when CAST is also included + cmd->raop_pending = outputs_flush(device_command_cb, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES); pb_timer_stop(); @@ -2396,7 +2368,7 @@ playback_start_bh(struct player_command *cmd) { int ret; - if ((laudio_status == LAUDIO_CLOSED) && (raop_sessions == 0)) + if ((laudio_status == LAUDIO_CLOSED) && (output_sessions == 0)) { DPRINTF(E_LOG, L_PLAYER, "Cannot start playback: no output started\n"); @@ -2441,9 +2413,9 @@ playback_start_bh(struct player_command *cmd) if (ret < 0) goto out_fail; - /* Everything OK, start RAOP */ - if (raop_sessions > 0) - raop_playback_start(last_rtptime + AIRTUNES_V2_PACKET_SAMPLES, &pb_pos_stamp); + /* Everything OK, start outputs */ + if (output_sessions > 0) + outputs_playback_start(last_rtptime + AIRTUNES_V2_PACKET_SAMPLES, &pb_pos_stamp); status_update(PLAY_PLAYING); @@ -2459,7 +2431,7 @@ static int playback_start_item(struct player_command *cmd, struct queue_item *qii) { uint32_t *dbmfi_id; - struct raop_device *rd; + struct output_device *device; struct player_source *ps_playing; struct queue_item *item; int ret; @@ -2536,14 +2508,14 @@ playback_start_item(struct player_command *cmd, struct queue_item *qii) /* Start RAOP sessions on selected devices if needed */ cmd->raop_pending = 0; - for (rd = dev_list; rd; rd = rd->next) + for (device = dev_list; device; device = device->next) { - if (rd->selected && !rd->session) + if (device->selected && !device->session) { - ret = raop_device_start(rd, device_restart_cb, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES); + ret = outputs_device_start(device, device_restart_cb, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES); if (ret < 0) { - DPRINTF(E_LOG, L_PLAYER, "Could not start selected AirPlay device %s\n", rd->name); + DPRINTF(E_LOG, L_PLAYER, "Could not start selected %s device '%s'\n", device->type_name, device->name); continue; } @@ -2552,28 +2524,28 @@ playback_start_item(struct player_command *cmd, struct queue_item *qii) } /* Try to autoselect a non-selected RAOP device if the above failed */ - if ((laudio_status == LAUDIO_CLOSED) && (cmd->raop_pending == 0) && (raop_sessions == 0)) - for (rd = dev_list; rd; rd = rd->next) + if ((laudio_status == LAUDIO_CLOSED) && (cmd->raop_pending == 0) && (output_sessions == 0)) + for (device = dev_list; device; device = device->next) { - if (!rd->session) + if (!device->session) { - speaker_select_raop(rd); - ret = raop_device_start(rd, device_restart_cb, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES); + speaker_select_output(device); + ret = outputs_device_start(device, device_restart_cb, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES); if (ret < 0) { - DPRINTF(E_DBG, L_PLAYER, "Could not autoselect AirPlay device %s\n", rd->name); - speaker_deselect_raop(rd); + DPRINTF(E_DBG, L_PLAYER, "Could not autoselect %s device '%s'\n", device->type_name, device->name); + speaker_deselect_output(device); continue; } - DPRINTF(E_INFO, L_PLAYER, "Autoselecting AirPlay device %s\n", rd->name); + DPRINTF(E_INFO, L_PLAYER, "Autoselecting %s device '%s'\n", device->type_name, device->name); cmd->raop_pending++; break; } } /* No luck finding valid output */ - if ((laudio_status == LAUDIO_CLOSED) && (cmd->raop_pending == 0) && (raop_sessions == 0)) + if ((laudio_status == LAUDIO_CLOSED) && (cmd->raop_pending == 0) && (output_sessions == 0)) { DPRINTF(E_LOG, L_PLAYER, "Could not start playback: no output selected or couldn't start any output\n"); @@ -2826,7 +2798,7 @@ playback_pause(struct player_command *cmd) if (player_state == PLAY_STOPPED) return -1; - cmd->raop_pending = raop_flush(device_command_cb, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES); + cmd->raop_pending = outputs_flush(device_command_cb, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES); if (laudio_status != LAUDIO_CLOSED) laudio_stop(); @@ -2850,7 +2822,7 @@ playback_pause(struct player_command *cmd) static int speaker_enumerate(struct player_command *cmd) { - struct raop_device *rd; + struct output_device *device; struct spk_enum *spk_enum; struct spk_flags flags; char *laudio_name; @@ -2859,7 +2831,7 @@ speaker_enumerate(struct player_command *cmd) laudio_name = cfg_getstr(cfg_getsec(cfg, "audio"), "nickname"); - /* Auto-select local audio if there are no AirPlay devices */ + /* Auto-select local audio if there are no other output devices */ if (!dev_list && !laudio_selected) speaker_select_laudio(); @@ -2874,18 +2846,18 @@ speaker_enumerate(struct player_command *cmd) DPRINTF(E_DBG, L_PLAYER, "*** laudio: abs %d rel %d\n", laudio_volume, laudio_relvol); #endif - for (rd = dev_list; rd; rd = rd->next) + for (device = dev_list; device; device = device->next) { - if (rd->advertised || rd->selected) + if (device->advertised || device->selected) { - flags.selected = rd->selected; - flags.has_password = rd->has_password; - flags.has_video = (rd->devtype == RAOP_DEV_APPLETV); + flags.selected = device->selected; + flags.has_password = device->has_password; + flags.has_video = device->has_video; - spk_enum->cb(rd->id, rd->name, rd->relvol, flags, spk_enum->arg); + spk_enum->cb(device->id, device->name, device->relvol, flags, spk_enum->arg); #ifdef DEBUG_RELVOL - DPRINTF(E_DBG, L_PLAYER, "*** %s: abs %d rel %d\n", rd->name, rd->volume, rd->relvol); + DPRINTF(E_DBG, L_PLAYER, "*** %s: abs %d rel %d\n", device->name, device->volume, device->relvol); #endif } } @@ -2894,13 +2866,13 @@ speaker_enumerate(struct player_command *cmd) } static int -speaker_activate(struct raop_device *rd) +speaker_activate(struct output_device *device) { struct timespec ts; uint64_t pos; int ret; - if (!rd) + if (!device) { /* Local */ DPRINTF(E_DBG, L_PLAYER, "Activating local audio\n"); @@ -2943,27 +2915,26 @@ speaker_activate(struct raop_device *rd) } else { - /* RAOP */ if (player_state == PLAY_PLAYING) { - DPRINTF(E_DBG, L_PLAYER, "Activating RAOP device %s\n", rd->name); + DPRINTF(E_DBG, L_PLAYER, "Activating %s device '%s'\n", device->type_name, device->name); - ret = raop_device_start(rd, device_activate_cb, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES); + ret = outputs_device_start(device, device_activate_cb, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES); if (ret < 0) { - DPRINTF(E_LOG, L_PLAYER, "Could not start device %s\n", rd->name); + DPRINTF(E_LOG, L_PLAYER, "Could not start %s device '%s'\n", device->type_name, device->name); return -1; } } else { - DPRINTF(E_DBG, L_PLAYER, "Probing RAOP device %s\n", rd->name); + DPRINTF(E_DBG, L_PLAYER, "Probing %s device '%s'\n", device->type_name, device->name); - ret = raop_device_probe(rd, device_probe_cb); + ret = outputs_device_probe(device, device_probe_cb); if (ret < 0) { - DPRINTF(E_LOG, L_PLAYER, "Could not probe device %s\n", rd->name); + DPRINTF(E_LOG, L_PLAYER, "Could not probe %s device '%s'\n", device->type_name, device->name); return -1; } @@ -2976,9 +2947,9 @@ speaker_activate(struct raop_device *rd) } static int -speaker_deactivate(struct raop_device *rd) +speaker_deactivate(struct output_device *device) { - if (!rd) + if (!device) { /* Local */ DPRINTF(E_DBG, L_PLAYER, "Deactivating local audio\n"); @@ -2995,11 +2966,10 @@ speaker_deactivate(struct raop_device *rd) } else { - /* RAOP */ - DPRINTF(E_DBG, L_PLAYER, "Deactivating RAOP device %s\n", rd->name); + DPRINTF(E_DBG, L_PLAYER, "Deactivating %s device '%s'\n", device->type_name, device->name); - raop_set_status_cb(rd->session, device_shutdown_cb); - raop_device_stop(rd->session); + outputs_status_cb(device->session, device_shutdown_cb); + outputs_device_stop(device->session); return 1; } @@ -3010,13 +2980,13 @@ speaker_deactivate(struct raop_device *rd) static int speaker_set(struct player_command *cmd) { - struct raop_device *rd; + struct output_device *device; uint64_t *ids; int nspk; int i; int ret; - ids = cmd->arg.raop_ids; + ids = cmd->arg.device_ids; if (ids) nspk = ids[0]; @@ -3028,40 +2998,39 @@ speaker_set(struct player_command *cmd) cmd->raop_pending = 0; cmd->ret = 0; - /* RAOP devices */ - for (rd = dev_list; rd; rd = rd->next) + for (device = dev_list; device; device = device->next) { for (i = 1; i <= nspk; i++) { - DPRINTF(E_DBG, L_PLAYER, "Set %" PRIu64 " device %" PRIu64 "\n", ids[i], rd->id); + DPRINTF(E_DBG, L_PLAYER, "Set %" PRIu64 " device %" PRIu64 "\n", ids[i], device->id); - if (ids[i] == rd->id) + if (ids[i] == device->id) break; } if (i <= nspk) { - if (rd->has_password && !rd->password) + if (device->has_password && !device->password) { - DPRINTF(E_INFO, L_PLAYER, "RAOP device %s is password-protected, but we don't have it\n", rd->name); + DPRINTF(E_INFO, L_PLAYER, "The %s device '%s' is password-protected, but we don't have it\n", device->type_name, device->name); cmd->ret = -2; continue; } - DPRINTF(E_DBG, L_PLAYER, "RAOP device %s selected\n", rd->name); + DPRINTF(E_DBG, L_PLAYER, "The %s device '%s' is selected\n", device->type_name, device->name); - if (!rd->selected) - speaker_select_raop(rd); + if (!device->selected) + speaker_select_output(device); - if (!rd->session) + if (!device->session) { - ret = speaker_activate(rd); + ret = speaker_activate(device); if (ret < 0) { - DPRINTF(E_LOG, L_PLAYER, "Could not activate RAOP device %s\n", rd->name); + DPRINTF(E_LOG, L_PLAYER, "Could not activate %s device '%s'\n", device->type_name, device->name); - speaker_deselect_raop(rd); + speaker_deselect_output(device); if (cmd->ret != -2) cmd->ret = -1; @@ -3073,17 +3042,17 @@ speaker_set(struct player_command *cmd) } else { - DPRINTF(E_DBG, L_PLAYER, "RAOP device %s NOT selected\n", rd->name); + DPRINTF(E_DBG, L_PLAYER, "The %s device '%s' is NOT selected\n", device->type_name, device->name); - if (rd->selected) - speaker_deselect_raop(rd); + if (device->selected) + speaker_deselect_output(device); - if (rd->session) + if (device->session) { - ret = speaker_deactivate(rd); + ret = speaker_deactivate(device); if (ret < 0) { - DPRINTF(E_LOG, L_PLAYER, "Could not deactivate RAOP device %s\n", rd->name); + DPRINTF(E_LOG, L_PLAYER, "Could not deactivate %s device '%s'\n", device->type_name, device->name); if (cmd->ret != -2) cmd->ret = -1; @@ -3154,7 +3123,7 @@ speaker_set(struct player_command *cmd) static int volume_set(struct player_command *cmd) { - struct raop_device *rd; + struct output_device *device; int volume; volume = cmd->arg.intval; @@ -3176,19 +3145,19 @@ volume_set(struct player_command *cmd) cmd->raop_pending = 0; - for (rd = dev_list; rd; rd = rd->next) + for (device = dev_list; device; device = device->next) { - if (!rd->selected) + if (!device->selected) continue; - rd->volume = rel_to_vol(rd->relvol); + device->volume = rel_to_vol(device->relvol); #ifdef DEBUG_RELVOL - DPRINTF(E_DBG, L_PLAYER, "*** %s: abs %d rel %d\n", rd->name, rd->volume, rd->relvol); + DPRINTF(E_DBG, L_PLAYER, "*** %s: abs %d rel %d\n", device->name, device->volume, device->relvol); #endif - if (rd->session) - cmd->raop_pending += raop_set_volume_one(rd->session, rd->volume, device_command_cb); + if (device->session) + cmd->raop_pending += outputs_device_volume_set(device, device_command_cb); } listener_notify(LISTENER_VOLUME); @@ -3202,7 +3171,7 @@ volume_set(struct player_command *cmd) static int volume_setrel_speaker(struct player_command *cmd) { - struct raop_device *rd; + struct output_device *device; uint64_t id; int relvol; @@ -3221,23 +3190,23 @@ volume_setrel_speaker(struct player_command *cmd) } else { - for (rd = dev_list; rd; rd = rd->next) + for (device = dev_list; device; device = device->next) { - if (rd->id != id) + if (device->id != id) continue; - if (!rd->selected) + if (!device->selected) return 0; - rd->relvol = relvol; - rd->volume = rel_to_vol(relvol); + device->relvol = relvol; + device->volume = rel_to_vol(relvol); #ifdef DEBUG_RELVOL - DPRINTF(E_DBG, L_PLAYER, "*** %s: abs %d rel %d\n", rd->name, rd->volume, rd->relvol); + DPRINTF(E_DBG, L_PLAYER, "*** %s: abs %d rel %d\n", device->name, device->volume, device->relvol); #endif - if (rd->session) - cmd->raop_pending = raop_set_volume_one(rd->session, rd->volume, device_command_cb); + if (device->session) + cmd->raop_pending = outputs_device_volume_set(device, device_command_cb); break; } @@ -3254,7 +3223,7 @@ volume_setrel_speaker(struct player_command *cmd) static int volume_setabs_speaker(struct player_command *cmd) { - struct raop_device *rd; + struct output_device *device; uint64_t id; int volume; @@ -3276,31 +3245,31 @@ volume_setabs_speaker(struct player_command *cmd) DPRINTF(E_DBG, L_PLAYER, "*** laudio: abs %d rel %d\n", laudio_volume, laudio_relvol); #endif - for (rd = dev_list; rd; rd = rd->next) + for (device = dev_list; device; device = device->next) { - if (!rd->selected) + if (!device->selected) continue; - if (rd->id != id) + if (device->id != id) { - rd->relvol = vol_to_rel(rd->volume); + device->relvol = vol_to_rel(device->volume); #ifdef DEBUG_RELVOL - DPRINTF(E_DBG, L_PLAYER, "*** %s: abs %d rel %d\n", rd->name, rd->volume, rd->relvol); + DPRINTF(E_DBG, L_PLAYER, "*** %s: abs %d rel %d\n", device->name, device->volume, device->relvol); #endif continue; } else { - rd->relvol = 100; - rd->volume = master_volume; + device->relvol = 100; + device->volume = master_volume; #ifdef DEBUG_RELVOL - DPRINTF(E_DBG, L_PLAYER, "*** %s: abs %d rel %d\n", rd->name, rd->volume, rd->relvol); + DPRINTF(E_DBG, L_PLAYER, "*** %s: abs %d rel %d\n", device->name, device->volume, device->relvol); #endif - if (rd->session) - cmd->raop_pending = raop_set_volume_one(rd->session, rd->volume, device_command_cb); + if (device->session) + cmd->raop_pending = outputs_device_volume_set(device, device_command_cb); } } @@ -4057,7 +4026,7 @@ player_speaker_set(uint64_t *ids) cmd.func = speaker_set; cmd.func_bh = NULL; - cmd.arg.raop_ids = ids; + cmd.arg.device_ids = ids; ret = sync_command(&cmd); @@ -4448,68 +4417,60 @@ player_queue_plid(uint32_t plid) } /* Non-blocking commands used by mDNS */ -static void -player_device_add(struct raop_device *rd) +int +player_device_add(void *device) { struct player_command *cmd; int ret; - cmd = (struct player_command *)malloc(sizeof(struct player_command)); + cmd = calloc(1, sizeof(struct player_command)); if (!cmd) { DPRINTF(E_LOG, L_PLAYER, "Could not allocate player_command\n"); - - device_free(rd); - return; + return -1; } - memset(cmd, 0, sizeof(struct player_command)); - cmd->nonblock = 1; cmd->func = device_add; - cmd->arg.rd = rd; + cmd->arg.device = device; ret = nonblock_command(cmd); if (ret < 0) { free(cmd); - device_free(rd); - - return; + return -1; } + + return 0; } -static void -player_device_remove(struct raop_device *rd) +int +player_device_remove(void *device) { struct player_command *cmd; int ret; - cmd = (struct player_command *)malloc(sizeof(struct player_command)); + cmd = calloc(1, sizeof(struct player_command)); if (!cmd) { DPRINTF(E_LOG, L_PLAYER, "Could not allocate player_command\n"); - - device_free(rd); - return; + return -1; } - memset(cmd, 0, sizeof(struct player_command)); - cmd->nonblock = 1; cmd->func = device_remove_family; - cmd->arg.rd = rd; + cmd->arg.device = device; ret = nonblock_command(cmd); if (ret < 0) { free(cmd); - device_free(rd); - - return; + return -1; } + + return 0; } /* Thread: worker */ @@ -4529,283 +4490,11 @@ player_metadata_send(struct player_metadata *pmd) command_deinit(&cmd); } - -/* RAOP devices discovery - mDNS callback */ -/* Thread: main (mdns) */ -/* Examples of txt content: - * 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"] - * Apple TV 3: - ["vv=2" "vs=200.54" "vn=65537" "tp=UDP" "sf=0x44" "pk=8...f" "am=AppleTV3,1" "md=0,1,2" "ft=0x5A7FFFF7,0xE" "et=0,3,5" "da=true" "cn=0,1,2,3"] - * Sony STR-DN1040: - ["fv=s9327.1090.0" "am=STR-DN1040" "vs=141.9" "vn=65537" "tp=UDP" "ss=16" "sr=44100" "sv=false" "pw=false" "md=0,2" "ft=0x44F0A00" "et=0,4" "da=true" "cn=0,1" "ch=2" "txtvers=1"] - * AirFoil: - ["rastx=iafs" "sm=false" "raver=3.5.3.0" "ek=1" "md=0,1,2" "ramach=Win32NT.6" "et=0,1" "cn=0,1" "sr=44100" "ss=16" "raAudioFormats=ALAC" "raflakyzeroconf=true" "pw=false" "rast=afs" "vn=3" "sv=false" "txtvers=1" "ch=2" "tp=UDP"] - * Xbmc 13: - ["am=Xbmc,1" "md=0,1,2" "vs=130.14" "da=true" "vn=3" "pw=false" "sr=44100" "ss=16" "sm=false" "tp=UDP" "sv=false" "et=0,1" "ek=1" "ch=2" "cn=0,1" "txtvers=1"] - * Shairport (abrasive/1.0): - ["pw=false" "txtvers=1" "vn=3" "sr=44100" "ss=16" "ch=2" "cn=0,1" "et=0,1" "ek=1" "sm=false" "tp=UDP"] - * JB2: - ["fv=95.8947" "am=JB2 Gen" "vs=103.2" "tp=UDP" "vn=65537" "pw=false" "s s=16" "sr=44100" "da=true" "sv=false" "et=0,4" "cn=0,1" "ch=2" "txtvers=1"] - * Airport Express 802.11g (Gen 1): - ["tp=TCP,UDP" "sm=false" "sv=false" "ek=1" "et=0,1" "cn=0,1" "ch=2" "ss=16" "sr=44100" "pw=false" "vn=3" "txtvers=1"] - * Airport Express 802.11n: - 802.11n Gen 2 model (firmware 7.6.4): "am=Airport4,107", "et=0,1" - 802.11n Gen 3 model (firmware 7.6.4): "am=Airport10,115", "et=0,4" - */ -static void -raop_device_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_device *rd; - cfg_t *airplay; - const char *p; - char *at_name; - char *password; - uint64_t id; - int ret; - - ret = safe_hextou64(name, &id); - if (ret < 0) - { - DPRINTF(E_LOG, L_PLAYER, "Could not extract AirPlay device ID (%s)\n", name); - - return; - } - - at_name = strchr(name, '@'); - if (!at_name) - { - DPRINTF(E_LOG, L_PLAYER, "Could not extract AirPlay device name (%s)\n", name); - - return; - } - at_name++; - - DPRINTF(E_DBG, L_PLAYER, "Event for AirPlay device %s (port %d, id %" PRIx64 ")\n", at_name, port, id); - - rd = calloc(1, sizeof(struct raop_device)); - if (!rd) - { - DPRINTF(E_LOG, L_PLAYER, "Out of memory for new AirPlay device\n"); - - return; - } - - rd->id = id; - rd->name = strdup(at_name); - - if (port < 0) - { - /* Device stopped advertising */ - switch (family) - { - case AF_INET: - rd->v4_port = 1; - break; - - case AF_INET6: - rd->v6_port = 1; - break; - } - - player_device_remove(rd); - - return; - } - - /* Protocol */ - p = keyval_get(txt, "tp"); - if (!p) - { - DPRINTF(E_LOG, L_PLAYER, "AirPlay %s: no tp field in TXT record!\n", name); - - goto free_rd; - } - - if (*p == '\0') - { - DPRINTF(E_LOG, L_PLAYER, "AirPlay %s: tp has no value\n", name); - - goto free_rd; - } - - if (!strstr(p, "UDP")) - { - DPRINTF(E_LOG, L_PLAYER, "AirPlay %s: device does not support AirTunes v2 (tp=%s), discarding\n", name, p); - - goto free_rd; - } - - /* Password protection */ - password = NULL; - p = keyval_get(txt, "pw"); - if (!p) - { - DPRINTF(E_INFO, L_PLAYER, "AirPlay %s: no pw field in TXT record, assuming no password protection\n", name); - - rd->has_password = 0; - } - else if (*p == '\0') - { - DPRINTF(E_LOG, L_PLAYER, "AirPlay %s: pw has no value\n", name); - - goto free_rd; - } - else - { - rd->has_password = (strcmp(p, "false") != 0); - } - - if (rd->has_password) - { - DPRINTF(E_LOG, L_PLAYER, "AirPlay device %s is password-protected\n", name); - - airplay = cfg_gettsec(cfg, "airplay", at_name); - if (airplay) - password = cfg_getstr(airplay, "password"); - - if (!password) - DPRINTF(E_LOG, L_PLAYER, "No password given in config for AirPlay device %s\n", name); - } - - rd->password = password; - - /* Device type */ - rd->devtype = RAOP_DEV_OTHER; - p = keyval_get(txt, "am"); - - if (!p) - rd->devtype = RAOP_DEV_APEX1_80211G; // First generation AirPort Express - else if (strncmp(p, "AirPort4", strlen("AirPort4")) == 0) - rd->devtype = RAOP_DEV_APEX2_80211N; // Second generation - else if (strncmp(p, "AirPort", strlen("AirPort")) == 0) - rd->devtype = RAOP_DEV_APEX3_80211N; // Third generation and newer - else if (strncmp(p, "AppleTV", strlen("AppleTV")) == 0) - rd->devtype = RAOP_DEV_APPLETV; - else if (*p == '\0') - DPRINTF(E_LOG, L_PLAYER, "AirPlay %s: am has no value\n", name); - - /* Encrypt stream */ - p = keyval_get(txt, "ek"); - if (p && (*p == '1')) - rd->encrypt = 1; - else - rd->encrypt = 0; - - /* Metadata support */ - p = keyval_get(txt, "md"); - if (p && (*p != '\0')) - rd->wants_metadata = 1; - else - rd->wants_metadata = 0; - - DPRINTF(E_INFO, L_PLAYER, "Adding AirPlay device %s: password: %u, encrypt: %u, metadata: %u, type %s\n", - name, rd->has_password, rd->encrypt, rd->wants_metadata, raop_devtype[rd->devtype]); - - rd->advertised = 1; - - switch (family) - { - case AF_INET: - rd->v4_address = strdup(address); - rd->v4_port = port; - break; - - case AF_INET6: - rd->v6_address = strdup(address); - rd->v6_port = port; - break; - } - - player_device_add(rd); - - return; - - free_rd: - device_free(rd); -} - -static void -cast_device_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_device *d; - const char *p; - uint32_t id; - - p = keyval_get(txt, "id"); - if (p) - id = djb_hash(p, strlen(p)); - else - id = 0; - - if (!id) - { - DPRINTF(E_LOG, L_PLAYER, "Could not extract ChromeCast device ID (%s)\n", name); - - return; - } - - DPRINTF(E_DBG, L_PLAYER, "Event for Chromecast device %s (port %d, id %" PRIu32 ")\n", name, port, id); - - d = calloc(1, sizeof(struct raop_device)); - if (!d) - { - DPRINTF(E_LOG, L_PLAYER, "Out of memory for new Chromecast device\n"); - - return; - } - - d->id = id; - d->name = strdup(name); - - if (port < 0) - { - /* Device stopped advertising */ - switch (family) - { - case AF_INET: - d->v4_port = 1; - break; - - case AF_INET6: - d->v6_port = 1; - break; - } - - player_device_remove(d); - - return; - } - - /* Device type */ - d->devtype = RAOP_DEV_CHROMECAST; - - DPRINTF(E_INFO, L_PLAYER, "Adding Chromecast device %s\n", name); - - d->advertised = 1; - - switch (family) - { - case AF_INET: - d->v4_address = strdup(address); - d->v4_port = port; - break; - - case AF_INET6: - d->v6_address = strdup(address); - d->v6_port = port; - break; - } - - player_device_add(d); -} - /* Thread: player */ static void * player(void *arg) { - struct raop_device *rd; + struct output_device *device; int ret; ret = db_perthread_init(); @@ -4828,11 +4517,11 @@ player(void *arg) if (ret < 0) DPRINTF(E_LOG, L_PLAYER, "Could not save state for local audio\n"); - for (rd = dev_list; rd; rd = rd->next) + for (device = dev_list; device; device = device->next) { - ret = db_speaker_save(rd->id, rd->selected, rd->volume); + ret = db_speaker_save(device->id, device->selected, device->volume); if (ret < 0) - DPRINTF(E_LOG, L_PLAYER, "Could not save state for speaker %s\n", rd->name); + DPRINTF(E_LOG, L_PLAYER, "Could not save state for %s device '%s'\n", device->type_name, device->name); } db_perthread_deinit(); @@ -4855,8 +4544,6 @@ player_init(void) { uint64_t interval; uint32_t rnd; - int raop_v6enabled; - int mdns_flags; int ret; player_exit = 0; @@ -4868,7 +4555,7 @@ player_init(void) laudio_selected = 0; laudio_status = LAUDIO_CLOSED; - raop_sessions = 0; + output_sessions = 0; cur_cmd = NULL; @@ -4940,8 +4627,6 @@ player_init(void) goto audio_fail; } - raop_v6enabled = cfg_getbool(cfg_getsec(cfg, "general"), "ipv6"); - ret = pipe2(exit_pipe, O_CLOEXEC); if (ret < 0) { @@ -4999,55 +4684,28 @@ player_init(void) if (ret < 0) { DPRINTF(E_LOG, L_PLAYER, "Local audio init failed\n"); - goto laudio_fail; } - ret = raop_init(&raop_v6enabled); + ret = outputs_init(); if (ret < 0) { - DPRINTF(E_LOG, L_PLAYER, "RAOP init failed\n"); - - goto raop_fail; + DPRINTF(E_FATAL, L_PLAYER, "Output initiation failed\n"); + goto outputs_fail; } - if (raop_v6enabled) - mdns_flags = MDNS_WANT_V4 | MDNS_WANT_V6 | MDNS_WANT_V6LL; - else - mdns_flags = MDNS_WANT_V4; - - ret = mdns_browse("_raop._tcp", mdns_flags, raop_device_cb); - if (ret < 0) - { - DPRINTF(E_FATAL, L_PLAYER, "Could not add mDNS browser for AirPlay devices\n"); - - goto mdns_browse_fail; - } - -#ifdef CHROMECAST - ret = mdns_browse("_googlecast._tcp", mdns_flags, cast_device_cb); - if (ret < 0) - { - DPRINTF(E_FATAL, L_PLAYER, "Could not add mDNS browser for Chromecast devices\n"); - - goto mdns_browse_fail; - } -#endif - ret = pthread_create(&tid_player, NULL, player, NULL); if (ret < 0) { - DPRINTF(E_LOG, L_PLAYER, "Could not spawn player thread: %s\n", strerror(errno)); - + DPRINTF(E_FATAL, L_PLAYER, "Could not spawn player thread: %s\n", strerror(errno)); goto thread_fail; } return 0; thread_fail: - mdns_browse_fail: - raop_deinit(); - raop_fail: + outputs_deinit(); + outputs_fail: laudio_deinit(); laudio_fail: evnew_fail: @@ -5106,7 +4764,7 @@ player_deinit(void) evbuffer_free(audio_buf); laudio_deinit(); - raop_deinit(); + outputs_deinit(); close(exit_pipe[0]); close(exit_pipe[1]); diff --git a/src/player.h b/src/player.h index 80cbafbf..a1b4f35d 100644 --- a/src/player.h +++ b/src/player.h @@ -190,6 +190,11 @@ player_queue_clear_history(void); void player_queue_plid(uint32_t plid); +int +player_device_add(void *device); + +int +player_device_remove(void *device); struct player_history * player_history_get(void); diff --git a/src/raop.h b/src/raop.h deleted file mode 100644 index 3ed2f1f3..00000000 --- a/src/raop.h +++ /dev/null @@ -1,139 +0,0 @@ - -#ifndef __RAOP_H__ -#define __RAOP_H__ - -#include - -union sockaddr_all -{ - struct sockaddr_in sin; - struct sockaddr_in6 sin6; - struct sockaddr sa; - struct sockaddr_storage ss; -}; - -/* Keep in sync with raop_devtype[] in player.c */ -enum raop_devtype { - RAOP_DEV_APEX1_80211G, - RAOP_DEV_APEX2_80211N, - RAOP_DEV_APEX3_80211N, - RAOP_DEV_APPLETV, - RAOP_DEV_CHROMECAST, - RAOP_DEV_OTHER, -}; - -struct raop_session; -struct raop_metadata; - -struct raop_device -{ - uint64_t id; - char *name; - - char *v4_address; - char *v6_address; - short v4_port; - short v6_port; - - enum raop_devtype devtype; - - unsigned selected:1; - unsigned advertised:1; - - unsigned encrypt:1; - unsigned wants_metadata:1; - unsigned has_password:1; - const char *password; - - int volume; - int relvol; - struct raop_session *session; - - struct raop_device *next; -}; - -/* RAOP session state */ - -/* Session is starting up */ -#define RAOP_F_STARTUP (1 << 15) - -/* Streaming is up (connection established) */ -#define RAOP_F_CONNECTED (1 << 16) - -enum raop_session_state - { - RAOP_STOPPED = 0, - - /* Session startup */ - RAOP_OPTIONS = RAOP_F_STARTUP | 0x01, - RAOP_ANNOUNCE = RAOP_F_STARTUP | 0x02, - RAOP_SETUP = RAOP_F_STARTUP | 0x03, - RAOP_RECORD = RAOP_F_STARTUP | 0x04, - - /* Session established - * - streaming ready (RECORD sent and acked, connection established) - * - commands (SET_PARAMETER) are possible - */ - RAOP_CONNECTED = RAOP_F_CONNECTED, - - /* Audio data is being sent */ - RAOP_STREAMING = RAOP_F_CONNECTED | 0x01, - - /* Session is failed, couldn't startup or error occurred */ - RAOP_FAILED = -1, - - /* Password issue: unknown password or bad password */ - RAOP_PASSWORD = -2, - }; - -typedef void (*raop_status_cb)(struct raop_device *dev, struct raop_session *rs, enum raop_session_state status); - -void -raop_metadata_purge(void); - -void -raop_metadata_prune(uint64_t rtptime); - -struct raop_metadata * -raop_metadata_prepare(int id); - -void -raop_metadata_send(struct raop_metadata *rmd, uint64_t rtptime, uint64_t offset, int startup); - -int -raop_device_probe(struct raop_device *rd, raop_status_cb cb); - -int -raop_device_start(struct raop_device *rd, raop_status_cb cb, uint64_t rtptime); - -void -raop_device_stop(struct raop_session *rs); - -void -raop_playback_start(uint64_t next_pkt, struct timespec *ts); - -void -raop_playback_stop(void); - -int -raop_set_volume_one(struct raop_session *rs, int volume, raop_status_cb cb); - -int -raop_flush(raop_status_cb cb, uint64_t rtptime); - - -void -raop_set_status_cb(struct raop_session *rs, raop_status_cb cb); - - -void -raop_v2_write(uint8_t *buf, uint64_t rtptime); - - -int -raop_init(int *v6enabled); - -void -raop_deinit(void); - -#endif /* !__RAOP_H__ */