chromecast: start the communication when the control class is created
[vlc.git] / modules / stream_out / chromecast / chromecast_ctrl.cpp
1 /*****************************************************************************
2  * chromecast_ctrl.cpp: Chromecast module for vlc
3  *****************************************************************************
4  * Copyright © 2014-2015 VideoLAN
5  *
6  * Authors: Adrien Maglo <magsoft@videolan.org>
7  *          Jean-Baptiste Kempf <jb@videolan.org>
8  *          Steve Lhomme <robux4@videolabs.io>
9  *
10  * This program is free software; you can redistribute it and/or modify it
11  * under the terms of the GNU Lesser General Public License as published by
12  * the Free Software Foundation; either version 2.1 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18  * GNU Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public License
21  * along with this program; if not, write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24
25 /*****************************************************************************
26  * Preamble
27  *****************************************************************************/
28
29 #ifdef HAVE_CONFIG_H
30 # include "config.h"
31 #endif
32
33 #include "chromecast.h"
34
35 #include <cassert>
36 #include <cerrno>
37 #ifdef HAVE_POLL
38 # include <poll.h>
39 #endif
40
41 #include "../../misc/webservices/json.h"
42
43 #define PACKET_MAX_LEN 10 * 1024
44
45 // Media player Chromecast app id
46 #define APP_ID "CC1AD845" // Default media player aka DEFAULT_MEDIA_RECEIVER_APPLICATION_ID
47
48 static const int CHROMECAST_CONTROL_PORT = 8009;
49
50 /* deadline regarding pings sent from receiver */
51 #define PING_WAIT_TIME 6000
52 #define PING_WAIT_RETRIES 0
53 /* deadline regarding pong we expect after pinging the receiver */
54 #define PONG_WAIT_TIME 500
55 #define PONG_WAIT_RETRIES 2
56
57 #define CONTROL_CFG_PREFIX "chromecast-"
58
59 static const std::string NAMESPACE_DEVICEAUTH       = "urn:x-cast:com.google.cast.tp.deviceauth";
60 static const std::string NAMESPACE_CONNECTION       = "urn:x-cast:com.google.cast.tp.connection";
61 static const std::string NAMESPACE_HEARTBEAT        = "urn:x-cast:com.google.cast.tp.heartbeat";
62 static const std::string NAMESPACE_RECEIVER         = "urn:x-cast:com.google.cast.receiver";
63
64 /**
65  * @brief Build a CastMessage to send to the Chromecast
66  * @param namespace_ the message namespace
67  * @param payloadType the payload type (CastMessage_PayloadType_STRING or
68  * CastMessage_PayloadType_BINARY
69  * @param payload the payload
70  * @param destinationId the destination idenifier
71  * @return the generated CastMessage
72  */
73 void intf_sys_t::buildMessage(const std::string & namespace_,
74                               const std::string & payload,
75                               const std::string & destinationId,
76                               castchannel::CastMessage_PayloadType payloadType)
77 {
78     castchannel::CastMessage msg;
79
80     msg.set_protocol_version(castchannel::CastMessage_ProtocolVersion_CASTV2_1_0);
81     msg.set_namespace_(namespace_);
82     msg.set_payload_type(payloadType);
83     msg.set_source_id("sender-vlc");
84     msg.set_destination_id(destinationId);
85     if (payloadType == castchannel::CastMessage_PayloadType_STRING)
86         msg.set_payload_utf8(payload);
87     else // CastMessage_PayloadType_BINARY
88         msg.set_payload_binary(payload);
89
90     sendMessage(msg);
91 }
92
93 /*****************************************************************************
94  * intf_sys_t: class definition
95  *****************************************************************************/
96 intf_sys_t::intf_sys_t(vlc_object_t * const p_this, int port, std::string device_addr, int device_port)
97  : p_module(p_this)
98  , i_port(port)
99  , i_target_port(device_port)
100  , targetIP(device_addr)
101  , receiverState(RECEIVER_IDLE)
102  , i_sock_fd(-1)
103  , p_creds(NULL)
104  , p_tls(NULL)
105  , conn_status(CHROMECAST_DISCONNECTED)
106  , cmd_status(NO_CMD_PENDING)
107  , i_receiver_requestId(0)
108  , i_requestId(0)
109 {
110     vlc_mutex_init(&lock);
111     vlc_cond_init(&loadCommandCond);
112
113     // Start the Chromecast event thread.
114     if (vlc_clone(&chromecastThread, ChromecastThread, this,
115                   VLC_THREAD_PRIORITY_LOW))
116     {
117         msg_Err( p_module, "Could not start the Chromecast talking thread");
118     }
119 }
120
121 intf_sys_t::~intf_sys_t()
122 {
123     switch (getConnectionStatus())
124     {
125     case CHROMECAST_APP_STARTED:
126         // Generate the close messages.
127         msgReceiverClose(appTransportId);
128         // ft
129     case CHROMECAST_AUTHENTICATED:
130         msgReceiverClose(DEFAULT_CHOMECAST_RECEIVER);
131         // ft
132     default:
133         break;
134     }
135
136     vlc_cancel(chromecastThread);
137     vlc_join(chromecastThread, NULL);
138
139     disconnectChromecast();
140
141     vlc_cond_destroy(&loadCommandCond);
142     vlc_mutex_destroy(&lock);
143 }
144
145 /**
146  * @brief Connect to the Chromecast
147  * @return the opened socket file descriptor or -1 on error
148  */
149 int intf_sys_t::connectChromecast()
150 {
151     unsigned devicePort = i_target_port;
152     if (devicePort == 0)
153         devicePort = CHROMECAST_CONTROL_PORT;
154     int fd = net_ConnectTCP( p_module, targetIP.c_str(), devicePort);
155     if (fd < 0)
156         return -1;
157
158     p_creds = vlc_tls_ClientCreate( p_module->p_parent );
159     if (p_creds == NULL)
160     {
161         net_Close(fd);
162         return -1;
163     }
164
165     p_tls = vlc_tls_ClientSessionCreateFD(p_creds, fd, targetIP.c_str(),
166                                                "tcps", NULL, NULL);
167
168     if (p_tls == NULL)
169     {
170         net_Close(fd);
171         vlc_tls_Delete(p_creds);
172         return -1;
173     }
174
175     return fd;
176 }
177
178
179 /**
180  * @brief Disconnect from the Chromecast
181  */
182 void intf_sys_t::disconnectChromecast()
183 {
184     if (p_tls)
185     {
186         vlc_tls_Close(p_tls);
187         vlc_tls_Delete(p_creds);
188         p_tls = NULL;
189         setConnectionStatus(CHROMECAST_DISCONNECTED);
190         appTransportId = "";
191         mediaSessionId = ""; // this session is not valid anymore
192         setPlayerStatus(NO_CMD_PENDING);
193         receiverState = RECEIVER_IDLE;
194     }
195 }
196
197
198 /**
199  * @brief Receive a data packet from the Chromecast
200  * @param p_module the module to log with
201  * @param b_msgReceived returns true if a message has been entirely received else false
202  * @param i_payloadSize returns the payload size of the message received
203  * @return the number of bytes received of -1 on error
204  */
205 // Use here only C linkage and POD types as this function is a cancelation point.
206 extern "C" int recvPacket(vlc_object_t *p_module, bool &b_msgReceived,
207                           uint32_t &i_payloadSize, int i_sock_fd, vlc_tls_t *p_tls,
208                           unsigned *pi_received, uint8_t *p_data, bool *pb_pingTimeout,
209                           int *pi_wait_delay, int *pi_wait_retries)
210 {
211     struct pollfd ufd[1];
212     ufd[0].fd = i_sock_fd;
213     ufd[0].events = POLLIN;
214
215     /* The Chromecast normally sends a PING command every 5 seconds or so.
216      * If we do not receive one after 6 seconds, we send a PING.
217      * If after this PING, we do not receive a PONG, then we consider the
218      * connection as dead. */
219     if (poll(ufd, 1, *pi_wait_delay) == 0)
220     {
221         if (*pb_pingTimeout)
222         {
223             if (!*pi_wait_retries)
224             {
225                 msg_Err( p_module, "No PONG answer received from the Chromecast");
226                 return 0; // Connection died
227             }
228             (*pi_wait_retries)--;
229         }
230         else
231         {
232             /* now expect a pong */
233             *pi_wait_delay = PONG_WAIT_TIME;
234             *pi_wait_retries = PONG_WAIT_RETRIES;
235             msg_Warn( p_module, "No PING received from the Chromecast, sending a PING");
236         }
237         *pb_pingTimeout = true;
238     }
239     else
240     {
241         *pb_pingTimeout = false;
242         /* reset to default ping waiting */
243         *pi_wait_delay = PING_WAIT_TIME;
244         *pi_wait_retries = PING_WAIT_RETRIES;
245     }
246
247     int i_ret;
248
249     /* Packet structure:
250      * +------------------------------------+------------------------------+
251      * | Payload size (uint32_t big endian) |         Payload data         |
252      * +------------------------------------+------------------------------+ */
253     while (*pi_received < PACKET_HEADER_LEN)
254     {
255         // We receive the header.
256         i_ret = tls_Recv(p_tls, p_data + *pi_received, PACKET_HEADER_LEN - *pi_received);
257         if (i_ret <= 0)
258             return i_ret;
259         *pi_received += i_ret;
260     }
261
262     // We receive the payload.
263
264     // Get the size of the payload
265     i_payloadSize = U32_AT( p_data );
266     const uint32_t i_maxPayloadSize = PACKET_MAX_LEN - PACKET_HEADER_LEN;
267
268     if (i_payloadSize > i_maxPayloadSize)
269     {
270         // Error case: the packet sent by the Chromecast is too long: we drop it.
271         msg_Err( p_module, "Packet too long: droping its data");
272
273         uint32_t i_size = i_payloadSize - (*pi_received - PACKET_HEADER_LEN);
274         if (i_size > i_maxPayloadSize)
275             i_size = i_maxPayloadSize;
276
277         i_ret = tls_Recv(p_tls, p_data + PACKET_HEADER_LEN, i_size);
278         if (i_ret <= 0)
279             return i_ret;
280         *pi_received += i_ret;
281
282         if (*pi_received < i_payloadSize + PACKET_HEADER_LEN)
283             return i_ret;
284
285         *pi_received = 0;
286         return -1;
287     }
288
289     // Normal case
290     i_ret = tls_Recv(p_tls, p_data + *pi_received,
291                      i_payloadSize - (*pi_received - PACKET_HEADER_LEN));
292     if (i_ret <= 0)
293         return i_ret;
294     *pi_received += i_ret;
295
296     if (*pi_received < i_payloadSize + PACKET_HEADER_LEN)
297         return i_ret;
298
299     assert(*pi_received == i_payloadSize + PACKET_HEADER_LEN);
300     *pi_received = 0;
301     b_msgReceived = true;
302     return i_ret;
303 }
304
305 /**
306  * @brief Process a message received from the Chromecast
307  * @param msg the CastMessage to process
308  * @return 0 if the message has been successfuly processed else -1
309  */
310 void intf_sys_t::processMessage(const castchannel::CastMessage &msg)
311 {
312     const std::string & namespace_ = msg.namespace_();
313
314 #ifndef NDEBUG
315     msg_Dbg( p_module, "processMessage: %s->%s %s", namespace_.c_str(), msg.destination_id().c_str(), msg.payload_utf8().c_str());
316 #endif
317
318     if (namespace_ == NAMESPACE_DEVICEAUTH)
319     {
320         castchannel::DeviceAuthMessage authMessage;
321         authMessage.ParseFromString(msg.payload_binary());
322
323         if (authMessage.has_error())
324         {
325             msg_Err( p_module, "Authentification error: %d", authMessage.error().error_type());
326         }
327         else if (!authMessage.has_response())
328         {
329             msg_Err( p_module, "Authentification message has no response field");
330         }
331         else
332         {
333             vlc_mutex_locker locker(&lock);
334             setConnectionStatus(CHROMECAST_AUTHENTICATED);
335             msgConnect(DEFAULT_CHOMECAST_RECEIVER);
336             msgReceiverGetStatus();
337         }
338     }
339     else if (namespace_ == NAMESPACE_HEARTBEAT)
340     {
341         json_value *p_data = json_parse(msg.payload_utf8().c_str());
342         std::string type((*p_data)["type"]);
343
344         if (type == "PING")
345         {
346             msg_Dbg( p_module, "PING received from the Chromecast");
347             msgPong();
348         }
349         else if (type == "PONG")
350         {
351             msg_Dbg( p_module, "PONG received from the Chromecast");
352         }
353         else
354         {
355             msg_Warn( p_module, "Heartbeat command not supported: %s", type.c_str());
356         }
357
358         json_value_free(p_data);
359     }
360     else if (namespace_ == NAMESPACE_RECEIVER)
361     {
362         json_value *p_data = json_parse(msg.payload_utf8().c_str());
363         std::string type((*p_data)["type"]);
364
365         if (type == "RECEIVER_STATUS")
366         {
367             json_value applications = (*p_data)["status"]["applications"];
368             const json_value *p_app = NULL;
369
370             vlc_mutex_locker locker(&lock);
371             for (unsigned i = 0; i < applications.u.array.length; ++i)
372             {
373                 std::string appId(applications[i]["appId"]);
374                 if (appId == APP_ID)
375                 {
376                     const char *pz_transportId = applications[i]["transportId"];
377                     if (pz_transportId != NULL)
378                     {
379                         appTransportId = std::string(pz_transportId);
380                         p_app = &applications[i];
381                     }
382                     break;
383                 }
384             }
385
386             if ( p_app )
387             {
388                 if (!appTransportId.empty()
389                         && getConnectionStatus() == CHROMECAST_AUTHENTICATED)
390                 {
391                     msgConnect(appTransportId);
392                     setConnectionStatus(CHROMECAST_APP_STARTED);
393                     msgPlayerLoad();
394                     setPlayerStatus(CMD_LOAD_SENT);
395                     vlc_cond_signal(&loadCommandCond);
396                 }
397             }
398             else
399             {
400                 switch(getConnectionStatus())
401                 {
402                 /* If the app is no longer present */
403                 case CHROMECAST_APP_STARTED:
404                     msg_Warn( p_module, "app is no longer present. closing");
405                     msgReceiverClose(appTransportId);
406                     setConnectionStatus(CHROMECAST_CONNECTION_DEAD);
407                     break;
408
409                 case CHROMECAST_AUTHENTICATED:
410                     msg_Dbg( p_module, "Chromecast was running no app, launch media_app");
411                     appTransportId = "";
412                     mediaSessionId = ""; // this session is not valid anymore
413                     receiverState = RECEIVER_IDLE;
414                     msgReceiverLaunchApp();
415                     break;
416
417                 default:
418                     break;
419                 }
420
421             }
422         }
423         else if (type == "LAUNCH_ERROR")
424         {
425             json_value reason = (*p_data)["reason"];
426             msg_Err( p_module, "Failed to start the MediaPlayer: %s",
427                     (const char *)reason);
428         }
429         else
430         {
431             msg_Warn( p_module, "Receiver command not supported: %s",
432                     msg.payload_utf8().c_str());
433         }
434
435         json_value_free(p_data);
436     }
437     else if (namespace_ == NAMESPACE_MEDIA)
438     {
439         json_value *p_data = json_parse(msg.payload_utf8().c_str());
440         std::string type((*p_data)["type"]);
441
442         if (type == "MEDIA_STATUS")
443         {
444             json_value status = (*p_data)["status"];
445             msg_Dbg( p_module, "Player state: %s sessionId:%d",
446                     status[0]["playerState"].operator const char *(),
447                     (int)(json_int_t) status[0]["mediaSessionId"]);
448
449             vlc_mutex_locker locker(&lock);
450             receiver_state oldPlayerState = receiverState;
451             std::string newPlayerState = status[0]["playerState"].operator const char *();
452
453             if (newPlayerState == "IDLE")
454                 receiverState = RECEIVER_IDLE;
455             else if (newPlayerState == "PLAYING")
456                 receiverState = RECEIVER_PLAYING;
457             else if (newPlayerState == "BUFFERING")
458                 receiverState = RECEIVER_BUFFERING;
459             else if (newPlayerState == "PAUSED")
460                 receiverState = RECEIVER_PAUSED;
461             else if (!newPlayerState.empty())
462                 msg_Warn( p_module, "Unknown Chromecast state %s", newPlayerState.c_str());
463
464             if (receiverState == RECEIVER_IDLE)
465                 mediaSessionId = ""; // this session is not valid anymore
466             else
467             {
468                 char session_id[32];
469                 if( snprintf( session_id, sizeof(session_id), "%" PRId64, (json_int_t) status[0]["mediaSessionId"] ) >= (int)sizeof(session_id) )
470                 {
471                     msg_Err( p_module, "snprintf() truncated string for mediaSessionId" );
472                     session_id[sizeof(session_id) - 1] = '\0';
473                 }
474                 if (!mediaSessionId.empty() && session_id[0] && mediaSessionId != session_id) {
475                     msg_Warn( p_module, "different mediaSessionId detected %s was %s", mediaSessionId.c_str(), this->mediaSessionId.c_str());
476                 }
477
478                 mediaSessionId = session_id;
479             }
480
481             if (receiverState != oldPlayerState)
482             {
483 #ifndef NDEBUG
484                 msg_Dbg( p_module, "change Chromecast player state from %d to %d", oldPlayerState, receiverState);
485 #endif
486                 switch( receiverState )
487                 {
488                 case RECEIVER_PLAYING:
489                     /* TODO reset demux PCR ? */
490                     setPlayerStatus(CMD_PLAYBACK_SENT);
491                     break;
492
493                 case RECEIVER_PAUSED:
494 #ifndef NDEBUG
495                     msg_Dbg( p_module, "Playback paused");
496 #endif
497                     break;
498
499                 case RECEIVER_IDLE:
500                     setPlayerStatus(NO_CMD_PENDING);
501                     break;
502                 }
503             }
504         }
505         else if (type == "LOAD_FAILED")
506         {
507             msg_Err( p_module, "Media load failed");
508             vlc_mutex_locker locker(&lock);
509             /* close the app to restart it */
510             if (getConnectionStatus() == CHROMECAST_APP_STARTED)
511                 msgReceiverClose(appTransportId);
512             else
513                 msgReceiverGetStatus();
514         }
515         else if (type == "INVALID_REQUEST")
516         {
517             msg_Dbg( p_module, "We sent an invalid request reason:%s", (*p_data)["reason"].operator const char *());
518         }
519         else
520         {
521             msg_Warn( p_module, "Media command not supported: %s",
522                     msg.payload_utf8().c_str());
523         }
524
525         json_value_free(p_data);
526     }
527     else if (namespace_ == NAMESPACE_CONNECTION)
528     {
529         json_value *p_data = json_parse(msg.payload_utf8().c_str());
530         std::string type((*p_data)["type"]);
531         json_value_free(p_data);
532
533         if (type == "CLOSE")
534         {
535             msg_Warn( p_module, "received close message");
536             vlc_mutex_locker locker(&lock);
537             setConnectionStatus(CHROMECAST_CONNECTION_DEAD);
538         }
539         else
540         {
541             msg_Warn( p_module, "Connection command not supported: %s",
542                     type.c_str());
543         }
544     }
545     else
546     {
547         msg_Err( p_module, "Unknown namespace: %s", msg.namespace_().c_str());
548     }
549 }
550
551 /*****************************************************************************
552  * Message preparation
553  *****************************************************************************/
554 void intf_sys_t::msgAuth()
555 {
556     castchannel::DeviceAuthMessage authMessage;
557     authMessage.mutable_challenge();
558
559     buildMessage(NAMESPACE_DEVICEAUTH, authMessage.SerializeAsString(),
560                  DEFAULT_CHOMECAST_RECEIVER, castchannel::CastMessage_PayloadType_BINARY);
561 }
562
563
564 void intf_sys_t::msgPing()
565 {
566     std::string s("{\"type\":\"PING\"}");
567     buildMessage( NAMESPACE_HEARTBEAT, s );
568 }
569
570
571 void intf_sys_t::msgPong()
572 {
573     std::string s("{\"type\":\"PONG\"}");
574     buildMessage( NAMESPACE_HEARTBEAT, s );
575 }
576
577 void intf_sys_t::msgConnect(const std::string & destinationId)
578 {
579     std::string s("{\"type\":\"CONNECT\"}");
580     buildMessage( NAMESPACE_CONNECTION, s, destinationId );
581 }
582
583
584 void intf_sys_t::msgReceiverClose(std::string destinationId)
585 {
586     std::string s("{\"type\":\"CLOSE\"}");
587     buildMessage( NAMESPACE_CONNECTION, s, destinationId );
588     if (appTransportId != destinationId)
589         setConnectionStatus( CHROMECAST_DISCONNECTED );
590     else
591     {
592         appTransportId = "";
593         setConnectionStatus( CHROMECAST_AUTHENTICATED );
594     }
595 }
596
597 void intf_sys_t::msgReceiverGetStatus()
598 {
599     std::stringstream ss;
600     ss << "{\"type\":\"GET_STATUS\","
601        <<  "\"requestId\":" << i_receiver_requestId++ << "}";
602
603     buildMessage( NAMESPACE_RECEIVER, ss.str() );
604 }
605
606 void intf_sys_t::msgReceiverLaunchApp()
607 {
608     std::stringstream ss;
609     ss << "{\"type\":\"LAUNCH\","
610        <<  "\"appId\":\"" << APP_ID << "\","
611        <<  "\"requestId\":" << i_receiver_requestId++ << "}";
612
613     buildMessage( NAMESPACE_RECEIVER, ss.str() );
614 }
615
616 void intf_sys_t::msgPlayerGetStatus()
617 {
618     std::stringstream ss;
619     ss << "{\"type\":\"GET_STATUS\","
620        <<  "\"requestId\":" << i_requestId++
621        << "}";
622
623     pushMediaPlayerMessage( ss );
624 }
625
626 void intf_sys_t::msgPlayerLoad()
627 {
628     char *psz_mime = var_InheritString(p_module, CONTROL_CFG_PREFIX "mime");
629     if (psz_mime == NULL)
630         return;
631
632     std::stringstream ss;
633     ss << "{\"type\":\"LOAD\","
634        <<  "\"media\":{\"contentId\":\"http://" << serverIP << ":"
635            << i_port
636            << "/stream\","
637        <<             "\"streamType\":\"LIVE\","
638        <<             "\"contentType\":\"" << std::string(psz_mime) << "\"},"
639        <<  "\"requestId\":" << i_requestId++ << "}";
640
641     free(psz_mime);
642
643     pushMediaPlayerMessage( ss );
644 }
645
646 void intf_sys_t::msgPlayerPlay()
647 {
648     assert(!mediaSessionId.empty());
649
650     std::stringstream ss;
651     ss << "{\"type\":\"PLAY\","
652        <<  "\"mediaSessionId\":" << mediaSessionId << ","
653        <<  "\"requestId\":" << i_requestId++
654        << "}";
655
656     pushMediaPlayerMessage( ss );
657 }
658
659 void intf_sys_t::msgPlayerStop()
660 {
661     assert(!mediaSessionId.empty());
662
663     std::stringstream ss;
664     ss << "{\"type\":\"STOP\","
665        <<  "\"mediaSessionId\":" << mediaSessionId << ","
666        <<  "\"requestId\":" << i_requestId++
667        << "}";
668
669     pushMediaPlayerMessage( ss );
670 }
671
672 void intf_sys_t::msgPlayerPause()
673 {
674     assert(!mediaSessionId.empty());
675
676     std::stringstream ss;
677     ss << "{\"type\":\"PAUSE\","
678        <<  "\"mediaSessionId\":" << mediaSessionId << ","
679        <<  "\"requestId\":" << i_requestId++
680        << "}";
681
682     pushMediaPlayerMessage( ss );
683 }
684
685 void intf_sys_t::msgPlayerSetVolume(float f_volume)
686 {
687     assert(!mediaSessionId.empty());
688
689     if ( f_volume < 0.0 || f_volume > 1.0)
690         return;
691
692     std::stringstream ss;
693     ss << "{\"type\":\"SET_VOLUME\","
694        <<  "\"volume\":{\"level\":" << f_volume << "},"
695        <<  "\"mediaSessionId\":" << mediaSessionId << ","
696        <<  "\"requestId\":" << i_requestId++
697        << "}";
698
699     pushMediaPlayerMessage( ss );
700 }
701
702 void intf_sys_t::msgPlayerSetMute(bool b_mute)
703 {
704     assert(!mediaSessionId.empty());
705
706     std::stringstream ss;
707     ss << "{\"type\":\"SET_VOLUME\","
708        <<  "\"volume\":{\"muted\":" << ( b_mute ? "true" : "false" ) << "},"
709        <<  "\"mediaSessionId\":" << mediaSessionId << ","
710        <<  "\"requestId\":" << i_requestId++
711        << "}";
712
713     pushMediaPlayerMessage( ss );
714 }
715
716 void intf_sys_t::msgPlayerSeek(const std::string & currentTime)
717 {
718     assert(!mediaSessionId.empty());
719
720     std::stringstream ss;
721     ss << "{\"type\":\"SEEK\","
722        <<  "\"currentTime\":" << currentTime << ","
723        <<  "\"mediaSessionId\":" << mediaSessionId << ","
724        <<  "\"requestId\":" << i_requestId++
725        << "}";
726
727     pushMediaPlayerMessage( ss );
728 }
729
730 /**
731  * @brief Send a message to the Chromecast
732  * @param msg the CastMessage to send
733  * @return vlc error code
734  */
735 int intf_sys_t::sendMessage(const castchannel::CastMessage &msg)
736 {
737     int i_size = msg.ByteSize();
738     uint8_t *p_data = new(std::nothrow) uint8_t[PACKET_HEADER_LEN + i_size];
739     if (p_data == NULL)
740         return VLC_ENOMEM;
741
742 #ifndef NDEBUG
743     msg_Dbg( p_module, "sendMessage: %s->%s %s", msg.namespace_().c_str(), msg.destination_id().c_str(), msg.payload_utf8().c_str());
744 #endif
745
746     SetDWBE(p_data, i_size);
747     msg.SerializeWithCachedSizesToArray(p_data + PACKET_HEADER_LEN);
748
749     vlc_mutex_locker locker(&lock);
750     int i_ret = tls_Send(p_tls, p_data, PACKET_HEADER_LEN + i_size);
751     delete[] p_data;
752     if (i_ret == PACKET_HEADER_LEN + i_size)
753         return VLC_SUCCESS;
754
755     return VLC_EGENERIC;
756 }
757
758 void intf_sys_t::pushMediaPlayerMessage(const std::stringstream & payload) {
759     assert(!appTransportId.empty());
760     buildMessage( NAMESPACE_MEDIA, payload.str(), appTransportId );
761 }
762
763 /*****************************************************************************
764  * Chromecast thread
765  *****************************************************************************/
766 void* intf_sys_t::ChromecastThread(void* p_data)
767 {
768     int canc;
769     intf_sys_t *p_sys = reinterpret_cast<intf_sys_t*>(p_data);
770     p_sys->setConnectionStatus( CHROMECAST_DISCONNECTED );
771
772     p_sys->i_sock_fd = p_sys->connectChromecast();
773     if (p_sys->i_sock_fd < 0)
774     {
775         canc = vlc_savecancel();
776         msg_Err( p_sys->p_module, "Could not connect the Chromecast" );
777         vlc_mutex_lock(&p_sys->lock);
778         p_sys->setConnectionStatus(CHROMECAST_CONNECTION_DEAD);
779         vlc_mutex_unlock(&p_sys->lock);
780         vlc_restorecancel(canc);
781         return NULL;
782     }
783
784     char psz_localIP[NI_MAXNUMERICHOST];
785     if (net_GetSockAddress(p_sys->i_sock_fd, psz_localIP, NULL))
786     {
787         canc = vlc_savecancel();
788         msg_Err( p_sys->p_module, "Cannot get local IP address" );
789         vlc_mutex_lock(&p_sys->lock);
790         p_sys->setConnectionStatus(CHROMECAST_CONNECTION_DEAD);
791         vlc_mutex_unlock(&p_sys->lock);
792         vlc_restorecancel(canc);
793         return NULL;
794     }
795
796     canc = vlc_savecancel();
797     p_sys->serverIP = psz_localIP;
798
799     vlc_mutex_lock(&p_sys->lock);
800     p_sys->setConnectionStatus(CHROMECAST_TLS_CONNECTED);
801     vlc_mutex_unlock(&p_sys->lock);
802
803     p_sys->msgAuth();
804     vlc_restorecancel(canc);
805
806     while (1)
807     {
808         p_sys->handleMessages();
809
810         vlc_mutex_locker locker(&p_sys->lock);
811         if ( p_sys->getConnectionStatus() == CHROMECAST_CONNECTION_DEAD )
812             break;
813     }
814
815     return NULL;
816 }
817
818 void intf_sys_t::handleMessages()
819 {
820     unsigned i_received = 0;
821     uint8_t p_packet[PACKET_MAX_LEN];
822     bool b_pingTimeout = false;
823
824     int i_waitdelay = PING_WAIT_TIME;
825     int i_retries = PING_WAIT_RETRIES;
826
827     bool b_msgReceived = false;
828     uint32_t i_payloadSize = 0;
829     int i_ret = recvPacket( p_module, b_msgReceived, i_payloadSize, i_sock_fd,
830                            p_tls, &i_received, p_packet, &b_pingTimeout,
831                            &i_waitdelay, &i_retries);
832
833     int canc = vlc_savecancel();
834     // Not cancellation-safe part.
835
836 #if defined(_WIN32)
837     if ((i_ret < 0 && WSAGetLastError() != WSAEWOULDBLOCK) || (i_ret == 0))
838 #else
839     if ((i_ret < 0 && errno != EAGAIN) || i_ret == 0)
840 #endif
841     {
842         msg_Err( p_module, "The connection to the Chromecast died (receiving).");
843         vlc_mutex_locker locker(&lock);
844         setConnectionStatus(CHROMECAST_CONNECTION_DEAD);
845         vlc_restorecancel(canc);
846         return;
847     }
848
849     if (b_pingTimeout)
850     {
851         msgPing();
852         msgReceiverGetStatus();
853     }
854
855     if (b_msgReceived)
856     {
857         castchannel::CastMessage msg;
858         msg.ParseFromArray(p_packet + PACKET_HEADER_LEN, i_payloadSize);
859         processMessage(msg);
860     }
861
862     vlc_restorecancel(canc);
863 }