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
)
42 , m_receiver_requestId( 0 )
46 devicePort
= CHROMECAST_CONTROL_PORT
;
48 m_creds
= vlc_tls_ClientCreate( m_module
->obj
.parent
);
50 throw std::runtime_error( "Failed to create TLS client" );
52 m_tls
= vlc_tls_SocketOpenTLS( m_creds
, targetIP
, devicePort
, "tcps",
56 vlc_tls_Delete(m_creds
);
57 throw std::runtime_error( "Failed to create client session" );
60 char psz_localIP
[NI_MAXNUMERICHOST
];
61 if (net_GetSockAddress( vlc_tls_GetFD(m_tls
), psz_localIP
, NULL
))
62 throw std::runtime_error( "Cannot get local IP address" );
64 m_serverIp
= psz_localIP
;
67 ChromecastCommunication::~ChromecastCommunication()
72 void ChromecastCommunication::disconnect()
77 vlc_tls_Delete(m_creds
);
83 * @brief Build a CastMessage to send to the Chromecast
84 * @param namespace_ the message namespace
85 * @param payloadType the payload type (CastMessage_PayloadType_STRING or
86 * CastMessage_PayloadType_BINARY
87 * @param payload the payload
88 * @param destinationId the destination idenifier
89 * @return the generated CastMessage
91 int ChromecastCommunication::buildMessage(const std::string
& namespace_
,
92 const std::string
& payload
,
93 const std::string
& destinationId
,
94 castchannel::CastMessage_PayloadType payloadType
)
96 castchannel::CastMessage msg
;
98 msg
.set_protocol_version(castchannel::CastMessage_ProtocolVersion_CASTV2_1_0
);
99 msg
.set_namespace_(namespace_
);
100 msg
.set_payload_type(payloadType
);
101 msg
.set_source_id("sender-vlc");
102 msg
.set_destination_id(destinationId
);
103 if (payloadType
== castchannel::CastMessage_PayloadType_STRING
)
104 msg
.set_payload_utf8(payload
);
105 else // CastMessage_PayloadType_BINARY
106 msg
.set_payload_binary(payload
);
108 return sendMessage(msg
);
112 * @brief Receive a data packet from the Chromecast
113 * @param p_data the buffer in which to store the data
114 * @param i_size the size of the buffer
115 * @param i_timeout maximum time to wait for a packet, in millisecond
116 * @param pb_timeout Output parameter that will contain true if no packet was received due to a timeout
117 * @return the number of bytes received of -1 on error
119 ssize_t
ChromecastCommunication::receive( uint8_t *p_data
, size_t i_size
, int i_timeout
, bool *pb_timeout
)
121 ssize_t i_received
= 0;
122 struct pollfd ufd
[1];
123 ufd
[0].fd
= vlc_tls_GetFD( m_tls
);
124 ufd
[0].events
= POLLIN
;
127 iov
.iov_base
= p_data
;
128 iov
.iov_len
= i_size
;
130 /* The Chromecast normally sends a PING command every 5 seconds or so.
131 * If we do not receive one after 6 seconds, we send a PING.
132 * If after this PING, we do not receive a PONG, then we consider the
133 * connection as dead. */
136 ssize_t i_ret
= m_tls
->readv( m_tls
, &iov
, 1 );
140 if ( WSAGetLastError() != WSAEWOULDBLOCK
)
142 if ( errno
!= EAGAIN
)
147 ssize_t val
= vlc_poll_i11e(ufd
, 1, i_timeout
);
155 assert( ufd
[0].revents
& POLLIN
);
158 else if ( i_ret
== 0 )
160 assert( i_size
>= (size_t)i_ret
);
163 iov
.iov_base
= (uint8_t*)iov
.iov_base
+ i_ret
;
164 iov
.iov_len
= i_size
;
165 } while ( i_size
> 0 );
170 /*****************************************************************************
171 * Message preparation
172 *****************************************************************************/
173 int ChromecastCommunication::msgAuth()
175 castchannel::DeviceAuthMessage authMessage
;
176 authMessage
.mutable_challenge();
178 return buildMessage(NAMESPACE_DEVICEAUTH
, authMessage
.SerializeAsString(),
179 DEFAULT_CHOMECAST_RECEIVER
, castchannel::CastMessage_PayloadType_BINARY
);
183 int ChromecastCommunication::msgPing()
185 std::string
s("{\"type\":\"PING\"}");
186 return buildMessage( NAMESPACE_HEARTBEAT
, s
, DEFAULT_CHOMECAST_RECEIVER
);
190 int ChromecastCommunication::msgPong()
192 std::string
s("{\"type\":\"PONG\"}");
193 return buildMessage( NAMESPACE_HEARTBEAT
, s
, DEFAULT_CHOMECAST_RECEIVER
);
196 int ChromecastCommunication::msgConnect( const std::string
& destinationId
)
198 std::string
s("{\"type\":\"CONNECT\"}");
199 return buildMessage( NAMESPACE_CONNECTION
, s
, destinationId
);
202 int ChromecastCommunication::msgReceiverClose( const std::string
& destinationId
)
204 std::string
s("{\"type\":\"CLOSE\"}");
205 return buildMessage( NAMESPACE_CONNECTION
, s
, destinationId
);
208 int ChromecastCommunication::msgReceiverGetStatus()
210 std::stringstream ss
;
211 ss
<< "{\"type\":\"GET_STATUS\","
212 << "\"requestId\":" << m_receiver_requestId
++ << "}";
214 return buildMessage( NAMESPACE_RECEIVER
, ss
.str(), DEFAULT_CHOMECAST_RECEIVER
);
217 int ChromecastCommunication::msgReceiverLaunchApp()
219 std::stringstream ss
;
220 ss
<< "{\"type\":\"LAUNCH\","
221 << "\"appId\":\"" << APP_ID
<< "\","
222 << "\"requestId\":" << m_receiver_requestId
++ << "}";
224 return buildMessage( NAMESPACE_RECEIVER
, ss
.str(), DEFAULT_CHOMECAST_RECEIVER
);
227 int ChromecastCommunication::msgPlayerGetStatus( const std::string
& destinationId
)
229 std::stringstream ss
;
230 ss
<< "{\"type\":\"GET_STATUS\","
231 << "\"requestId\":" << m_requestId
++
234 return pushMediaPlayerMessage( destinationId
, ss
);
237 std::string
ChromecastCommunication::GetMedia( unsigned int i_port
,
238 const std::string
& mime
,
239 const vlc_meta_t
*p_meta
)
241 std::stringstream ss
;
243 bool b_music
= strncmp(mime
.c_str(), "audio", strlen("audio")) == 0;
245 const char *psz_title
= NULL
;
246 const char *psz_artwork
= NULL
;
247 const char *psz_artist
= NULL
;
248 const char *psz_album
= NULL
;
249 const char *psz_albumartist
= NULL
;
250 const char *psz_tracknumber
= NULL
;
251 const char *psz_discnumber
= NULL
;
255 psz_title
= vlc_meta_Get( p_meta
, vlc_meta_Title
);
256 psz_artwork
= vlc_meta_Get( p_meta
, vlc_meta_ArtworkURL
);
258 if( b_music
&& psz_title
)
260 psz_artist
= vlc_meta_Get( p_meta
, vlc_meta_Artist
);
261 psz_album
= vlc_meta_Get( p_meta
, vlc_meta_Album
);
262 psz_albumartist
= vlc_meta_Get( p_meta
, vlc_meta_AlbumArtist
);
263 psz_tracknumber
= vlc_meta_Get( p_meta
, vlc_meta_TrackNumber
);
264 psz_discnumber
= vlc_meta_Get( p_meta
, vlc_meta_DiscNumber
);
268 psz_title
= vlc_meta_Get( p_meta
, vlc_meta_NowPlaying
);
270 psz_title
= vlc_meta_Get( p_meta
, vlc_meta_ESNowPlaying
);
275 ss
<< "\"metadata\":{"
276 << " \"metadataType\":" << ( b_music
? "3" : "0" )
277 << ",\"title\":\"" << psz_title
<< "\"";
281 ss
<< ",\"artist\":\"" << psz_artist
<< "\"";
283 ss
<< ",\"album\":\"" << psz_album
<< "\"";
284 if( psz_albumartist
)
285 ss
<< ",\"albumArtist\":\"" << psz_albumartist
<< "\"";
286 if( psz_tracknumber
)
287 ss
<< ",\"trackNumber\":\"" << psz_tracknumber
<< "\"";
289 ss
<< ",\"discNumber\":\"" << psz_discnumber
<< "\"";
292 if ( psz_artwork
&& !strncmp( psz_artwork
, "http", 4 ) )
293 ss
<< ",\"images\":[{\"url\":\"" << psz_artwork
<< "\"}]";
299 std::stringstream chromecast_url
;
300 chromecast_url
<< "http://" << m_serverIp
<< ":" << i_port
<< "/stream";
302 msg_Dbg( m_module
, "s_chromecast_url: %s", chromecast_url
.str().c_str());
304 ss
<< "\"contentId\":\"" << chromecast_url
.str() << "\""
305 << ",\"streamType\":\"LIVE\""
306 << ",\"contentType\":\"" << mime
<< "\"";
311 int ChromecastCommunication::msgPlayerLoad( const std::string
& destinationId
, unsigned int i_port
,
312 const std::string
& mime
, const vlc_meta_t
*p_meta
)
314 std::stringstream ss
;
315 ss
<< "{\"type\":\"LOAD\","
316 << "\"media\":{" << GetMedia( i_port
, mime
, p_meta
) << "},"
317 << "\"autoplay\":\"false\","
318 << "\"requestId\":" << m_requestId
++
321 return pushMediaPlayerMessage( destinationId
, ss
);
324 int ChromecastCommunication::msgPlayerPlay( const std::string
& destinationId
, int64_t mediaSessionId
)
326 assert(mediaSessionId
!= 0);
328 std::stringstream ss
;
329 ss
<< "{\"type\":\"PLAY\","
330 << "\"mediaSessionId\":" << mediaSessionId
<< ","
331 << "\"requestId\":" << m_requestId
++
334 return pushMediaPlayerMessage( destinationId
, ss
);
337 int ChromecastCommunication::msgPlayerStop( const std::string
& destinationId
, int64_t mediaSessionId
)
339 assert(mediaSessionId
!= 0);
341 std::stringstream ss
;
342 ss
<< "{\"type\":\"STOP\","
343 << "\"mediaSessionId\":" << mediaSessionId
<< ","
344 << "\"requestId\":" << m_requestId
++
347 return pushMediaPlayerMessage( destinationId
, ss
);
350 int ChromecastCommunication::msgPlayerPause( const std::string
& destinationId
, int64_t mediaSessionId
)
352 assert(mediaSessionId
!= 0);
354 std::stringstream ss
;
355 ss
<< "{\"type\":\"PAUSE\","
356 << "\"mediaSessionId\":" << mediaSessionId
<< ","
357 << "\"requestId\":" << m_requestId
++
360 return pushMediaPlayerMessage( destinationId
, ss
);
363 int ChromecastCommunication::msgPlayerSetVolume( const std::string
& destinationId
, int64_t mediaSessionId
, float f_volume
, bool b_mute
)
365 assert(mediaSessionId
!= 0);
367 if ( f_volume
< 0.0 || f_volume
> 1.0)
370 std::stringstream ss
;
371 ss
<< "{\"type\":\"SET_VOLUME\","
372 << "\"volume\":{\"level\":" << f_volume
<< ",\"muted\":" << ( b_mute
? "true" : "false" ) << "},"
373 << "\"mediaSessionId\":" << mediaSessionId
<< ","
374 << "\"requestId\":" << m_requestId
++
377 return pushMediaPlayerMessage( destinationId
, ss
);
380 int ChromecastCommunication::msgPlayerSeek( const std::string
& destinationId
, int64_t mediaSessionId
, const std::string
& currentTime
)
382 assert(mediaSessionId
!= 0);
384 std::stringstream ss
;
385 ss
<< "{\"type\":\"SEEK\","
386 << "\"currentTime\":" << currentTime
<< ","
387 << "\"mediaSessionId\":" << mediaSessionId
<< ","
388 << "\"requestId\":" << m_requestId
++
391 return pushMediaPlayerMessage( destinationId
, ss
);
395 * @brief Send a message to the Chromecast
396 * @param msg the CastMessage to send
397 * @return vlc error code
399 int ChromecastCommunication::sendMessage( const castchannel::CastMessage
&msg
)
401 int i_size
= msg
.ByteSize();
402 uint8_t *p_data
= new(std::nothrow
) uint8_t[PACKET_HEADER_LEN
+ i_size
];
407 msg_Dbg( m_module
, "sendMessage: %s->%s %s", msg
.namespace_().c_str(), msg
.destination_id().c_str(), msg
.payload_utf8().c_str());
410 SetDWBE(p_data
, i_size
);
411 msg
.SerializeWithCachedSizesToArray(p_data
+ PACKET_HEADER_LEN
);
413 int i_ret
= vlc_tls_Write(m_tls
, p_data
, PACKET_HEADER_LEN
+ i_size
);
415 if (i_ret
== PACKET_HEADER_LEN
+ i_size
)
418 msg_Warn( m_module
, "failed to send message %s (%s)", msg
.payload_utf8().c_str(), strerror( errno
) );
423 int ChromecastCommunication::pushMediaPlayerMessage( const std::string
& destinationId
, const std::stringstream
& payload
)
425 assert(!destinationId
.empty());
426 return buildMessage( NAMESPACE_MEDIA
, payload
.str(), destinationId
);