chromecast: fix the device loading timeout
[vlc.git] / modules / stream_out / chromecast / cast.cpp
blobb5da7176be3692e79f5e608c89c31863aae0302c
1 /*****************************************************************************
2 * cast.cpp: Chromecast module for vlc
3 *****************************************************************************
4 * Copyright © 2014 VideoLAN
6 * Authors: Adrien Maglo <magsoft@videolan.org>
7 * Jean-Baptiste Kempf <jb@videolan.org>
9 * This program is free software; you can redistribute it and/or modify it
10 * under the terms of the GNU Lesser General Public License as published by
11 * the Free Software Foundation; either version 2.1 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public License
20 * along with this program; if not, write to the Free Software Foundation,
21 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22 *****************************************************************************/
24 /*****************************************************************************
25 * Preamble
26 *****************************************************************************/
28 #ifdef HAVE_CONFIG_H
29 # include "config.h"
30 #endif
32 #ifdef HAVE_POLL
33 # include <poll.h>
34 #endif
36 #ifndef __STDC_CONSTANT_MACROS
37 # define __STDC_CONSTANT_MACROS
38 #endif
40 #include <vlc_common.h>
41 #include <vlc_plugin.h>
42 #include <vlc_sout.h>
43 #include <vlc_tls.h>
44 #include <vlc_url.h>
45 #include <vlc_threads.h>
46 #include <vlc_atomic.h>
48 #include <cerrno>
50 #include <sstream>
51 #include <queue>
53 #include <google/protobuf/io/zero_copy_stream_impl.h>
54 #include <google/protobuf/io/coded_stream.h>
55 #include "cast_channel.pb.h"
57 #include "../../misc/webservices/json.h"
59 // Status
60 enum
62 CHROMECAST_DISCONNECTED,
63 CHROMECAST_TLS_CONNECTED,
64 CHROMECAST_AUTHENTICATED,
65 CHROMECAST_APP_STARTED,
66 CHROMECAST_MEDIA_LOAD_SENT,
67 CHROMECAST_CONNECTION_DEAD,
70 #define PACKET_MAX_LEN 10 * 1024
71 #define PACKET_HEADER_LEN 4
73 struct sout_stream_sys_t
75 sout_stream_sys_t()
76 : p_tls(NULL), i_requestId(0),
77 i_status(CHROMECAST_DISCONNECTED), p_out(NULL)
79 atomic_init(&ab_error, false);
82 std::string serverIP;
84 int i_sock_fd;
85 vlc_tls_creds_t *p_creds;
86 vlc_tls_t *p_tls;
88 vlc_thread_t chromecastThread;
90 unsigned i_requestId;
91 std::string appTransportId;
93 std::queue<castchannel::CastMessage> messagesToSend;
95 int i_status;
96 atomic_bool ab_error;
97 vlc_mutex_t lock;
98 vlc_cond_t loadCommandCond;
100 sout_stream_t *p_out;
103 // Media player Chromecast app id
104 #define APP_ID "CC1AD845" // Default media player
106 #define CHROMECAST_CONTROL_PORT 8009
107 #define HTTP_PORT 8010
109 #define SOUT_CFG_PREFIX "sout-chromecast-"
111 /* deadline regarding pings sent from receiver */
112 #define PING_WAIT_TIME 6000
113 #define PING_WAIT_RETRIES 0
114 /* deadline regarding pong we expect after pinging the receiver */
115 #define PONG_WAIT_TIME 500
116 #define PONG_WAIT_RETRIES 2
118 /*****************************************************************************
119 * Local prototypes
120 *****************************************************************************/
121 static int Open(vlc_object_t *);
122 static void Close(vlc_object_t *);
123 static void Clean(sout_stream_t *p_stream);
124 static int connectChromecast(sout_stream_t *p_stream, char *psz_ipChromecast);
125 static void disconnectChromecast(sout_stream_t *p_stream);
126 static int sendMessages(sout_stream_t *p_stream);
128 static void msgAuth(sout_stream_t *p_stream);
129 static void msgPing(sout_stream_t *p_stream);
130 static void msgPong(sout_stream_t *p_stream);
131 static void msgConnect(sout_stream_t *p_stream, std::string destinationId);
132 static void msgClose(sout_stream_t *p_stream, std::string destinationId);
133 static void msgLaunch(sout_stream_t *p_stream);
134 static void msgLoad(sout_stream_t *p_stream);
135 static void msgStatus(sout_stream_t *p_stream);
137 static void *chromecastThread(void *data);
139 static const char *const ppsz_sout_options[] = {
140 "ip", "http-port", "mux", "mime", NULL
143 /*****************************************************************************
144 * Module descriptor
145 *****************************************************************************/
147 #define IP_TEXT N_("Chromecast IP address")
148 #define IP_LONGTEXT N_("This sets the IP adress of the Chromecast receiver.")
149 #define HTTP_PORT_TEXT N_("HTTP port")
150 #define HTTP_PORT_LONGTEXT N_("This sets the HTTP port of the server " \
151 "used to stream the media to the Chromecast.")
152 #define MUX_TEXT N_("Muxer")
153 #define MUX_LONGTEXT N_("This sets the muxer used to stream to the Chromecast.")
154 #define MIME_TEXT N_("MIME content type")
155 #define MIME_LONGTEXT N_("This sets the media MIME content type sent to the Chromecast.")
157 vlc_module_begin ()
159 set_shortname(N_("Chromecast"))
160 set_description(N_("Chromecast stream output"))
161 set_capability("sout stream", 0)
162 add_shortcut("chromecast")
163 set_category(CAT_SOUT)
164 set_subcategory(SUBCAT_SOUT_STREAM)
165 set_callbacks(Open, Close)
167 add_string(SOUT_CFG_PREFIX "ip", "", IP_TEXT, IP_LONGTEXT, false)
168 add_integer(SOUT_CFG_PREFIX "http-port", HTTP_PORT, HTTP_PORT_TEXT, HTTP_PORT_LONGTEXT, false)
169 add_string(SOUT_CFG_PREFIX "mux", "mp4stream", MUX_TEXT, MUX_LONGTEXT, false)
170 add_string(SOUT_CFG_PREFIX "mime", "video/mp4", MIME_TEXT, MIME_LONGTEXT, false)
172 vlc_module_end ()
175 /*****************************************************************************
176 * Sout callbacks
177 *****************************************************************************/
178 static sout_stream_id_sys_t *Add(sout_stream_t *p_stream, es_format_t *p_fmt)
180 sout_stream_sys_t *p_sys = p_stream->p_sys;
181 return p_sys->p_out->pf_add(p_sys->p_out, p_fmt);
185 static int Del(sout_stream_t *p_stream, sout_stream_id_sys_t *id)
187 sout_stream_sys_t *p_sys = p_stream->p_sys;
188 return p_sys->p_out->pf_del(p_sys->p_out, id);
192 static int Send(sout_stream_t *p_stream, sout_stream_id_sys_t *id,
193 block_t *p_buffer)
195 sout_stream_sys_t *p_sys = p_stream->p_sys;
196 if (atomic_load(&p_sys->ab_error))
197 return VLC_EGENERIC;
199 return p_sys->p_out->pf_send(p_sys->p_out, id, p_buffer);
203 /*****************************************************************************
204 * Open: connect to the Chromecast and initialize the sout
205 *****************************************************************************/
206 static int Open(vlc_object_t *p_this)
208 sout_stream_t *p_stream = (sout_stream_t*)p_this;
209 sout_stream_sys_t *p_sys;
210 p_sys = new(std::nothrow) sout_stream_sys_t;
211 if (p_sys == NULL)
212 return VLC_ENOMEM;
213 p_stream->p_sys = p_sys;
215 config_ChainParse(p_stream, SOUT_CFG_PREFIX, ppsz_sout_options, p_stream->p_cfg);
217 char *psz_ipChromecast = var_GetNonEmptyString(p_stream, SOUT_CFG_PREFIX "ip");
218 if (psz_ipChromecast == NULL)
220 msg_Err(p_stream, "No Chromecast receiver IP provided");
221 Clean(p_stream);
222 return VLC_EGENERIC;
225 p_sys->i_sock_fd = connectChromecast(p_stream, psz_ipChromecast);
226 free(psz_ipChromecast);
227 if (p_sys->i_sock_fd < 0)
229 msg_Err(p_stream, "Could not connect the Chromecast");
230 Clean(p_stream);
231 return VLC_EGENERIC;
233 p_sys->i_status = CHROMECAST_TLS_CONNECTED;
235 char psz_localIP[NI_MAXNUMERICHOST];
236 if (net_GetSockAddress(p_sys->i_sock_fd, psz_localIP, NULL))
238 msg_Err(p_this, "Cannot get local IP address");
239 Clean(p_stream);
240 return VLC_EGENERIC;
242 p_sys->serverIP = psz_localIP;
244 char *psz_mux = var_GetNonEmptyString(p_stream, SOUT_CFG_PREFIX "mux");
245 if (psz_mux == NULL)
247 Clean(p_stream);
248 return VLC_EGENERIC;
250 char *psz_chain = NULL;
251 int i_bytes = asprintf(&psz_chain, "http{dst=:%u/stream,mux=%s}",
252 (unsigned)var_InheritInteger(p_stream, SOUT_CFG_PREFIX"http-port"),
253 psz_mux);
254 free(psz_mux);
255 if (i_bytes < 0)
257 Clean(p_stream);
258 return VLC_EGENERIC;
261 p_sys->p_out = sout_StreamChainNew(p_stream->p_sout, psz_chain, NULL, NULL);
262 free(psz_chain);
263 if (p_sys->p_out == NULL)
265 Clean(p_stream);
266 return VLC_EGENERIC;
269 vlc_mutex_init(&p_sys->lock);
270 vlc_cond_init(&p_sys->loadCommandCond);
272 // Start the Chromecast event thread.
273 if (vlc_clone(&p_sys->chromecastThread, chromecastThread, p_stream,
274 VLC_THREAD_PRIORITY_LOW))
276 msg_Err(p_stream, "Could not start the Chromecast talking thread");
277 Clean(p_stream);
278 return VLC_EGENERIC;
281 /* Ugly part:
282 * We want to be sure that the Chromecast receives the first data packet sent by
283 * the HTTP server. */
285 // Lock the sout thread until we have sent the media loading command to the Chromecast.
286 int i_ret = 0;
287 const mtime_t deadline = mdate() + 6 * CLOCK_FREQ;
288 vlc_mutex_lock(&p_sys->lock);
289 while (p_sys->i_status != CHROMECAST_MEDIA_LOAD_SENT)
291 i_ret = vlc_cond_timedwait(&p_sys->loadCommandCond, &p_sys->lock, deadline);
292 if (i_ret == ETIMEDOUT)
294 msg_Err(p_stream, "Timeout reached before sending the media loading command");
295 vlc_mutex_unlock(&p_sys->lock);
296 vlc_cancel(p_sys->chromecastThread);
297 Clean(p_stream);
298 return VLC_EGENERIC;
301 vlc_mutex_unlock(&p_sys->lock);
303 /* Even uglier: sleep more to let to the Chromecast initiate the connection
304 * to the http server. */
305 msleep(2 * CLOCK_FREQ);
307 // Set the sout callbacks.
308 p_stream->pf_add = Add;
309 p_stream->pf_del = Del;
310 p_stream->pf_send = Send;
312 return VLC_SUCCESS;
316 /*****************************************************************************
317 * Close: destroy interface
318 *****************************************************************************/
319 static void Close(vlc_object_t *p_this)
321 sout_stream_t *p_stream = (sout_stream_t *)p_this;
322 sout_stream_sys_t *p_sys = p_stream->p_sys;
324 vlc_cancel(p_sys->chromecastThread);
325 vlc_join(p_sys->chromecastThread, NULL);
327 switch (p_sys->i_status)
329 case CHROMECAST_MEDIA_LOAD_SENT:
330 case CHROMECAST_APP_STARTED:
331 // Generate the close messages.
332 msgClose(p_stream, p_sys->appTransportId);
333 // ft
334 case CHROMECAST_AUTHENTICATED:
335 msgClose(p_stream, "receiver-0");
336 // Send the just added close messages.
337 sendMessages(p_stream);
338 // ft
339 default:
340 break;
343 Clean(p_stream);
348 * @brief Clean and release the variables in a sout_stream_sys_t structure
350 static void Clean(sout_stream_t *p_stream)
352 sout_stream_sys_t *p_sys = p_stream->p_sys;
354 if (p_sys->p_out)
356 vlc_mutex_destroy(&p_sys->lock);
357 vlc_cond_destroy(&p_sys->loadCommandCond);
358 sout_StreamChainDelete(p_sys->p_out, p_sys->p_out);
361 disconnectChromecast(p_stream);
363 delete p_sys;
368 * @brief Connect to the Chromecast
369 * @param p_stream the sout_stream_t structure
370 * @return the opened socket file descriptor or -1 on error
372 static int connectChromecast(sout_stream_t *p_stream, char *psz_ipChromecast)
374 sout_stream_sys_t *p_sys = p_stream->p_sys;
375 int fd = net_ConnectTCP(p_stream, psz_ipChromecast, CHROMECAST_CONTROL_PORT);
376 if (fd < 0)
377 return -1;
379 p_sys->p_creds = vlc_tls_ClientCreate(VLC_OBJECT(p_stream));
380 if (p_sys->p_creds == NULL)
381 return -1;
383 p_sys->p_tls = vlc_tls_ClientSessionCreate(p_sys->p_creds, fd, psz_ipChromecast,
384 "tcps", NULL, NULL);
386 if (p_sys->p_tls == NULL)
388 vlc_tls_Delete(p_sys->p_creds);
389 return -1;
392 return fd;
397 * @brief Disconnect from the Chromecast
399 static void disconnectChromecast(sout_stream_t *p_stream)
401 sout_stream_sys_t *p_sys = p_stream->p_sys;
403 if (p_sys->p_tls)
405 vlc_tls_SessionDelete(p_sys->p_tls);
406 vlc_tls_Delete(p_sys->p_creds);
407 p_sys->p_tls = NULL;
408 p_sys->i_status = CHROMECAST_DISCONNECTED;
414 * @brief Send a message to the Chromecast
415 * @param p_stream the sout_stream_t structure
416 * @param msg the CastMessage to send
417 * @return the number of bytes sent or -1 on error
419 static int sendMessage(sout_stream_t *p_stream, castchannel::CastMessage &msg)
421 sout_stream_sys_t *p_sys = p_stream->p_sys;
423 uint32_t i_size = msg.ByteSize();
424 uint32_t i_sizeNetwork = hton32(i_size);
426 char *p_data = new(std::nothrow) char[PACKET_HEADER_LEN + i_size];
427 if (p_data == NULL)
428 return -1;
430 memcpy(p_data, &i_sizeNetwork, PACKET_HEADER_LEN);
431 msg.SerializeWithCachedSizesToArray((uint8_t *)(p_data + PACKET_HEADER_LEN));
433 int i_ret = tls_Send(p_sys->p_tls, p_data, PACKET_HEADER_LEN + i_size);
434 delete[] p_data;
436 return i_ret;
441 * @brief Send all the messages in the pending queue to the Chromecast
442 * @param p_stream the sout_stream_t structure
443 * @param msg the CastMessage to send
444 * @return the number of bytes sent or -1 on error
446 static int sendMessages(sout_stream_t *p_stream)
448 sout_stream_sys_t *p_sys = p_stream->p_sys;
450 int i_ret = 0;
451 while (!p_sys->messagesToSend.empty())
453 unsigned i_retSend = sendMessage(p_stream, p_sys->messagesToSend.front());
454 if (i_retSend <= 0)
455 return i_retSend;
457 p_sys->messagesToSend.pop();
458 i_ret += i_retSend;
461 return i_ret;
468 * @brief Receive a data packet from the Chromecast
469 * @param p_stream the sout_stream_t structure
470 * @param b_msgReceived returns true if a message has been entirely received else false
471 * @param i_payloadSize returns the payload size of the message received
472 * @return the number of bytes received of -1 on error
474 // Use here only C linkage and POD types as this function is a cancelation point.
475 extern "C" int recvPacket(sout_stream_t *p_stream, bool &b_msgReceived,
476 uint32_t &i_payloadSize, int i_sock_fd, vlc_tls_t *p_tls,
477 unsigned *pi_received, char *p_data, bool *pb_pingTimeout,
478 int *pi_wait_delay, int *pi_wait_retries)
480 struct pollfd ufd[1];
481 ufd[0].fd = i_sock_fd;
482 ufd[0].events = POLLIN;
484 /* The Chromecast normally sends a PING command every 5 seconds or so.
485 * If we do not receive one after 6 seconds, we send a PING.
486 * If after this PING, we do not receive a PONG, then we consider the
487 * connection as dead. */
488 if (poll(ufd, 1, *pi_wait_delay) == 0)
490 if (*pb_pingTimeout)
492 if (!*pi_wait_retries)
494 msg_Err(p_stream, "No PONG answer received from the Chromecast");
495 return 0; // Connection died
497 (*pi_wait_retries)--;
499 else
501 /* now expect a pong */
502 *pi_wait_delay = PONG_WAIT_TIME;
503 *pi_wait_retries = PONG_WAIT_RETRIES;
504 msg_Warn(p_stream, "No PING received from the Chromecast, sending a PING");
506 *pb_pingTimeout = true;
508 else
510 *pb_pingTimeout = false;
511 /* reset to default ping waiting */
512 *pi_wait_delay = PING_WAIT_TIME;
513 *pi_wait_retries = PING_WAIT_RETRIES;
516 int i_ret;
518 /* Packet structure:
519 * +------------------------------------+------------------------------+
520 * | Payload size (uint32_t big endian) | Payload data |
521 * +------------------------------------+------------------------------+ */
522 if (*pi_received < PACKET_HEADER_LEN)
524 // We receive the header.
525 i_ret = tls_Recv(p_tls, p_data, PACKET_HEADER_LEN - *pi_received);
526 if (i_ret <= 0)
527 return i_ret;
528 *pi_received += i_ret;
530 else
532 // We receive the payload.
534 // Get the size of the payload
535 memcpy(&i_payloadSize, p_data, PACKET_HEADER_LEN);
536 i_payloadSize = hton32(i_payloadSize);
537 const uint32_t i_maxPayloadSize = PACKET_MAX_LEN - PACKET_HEADER_LEN;
539 if (i_payloadSize > i_maxPayloadSize)
541 // Error case: the packet sent by the Chromecast is too long: we drop it.
542 msg_Err(p_stream, "Packet too long: droping its data");
544 uint32_t i_size = i_payloadSize - (*pi_received - PACKET_HEADER_LEN);
545 if (i_size > i_maxPayloadSize)
546 i_size = i_maxPayloadSize;
548 i_ret = tls_Recv(p_tls, p_data + PACKET_HEADER_LEN, i_size);
549 if (i_ret <= 0)
550 return i_ret;
551 *pi_received += i_ret;
553 if (*pi_received < i_payloadSize + PACKET_HEADER_LEN)
554 return i_ret;
556 *pi_received = 0;
557 return -1;
560 // Normal case
561 i_ret = tls_Recv(p_tls, p_data + *pi_received,
562 i_payloadSize - (*pi_received - PACKET_HEADER_LEN));
563 if (i_ret <= 0)
564 return i_ret;
565 *pi_received += i_ret;
567 if (*pi_received < i_payloadSize + PACKET_HEADER_LEN)
568 return i_ret;
570 assert(*pi_received == i_payloadSize + PACKET_HEADER_LEN);
571 *pi_received = 0;
572 b_msgReceived = true;
573 return i_ret;
576 return i_ret;
581 * @brief Process a message received from the Chromecast
582 * @param p_stream the sout_stream_t structure
583 * @param msg the CastMessage to process
584 * @return 0 if the message has been successfuly processed else -1
586 static int processMessage(sout_stream_t *p_stream, const castchannel::CastMessage &msg)
588 int i_ret = 0;
589 sout_stream_sys_t *p_sys = p_stream->p_sys;
590 std::string namespace_ = msg.namespace_();
592 if (namespace_ == "urn:x-cast:com.google.cast.tp.deviceauth")
594 castchannel::DeviceAuthMessage authMessage;
595 authMessage.ParseFromString(msg.payload_binary());
597 if (authMessage.has_error())
599 msg_Err(p_stream, "Authentification error: %d", authMessage.error().error_type());
600 i_ret = -1;
602 else if (!authMessage.has_response())
604 msg_Err(p_stream, "Authentification message has no response field");
605 i_ret = -1;
607 else
609 vlc_mutex_locker locker(&p_sys->lock);
610 p_sys->i_status = CHROMECAST_AUTHENTICATED;
611 msgConnect(p_stream, "receiver-0");
612 msgLaunch(p_stream);
615 else if (namespace_ == "urn:x-cast:com.google.cast.tp.heartbeat")
617 json_value *p_data = json_parse(msg.payload_utf8().c_str());
618 std::string type((*p_data)["type"]);
620 if (type == "PING")
622 msg_Dbg(p_stream, "PING received from the Chromecast");
623 msgPong(p_stream);
625 else if (type == "PONG")
627 msg_Dbg(p_stream, "PONG received from the Chromecast");
629 else
631 msg_Err(p_stream, "Heartbeat command not supported");
632 i_ret = -1;
635 json_value_free(p_data);
637 else if (namespace_ == "urn:x-cast:com.google.cast.receiver")
639 json_value *p_data = json_parse(msg.payload_utf8().c_str());
640 std::string type((*p_data)["type"]);
642 if (type == "RECEIVER_STATUS")
644 json_value applications = (*p_data)["status"]["applications"];
645 const json_value *p_app = NULL;
646 for (unsigned i = 0; i < applications.u.array.length; ++i)
648 std::string appId(applications[i]["appId"]);
649 if (appId == APP_ID)
651 p_app = &applications[i];
652 vlc_mutex_lock(&p_sys->lock);
653 if (p_sys->appTransportId.empty())
654 p_sys->appTransportId = std::string(applications[i]["transportId"]);
655 vlc_mutex_unlock(&p_sys->lock);
656 break;
660 vlc_mutex_lock(&p_sys->lock);
661 if ( p_app )
663 if (!p_sys->appTransportId.empty()
664 && p_sys->i_status == CHROMECAST_AUTHENTICATED)
666 p_sys->i_status = CHROMECAST_APP_STARTED;
667 msgConnect(p_stream, p_sys->appTransportId);
668 msgLoad(p_stream);
669 p_sys->i_status = CHROMECAST_MEDIA_LOAD_SENT;
670 vlc_cond_signal(&p_sys->loadCommandCond);
673 else
675 switch(p_sys->i_status)
677 /* If the app is no longer present */
678 case CHROMECAST_APP_STARTED:
679 case CHROMECAST_MEDIA_LOAD_SENT:
680 msg_Warn(p_stream, "app is no longer present. closing");
681 msgClose(p_stream, p_sys->appTransportId);
682 vlc_mutex_lock(&p_sys->lock);
683 p_sys->i_status = CHROMECAST_CONNECTION_DEAD;
684 vlc_mutex_unlock(&p_sys->lock);
685 // ft
686 default:
687 break;
691 vlc_mutex_unlock(&p_sys->lock);
693 else
695 msg_Err(p_stream, "Receiver command not supported: %s",
696 msg.payload_utf8().c_str());
697 i_ret = -1;
700 json_value_free(p_data);
702 else if (namespace_ == "urn:x-cast:com.google.cast.media")
704 json_value *p_data = json_parse(msg.payload_utf8().c_str());
705 std::string type((*p_data)["type"]);
707 if (type == "MEDIA_STATUS")
709 json_value status = (*p_data)["status"];
710 msg_Dbg(p_stream, "Player state: %s",
711 status[0]["playerState"].operator const char *());
713 else if (type == "LOAD_FAILED")
715 msg_Err(p_stream, "Media load failed");
716 atomic_store(&p_sys->ab_error, true);
717 msgClose(p_stream, p_sys->appTransportId);
718 vlc_mutex_lock(&p_sys->lock);
719 p_sys->i_status = CHROMECAST_CONNECTION_DEAD;
720 vlc_mutex_unlock(&p_sys->lock);
722 else
724 msg_Err(p_stream, "Media command not supported: %s",
725 msg.payload_utf8().c_str());
726 i_ret = -1;
729 json_value_free(p_data);
731 else if (namespace_ == "urn:x-cast:com.google.cast.tp.connection")
733 json_value *p_data = json_parse(msg.payload_utf8().c_str());
734 std::string type((*p_data)["type"]);
735 json_value_free(p_data);
737 if (type == "CLOSE")
739 msg_Warn(p_stream, "received close message");
740 vlc_mutex_lock(&p_sys->lock);
741 p_sys->i_status = CHROMECAST_CONNECTION_DEAD;
742 vlc_mutex_unlock(&p_sys->lock);
745 else
747 msg_Err(p_stream, "Unknown namespace: %s", msg.namespace_().c_str());
748 i_ret = -1;
751 return i_ret;
756 * @brief Build a CastMessage to send to the Chromecast
757 * @param namespace_ the message namespace
758 * @param payloadType the payload type (CastMessage_PayloadType_STRING or
759 * CastMessage_PayloadType_BINARY
760 * @param payload the payload
761 * @param destinationId the destination idenifier
762 * @return the generated CastMessage
764 static castchannel::CastMessage buildMessage(std::string namespace_,
765 castchannel::CastMessage_PayloadType payloadType,
766 std::string payload, std::string destinationId = "receiver-0")
768 castchannel::CastMessage msg;
770 msg.set_protocol_version(castchannel::CastMessage_ProtocolVersion_CASTV2_1_0);
771 msg.set_namespace_(namespace_);
772 msg.set_payload_type(payloadType);
773 msg.set_source_id("sender-vlc");
774 msg.set_destination_id(destinationId);
775 if (payloadType == castchannel::CastMessage_PayloadType_STRING)
776 msg.set_payload_utf8(payload);
777 else // CastMessage_PayloadType_BINARY
778 msg.set_payload_binary(payload);
780 return msg;
784 /*****************************************************************************
785 * Message preparation
786 *****************************************************************************/
787 static void msgAuth(sout_stream_t *p_stream)
789 sout_stream_sys_t *p_sys = p_stream->p_sys;
791 castchannel::DeviceAuthMessage authMessage;
792 authMessage.mutable_challenge();
793 std::string authMessageString;
794 authMessage.SerializeToString(&authMessageString);
796 castchannel::CastMessage msg = buildMessage("urn:x-cast:com.google.cast.tp.deviceauth",
797 castchannel::CastMessage_PayloadType_BINARY, authMessageString);
799 p_sys->messagesToSend.push(msg);
803 static void msgPing(sout_stream_t *p_stream)
805 sout_stream_sys_t *p_sys = p_stream->p_sys;
807 std::string s("{\"type\":\"PING\"}");
808 castchannel::CastMessage msg = buildMessage("urn:x-cast:com.google.cast.tp.heartbeat",
809 castchannel::CastMessage_PayloadType_STRING, s);
811 p_sys->messagesToSend.push(msg);
815 static void msgPong(sout_stream_t *p_stream)
817 sout_stream_sys_t *p_sys = p_stream->p_sys;
819 std::string s("{\"type\":\"PONG\"}");
820 castchannel::CastMessage msg = buildMessage("urn:x-cast:com.google.cast.tp.heartbeat",
821 castchannel::CastMessage_PayloadType_STRING, s);
823 p_sys->messagesToSend.push(msg);
827 static void msgConnect(sout_stream_t *p_stream, std::string destinationId)
829 sout_stream_sys_t *p_sys = p_stream->p_sys;
831 std::string s("{\"type\":\"CONNECT\"}");
832 castchannel::CastMessage msg = buildMessage("urn:x-cast:com.google.cast.tp.connection",
833 castchannel::CastMessage_PayloadType_STRING, s, destinationId);
835 p_sys->messagesToSend.push(msg);
839 static void msgClose(sout_stream_t *p_stream, std::string destinationId)
841 sout_stream_sys_t *p_sys = p_stream->p_sys;
843 std::string s("{\"type\":\"CLOSE\"}");
844 castchannel::CastMessage msg = buildMessage("urn:x-cast:com.google.cast.tp.connection",
845 castchannel::CastMessage_PayloadType_STRING, s, destinationId);
847 p_sys->messagesToSend.push(msg);
850 static void msgStatus(sout_stream_t *p_stream)
852 sout_stream_sys_t *p_sys = p_stream->p_sys;
854 std::stringstream ss;
855 ss << "{\"type\":\"GET_STATUS\"}";
857 castchannel::CastMessage msg = buildMessage("urn:x-cast:com.google.cast.receiver",
858 castchannel::CastMessage_PayloadType_STRING, ss.str());
860 p_sys->messagesToSend.push(msg);
863 static void msgLaunch(sout_stream_t *p_stream)
865 sout_stream_sys_t *p_sys = p_stream->p_sys;
867 std::stringstream ss;
868 ss << "{\"type\":\"LAUNCH\","
869 << "\"appId\":\"" << APP_ID << "\","
870 << "\"requestId\":" << p_stream->p_sys->i_requestId++ << "}";
872 castchannel::CastMessage msg = buildMessage("urn:x-cast:com.google.cast.receiver",
873 castchannel::CastMessage_PayloadType_STRING, ss.str());
875 p_sys->messagesToSend.push(msg);
879 static void msgLoad(sout_stream_t *p_stream)
881 sout_stream_sys_t *p_sys = p_stream->p_sys;
883 char *psz_mime = var_GetNonEmptyString(p_stream, SOUT_CFG_PREFIX "mime");
884 if (psz_mime == NULL)
885 return;
887 std::stringstream ss;
888 ss << "{\"type\":\"LOAD\","
889 << "\"media\":{\"contentId\":\"http://" << p_sys->serverIP << ":"
890 << var_InheritInteger(p_stream, SOUT_CFG_PREFIX"http-port")
891 << "/stream\","
892 << "\"streamType\":\"LIVE\","
893 << "\"contentType\":\"" << std::string(psz_mime) << "\"},"
894 << "\"requestId\":" << p_stream->p_sys->i_requestId++ << "}";
896 free(psz_mime);
898 castchannel::CastMessage msg = buildMessage("urn:x-cast:com.google.cast.media",
899 castchannel::CastMessage_PayloadType_STRING, ss.str(), p_sys->appTransportId);
901 p_sys->messagesToSend.push(msg);
905 /*****************************************************************************
906 * Chromecast thread
907 *****************************************************************************/
908 static void* chromecastThread(void* p_data)
910 int canc = vlc_savecancel();
911 // Not cancellation-safe part.
912 sout_stream_t* p_stream = (sout_stream_t*)p_data;
913 sout_stream_sys_t* p_sys = p_stream->p_sys;
915 unsigned i_received = 0;
916 char p_packet[PACKET_MAX_LEN];
917 bool b_pingTimeout = false;
919 int i_waitdelay = PING_WAIT_TIME;
920 int i_retries = PING_WAIT_RETRIES;
922 msgAuth(p_stream);
923 sendMessages(p_stream);
924 vlc_restorecancel(canc);
926 while (1)
928 bool b_msgReceived = false;
929 uint32_t i_payloadSize = 0;
930 int i_ret = recvPacket(p_stream, b_msgReceived, i_payloadSize, p_sys->i_sock_fd,
931 p_sys->p_tls, &i_received, p_packet, &b_pingTimeout,
932 &i_waitdelay, &i_retries);
934 canc = vlc_savecancel();
935 // Not cancellation-safe part.
937 if ((i_ret < 0 && errno != EAGAIN) || i_ret == 0)
939 msg_Err(p_stream, "The connection to the Chromecast died.");
940 vlc_mutex_locker locker(&p_sys->lock);
941 p_sys->i_status = CHROMECAST_CONNECTION_DEAD;
942 atomic_store(&p_sys->ab_error, true);
943 break;
946 if (b_pingTimeout)
948 msgPing(p_stream);
949 msgStatus(p_stream);
952 if (b_msgReceived)
954 castchannel::CastMessage msg;
955 msg.ParseFromArray(p_packet + PACKET_HEADER_LEN, i_payloadSize);
956 processMessage(p_stream, msg);
959 // Send the answer messages if there is any.
960 if (!p_sys->messagesToSend.empty())
962 i_ret = sendMessages(p_stream);
963 if ((i_ret < 0 && errno != EAGAIN) || i_ret == 0)
965 msg_Err(p_stream, "The connection to the Chromecast died.");
966 vlc_mutex_locker locker(&p_sys->lock);
967 p_sys->i_status = CHROMECAST_CONNECTION_DEAD;
971 vlc_mutex_lock(&p_sys->lock);
972 if ( p_sys->i_status == CHROMECAST_CONNECTION_DEAD )
974 atomic_store(&p_sys->ab_error, true);
975 vlc_mutex_unlock(&p_sys->lock);
976 break;
978 vlc_mutex_unlock(&p_sys->lock);
980 vlc_restorecancel(canc);
983 return NULL;