1 /*****************************************************************************
2 * chromecast_communication.cpp: Handle chromecast protocol messages
3 *****************************************************************************
4 * Copyright © 2014-2017 VideoLAN
6 * Authors: Adrien Maglo <magsoft@videolan.org>
7 * Jean-Baptiste Kempf <jb@videolan.org>
8 * Steve Lhomme <robux4@videolabs.io>
9 * Hugo Beauzée-Luyssen <hugo@beauzee.fr>
11 * This program is free software; you can redistribute it and/or modify it
12 * under the terms of the GNU Lesser General Public License as published by
13 * the Free Software Foundation; either version 2.1 of the License, or
14 * (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU Lesser General Public License for more details.
21 * You should have received a copy of the GNU Lesser General Public License
22 * along with this program; if not, write to the Free Software Foundation,
23 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
24 *****************************************************************************/
30 #include "chromecast.h"
35 /* deadline regarding pings sent from receiver */
36 #define PING_WAIT_TIME 6000
38 ChromecastCommunication::ChromecastCommunication( vlc_object_t* p_module, const char* targetIP, unsigned int devicePort )
39 : m_module( p_module )
43 , m_receiver_requestId( 0 )
47 devicePort = CHROMECAST_CONTROL_PORT;
48 m_sock_fd = net_ConnectTCP( m_module, targetIP, devicePort);
50 throw std::runtime_error( "Failed to connect to the chromecast" );
52 char psz_localIP[NI_MAXNUMERICHOST];
53 if ( net_GetSockAddress( m_sock_fd, psz_localIP, NULL ) )
54 throw std::runtime_error( "Cannot get local IP address" );
55 m_serverIp = psz_localIP;
57 m_creds = vlc_tls_ClientCreate( m_module->obj.parent );
61 throw std::runtime_error( "Failed to create TLS client" );
64 m_tls = vlc_tls_ClientSessionCreateFD( m_creds, m_sock_fd, targetIP, "tcps", NULL, NULL );
69 vlc_tls_Delete(m_creds);
70 throw std::runtime_error( "Failed to create client session" );
74 ChromecastCommunication::~ChromecastCommunication()
79 void ChromecastCommunication::disconnect()
84 vlc_tls_Delete(m_creds);
90 * @brief Build a CastMessage to send to the Chromecast
91 * @param namespace_ the message namespace
92 * @param payloadType the payload type (CastMessage_PayloadType_STRING or
93 * CastMessage_PayloadType_BINARY
94 * @param payload the payload
95 * @param destinationId the destination idenifier
96 * @return the generated CastMessage
98 void ChromecastCommunication::buildMessage(const std::string & namespace_,
99 const std::string & payload,
100 const std::string & destinationId,
101 castchannel::CastMessage_PayloadType payloadType)
103 castchannel::CastMessage msg;
105 msg.set_protocol_version(castchannel::CastMessage_ProtocolVersion_CASTV2_1_0);
106 msg.set_namespace_(namespace_);
107 msg.set_payload_type(payloadType);
108 msg.set_source_id("sender-vlc");
109 msg.set_destination_id(destinationId);
110 if (payloadType == castchannel::CastMessage_PayloadType_STRING)
111 msg.set_payload_utf8(payload);
112 else // CastMessage_PayloadType_BINARY
113 msg.set_payload_binary(payload);
119 * @brief Receive a data packet from the Chromecast
120 * @param p_module the module to log with
121 * @param b_msgReceived returns true if a message has been entirely received else false
122 * @param i_payloadSize returns the payload size of the message received
123 * @return the number of bytes received of -1 on error
125 ssize_t ChromecastCommunication::recvPacket( uint8_t *p_data )
127 ssize_t i_payloadSize = -1;
128 struct pollfd ufd[1];
129 ufd[0].fd = m_sock_fd;
130 ufd[0].events = POLLIN;
132 /* The Chromecast normally sends a PING command every 5 seconds or so.
133 * If we do not receive one after 6 seconds, we send a PING.
134 * If after this PING, we do not receive a PONG, then we consider the
135 * connection as dead. */
136 ssize_t val = vlc_poll_i11e(ufd, 1, PING_WAIT_TIME);
139 if ( errno != EINTR )
146 if ( ufd[0].revents & POLLIN )
148 /* we have received stuff */
151 * +------------------------------------+------------------------------+
152 * | Payload size (uint32_t big endian) | Payload data |
153 * +------------------------------------+------------------------------+ */
154 // We receive the header.
155 ssize_t i_ret = vlc_tls_Read(m_tls, p_data, PACKET_HEADER_LEN, true);
156 if (i_ret < PACKET_HEADER_LEN)
159 // We receive the payload.
161 // Get the size of the payload
162 i_payloadSize = U32_AT( p_data );
163 const uint32_t i_maxPayloadSize = PACKET_MAX_LEN - PACKET_HEADER_LEN;
165 if (i_payloadSize > i_maxPayloadSize)
167 // Error case: the packet sent by the Chromecast is too long: we drop it.
168 msg_Err( m_module, "Packet too long: dropping its data");
170 i_ret = vlc_tls_Read(m_tls, p_data + PACKET_HEADER_LEN,
171 i_maxPayloadSize, false);
172 return i_ret ? i_ret : -1;
176 i_ret = vlc_tls_Read(m_tls, p_data + PACKET_HEADER_LEN, i_payloadSize,
178 if (i_ret < i_payloadSize)
181 return i_payloadSize;
185 /*****************************************************************************
186 * Message preparation
187 *****************************************************************************/
188 void ChromecastCommunication::msgAuth()
190 castchannel::DeviceAuthMessage authMessage;
191 authMessage.mutable_challenge();
193 buildMessage(NAMESPACE_DEVICEAUTH, authMessage.SerializeAsString(),
194 DEFAULT_CHOMECAST_RECEIVER, castchannel::CastMessage_PayloadType_BINARY);
198 void ChromecastCommunication::msgPing()
200 std::string s("{\"type\":\"PING\"}");
201 buildMessage( NAMESPACE_HEARTBEAT, s, DEFAULT_CHOMECAST_RECEIVER );
205 void ChromecastCommunication::msgPong()
207 std::string s("{\"type\":\"PONG\"}");
208 buildMessage( NAMESPACE_HEARTBEAT, s, DEFAULT_CHOMECAST_RECEIVER );
211 void ChromecastCommunication::msgConnect( const std::string& destinationId )
213 std::string s("{\"type\":\"CONNECT\"}");
214 buildMessage( NAMESPACE_CONNECTION, s, destinationId );
217 void ChromecastCommunication::msgReceiverClose( const std::string& destinationId )
219 std::string s("{\"type\":\"CLOSE\"}");
220 buildMessage( NAMESPACE_CONNECTION, s, destinationId );
223 void ChromecastCommunication::msgReceiverGetStatus()
225 std::stringstream ss;
226 ss << "{\"type\":\"GET_STATUS\","
227 << "\"requestId\":" << m_receiver_requestId++ << "}";
229 buildMessage( NAMESPACE_RECEIVER, ss.str(), DEFAULT_CHOMECAST_RECEIVER );
232 void ChromecastCommunication::msgReceiverLaunchApp()
234 std::stringstream ss;
235 ss << "{\"type\":\"LAUNCH\","
236 << "\"appId\":\"" << APP_ID << "\","
237 << "\"requestId\":" << m_receiver_requestId++ << "}";
239 buildMessage( NAMESPACE_RECEIVER, ss.str(), DEFAULT_CHOMECAST_RECEIVER );
242 void ChromecastCommunication::msgPlayerGetStatus( const std::string& destinationId )
244 std::stringstream ss;
245 ss << "{\"type\":\"GET_STATUS\","
246 << "\"requestId\":" << m_requestId++
249 pushMediaPlayerMessage( destinationId, ss );
252 std::string ChromecastCommunication::GetMedia( unsigned int i_port,
253 const std::string& title, const std::string& artwork,
254 const std::string& mime )
256 std::stringstream ss;
260 ss << "\"metadata\":{"
261 << " \"metadataType\":0"
262 << ",\"title\":\"" << title << "\"";
264 if ( artwork.size() && !strncmp(artwork.c_str(), "http", 4))
265 ss << ",\"images\":[\"" << artwork << "\"]";
270 std::stringstream chromecast_url;
271 chromecast_url << "http://" << m_serverIp << ":" << i_port << "/stream";
273 msg_Dbg( m_module, "s_chromecast_url: %s", chromecast_url.str().c_str());
275 ss << "\"contentId\":\"" << chromecast_url.str() << "\""
276 << ",\"streamType\":\"LIVE\""
277 << ",\"contentType\":\"" << mime << "\"";
282 void ChromecastCommunication::msgPlayerLoad( const std::string& destinationId, unsigned int i_port,
283 const std::string& title, const std::string& artwork,
284 const std::string& mime )
286 std::stringstream ss;
287 ss << "{\"type\":\"LOAD\","
288 << "\"media\":{" << GetMedia( i_port, title, artwork, mime ) << "},"
289 << "\"autoplay\":\"false\","
290 << "\"requestId\":" << m_requestId++
293 pushMediaPlayerMessage( destinationId, ss );
296 void ChromecastCommunication::msgPlayerPlay( const std::string& destinationId, const std::string& mediaSessionId )
298 assert(!mediaSessionId.empty());
300 std::stringstream ss;
301 ss << "{\"type\":\"PLAY\","
302 << "\"mediaSessionId\":" << mediaSessionId << ","
303 << "\"requestId\":" << m_requestId++
306 pushMediaPlayerMessage( destinationId, ss );
309 void ChromecastCommunication::msgPlayerStop( const std::string& destinationId, const std::string& mediaSessionId )
311 assert(!mediaSessionId.empty());
313 std::stringstream ss;
314 ss << "{\"type\":\"STOP\","
315 << "\"mediaSessionId\":" << mediaSessionId << ","
316 << "\"requestId\":" << m_requestId++
319 pushMediaPlayerMessage( destinationId, ss );
322 void ChromecastCommunication::msgPlayerPause( const std::string& destinationId, const std::string& mediaSessionId )
324 assert(!mediaSessionId.empty());
326 std::stringstream ss;
327 ss << "{\"type\":\"PAUSE\","
328 << "\"mediaSessionId\":" << mediaSessionId << ","
329 << "\"requestId\":" << m_requestId++
332 pushMediaPlayerMessage( destinationId, ss );
335 void ChromecastCommunication::msgPlayerSetVolume( const std::string& destinationId, const std::string& mediaSessionId, float f_volume, bool b_mute )
337 assert(!mediaSessionId.empty());
339 if ( f_volume < 0.0 || f_volume > 1.0)
342 std::stringstream ss;
343 ss << "{\"type\":\"SET_VOLUME\","
344 << "\"volume\":{\"level\":" << f_volume << ",\"muted\":" << ( b_mute ? "true" : "false" ) << "},"
345 << "\"mediaSessionId\":" << mediaSessionId << ","
346 << "\"requestId\":" << m_requestId++
349 pushMediaPlayerMessage( destinationId, ss );
352 void ChromecastCommunication::msgPlayerSeek( const std::string& destinationId, const std::string& mediaSessionId, const std::string& currentTime )
354 assert(!mediaSessionId.empty());
356 std::stringstream ss;
357 ss << "{\"type\":\"SEEK\","
358 << "\"currentTime\":" << currentTime << ","
359 << "\"mediaSessionId\":" << mediaSessionId << ","
360 << "\"requestId\":" << m_requestId++
363 pushMediaPlayerMessage( destinationId, ss );
367 * @brief Send a message to the Chromecast
368 * @param msg the CastMessage to send
369 * @return vlc error code
371 int ChromecastCommunication::sendMessage( const castchannel::CastMessage &msg )
373 int i_size = msg.ByteSize();
374 uint8_t *p_data = new(std::nothrow) uint8_t[PACKET_HEADER_LEN + i_size];
379 msg_Dbg( m_module, "sendMessage: %s->%s %s", msg.namespace_().c_str(), msg.destination_id().c_str(), msg.payload_utf8().c_str());
382 SetDWBE(p_data, i_size);
383 msg.SerializeWithCachedSizesToArray(p_data + PACKET_HEADER_LEN);
385 int i_ret = vlc_tls_Write(m_tls, p_data, PACKET_HEADER_LEN + i_size);
387 if (i_ret == PACKET_HEADER_LEN + i_size)
390 msg_Warn( m_module, "failed to send message %s (%s)", msg.payload_utf8().c_str(), strerror( errno ) );
395 void ChromecastCommunication::pushMediaPlayerMessage( const std::string& destinationId, const std::stringstream & payload )
397 assert(!destinationId.empty());
398 buildMessage( NAMESPACE_MEDIA, payload.str(), destinationId );