/** * @brief Send a message to the Chromecast * @param msg the CastMessage to send * @return the number of bytes sent or -1 on error */ int intf_sys_t::sendMessage(castchannel::CastMessage &msg) { uint32_t i_size = msg.ByteSize(); uint32_t i_sizeNetwork = hton32(i_size); char *p_data = new(std::nothrow) char[PACKET_HEADER_LEN + i_size]; if (p_data == NULL) return -1; memcpy(p_data, &i_sizeNetwork, PACKET_HEADER_LEN); msg.SerializeWithCachedSizesToArray((uint8_t *)(p_data + PACKET_HEADER_LEN)); int i_ret = tls_Send(p_tls, p_data, PACKET_HEADER_LEN + i_size); delete[] p_data; return i_ret; }
/** * @brief Process a message received from the Chromecast * @param msg the CastMessage to process * @return 0 if the message has been successfuly processed else -1 */ void intf_sys_t::processMessage(const castchannel::CastMessage &msg) { const std::string & namespace_ = msg.namespace_(); #ifndef NDEBUG msg_Dbg(p_stream,"processMessage: %s->%s %s", namespace_.c_str(), msg.destination_id().c_str(), msg.payload_utf8().c_str()); #endif if (namespace_ == NAMESPACE_DEVICEAUTH) { castchannel::DeviceAuthMessage authMessage; authMessage.ParseFromString(msg.payload_binary()); if (authMessage.has_error()) { msg_Err(p_stream, "Authentification error: %d", authMessage.error().error_type()); } else if (!authMessage.has_response()) { msg_Err(p_stream, "Authentification message has no response field"); } else { vlc_mutex_locker locker(&lock); setConnectionStatus(CHROMECAST_AUTHENTICATED); msgConnect(DEFAULT_CHOMECAST_RECEIVER); msgReceiverLaunchApp(); } } else if (namespace_ == NAMESPACE_HEARTBEAT) { json_value *p_data = json_parse(msg.payload_utf8().c_str()); std::string type((*p_data)["type"]); if (type == "PING") { msg_Dbg(p_stream, "PING received from the Chromecast"); msgPong(); } else if (type == "PONG") { msg_Dbg(p_stream, "PONG received from the Chromecast"); } else { msg_Warn(p_stream, "Heartbeat command not supported: %s", type.c_str()); } json_value_free(p_data); } else if (namespace_ == NAMESPACE_RECEIVER) { json_value *p_data = json_parse(msg.payload_utf8().c_str()); std::string type((*p_data)["type"]); if (type == "RECEIVER_STATUS") { json_value applications = (*p_data)["status"]["applications"]; const json_value *p_app = NULL; vlc_mutex_locker locker(&lock); for (unsigned i = 0; i < applications.u.array.length; ++i) { std::string appId(applications[i]["appId"]); if (appId == APP_ID) { const char *pz_transportId = applications[i]["transportId"]; if (pz_transportId != NULL) { appTransportId = std::string(pz_transportId); p_app = &applications[i]; } break; } } if ( p_app ) { if (!appTransportId.empty() && getConnectionStatus() == CHROMECAST_AUTHENTICATED) { msgConnect(appTransportId); setConnectionStatus(CHROMECAST_APP_STARTED); msgPlayerLoad(); setConnectionStatus(CHROMECAST_MEDIA_LOAD_SENT); vlc_cond_signal(&loadCommandCond); } } else { switch(getConnectionStatus()) { /* If the app is no longer present */ case CHROMECAST_APP_STARTED: case CHROMECAST_MEDIA_LOAD_SENT: msg_Warn(p_stream, "app is no longer present. closing"); msgReceiverClose(appTransportId); setConnectionStatus(CHROMECAST_CONNECTION_DEAD); break; case CHROMECAST_AUTHENTICATED: msg_Dbg(p_stream, "Chromecast was running no app, launch media_app"); appTransportId = ""; msgReceiverLaunchApp(); break; default: break; } } } else if (type == "LAUNCH_ERROR") { json_value reason = (*p_data)["reason"]; msg_Err(p_stream, "Failed to start the MediaPlayer: %s", (const char *)reason); } else { msg_Warn(p_stream, "Receiver command not supported: %s", msg.payload_utf8().c_str()); } json_value_free(p_data); } else if (namespace_ == NAMESPACE_MEDIA) { json_value *p_data = json_parse(msg.payload_utf8().c_str()); std::string type((*p_data)["type"]); if (type == "MEDIA_STATUS") { json_value status = (*p_data)["status"]; msg_Dbg(p_stream, "Player state: %s sessionId:%d", status[0]["playerState"].operator const char *(), (int)(json_int_t) status[0]["mediaSessionId"]); } else if (type == "LOAD_FAILED") { msg_Err(p_stream, "Media load failed"); msgReceiverClose(appTransportId); vlc_mutex_locker locker(&lock); setConnectionStatus(CHROMECAST_CONNECTION_DEAD); } else if (type == "INVALID_REQUEST") { msg_Dbg(p_stream, "We sent an invalid request reason:%s", (*p_data)["reason"].operator const char *()); } else { msg_Warn(p_stream, "Media command not supported: %s", msg.payload_utf8().c_str()); } json_value_free(p_data); } else if (namespace_ == NAMESPACE_CONNECTION) { json_value *p_data = json_parse(msg.payload_utf8().c_str()); std::string type((*p_data)["type"]); json_value_free(p_data); if (type == "CLOSE") { msg_Warn(p_stream, "received close message"); vlc_mutex_locker locker(&lock); setConnectionStatus(CHROMECAST_CONNECTION_DEAD); } else { msg_Warn(p_stream, "Connection command not supported: %s", type.c_str()); } } else { msg_Err(p_stream, "Unknown namespace: %s", msg.namespace_().c_str()); } }
/** * @brief Process a message received from the Chromecast * @param p_stream the sout_stream_t structure * @param msg the CastMessage to process * @return 0 if the message has been successfuly processed else -1 */ static int processMessage(sout_stream_t *p_stream, const castchannel::CastMessage &msg) { int i_ret = 0; sout_stream_sys_t *p_sys = p_stream->p_sys; std::string namespace_ = msg.namespace_(); if (namespace_ == "urn:x-cast:com.google.cast.tp.deviceauth") { castchannel::DeviceAuthMessage authMessage; authMessage.ParseFromString(msg.payload_binary()); if (authMessage.has_error()) { msg_Err(p_stream, "Authentification error: %d", authMessage.error().error_type()); i_ret = -1; } else if (!authMessage.has_response()) { msg_Err(p_stream, "Authentification message has no response field"); i_ret = -1; } else { vlc_mutex_locker locker(&p_sys->lock); p_sys->i_status = CHROMECAST_AUTHENTICATED; msgConnect(p_stream, "receiver-0"); msgLaunch(p_stream); } } else if (namespace_ == "urn:x-cast:com.google.cast.tp.heartbeat") { json_value *p_data = json_parse(msg.payload_utf8().c_str()); std::string type((*p_data)["type"]); if (type == "PING") { msg_Dbg(p_stream, "PING received from the Chromecast"); msgPong(p_stream); } else if (type == "PONG") { msg_Dbg(p_stream, "PONG received from the Chromecast"); } else { msg_Err(p_stream, "Heartbeat command not supported"); i_ret = -1; } json_value_free(p_data); } else if (namespace_ == "urn:x-cast:com.google.cast.receiver") { json_value *p_data = json_parse(msg.payload_utf8().c_str()); std::string type((*p_data)["type"]); if (type == "RECEIVER_STATUS") { json_value applications = (*p_data)["status"]["applications"]; const json_value *p_app = NULL; for (unsigned i = 0; i < applications.u.array.length; ++i) { std::string appId(applications[i]["appId"]); if (appId == APP_ID) { p_app = &applications[i]; vlc_mutex_lock(&p_sys->lock); if (p_sys->appTransportId.empty()) p_sys->appTransportId = std::string(applications[i]["transportId"]); vlc_mutex_unlock(&p_sys->lock); break; } } vlc_mutex_lock(&p_sys->lock); if ( p_app ) { if (!p_sys->appTransportId.empty() && p_sys->i_status == CHROMECAST_AUTHENTICATED) { p_sys->i_status = CHROMECAST_APP_STARTED; msgConnect(p_stream, p_sys->appTransportId); msgLoad(p_stream); p_sys->i_status = CHROMECAST_MEDIA_LOAD_SENT; vlc_cond_signal(&p_sys->loadCommandCond); } } else { switch(p_sys->i_status) { /* If the app is no longer present */ case CHROMECAST_APP_STARTED: case CHROMECAST_MEDIA_LOAD_SENT: msg_Warn(p_stream, "app is no longer present. closing"); msgClose(p_stream, p_sys->appTransportId); p_sys->i_status = CHROMECAST_CONNECTION_DEAD; // ft default: break; } } vlc_mutex_unlock(&p_sys->lock); } else { msg_Err(p_stream, "Receiver command not supported: %s", msg.payload_utf8().c_str()); i_ret = -1; } json_value_free(p_data); } else if (namespace_ == "urn:x-cast:com.google.cast.media") { json_value *p_data = json_parse(msg.payload_utf8().c_str()); std::string type((*p_data)["type"]); if (type == "MEDIA_STATUS") { json_value status = (*p_data)["status"]; msg_Dbg(p_stream, "Player state: %s", status[0]["playerState"].operator const char *()); } else if (type == "LOAD_FAILED") { msg_Err(p_stream, "Media load failed"); atomic_store(&p_sys->ab_error, true); msgClose(p_stream, p_sys->appTransportId); vlc_mutex_lock(&p_sys->lock); p_sys->i_status = CHROMECAST_CONNECTION_DEAD; vlc_mutex_unlock(&p_sys->lock); } else { msg_Err(p_stream, "Media command not supported: %s", msg.payload_utf8().c_str()); i_ret = -1; } json_value_free(p_data); } else if (namespace_ == "urn:x-cast:com.google.cast.tp.connection") { json_value *p_data = json_parse(msg.payload_utf8().c_str()); std::string type((*p_data)["type"]); json_value_free(p_data); if (type == "CLOSE") { msg_Warn(p_stream, "received close message"); vlc_mutex_lock(&p_sys->lock); p_sys->i_status = CHROMECAST_CONNECTION_DEAD; vlc_mutex_unlock(&p_sys->lock); } } else { msg_Err(p_stream, "Unknown namespace: %s", msg.namespace_().c_str()); i_ret = -1; } return i_ret; }