[chromecast] Try to improve session handling + actually remove device on mdns cb

This commit is contained in:
ejurgensen 2016-02-02 22:37:08 +01:00
parent e4d209f8a0
commit 483a428bae
2 changed files with 104 additions and 78 deletions

View File

@ -83,9 +83,11 @@ typedef void (*cast_reply_cb)(struct cast_session *cs, struct cast_msg_payload *
// Session is starting up // Session is starting up
#define CAST_STATE_F_STARTUP (1 << 14) #define CAST_STATE_F_STARTUP (1 << 14)
// The default receiver app is ready // The default receiver app is ready
#define CAST_STATE_F_MEDIA_CONNECTED (1 << 15) #define CAST_STATE_F_MEDIA_CONNECTED (1 << 14)
// Media is loaded in the receiver app // Media is loaded in the receiver app
#define CAST_STATE_F_MEDIA_LOADED (1 << 16) #define CAST_STATE_F_MEDIA_LOADED (1 << 15)
// Media is playing in the receiver app
#define CAST_STATE_F_MEDIA_PLAYING (1 << 16)
// Beware, the order of this enum has meaning // Beware, the order of this enum has meaning
enum cast_state enum cast_state
@ -104,14 +106,12 @@ enum cast_state
CAST_STATE_MEDIA_CONNECTED = CAST_STATE_F_MEDIA_CONNECTED, CAST_STATE_MEDIA_CONNECTED = CAST_STATE_F_MEDIA_CONNECTED,
// Receiver app has loaded our media // Receiver app has loaded our media
CAST_STATE_MEDIA_LOADED = CAST_STATE_F_MEDIA_CONNECTED | CAST_STATE_F_MEDIA_LOADED, CAST_STATE_MEDIA_LOADED = CAST_STATE_F_MEDIA_CONNECTED | CAST_STATE_F_MEDIA_LOADED,
// After LOAD
CAST_STATE_MEDIA_BUFFERING = CAST_STATE_F_MEDIA_CONNECTED | CAST_STATE_F_MEDIA_LOADED | 0x01,
// After PLAY
CAST_STATE_MEDIA_PLAYING = CAST_STATE_F_MEDIA_CONNECTED | CAST_STATE_F_MEDIA_LOADED | 0x02,
// After PAUSE // After PAUSE
CAST_STATE_MEDIA_PAUSED = CAST_STATE_F_MEDIA_CONNECTED | CAST_STATE_F_MEDIA_LOADED | 0x03, CAST_STATE_MEDIA_PAUSED = CAST_STATE_F_MEDIA_CONNECTED | CAST_STATE_F_MEDIA_LOADED | 0x01,
// After STOP // After LOAD
CAST_STATE_MEDIA_IDLE = CAST_STATE_F_MEDIA_CONNECTED | CAST_STATE_F_MEDIA_LOADED | 0x04, CAST_STATE_MEDIA_BUFFERING = CAST_STATE_F_MEDIA_CONNECTED | CAST_STATE_F_MEDIA_LOADED | CAST_STATE_F_MEDIA_PLAYING,
// After PLAY
CAST_STATE_MEDIA_PLAYING = CAST_STATE_F_MEDIA_CONNECTED | CAST_STATE_F_MEDIA_LOADED | CAST_STATE_F_MEDIA_PLAYING | 0x01,
}; };
struct cast_session struct cast_session
@ -197,6 +197,7 @@ struct cast_msg_payload
const char *app_id; const char *app_id;
const char *session_id; const char *session_id;
const char *transport_id; const char *transport_id;
const char *player_state;
int media_session_id; int media_session_id;
}; };
@ -326,7 +327,7 @@ extern struct event_base *evbase_player;
static gnutls_certificate_credentials_t tls_credentials; static gnutls_certificate_credentials_t tls_credentials;
static struct cast_session *sessions; static struct cast_session *sessions;
static struct event *flush_timer; static struct event *flush_timer;
static struct timeval cast_timeout = { 8, 0 };
/* ------------------------------- MISC HELPERS ----------------------------- */ /* ------------------------------- MISC HELPERS ----------------------------- */
@ -533,7 +534,7 @@ cast_msg_send(struct cast_session *cs, enum cast_msg_types type, cast_reply_cb r
int ret; int ret;
#ifdef DEBUG_LOG_MODE #ifdef DEBUG_LOG_MODE
DPRINTF(E_DBG, L_CAST, "Preparing to send message type %d to %s\n", type, cs->devname); DPRINTF(E_DBG, L_CAST, "Preparing to send message type %d to '%s'\n", type, cs->devname);
#endif #endif
msg.source_id = "sender-0"; msg.source_id = "sender-0";
@ -646,17 +647,23 @@ cast_msg_parse(struct cast_msg_payload *payload, char *s)
// Isn't this marvelous // Isn't this marvelous
if ( json_object_object_get_ex(haystack, "status", &needle) && if ( json_object_object_get_ex(haystack, "status", &needle) &&
(json_object_get_type(needle) == json_type_array) && (json_object_get_type(needle) == json_type_array) &&
(somehay = json_object_array_get_idx(needle, 0)) && (somehay = json_object_array_get_idx(needle, 0)) )
json_object_object_get_ex(somehay, "mediaSessionId", &needle) && {
if ( json_object_object_get_ex(somehay, "mediaSessionId", &needle) &&
(json_object_get_type(needle) == json_type_int) ) (json_object_get_type(needle) == json_type_int) )
payload->media_session_id = json_object_get_int(needle); payload->media_session_id = json_object_get_int(needle);
if ( ! (json_object_object_get_ex(haystack, "status", &somehay) && if ( json_object_object_get_ex(somehay, "playerState", &needle) &&
(json_object_get_type(needle) == json_type_string) )
payload->player_state = json_object_get_string(needle);
}
if ( json_object_object_get_ex(haystack, "status", &somehay) &&
json_object_object_get_ex(somehay, "applications", &needle) && json_object_object_get_ex(somehay, "applications", &needle) &&
(json_object_get_type(needle) == json_type_array) && (json_object_get_type(needle) == json_type_array) &&
(somehay = json_object_array_get_idx(needle, 0))) ) (somehay = json_object_array_get_idx(needle, 0)) )
return haystack; {
if ( json_object_object_get_ex(somehay, "appId", &needle) && if ( json_object_object_get_ex(somehay, "appId", &needle) &&
(json_object_get_type(needle) == json_type_string) ) (json_object_get_type(needle) == json_type_string) )
payload->app_id = json_object_get_string(needle); payload->app_id = json_object_get_string(needle);
@ -668,6 +675,7 @@ cast_msg_parse(struct cast_msg_payload *payload, char *s)
if ( json_object_object_get_ex(somehay, "transportId", &needle) && if ( json_object_object_get_ex(somehay, "transportId", &needle) &&
(json_object_get_type(needle) == json_type_string) ) (json_object_get_type(needle) == json_type_string) )
payload->transport_id = json_object_get_string(needle); payload->transport_id = json_object_get_string(needle);
}
return haystack; return haystack;
} }
@ -714,8 +722,6 @@ cast_msg_process(struct cast_session *cs, const uint8_t *data, size_t len)
if (payload.type == UNKNOWN) if (payload.type == UNKNOWN)
goto out_free_parsed; goto out_free_parsed;
// TODO: UPDATE SESSION STATUS AND READ BROADCASTS
i = payload.request_id % CALLBACK_REGISTER_SIZE; i = payload.request_id % CALLBACK_REGISTER_SIZE;
if (payload.request_id && cs->callback_register[i]) if (payload.request_id && cs->callback_register[i])
{ {
@ -724,22 +730,36 @@ cast_msg_process(struct cast_session *cs, const uint8_t *data, size_t len)
goto out_free_parsed; goto out_free_parsed;
} }
// TODO: UPDATE SESSION STATUS AND READ BROADCASTS
if (payload.type == RECEIVER_STATUS && (cs->state & CAST_STATE_F_MEDIA_CONNECTED)) if (payload.type == RECEIVER_STATUS && (cs->state & CAST_STATE_F_MEDIA_CONNECTED))
{ {
unknown_app_id = payload.app_id && (strcmp(payload.app_id, CAST_APP_ID) != 0); unknown_app_id = payload.app_id && (strcmp(payload.app_id, CAST_APP_ID) != 0);
unknown_session_id = payload.session_id && (strcmp(payload.session_id, cs->session_id) != 0); unknown_session_id = payload.session_id && (strcmp(payload.session_id, cs->session_id) != 0);
if (unknown_app_id || unknown_session_id) if (unknown_app_id || unknown_session_id)
{ {
DPRINTF(E_WARN, L_CAST, "Our session on %s was hijacked\n", cs->devname); DPRINTF(E_WARN, L_CAST, "Our session on '%s' was hijacked\n", cs->devname);
// Downgrade state, we don't have the receiver app any more // Downgrade state, we don't have the receiver app any more
cs->state = CAST_STATE_CONNECTED; cs->state = CAST_STATE_CONNECTED;
cast_session_shutdown(cs, CAST_STATE_FAILED); cast_session_shutdown(cs, CAST_STATE_FAILED);
DPRINTF(E_WARN, L_CAST, "POINT 1\n");
goto out_free_parsed; goto out_free_parsed;
} }
} }
if (payload.type == MEDIA_STATUS && (cs->state & CAST_STATE_F_MEDIA_PLAYING))
{
if (payload.player_state && (strcmp(payload.player_state, "PAUSED") == 0))
{
DPRINTF(E_WARN, L_CAST, "Something paused our session on '%s'\n", cs->devname);
/* cs->state = CAST_STATE_MEDIA_CONNECTED;
// Kill the session, the player will need to restart it
cast_session_shutdown(cs, CAST_STATE_NULL);
goto out_free_parsed;
*/ }
}
out_free_parsed: out_free_parsed:
cast_msg_parse_free(hdl); cast_msg_parse_free(hdl);
out_free_unpacked: out_free_unpacked:
@ -772,7 +792,10 @@ cast_status(struct cast_session *cs)
case CAST_STATE_MEDIA_CONNECTED: case CAST_STATE_MEDIA_CONNECTED:
state = OUTPUT_STATE_CONNECTED; state = OUTPUT_STATE_CONNECTED;
break; break;
case CAST_STATE_MEDIA_LOADED ... CAST_STATE_MEDIA_IDLE: case CAST_STATE_MEDIA_LOADED ... CAST_STATE_MEDIA_PAUSED:
state = OUTPUT_STATE_CONNECTED;
break;
case CAST_STATE_MEDIA_BUFFERING ... CAST_STATE_MEDIA_PLAYING:
state = OUTPUT_STATE_STREAMING; state = OUTPUT_STATE_STREAMING;
break; break;
default: default:
@ -934,7 +957,7 @@ cast_cb_load(struct cast_session *cs, struct cast_msg_payload *payload)
{ {
if ((payload->type == MEDIA_LOAD_FAILED) || (payload->type == MEDIA_LOAD_CANCELLED)) if ((payload->type == MEDIA_LOAD_FAILED) || (payload->type == MEDIA_LOAD_CANCELLED))
{ {
DPRINTF(E_LOG, L_CAST, "The device %s could not start playback\n", cs->devname); DPRINTF(E_LOG, L_CAST, "The device '%s' could not start playback\n", cs->devname);
cast_session_shutdown(cs, CAST_STATE_FAILED); cast_session_shutdown(cs, CAST_STATE_FAILED);
return; return;
} }
@ -947,7 +970,9 @@ cast_cb_load(struct cast_session *cs, struct cast_msg_payload *payload)
} }
cs->media_session_id = payload->media_session_id; cs->media_session_id = payload->media_session_id;
cs->state = CAST_STATE_MEDIA_LOADED; // TODO don't autoplay
// cs->state = CAST_STATE_MEDIA_LOADED;
cs->state = CAST_STATE_MEDIA_PLAYING;
cast_status(cs); cast_status(cs);
} }
@ -963,7 +988,7 @@ cast_cb_flush(struct cast_session *cs, struct cast_msg_payload *payload)
{ {
if (payload->type != MEDIA_STATUS) if (payload->type != MEDIA_STATUS)
{ {
DPRINTF(E_LOG, L_CAST, "Unexpected reply to PAUSE request from %s - will continue\n", cs->devname); DPRINTF(E_LOG, L_CAST, "Unexpected reply to PAUSE request from '%s' - will continue\n", cs->devname);
} }
cs->state = CAST_STATE_MEDIA_PAUSED; cs->state = CAST_STATE_MEDIA_PAUSED;
@ -986,8 +1011,16 @@ cast_listen_cb(int fd, short what, void *arg)
cs = (struct cast_session *)arg; cs = (struct cast_session *)arg;
if (what == EV_TIMEOUT)
{
DPRINTF(E_LOG, L_CAST, "No heartbeat from '%s', shutting down\n", cs->devname);
cs->state = CAST_STATE_CONNECTED;
cast_session_shutdown(cs, CAST_STATE_FAILED);
return;
}
#ifdef DEBUG_LOG_MODE #ifdef DEBUG_LOG_MODE
DPRINTF(E_DBG, L_CAST, "New data from %s\n", cs->devname); DPRINTF(E_DBG, L_CAST, "New data from '%s'\n", cs->devname);
#endif #endif
received = 0; received = 0;
@ -1039,29 +1072,21 @@ 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) 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 output_device *device; struct output_device *device;
const char *p;
uint32_t id; uint32_t id;
p = keyval_get(txt, "id"); id = djb_hash(name, strlen(name));
if (p)
id = djb_hash(p, strlen(p));
else
id = 0;
if (!id) if (!id)
{ {
DPRINTF(E_LOG, L_PLAYER, "Could not extract ChromeCast device ID (%s)\n", name); DPRINTF(E_LOG, L_CAST, "Could not hash ChromeCast device name (%s)\n", name);
return; return;
} }
DPRINTF(E_DBG, L_PLAYER, "Event for Chromecast device %s (port %d, id %" PRIu32 ")\n", name, port, id); DPRINTF(E_DBG, L_CAST, "Event for Chromecast device '%s' (port %d, id %" PRIu32 ")\n", name, port, id);
device = calloc(1, sizeof(struct output_device)); device = calloc(1, sizeof(struct output_device));
if (!device) if (!device)
{ {
DPRINTF(E_LOG, L_PLAYER, "Out of memory for new Chromecast device\n"); DPRINTF(E_LOG, L_CAST, "Out of memory for new Chromecast device\n");
return; return;
} }
@ -1089,7 +1114,7 @@ cast_device_cb(const char *name, const char *type, const char *domain, const cha
return; return;
} }
DPRINTF(E_INFO, L_PLAYER, "Adding Chromecast device %s\n", name); DPRINTF(E_INFO, L_CAST, "Adding Chromecast device '%s'\n", name);
device->advertised = 1; device->advertised = 1;
@ -1207,7 +1232,7 @@ cast_session_make(struct output_device *device, int family, output_status_cb cb)
flags = fcntl(cs->server_fd, F_GETFL, 0); flags = fcntl(cs->server_fd, F_GETFL, 0);
fcntl(cs->server_fd, F_SETFL, flags | O_NONBLOCK); fcntl(cs->server_fd, F_SETFL, flags | O_NONBLOCK);
event_add(cs->ev, NULL); event_add(cs->ev, &cast_timeout);
cs->devname = strdup(device->name); cs->devname = strdup(device->name);
cs->address = strdup(address); cs->address = strdup(address);
@ -1219,7 +1244,7 @@ cast_session_make(struct output_device *device, int family, output_status_cb cb)
proto = gnutls_protocol_get_name(gnutls_protocol_get_version(cs->tls_session)); proto = gnutls_protocol_get_name(gnutls_protocol_get_version(cs->tls_session));
DPRINTF(E_INFO, L_CAST, "Connection to %s established using %s\n", cs->devname, proto); DPRINTF(E_INFO, L_CAST, "Connection to '%s' established using %s\n", cs->devname, proto);
return cs; return cs;
@ -1243,9 +1268,14 @@ cast_session_shutdown(struct cast_session *cs, enum cast_state wanted_state)
int pending; int pending;
int ret; int ret;
if (wanted_state >= cs->state) if (cs->state == wanted_state)
{ {
DPRINTF(E_LOG, L_CAST, "Bug! Shutdown request wanted_state should be lower than current state\n"); cast_status(cs);
return;
}
else if (cs->state < wanted_state)
{
DPRINTF(E_LOG, L_CAST, "Bug! Shutdown request got wanted_state that is higher than current state\n");
return; return;
} }
@ -1254,7 +1284,7 @@ cast_session_shutdown(struct cast_session *cs, enum cast_state wanted_state)
pending = 0; pending = 0;
switch (cs->state) switch (cs->state)
{ {
case CAST_STATE_MEDIA_LOADED ... CAST_STATE_MEDIA_IDLE: case CAST_STATE_MEDIA_LOADED ... CAST_STATE_MEDIA_PLAYING:
ret = cast_msg_send(cs, MEDIA_STOP, cast_cb_stop_media); ret = cast_msg_send(cs, MEDIA_STOP, cast_cb_stop_media);
pending = 1; pending = 1;
break; break;
@ -1483,7 +1513,7 @@ cast_flush(output_status_cb cb, uint64_t rtptime)
{ {
next = cs->next; next = cs->next;
if (!(cs->state & CAST_STATE_F_MEDIA_LOADED)) if (!(cs->state & CAST_STATE_F_MEDIA_PLAYING))
continue; continue;
ret = cast_msg_send(cs, MEDIA_PAUSE, cast_cb_flush); ret = cast_msg_send(cs, MEDIA_PAUSE, cast_cb_flush);
@ -1553,7 +1583,7 @@ cast_init(void)
ret = mdns_browse("_googlecast._tcp", mdns_flags, cast_device_cb); ret = mdns_browse("_googlecast._tcp", mdns_flags, cast_device_cb);
if (ret < 0) if (ret < 0)
{ {
DPRINTF(E_LOG, L_PLAYER, "Could not add mDNS browser for Chromecast devices\n"); DPRINTF(E_LOG, L_CAST, "Could not add mDNS browser for Chromecast devices\n");
goto out_free_flush_timer; goto out_free_flush_timer;
} }

View File

@ -1885,20 +1885,21 @@ device_streaming_cb(struct output_device *device, struct output_session *session
DPRINTF(E_DBG, L_PLAYER, "CALLBACK streaming_cb %d\n", status); DPRINTF(E_DBG, L_PLAYER, "CALLBACK streaming_cb %d\n", status);
if (status == OUTPUT_STATE_FAILED)
{
output_sessions--;
ret = device_check(device); ret = device_check(device);
if (ret < 0) if (ret < 0)
{ {
DPRINTF(E_WARN, L_PLAYER, "Output device disappeared during streaming!\n"); DPRINTF(E_LOG, L_PLAYER, "Output device disappeared during streaming!\n");
output_sessions--;
return; return;
} }
if (status == OUTPUT_STATE_FAILED)
{
DPRINTF(E_LOG, L_PLAYER, "The %s device '%s' FAILED\n", device->type_name, device->name); DPRINTF(E_LOG, L_PLAYER, "The %s device '%s' FAILED\n", device->type_name, device->name);
output_sessions--;
if (player_state == PLAY_PLAYING) if (player_state == PLAY_PLAYING)
speaker_deselect_output(device); speaker_deselect_output(device);
@ -1906,21 +1907,16 @@ device_streaming_cb(struct output_device *device, struct output_session *session
if (!device->advertised) if (!device->advertised)
device_remove(device); device_remove(device);
if (output_sessions == 0)
playback_abort();
} }
else if (status == OUTPUT_STATE_STOPPED) else if (status == OUTPUT_STATE_STOPPED)
{ {
output_sessions--;
ret = device_check(device);
if (ret < 0)
{
DPRINTF(E_WARN, L_PLAYER, "Output device disappeared during streaming!\n");
return;
}
DPRINTF(E_INFO, L_PLAYER, "The %s device '%s' stopped\n", device->type_name, device->name); DPRINTF(E_INFO, L_PLAYER, "The %s device '%s' stopped\n", device->type_name, device->name);
output_sessions--;
device->session = NULL; device->session = NULL;
if (!device->advertised) if (!device->advertised)