chromecast: return a VLC status from msg methods
[vlc.git] / modules / stream_out / chromecast / chromecast_communication.cpp
blobb707ea67596f055e896dea3f8deb80d43a6881ea
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 *****************************************************************************/
26 #ifdef HAVE_CONFIG_H
27 # include "config.h"
28 #endif
30 #include "chromecast.h"
31 #ifdef HAVE_POLL
32 # include <poll.h>
33 #endif
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 )
40 , m_creds( NULL )
41 , m_tls( NULL )
42 , m_receiver_requestId( 0 )
43 , m_requestId( 0 )
45 if (devicePort == 0)
46 devicePort = CHROMECAST_CONTROL_PORT;
48 m_creds = vlc_tls_ClientCreate( m_module->obj.parent );
49 if (m_creds == NULL)
50 throw std::runtime_error( "Failed to create TLS client" );
52 m_tls = vlc_tls_SocketOpenTLS( m_creds, targetIP, devicePort, "tcps",
53 NULL, NULL );
54 if (m_tls == NULL)
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()
69 disconnect();
72 void ChromecastCommunication::disconnect()
74 if ( m_tls != NULL )
76 vlc_tls_Close(m_tls);
77 vlc_tls_Delete(m_creds);
78 m_tls = NULL;
82 /**
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;
126 struct iovec iov;
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 );
137 if ( i_ret < 0 )
139 #ifdef _WIN32
140 if ( WSAGetLastError() != WSAEWOULDBLOCK )
141 #else
142 if ( errno != EAGAIN )
143 #endif
145 return -1;
147 ssize_t val = vlc_poll_i11e(ufd, 1, i_timeout);
148 if ( val < 0 )
149 return -1;
150 else if ( val == 0 )
152 *pb_timeout = true;
153 return i_received;
155 assert( ufd[0].revents & POLLIN );
156 continue;
158 else if ( i_ret == 0 )
159 return -1;
160 assert( i_size >= (size_t)i_ret );
161 i_size -= i_ret;
162 i_received += i_ret;
163 iov.iov_base = (uint8_t*)iov.iov_base + i_ret;
164 iov.iov_len = i_size;
165 } while ( i_size > 0 );
166 return i_received;
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++
232 << "}";
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;
253 if( p_meta )
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 );
266 if( !psz_title )
268 psz_title = vlc_meta_Get( p_meta, vlc_meta_NowPlaying );
269 if( !psz_title )
270 psz_title = vlc_meta_Get( p_meta, vlc_meta_ESNowPlaying );
273 if ( psz_title )
275 ss << "\"metadata\":{"
276 << " \"metadataType\":" << ( b_music ? "3" : "0" )
277 << ",\"title\":\"" << psz_title << "\"";
278 if( b_music )
280 if( psz_artist )
281 ss << ",\"artist\":\"" << psz_artist << "\"";
282 if( psz_album )
283 ss << ",\"album\":\"" << psz_album << "\"";
284 if( psz_albumartist )
285 ss << ",\"albumArtist\":\"" << psz_albumartist << "\"";
286 if( psz_tracknumber )
287 ss << ",\"trackNumber\":\"" << psz_tracknumber << "\"";
288 if( psz_discnumber )
289 ss << ",\"discNumber\":\"" << psz_discnumber << "\"";
292 if ( psz_artwork && !strncmp( psz_artwork, "http", 4 ) )
293 ss << ",\"images\":[{\"url\":\"" << psz_artwork << "\"}]";
295 ss << "},";
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 << "\"";
308 return ss.str();
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++
319 << "}";
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++
332 << "}";
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++
345 << "}";
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++
358 << "}";
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)
368 return VLC_EGENERIC;
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++
375 << "}";
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++
389 << "}";
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];
403 if (p_data == NULL)
404 return VLC_ENOMEM;
406 #ifndef NDEBUG
407 msg_Dbg( m_module, "sendMessage: %s->%s %s", msg.namespace_().c_str(), msg.destination_id().c_str(), msg.payload_utf8().c_str());
408 #endif
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);
414 delete[] p_data;
415 if (i_ret == PACKET_HEADER_LEN + i_size)
416 return VLC_SUCCESS;
418 msg_Warn( m_module, "failed to send message %s (%s)", msg.payload_utf8().c_str(), strerror( errno ) );
420 return VLC_EGENERIC;
423 int ChromecastCommunication::pushMediaPlayerMessage( const std::string& destinationId, const std::stringstream & payload )
425 assert(!destinationId.empty());
426 return buildMessage( NAMESPACE_MEDIA, payload.str(), destinationId );