/***************************************************************************** * cast.cpp: Chromecast module for vlc ***************************************************************************** * Copyright © 2014 VideoLAN * * Authors: Adrien Maglo * Jean-Baptiste Kempf * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. *****************************************************************************/ /***************************************************************************** * Preamble *****************************************************************************/ #ifdef HAVE_CONFIG_H # include "config.h" #endif #ifdef HAVE_POLL # include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include "cast_channel.pb.h" #include "../../misc/webservices/json.h" // Status enum { CHROMECAST_DISCONNECTED, CHROMECAST_TLS_CONNECTED, CHROMECAST_AUTHENTICATED, CHROMECAST_APP_STARTED, CHROMECAST_MEDIA_LOAD_SENT, CHROMECAST_CONNECTION_DEAD, }; #define PACKET_MAX_LEN 10 * 1024 #define PACKET_HEADER_LEN 4 struct sout_stream_sys_t { sout_stream_sys_t() : i_status(CHROMECAST_DISCONNECTED), p_out(NULL) { atomic_init(&ab_error, false); } std::string serverIP; int i_sock_fd; vlc_tls_creds_t *p_creds; vlc_tls_t *p_tls; vlc_thread_t chromecastThread; unsigned i_requestId; std::string appTransportId; std::queue messagesToSend; int i_status; atomic_bool ab_error; vlc_mutex_t lock; vlc_cond_t loadCommandCond; sout_stream_t *p_out; }; // Media player Chromecast app id #define APP_ID "CC1AD845" // Default media player #define CHROMECAST_CONTROL_PORT 8009 #define HTTP_PORT 8010 #define SOUT_CFG_PREFIX "sout-chromecast-" /***************************************************************************** * Local prototypes *****************************************************************************/ static int Open(vlc_object_t *); static void Close(vlc_object_t *); static void Clean(sout_stream_sys_t *p_sys); static int connectChromecast(sout_stream_t *p_stream, char *psz_ipChromecast); static void disconnectChromecast(sout_stream_t *p_stream); static int sendMessages(sout_stream_t *p_stream); static void msgAuth(sout_stream_t *p_stream); static void msgPing(sout_stream_t *p_stream); static void msgPong(sout_stream_t *p_stream); static void msgConnect(sout_stream_t *p_stream, std::string destinationId); static void msgClose(sout_stream_t *p_stream, std::string destinationId); static void msgLaunch(sout_stream_t *p_stream); static void msgLoad(sout_stream_t *p_stream); static void msgStatus(sout_stream_t *p_stream); static void *chromecastThread(void *data); static const char *const ppsz_sout_options[] = { "ip", "http-port", "mux", "mime", NULL }; /***************************************************************************** * Module descriptor *****************************************************************************/ #define IP_TEXT N_("Chromecast IP address") #define IP_LONGTEXT N_("This sets the IP adress of the Chromecast receiver.") #define HTTP_PORT_TEXT N_("HTTP port") #define HTTP_PORT_LONGTEXT N_("This sets the HTTP port of the server " \ "used to stream the media to the Chromecast.") #define MUX_TEXT N_("Muxer") #define MUX_LONGTEXT N_("This sets the muxer used to stream to the Chromecast.") #define MIME_TEXT N_("MIME content type") #define MIME_LONGTEXT N_("This sets the media MIME content type sent to the Chromecast.") vlc_module_begin () set_shortname(N_("Chromecast")) set_description(N_("Chromecast stream output")) set_capability("sout stream", 0) add_shortcut("chromecast") set_category(CAT_SOUT) set_subcategory(SUBCAT_SOUT_STREAM) set_callbacks(Open, Close) add_string(SOUT_CFG_PREFIX "ip", "", IP_TEXT, IP_LONGTEXT, false) add_integer(SOUT_CFG_PREFIX "http-port", HTTP_PORT, HTTP_PORT_TEXT, HTTP_PORT_LONGTEXT, false) add_string(SOUT_CFG_PREFIX "mux", "mp4stream", MUX_TEXT, MUX_LONGTEXT, false) add_string(SOUT_CFG_PREFIX "mime", "video/mp4", MIME_TEXT, MIME_LONGTEXT, false) vlc_module_end () /***************************************************************************** * Sout callbacks *****************************************************************************/ static sout_stream_id_sys_t *Add(sout_stream_t *p_stream, es_format_t *p_fmt) { sout_stream_sys_t *p_sys = p_stream->p_sys; return p_sys->p_out->pf_add(p_sys->p_out, p_fmt); } static int Del(sout_stream_t *p_stream, sout_stream_id_sys_t *id) { sout_stream_sys_t *p_sys = p_stream->p_sys; return p_sys->p_out->pf_del(p_sys->p_out, id); } static int Send(sout_stream_t *p_stream, sout_stream_id_sys_t *id, block_t *p_buffer) { sout_stream_sys_t *p_sys = p_stream->p_sys; if (atomic_load(&p_sys->ab_error)) return VLC_EGENERIC; return p_sys->p_out->pf_send(p_sys->p_out, id, p_buffer); } /***************************************************************************** * Open: connect to the Chromecast and initialize the sout *****************************************************************************/ static int Open(vlc_object_t *p_this) { sout_stream_t *p_stream = (sout_stream_t*)p_this; sout_stream_sys_t *p_sys; p_sys = new(std::nothrow) sout_stream_sys_t; if (p_sys == NULL) return VLC_ENOMEM; p_stream->p_sys = p_sys; config_ChainParse(p_stream, SOUT_CFG_PREFIX, ppsz_sout_options, p_stream->p_cfg); char *psz_ipChromecast = var_GetNonEmptyString(p_stream, SOUT_CFG_PREFIX "ip"); if (psz_ipChromecast == NULL) { msg_Err(p_stream, "No Chromecast receiver IP provided"); Clean(p_sys); return VLC_EGENERIC; } p_sys->i_sock_fd = connectChromecast(p_stream, psz_ipChromecast); free(psz_ipChromecast); if (p_sys->i_sock_fd < 0) { msg_Err(p_stream, "Could not connect the Chromecast"); Clean(p_sys); return VLC_EGENERIC; } p_sys->i_status = CHROMECAST_TLS_CONNECTED; char psz_localIP[NI_MAXNUMERICHOST]; if (net_GetSockAddress(p_sys->i_sock_fd, psz_localIP, NULL)) { msg_Err(p_this, "Cannot get local IP address"); Clean(p_sys); return VLC_EGENERIC; } p_sys->serverIP = psz_localIP; char *psz_mux = var_GetNonEmptyString(p_stream, SOUT_CFG_PREFIX "mux"); if (psz_mux == NULL) { Clean(p_sys); return VLC_EGENERIC; } char *psz_chain = NULL; int i_bytes = asprintf(&psz_chain, "http{dst=:%u/stream,mux=%s}", (unsigned)var_InheritInteger(p_stream, SOUT_CFG_PREFIX"http-port"), psz_mux); free(psz_mux); if (i_bytes < 0) { Clean(p_sys); return VLC_EGENERIC; } p_sys->p_out = sout_StreamChainNew(p_stream->p_sout, psz_chain, NULL, NULL); free(psz_chain); if (p_sys->p_out == NULL) { Clean(p_sys); return VLC_EGENERIC; } vlc_mutex_init(&p_sys->lock); vlc_cond_init(&p_sys->loadCommandCond); // Start the Chromecast event thread. if (vlc_clone(&p_sys->chromecastThread, chromecastThread, p_stream, VLC_THREAD_PRIORITY_LOW)) { msg_Err(p_stream, "Could not start the Chromecast talking thread"); Clean(p_sys); return VLC_EGENERIC; } /* Ugly part: * We want to be sure that the Chromecast receives the first data packet sent by * the HTTP server. */ // Lock the sout thread until we have sent the media loading command to the Chromecast. int i_ret = 0; { const mtime_t deadline = mdate() + 6 * CLOCK_FREQ; vlc_mutex_locker locker(&p_sys->lock); while (p_sys->i_status != CHROMECAST_MEDIA_LOAD_SENT) i_ret = vlc_cond_timedwait(&p_sys->loadCommandCond, &p_sys->lock, deadline); } if (i_ret == ETIMEDOUT) { msg_Err(p_stream, "Timeout reached before sending the media loading command"); vlc_cancel(p_sys->chromecastThread); vlc_join(p_sys->chromecastThread, NULL); Clean(p_sys); return VLC_EGENERIC; } /* Even uglier: sleep more to let to the Chromecast initiate the connection * to the http server. */ msleep(2 * CLOCK_FREQ); // Set the sout callbacks. p_stream->pf_add = Add; p_stream->pf_del = Del; p_stream->pf_send = Send; return VLC_SUCCESS; } /***************************************************************************** * Close: destroy interface *****************************************************************************/ static void Close(vlc_object_t *p_this) { sout_stream_t *p_stream = (sout_stream_t *)p_this; sout_stream_sys_t *p_sys = p_stream->p_sys; vlc_cancel(p_sys->chromecastThread); vlc_join(p_sys->chromecastThread, NULL); switch (p_sys->i_status) { case CHROMECAST_MEDIA_LOAD_SENT: case CHROMECAST_APP_STARTED: // Generate the close messages. msgClose(p_stream, p_sys->appTransportId); // ft case CHROMECAST_AUTHENTICATED: msgClose(p_stream, "receiver-0"); // Send the just added close messages. sendMessages(p_stream); // ft case CHROMECAST_TLS_CONNECTED: case CHROMECAST_CONNECTION_DEAD: disconnectChromecast(p_stream); p_sys->i_status = CHROMECAST_DISCONNECTED; // ft default: break; } Clean(p_sys); } /** * @brief Clean and release the variables in a sout_stream_sys_t structure */ static void Clean(sout_stream_sys_t *p_sys) { if (p_sys->p_out) { vlc_mutex_destroy(&p_sys->lock); vlc_cond_destroy(&p_sys->loadCommandCond); sout_StreamChainDelete(p_sys->p_out, p_sys->p_out); } delete p_sys; } /** * @brief Connect to the Chromecast * @param p_stream the sout_stream_t structure * @return the opened socket file descriptor or -1 on error */ static int connectChromecast(sout_stream_t *p_stream, char *psz_ipChromecast) { sout_stream_sys_t *p_sys = p_stream->p_sys; int fd = net_ConnectTCP(p_stream, psz_ipChromecast, CHROMECAST_CONTROL_PORT); if (fd < 0) return -1; p_sys->p_creds = vlc_tls_ClientCreate(VLC_OBJECT(p_stream)); if (p_sys->p_creds == NULL) return -1; p_sys->p_tls = vlc_tls_ClientSessionCreate(p_sys->p_creds, fd, psz_ipChromecast, "tcps", NULL, NULL); if (p_sys->p_tls == NULL) { vlc_tls_Delete(p_sys->p_creds); return -1; } return fd; } /** * @brief Disconnect from the Chromecast */ static void disconnectChromecast(sout_stream_t *p_stream) { sout_stream_sys_t *p_sys = p_stream->p_sys; if (p_sys->p_tls) { vlc_tls_SessionDelete(p_sys->p_tls); vlc_tls_Delete(p_sys->p_creds); p_sys->p_tls = NULL; } } /** * @brief Send a message to the Chromecast * @param p_stream the sout_stream_t structure * @param msg the CastMessage to send * @return the number of bytes sent or -1 on error */ static int sendMessage(sout_stream_t *p_stream, castchannel::CastMessage &msg) { sout_stream_sys_t *p_sys = p_stream->p_sys; 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_sys->p_tls, p_data, PACKET_HEADER_LEN + i_size); delete[] p_data; return i_ret; } /** * @brief Send all the messages in the pending queue to the Chromecast * @param p_stream the sout_stream_t structure * @param msg the CastMessage to send * @return the number of bytes sent or -1 on error */ static int sendMessages(sout_stream_t *p_stream) { sout_stream_sys_t *p_sys = p_stream->p_sys; int i_ret = 0; while (!p_sys->messagesToSend.empty()) { unsigned i_retSend = sendMessage(p_stream, p_sys->messagesToSend.front()); if (i_retSend <= 0) return i_retSend; p_sys->messagesToSend.pop(); i_ret += i_retSend; } return i_ret; } /** * @brief Receive a data packet from the Chromecast * @param p_stream the sout_stream_t structure * @param b_msgReceived returns true if a message has been entirely received else false * @param i_payloadSize returns the payload size of the message received * @return the number of bytes received of -1 on error */ // Use here only C linkage and POD types as this function is a cancelation point. extern "C" int recvPacket(sout_stream_t *p_stream, bool &b_msgReceived, uint32_t &i_payloadSize, int i_sock_fd, vlc_tls_t *p_tls, unsigned *pi_received, char *p_data, bool *pb_pingTimeout) { struct pollfd ufd[1]; ufd[0].fd = i_sock_fd; ufd[0].events = POLLIN; /* The Chromecast normally sends a PING command every 5 seconds or so. * If we do not receive one after 6 seconds, we send a PING. * If after this PING, we do not receive a PONG, then we consider the * connection as dead. */ if (poll(ufd, 1, 6000) == 0) { if (*pb_pingTimeout) { msg_Err(p_stream, "No PONG answer received from the Chromecast"); return 0; // Connection died } msg_Warn(p_stream, "No PING received from the Chromecast, sending a PING"); *pb_pingTimeout = true; } else *pb_pingTimeout = false; int i_ret; /* Packet structure: * +------------------------------------+------------------------------+ * | Payload size (uint32_t big endian) | Payload data | * +------------------------------------+------------------------------+ */ if (*pi_received < PACKET_HEADER_LEN) { // We receive the header. i_ret = tls_Recv(p_tls, p_data, PACKET_HEADER_LEN - *pi_received); if (i_ret <= 0) return i_ret; *pi_received += i_ret; } else { // We receive the payload. // Get the size of the payload memcpy(&i_payloadSize, p_data, PACKET_HEADER_LEN); i_payloadSize = hton32(i_payloadSize); const uint32_t i_maxPayloadSize = PACKET_MAX_LEN - PACKET_HEADER_LEN; if (i_payloadSize > i_maxPayloadSize) { // Error case: the packet sent by the Chromecast is too long: we drop it. msg_Err(p_stream, "Packet too long: droping its data"); uint32_t i_size = i_payloadSize - (*pi_received - PACKET_HEADER_LEN); if (i_size > i_maxPayloadSize) i_size = i_maxPayloadSize; i_ret = tls_Recv(p_tls, p_data + PACKET_HEADER_LEN, i_size); if (i_ret <= 0) return i_ret; *pi_received += i_ret; if (*pi_received < i_payloadSize + PACKET_HEADER_LEN) return i_ret; *pi_received = 0; return -1; } // Normal case i_ret = tls_Recv(p_tls, p_data + *pi_received, i_payloadSize - (*pi_received - PACKET_HEADER_LEN)); if (i_ret <= 0) return i_ret; *pi_received += i_ret; if (*pi_received < i_payloadSize + PACKET_HEADER_LEN) return i_ret; assert(*pi_received == i_payloadSize + PACKET_HEADER_LEN); *pi_received = 0; b_msgReceived = true; return i_ret; } return i_ret; } /** * @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); i_ret = -1; // 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); } 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; } /** * @brief Build a CastMessage to send to the Chromecast * @param namespace_ the message namespace * @param payloadType the payload type (CastMessage_PayloadType_STRING or * CastMessage_PayloadType_BINARY * @param payload the payload * @param destinationId the destination idenifier * @return the generated CastMessage */ static castchannel::CastMessage buildMessage(std::string namespace_, castchannel::CastMessage_PayloadType payloadType, std::string payload, std::string destinationId = "receiver-0") { castchannel::CastMessage msg; msg.set_protocol_version(castchannel::CastMessage_ProtocolVersion_CASTV2_1_0); msg.set_namespace_(namespace_); msg.set_payload_type(payloadType); msg.set_source_id("sender-vlc"); msg.set_destination_id(destinationId); if (payloadType == castchannel::CastMessage_PayloadType_STRING) msg.set_payload_utf8(payload); else // CastMessage_PayloadType_BINARY msg.set_payload_binary(payload); return msg; } /***************************************************************************** * Message preparation *****************************************************************************/ static void msgAuth(sout_stream_t *p_stream) { sout_stream_sys_t *p_sys = p_stream->p_sys; castchannel::DeviceAuthMessage authMessage; authMessage.mutable_challenge(); std::string authMessageString; authMessage.SerializeToString(&authMessageString); castchannel::CastMessage msg = buildMessage("urn:x-cast:com.google.cast.tp.deviceauth", castchannel::CastMessage_PayloadType_BINARY, authMessageString); p_sys->messagesToSend.push(msg); } static void msgPing(sout_stream_t *p_stream) { sout_stream_sys_t *p_sys = p_stream->p_sys; std::string s("{\"type\":\"PING\"}"); castchannel::CastMessage msg = buildMessage("urn:x-cast:com.google.cast.tp.heartbeat", castchannel::CastMessage_PayloadType_STRING, s); p_sys->messagesToSend.push(msg); } static void msgPong(sout_stream_t *p_stream) { sout_stream_sys_t *p_sys = p_stream->p_sys; std::string s("{\"type\":\"PONG\"}"); castchannel::CastMessage msg = buildMessage("urn:x-cast:com.google.cast.tp.heartbeat", castchannel::CastMessage_PayloadType_STRING, s); p_sys->messagesToSend.push(msg); } static void msgConnect(sout_stream_t *p_stream, std::string destinationId) { sout_stream_sys_t *p_sys = p_stream->p_sys; std::string s("{\"type\":\"CONNECT\"}"); castchannel::CastMessage msg = buildMessage("urn:x-cast:com.google.cast.tp.connection", castchannel::CastMessage_PayloadType_STRING, s, destinationId); p_sys->messagesToSend.push(msg); } static void msgClose(sout_stream_t *p_stream, std::string destinationId) { sout_stream_sys_t *p_sys = p_stream->p_sys; std::string s("{\"type\":\"CLOSE\"}"); castchannel::CastMessage msg = buildMessage("urn:x-cast:com.google.cast.tp.connection", castchannel::CastMessage_PayloadType_STRING, s, destinationId); p_sys->messagesToSend.push(msg); } static void msgStatus(sout_stream_t *p_stream) { sout_stream_sys_t *p_sys = p_stream->p_sys; std::stringstream ss; ss << "{\"type\":\"GET_STATUS\"}"; castchannel::CastMessage msg = buildMessage("urn:x-cast:com.google.cast.receiver", castchannel::CastMessage_PayloadType_STRING, ss.str()); p_sys->messagesToSend.push(msg); } static void msgLaunch(sout_stream_t *p_stream) { sout_stream_sys_t *p_sys = p_stream->p_sys; std::stringstream ss; ss << "{\"type\":\"LAUNCH\"," << "\"appId\":\"" << APP_ID << "\"," << "\"requestId\":" << p_stream->p_sys->i_requestId++ << "}"; castchannel::CastMessage msg = buildMessage("urn:x-cast:com.google.cast.receiver", castchannel::CastMessage_PayloadType_STRING, ss.str()); p_sys->messagesToSend.push(msg); } static void msgLoad(sout_stream_t *p_stream) { sout_stream_sys_t *p_sys = p_stream->p_sys; char *psz_mime = var_GetNonEmptyString(p_stream, SOUT_CFG_PREFIX "mime"); if (psz_mime == NULL) return; std::stringstream ss; ss << "{\"type\":\"LOAD\"," << "\"media\":{\"contentId\":\"http://" << p_sys->serverIP << ":" << var_InheritInteger(p_stream, SOUT_CFG_PREFIX"http-port") << "/stream\"," << "\"streamType\":\"LIVE\"," << "\"contentType\":\"" << std::string(psz_mime) << "\"}," << "\"requestId\":" << p_stream->p_sys->i_requestId++ << "}"; free(psz_mime); castchannel::CastMessage msg = buildMessage("urn:x-cast:com.google.cast.media", castchannel::CastMessage_PayloadType_STRING, ss.str(), p_sys->appTransportId); p_sys->messagesToSend.push(msg); } /***************************************************************************** * Chromecast thread *****************************************************************************/ static void* chromecastThread(void* p_data) { int canc = vlc_savecancel(); // Not cancellation-safe part. sout_stream_t* p_stream = (sout_stream_t*)p_data; sout_stream_sys_t* p_sys = p_stream->p_sys; unsigned i_received = 0; char p_packet[PACKET_MAX_LEN]; bool b_pingTimeout = false; msgAuth(p_stream); sendMessages(p_stream); vlc_restorecancel(canc); while (1) { bool b_msgReceived = false; uint32_t i_payloadSize = 0; int i_ret = recvPacket(p_stream, b_msgReceived, i_payloadSize, p_sys->i_sock_fd, p_sys->p_tls, &i_received, p_packet, &b_pingTimeout); canc = vlc_savecancel(); // Not cancellation-safe part. if ((i_ret < 0 && errno != EAGAIN) || i_ret == 0) { msg_Err(p_stream, "The connection to the Chromecast died."); vlc_mutex_locker locker(&p_sys->lock); p_sys->i_status = CHROMECAST_CONNECTION_DEAD; atomic_store(&p_sys->ab_error, true); break; } if (b_pingTimeout) { msgPing(p_stream); msgStatus(p_stream); } if (b_msgReceived) { castchannel::CastMessage msg; msg.ParseFromArray(p_packet + PACKET_HEADER_LEN, i_payloadSize); processMessage(p_stream, msg); } // Send the answer messages if there is any. if (!p_sys->messagesToSend.empty()) { i_ret = sendMessages(p_stream); if ((i_ret < 0 && errno != EAGAIN) || i_ret == 0) { msg_Err(p_stream, "The connection to the Chromecast died."); vlc_mutex_locker locker(&p_sys->lock); p_sys->i_status = CHROMECAST_CONNECTION_DEAD; } } vlc_mutex_lock(&p_sys->lock); if ( p_sys->i_status == CHROMECAST_CONNECTION_DEAD ) { atomic_store(&p_sys->ab_error, true); vlc_mutex_unlock(&p_sys->lock); break; } vlc_mutex_unlock(&p_sys->lock); vlc_restorecancel(canc); } return NULL; }