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 * Hugo Beauzée-Luyssen <hugo@beauzee.fr>
10 *
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.
15 *
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.
20 *
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 *****************************************************************************/
25
26 /*****************************************************************************
27 * Preamble
28 *****************************************************************************/
29
30 #ifdef HAVE_CONFIG_H
31 # include "config.h"
32 #endif
33
34 #include "chromecast.h"
35
36 #include <cassert>
37 #include <cerrno>
38 #include <iomanip>
39
40 #include <vlc_stream.h>
41 #include <vlc_rand.h>
42
43 #include "../../misc/webservices/json.h"
44
45 /* deadline regarding pings sent from receiver */
46 #define PING_WAIT_TIME 6000
47 #define PING_WAIT_RETRIES 1
48
49 static int httpd_file_fill_cb( httpd_file_sys_t *data, httpd_file_t *http_file,
50 uint8_t *psz_request, uint8_t **pp_data, int *pi_data );
51
StateToStr(States s)52 static const char* StateToStr( States s )
53 {
54 switch (s )
55 {
56 case Authenticating:
57 return "Authenticating";
58 case Connecting:
59 return "Connecting";
60 case Connected:
61 return "Connected";
62 case Launching:
63 return "Lauching";
64 case Ready:
65 return "Ready";
66 case LoadFailed:
67 return "LoadFailed";
68 case Loading:
69 return "Loading";
70 case Buffering:
71 return "Buffering";
72 case Playing:
73 return "Playing";
74 case Paused:
75 return "Paused";
76 case Stopping:
77 return "Stopping";
78 case Stopped:
79 return "Stopped";
80 case Dead:
81 return "Dead";
82 case TakenOver:
83 return "TakenOver";
84 }
85 vlc_assert_unreachable();
86 }
87
88 /*****************************************************************************
89 * intf_sys_t: class definition
90 *****************************************************************************/
intf_sys_t(vlc_object_t * const p_this,int port,std::string device_addr,int device_port,httpd_host_t * httpd_host)91 intf_sys_t::intf_sys_t(vlc_object_t * const p_this, int port, std::string device_addr,
92 int device_port, httpd_host_t *httpd_host)
93 : m_module(p_this)
94 , m_device_port(device_port)
95 , m_device_addr(device_addr)
96 , m_last_request_id( 0 )
97 , m_mediaSessionId( 0 )
98 , m_on_input_event( NULL )
99 , m_on_input_event_data( NULL )
100 , m_on_paused_changed( NULL )
101 , m_on_paused_changed_data( NULL )
102 , m_state( Authenticating )
103 , m_retry_on_fail( false )
104 , m_played_once( false )
105 , m_request_stop( false )
106 , m_request_load( false )
107 , m_paused( false )
108 , m_input_eof( false )
109 , m_cc_eof( false )
110 , m_pace( false )
111 , m_meta( NULL )
112 , m_httpd( httpd_host, port )
113 , m_httpd_file(NULL)
114 , m_art_url(NULL)
115 , m_art_idx(0)
116 , m_cc_time_date( VLC_TS_INVALID )
117 , m_cc_time( VLC_TS_INVALID )
118 , m_pingRetriesLeft( PING_WAIT_RETRIES )
119 {
120 m_communication = new ChromecastCommunication( p_this,
121 getHttpStreamPath(), getHttpStreamPort(),
122 m_device_addr.c_str(), m_device_port );
123
124 m_ctl_thread_interrupt = vlc_interrupt_create();
125 if( unlikely(m_ctl_thread_interrupt == NULL) )
126 throw std::runtime_error( "error creating interrupt context" );
127
128 vlc_mutex_init(&m_lock);
129 vlc_cond_init( &m_stateChangedCond );
130 vlc_cond_init( &m_pace_cond );
131
132 std::stringstream ss;
133 ss << "http://" << m_communication->getServerIp() << ":" << port;
134 m_art_http_ip = ss.str();
135
136 m_common.p_opaque = this;
137 m_common.pf_set_demux_enabled = set_demux_enabled;
138 m_common.pf_get_time = get_time;
139 m_common.pf_pace = pace;
140 m_common.pf_send_input_event = send_input_event;
141 m_common.pf_set_pause_state = set_pause_state;
142 m_common.pf_set_meta = set_meta;
143
144 assert( var_Type( m_module->obj.parent->obj.parent, CC_SHARED_VAR_NAME) == 0 );
145 if (var_Create( m_module->obj.parent->obj.parent, CC_SHARED_VAR_NAME, VLC_VAR_ADDRESS ) == VLC_SUCCESS )
146 var_SetAddress( m_module->obj.parent->obj.parent, CC_SHARED_VAR_NAME, &m_common );
147
148 // Start the Chromecast event thread.
149 if (vlc_clone(&m_chromecastThread, ChromecastThread, this,
150 VLC_THREAD_PRIORITY_LOW))
151 {
152 vlc_interrupt_destroy( m_ctl_thread_interrupt );
153 vlc_cond_destroy( &m_stateChangedCond );
154 vlc_cond_destroy( &m_pace_cond );
155 var_SetAddress( m_module->obj.parent->obj.parent, CC_SHARED_VAR_NAME, NULL );
156 throw std::runtime_error( "error creating cc thread" );
157 }
158 }
159
~intf_sys_t()160 intf_sys_t::~intf_sys_t()
161 {
162 var_Destroy( m_module->obj.parent->obj.parent, CC_SHARED_VAR_NAME );
163
164 vlc_mutex_lock(&m_lock);
165 if( m_communication )
166 {
167 switch ( m_state )
168 {
169 case Ready:
170 case Loading:
171 case Buffering:
172 case Playing:
173 case Paused:
174 case Stopping:
175 case Stopped:
176 // Generate the close messages.
177 m_communication->msgReceiverClose( m_appTransportId );
178 /* fallthrough */
179 case Connecting:
180 case Connected:
181 case Launching:
182 m_communication->msgReceiverClose(DEFAULT_CHOMECAST_RECEIVER);
183 /* fallthrough */
184 default:
185 break;
186 }
187
188 vlc_mutex_unlock(&m_lock);
189 vlc_interrupt_kill( m_ctl_thread_interrupt );
190 vlc_join(m_chromecastThread, NULL);
191
192 delete m_communication;
193 }
194 else
195 vlc_mutex_unlock(&m_lock);
196
197 vlc_interrupt_destroy( m_ctl_thread_interrupt );
198
199 if (m_meta != NULL)
200 vlc_meta_Delete(m_meta);
201
202 if( m_httpd_file )
203 httpd_FileDelete( m_httpd_file );
204
205 free( m_art_url );
206
207 vlc_cond_destroy(&m_stateChangedCond);
208 vlc_cond_destroy(&m_pace_cond);
209 vlc_mutex_destroy(&m_lock);
210 }
211
reinit()212 void intf_sys_t::reinit()
213 {
214 assert( m_state == Dead );
215
216 if( m_communication )
217 {
218 vlc_join( m_chromecastThread, NULL );
219 delete m_communication;
220 m_communication = NULL;
221 }
222
223 try
224 {
225 m_communication = new ChromecastCommunication( m_module,
226 getHttpStreamPath(),
227 getHttpStreamPort(),
228 m_device_addr.c_str(),
229 m_device_port );
230 } catch (const std::runtime_error& err )
231 {
232 msg_Warn( m_module, "failed to re-init ChromecastCommunication (%s)", err.what() );
233 m_communication = NULL;
234 return;
235 }
236
237 m_state = Authenticating;
238 if( vlc_clone( &m_chromecastThread, ChromecastThread, this, VLC_THREAD_PRIORITY_LOW) )
239 {
240 m_state = Dead;
241 delete m_communication;
242 m_communication = NULL;
243 }
244 }
245
httpd_file_fill(uint8_t * psz_request,uint8_t ** pp_data,int * pi_data)246 int intf_sys_t::httpd_file_fill( uint8_t *psz_request, uint8_t **pp_data, int *pi_data )
247 {
248 (void) psz_request;
249
250 vlc_mutex_lock( &m_lock );
251 if( !m_art_url )
252 {
253 vlc_mutex_unlock( &m_lock );
254 return VLC_EGENERIC;
255 }
256 char *psz_art = strdup( m_art_url );
257 vlc_mutex_unlock( &m_lock );
258
259 stream_t *s = vlc_stream_NewURL( m_module, psz_art );
260 free( psz_art );
261 if( !s )
262 return VLC_EGENERIC;
263
264 uint64_t size;
265 if( vlc_stream_GetSize( s, &size ) != VLC_SUCCESS
266 || size > INT64_C( 10000000 ) )
267 {
268 msg_Warn( m_module, "art stream is too big or invalid" );
269 vlc_stream_Delete( s );
270 return VLC_EGENERIC;
271 }
272
273 *pp_data = (uint8_t *)malloc( size );
274 if( !*pp_data )
275 {
276 vlc_stream_Delete( s );
277 return VLC_EGENERIC;
278 }
279
280 ssize_t read = vlc_stream_Read( s, *pp_data, size );
281 vlc_stream_Delete( s );
282
283 if( read < 0 || (size_t)read != size )
284 {
285 free( *pp_data );
286 *pp_data = NULL;
287 return VLC_EGENERIC;
288 }
289 *pi_data = size;
290
291 return VLC_SUCCESS;
292 }
293
httpd_file_fill_cb(httpd_file_sys_t * data,httpd_file_t * http_file,uint8_t * psz_request,uint8_t ** pp_data,int * pi_data)294 static int httpd_file_fill_cb( httpd_file_sys_t *data, httpd_file_t *http_file,
295 uint8_t *psz_request, uint8_t **pp_data, int *pi_data )
296 {
297 (void) http_file;
298 intf_sys_t *p_sys = static_cast<intf_sys_t*>((void *)data);
299 return p_sys->httpd_file_fill( psz_request, pp_data, pi_data );
300 }
301
prepareHttpArtwork()302 void intf_sys_t::prepareHttpArtwork()
303 {
304 const char *psz_art = m_meta ? vlc_meta_Get( m_meta, vlc_meta_ArtworkURL ) : NULL;
305 /* Abort if there is no art or if the art is already served */
306 if( !psz_art || strncmp( psz_art, "http", 4) == 0 )
307 return;
308
309 std::stringstream ss_art_idx;
310
311 if( m_art_url && strcmp( m_art_url, psz_art ) == 0 )
312 {
313 /* Same art: use the previous cached artwork url */
314 assert( m_art_idx != 0 );
315 ss_art_idx << getHttpArtRoot() << "/" << (m_art_idx - 1);
316 }
317 else
318 {
319 /* New art: create a new httpd file instance with a new url. The
320 * artwork has to be different since the CC will cache the content. */
321
322 ss_art_idx << getHttpArtRoot() << "/" << m_art_idx;
323 m_art_idx++;
324
325 vlc_mutex_unlock( &m_lock );
326
327 if( m_httpd_file )
328 httpd_FileDelete( m_httpd_file );
329 m_httpd_file = httpd_FileNew( m_httpd.m_host, ss_art_idx.str().c_str(),
330 "application/octet-stream", NULL, NULL,
331 httpd_file_fill_cb, (httpd_file_sys_t *) this );
332
333 vlc_mutex_lock( &m_lock );
334 if( !m_httpd_file )
335 return;
336
337 free( m_art_url );
338 m_art_url = strdup( psz_art );
339 }
340
341 std::stringstream ss;
342 ss << m_art_http_ip << ss_art_idx.str();
343 vlc_meta_Set( m_meta, vlc_meta_ArtworkURL, ss.str().c_str() );
344 }
345
tryLoad()346 void intf_sys_t::tryLoad()
347 {
348 if( !m_request_load )
349 return;
350
351 if ( !isStateReady() )
352 {
353 if ( m_state == Dead )
354 {
355 msg_Warn( m_module, "no Chromecast hook possible");
356 m_request_load = false;
357 }
358 else if( m_state == Connected )
359 {
360 assert( m_communication );
361 msg_Dbg( m_module, "Starting the media receiver application" );
362 // Don't use setState as we don't want to signal the condition in this case.
363 m_state = Launching;
364 m_communication->msgReceiverLaunchApp();
365 }
366 return;
367 }
368
369 m_request_load = false;
370
371 // We should now be in the ready state, and therefor have a valid transportId
372 assert( m_appTransportId.empty() == false );
373 // Reset the mediaSessionID to allow the new session to become the current one.
374 // we cannot start a new load when the last one is still processing
375 m_last_request_id =
376 m_communication->msgPlayerLoad( m_appTransportId, m_mime, m_meta );
377 if( m_last_request_id != ChromecastCommunication::kInvalidId )
378 m_state = Loading;
379 }
380
setRetryOnFail(bool enabled)381 void intf_sys_t::setRetryOnFail( bool enabled )
382 {
383 vlc_mutex_locker locker(&m_lock);
384 m_retry_on_fail = enabled;
385 }
386
setHasInput(const std::string mime_type)387 void intf_sys_t::setHasInput( const std::string mime_type )
388 {
389 vlc_mutex_locker locker(&m_lock);
390 msg_Dbg( m_module, "Loading content" );
391
392 if( m_state == Dead )
393 reinit();
394
395 this->m_mime = mime_type;
396
397 /* new input: clear message queue */
398 std::queue<QueueableMessages> empty;
399 std::swap(m_msgQueue, empty);
400
401 prepareHttpArtwork();
402
403 m_request_stop = false;
404 m_played_once = false;
405 m_paused = false;
406 m_cc_eof = false;
407 m_request_load = true;
408 m_cc_time_last_request_date = VLC_TS_INVALID;
409 m_cc_time_date = VLC_TS_INVALID;
410 m_cc_time = VLC_TS_INVALID;
411 m_mediaSessionId = 0;
412
413 tryLoad();
414
415 vlc_cond_signal( &m_stateChangedCond );
416 }
417
isStateError() const418 bool intf_sys_t::isStateError() const
419 {
420 switch( m_state )
421 {
422 case LoadFailed:
423 case Dead:
424 case TakenOver:
425 return true;
426 default:
427 return false;
428 }
429 }
430
isStatePlaying() const431 bool intf_sys_t::isStatePlaying() const
432 {
433 switch( m_state )
434 {
435 case Loading:
436 case Buffering:
437 case Playing:
438 case Paused:
439 return true;
440 default:
441 return false;
442 }
443 }
444
isStateReady() const445 bool intf_sys_t::isStateReady() const
446 {
447 switch( m_state )
448 {
449 case Connected:
450 case Launching:
451 case Authenticating:
452 case Connecting:
453 case Stopping:
454 case Stopped:
455 case Dead:
456 return false;
457 default:
458 return true;
459 }
460 }
461
setPacing(bool do_pace)462 void intf_sys_t::setPacing(bool do_pace)
463 {
464 vlc_mutex_lock( &m_lock );
465 if( m_pace == do_pace )
466 {
467 vlc_mutex_unlock( &m_lock );
468 return;
469 }
470 m_pace = do_pace;
471 vlc_mutex_unlock( &m_lock );
472 vlc_cond_signal( &m_pace_cond );
473 }
474
interrupt_wake_up_cb(void * data)475 static void interrupt_wake_up_cb( void *data )
476 {
477 intf_sys_t *p_sys = static_cast<intf_sys_t*>((void *)data);
478 p_sys->interrupt_wake_up();
479 }
480
interrupt_wake_up()481 void intf_sys_t::interrupt_wake_up()
482 {
483 vlc_mutex_locker locker( &m_lock );
484 m_interrupted = true;
485 vlc_cond_signal( &m_pace_cond );
486 }
487
pace()488 int intf_sys_t::pace()
489 {
490 vlc_mutex_locker locker(&m_lock);
491
492 m_interrupted = false;
493 vlc_interrupt_register( interrupt_wake_up_cb, this );
494 int ret = 0;
495 mtime_t deadline = mdate() + INT64_C(500000);
496
497 /* Wait for the sout to send more data via http (m_pace), or wait for the
498 * CC to finish. In case the demux filter is EOF, we always wait for
499 * 500msec (unless interrupted from the input thread). */
500 while( !isFinishedPlaying() && ( m_pace || m_input_eof ) && !m_interrupted && ret == 0 )
501 ret = vlc_cond_timedwait( &m_pace_cond, &m_lock, deadline );
502
503 vlc_interrupt_unregister();
504
505 if( m_cc_eof )
506 return CC_PACE_OK_ENDED;
507 else if( isStateError() || m_state == Stopped )
508 {
509 if( m_state == LoadFailed && m_retry_on_fail )
510 {
511 m_state = Ready;
512 return CC_PACE_ERR_RETRY;
513 }
514 return CC_PACE_ERR;
515 }
516
517 return ret == 0 ? CC_PACE_OK : CC_PACE_OK_WAIT;
518 }
519
sendInputEvent(enum cc_input_event event,union cc_input_arg arg)520 void intf_sys_t::sendInputEvent(enum cc_input_event event, union cc_input_arg arg)
521 {
522 vlc_mutex_lock(&m_lock);
523 on_input_event_itf on_input_event = m_on_input_event;
524 void *data = m_on_input_event_data;
525
526 switch (event)
527 {
528 case CC_INPUT_EVENT_EOF:
529 if (m_input_eof != arg.eof)
530 m_input_eof = arg.eof;
531 else
532 {
533 /* Don't send twice the same event */
534 on_input_event = NULL;
535 data = NULL;
536 }
537 break;
538 default:
539 break;
540 }
541 vlc_mutex_unlock(&m_lock);
542
543 if (on_input_event)
544 on_input_event(data, event, arg);
545 }
546
547 /**
548 * @brief Process a message received from the Chromecast
549 * @param msg the CastMessage to process
550 * @return 0 if the message has been successfuly processed else -1
551 */
processMessage(const castchannel::CastMessage & msg)552 bool intf_sys_t::processMessage(const castchannel::CastMessage &msg)
553 {
554 const std::string & namespace_ = msg.namespace_();
555
556 #ifndef NDEBUG
557 msg_Dbg( m_module, "processMessage: %s->%s %s", namespace_.c_str(), msg.destination_id().c_str(), msg.payload_utf8().c_str());
558 #endif
559
560 bool ret = true;
561 if (namespace_ == NAMESPACE_DEVICEAUTH)
562 processAuthMessage( msg );
563 else if (namespace_ == NAMESPACE_HEARTBEAT)
564 processHeartBeatMessage( msg );
565 else if (namespace_ == NAMESPACE_RECEIVER)
566 ret = processReceiverMessage( msg );
567 else if (namespace_ == NAMESPACE_MEDIA)
568 processMediaMessage( msg );
569 else if (namespace_ == NAMESPACE_CONNECTION)
570 processConnectionMessage( msg );
571 else
572 {
573 msg_Err( m_module, "Unknown namespace: %s", msg.namespace_().c_str());
574 }
575 return ret;
576 }
577
queueMessage(QueueableMessages msg)578 void intf_sys_t::queueMessage( QueueableMessages msg )
579 {
580 // Assume lock is held by the called
581 m_msgQueue.push( msg );
582 vlc_interrupt_raise( m_ctl_thread_interrupt );
583 }
584
httpd_info_t(httpd_host_t * host,int port)585 intf_sys_t::httpd_info_t::httpd_info_t( httpd_host_t* host, int port )
586 : m_host( host )
587 , m_port( port )
588 {
589 for( int i = 0; i < 3; ++i )
590 {
591 std::ostringstream ss;
592 ss << "/chromecast"
593 << "/" << mdate()
594 << "/" << static_cast<uint64_t>( vlc_mrand48() );
595
596 m_root = ss.str();
597 m_url = httpd_UrlNew( m_host, m_root.c_str(), NULL, NULL );
598 if( m_url )
599 break;
600 }
601
602 if( m_url == NULL )
603 throw std::runtime_error( "unable to bind to http path" );
604 }
605
~httpd_info_t()606 intf_sys_t::httpd_info_t::~httpd_info_t()
607
608 {
609 if( m_url )
610 httpd_UrlDelete( m_url );
611 }
612
613 /*****************************************************************************
614 * Chromecast thread
615 *****************************************************************************/
ChromecastThread(void * p_data)616 void* intf_sys_t::ChromecastThread(void* p_data)
617 {
618 intf_sys_t *p_sys = static_cast<intf_sys_t*>(p_data);
619 p_sys->mainLoop();
620 return NULL;
621 }
622
mainLoop()623 void intf_sys_t::mainLoop()
624 {
625 vlc_savecancel();
626 vlc_interrupt_set( m_ctl_thread_interrupt );
627
628 // State was already initialized as Authenticating
629 m_communication->msgAuth();
630
631 while ( !vlc_killed() )
632 {
633 if ( !handleMessages() )
634 break;
635 // Reset the interrupt state to avoid commands not being sent (since
636 // the context is still flagged as interrupted)
637 vlc_interrupt_unregister();
638 vlc_mutex_locker lock( &m_lock );
639 while ( m_msgQueue.empty() == false )
640 {
641 QueueableMessages msg = m_msgQueue.front();
642 switch ( msg )
643 {
644 case Stop:
645 doStop();
646 break;
647 }
648 m_msgQueue.pop();
649 }
650 }
651 }
652
processAuthMessage(const castchannel::CastMessage & msg)653 void intf_sys_t::processAuthMessage( const castchannel::CastMessage& msg )
654 {
655 castchannel::DeviceAuthMessage authMessage;
656 if ( authMessage.ParseFromString(msg.payload_binary()) == false )
657 {
658 msg_Warn( m_module, "Failed to parse the payload" );
659 return;
660 }
661
662 if (authMessage.has_error())
663 {
664 msg_Err( m_module, "Authentification error: %d", authMessage.error().error_type());
665 }
666 else if (!authMessage.has_response())
667 {
668 msg_Err( m_module, "Authentification message has no response field");
669 }
670 else
671 {
672 vlc_mutex_locker locker(&m_lock);
673 setState( Connecting );
674 m_communication->msgConnect(DEFAULT_CHOMECAST_RECEIVER);
675 m_communication->msgReceiverGetStatus();
676 }
677 }
678
processHeartBeatMessage(const castchannel::CastMessage & msg)679 void intf_sys_t::processHeartBeatMessage( const castchannel::CastMessage& msg )
680 {
681 json_value *p_data = json_parse(msg.payload_utf8().c_str());
682 std::string type((*p_data)["type"]);
683
684 if (type == "PING")
685 {
686 msg_Dbg( m_module, "PING received from the Chromecast");
687 m_communication->msgPong();
688 }
689 else if (type == "PONG")
690 {
691 msg_Dbg( m_module, "PONG received from the Chromecast");
692 m_pingRetriesLeft = PING_WAIT_RETRIES;
693 }
694 else
695 {
696 msg_Warn( m_module, "Heartbeat command not supported: %s", type.c_str());
697 }
698
699 json_value_free(p_data);
700 }
701
processReceiverMessage(const castchannel::CastMessage & msg)702 bool intf_sys_t::processReceiverMessage( const castchannel::CastMessage& msg )
703 {
704 json_value *p_data = json_parse(msg.payload_utf8().c_str());
705 std::string type((*p_data)["type"]);
706
707 bool ret = true;
708 if (type == "RECEIVER_STATUS")
709 {
710 json_value applications = (*p_data)["status"]["applications"];
711 const json_value *p_app = NULL;
712
713 for (unsigned i = 0; i < applications.u.array.length; ++i)
714 {
715 if ( strcmp( applications[i]["appId"], APP_ID ) == 0 )
716 {
717 if ( (const char*)applications[i]["transportId"] != NULL)
718 {
719 p_app = &applications[i];
720 break;
721 }
722 }
723 }
724
725 vlc_mutex_locker locker(&m_lock);
726
727 switch ( m_state )
728 {
729 case Connecting:
730 // We were connecting & fetching the current status.
731 // The media receiver app is running, we are ready to proceed
732 if ( p_app != NULL )
733 {
734 msg_Dbg( m_module, "Media receiver application was already running" );
735 m_appTransportId = (const char*)(*p_app)["transportId"];
736 m_communication->msgConnect( m_appTransportId );
737 setState( Ready );
738 }
739 else
740 {
741 setState( Connected );
742 }
743 break;
744 case Launching:
745 // We already asked for the media receiver application to start
746 if ( p_app != NULL )
747 {
748 msg_Dbg( m_module, "Media receiver application has been started." );
749 m_appTransportId = (const char*)(*p_app)["transportId"];
750 m_communication->msgConnect( m_appTransportId );
751 setState( Ready );
752 }
753 break;
754 case Loading:
755 case Playing:
756 case Paused:
757 case Ready:
758 case TakenOver:
759 case Dead:
760 if ( p_app == NULL )
761 {
762 msg_Warn( m_module, "Media receiver application got closed." );
763 setState( Stopped );
764 m_appTransportId = "";
765 m_mediaSessionId = 0;
766 }
767 break;
768 case Connected:
769 // We might receive a RECEIVER_STATUS while being connected, when pinging/asking the status
770 if ( p_app == NULL )
771 break;
772 // else: fall through and warn
773 default:
774 msg_Warn( m_module, "Unexpected RECEIVER_STATUS with state %s. "
775 "Checking media status",
776 StateToStr( m_state ) );
777 // This is likely because the chromecast refused the playback, but
778 // let's check by explicitely probing the media status
779 if (m_last_request_id == 0)
780 m_last_request_id = m_communication->msgPlayerGetStatus( m_appTransportId );
781 break;
782 }
783 }
784 else if (type == "LAUNCH_ERROR")
785 {
786 json_value reason = (*p_data)["reason"];
787 msg_Err( m_module, "Failed to start the MediaPlayer: %s",
788 (const char *)reason);
789 vlc_mutex_locker locker(&m_lock);
790 m_appTransportId = "";
791 m_mediaSessionId = 0;
792 setState( Dead );
793 ret = false;
794 }
795 else
796 {
797 msg_Warn( m_module, "Receiver command not supported: %s",
798 msg.payload_utf8().c_str());
799 }
800
801 json_value_free(p_data);
802 return ret;
803 }
804
processMediaMessage(const castchannel::CastMessage & msg)805 void intf_sys_t::processMediaMessage( const castchannel::CastMessage& msg )
806 {
807 json_value *p_data = json_parse(msg.payload_utf8().c_str());
808 std::string type((*p_data)["type"]);
809 int64_t requestId = (json_int_t) (*p_data)["requestId"];
810
811 vlc_mutex_locker locker( &m_lock );
812
813 if ((m_last_request_id != 0 && requestId != m_last_request_id))
814 {
815 json_value_free(p_data);
816 return;
817 }
818 m_last_request_id = 0;
819
820 if (type == "MEDIA_STATUS")
821 {
822 json_value status = (*p_data)["status"];
823
824 int64_t sessionId = (json_int_t) status[0]["mediaSessionId"];
825 std::string newPlayerState = (const char*)status[0]["playerState"];
826 std::string idleReason = (const char*)status[0]["idleReason"];
827
828 msg_Dbg( m_module, "Player state: %s sessionId: %" PRId64,
829 status[0]["playerState"].operator const char *(),
830 sessionId );
831
832 if (sessionId != 0 && m_mediaSessionId != 0 && m_mediaSessionId != sessionId)
833 {
834 msg_Warn( m_module, "Ignoring message for a different media session");
835 json_value_free(p_data);
836 return;
837 }
838
839 if (newPlayerState == "IDLE" || newPlayerState.empty() == true )
840 {
841 /* Idle state is expected when the media receiver application is
842 * started. In case the state is still Buffering, it denotes an error.
843 * In most case, we'd receive a RECEIVER_STATUS message, which causes
844 * use to ask for the MEDIA_STATUS before assuming an error occured.
845 * If the chromecast silently gave up on playing our stream, we also
846 * might have an empty status array.
847 * If the media load indeed failed, we need to try another
848 * transcode/remux configuration, or give up.
849 * In case we are now loading, we might also receive an INTERRUPTED
850 * state for the previous session, which we wouldn't ignore earlier
851 * since our mediaSessionID was reset to 0.
852 * In this case, don't assume we're being taken over, as we are
853 * actually doing the take over.
854 */
855 if ( m_state != Ready && m_state != LoadFailed && m_state != Loading )
856 {
857 // The playback stopped
858 if ( idleReason == "INTERRUPTED" )
859 {
860 setState( TakenOver );
861 // Do not reset the mediaSessionId to ensure we refuse all
862 // other MEDIA_STATUS from the new session.
863 }
864 else if ( idleReason == "ERROR" && m_state == Playing )
865 setState( LoadFailed );
866 else if ( m_state == Buffering )
867 setState( LoadFailed );
868 else
869 {
870 if (idleReason == "FINISHED")
871 m_cc_eof = true;
872 setState( Ready );
873 }
874 }
875 }
876 else
877 {
878 if ( m_mediaSessionId == 0 )
879 {
880 m_mediaSessionId = sessionId;
881 msg_Dbg( m_module, "New mediaSessionId: %" PRId64, m_mediaSessionId );
882 }
883
884 if (m_request_stop)
885 {
886 m_request_stop = false;
887 m_last_request_id =
888 m_communication->msgPlayerStop( m_appTransportId, m_mediaSessionId );
889 setState( Stopping );
890 }
891 else if (newPlayerState == "PLAYING")
892 {
893 mtime_t currentTime = timeCCToVLC((double) status[0]["currentTime"]);
894 m_cc_time = currentTime;
895 m_cc_time_date = mdate();
896
897 setState( Playing );
898 }
899 else if (newPlayerState == "BUFFERING")
900 {
901 if ( m_state != Buffering )
902 {
903 /* EOF when state goes from Playing to Buffering. There can
904 * be a lot of false positives (when seeking or when the cc
905 * request more input) but this state is fetched only when
906 * the input has reached EOF. */
907
908 setState( Buffering );
909 }
910 }
911 else if (newPlayerState == "PAUSED")
912 {
913 if ( m_state != Paused )
914 {
915 setState( Paused );
916 }
917 }
918 else if ( newPlayerState == "LOADING" )
919 {
920 if ( m_state != Loading )
921 {
922 msg_Dbg( m_module, "Chromecast is loading the stream" );
923 setState( Loading );
924 }
925 }
926 else
927 msg_Warn( m_module, "Unknown Chromecast MEDIA_STATUS state %s", newPlayerState.c_str());
928 }
929 }
930 else if (type == "LOAD_FAILED")
931 {
932 msg_Err( m_module, "Media load failed");
933 setState( LoadFailed );
934 }
935 else if (type == "LOAD_CANCELLED")
936 {
937 msg_Dbg( m_module, "LOAD canceled by another command");
938 }
939 else if (type == "INVALID_REQUEST")
940 {
941 msg_Dbg( m_module, "We sent an invalid request reason:%s", (const char*)(*p_data)["reason"] );
942 }
943 else
944 {
945 msg_Warn( m_module, "Media command not supported: %s",
946 msg.payload_utf8().c_str());
947 }
948
949 json_value_free(p_data);
950 }
951
processConnectionMessage(const castchannel::CastMessage & msg)952 void intf_sys_t::processConnectionMessage( const castchannel::CastMessage& msg )
953 {
954 json_value *p_data = json_parse(msg.payload_utf8().c_str());
955 std::string type((*p_data)["type"]);
956 json_value_free(p_data);
957
958 if ( type == "CLOSE" )
959 {
960 // Close message indicates an application is being closed, not the connection.
961 // From this point on, we need to relaunch the media receiver app
962 vlc_mutex_locker locker(&m_lock);
963 m_appTransportId = "";
964 m_mediaSessionId = 0;
965 setState( Connected );
966 }
967 else
968 {
969 msg_Warn( m_module, "Connection command not supported: %s",
970 type.c_str());
971 }
972 }
973
handleMessages()974 bool intf_sys_t::handleMessages()
975 {
976 uint8_t p_packet[PACKET_MAX_LEN];
977 size_t i_payloadSize = 0;
978 size_t i_received = 0;
979 bool b_timeout = false;
980 mtime_t i_begin_time = mdate();
981
982 /* Packet structure:
983 * +------------------------------------+------------------------------+
984 * | Payload size (uint32_t big endian) | Payload data |
985 * +------------------------------------+------------------------------+
986 */
987 while ( true )
988 {
989 // If we haven't received the payload size yet, let's wait for it. Otherwise, we know
990 // how many bytes to read
991 ssize_t i_ret = m_communication->receive( p_packet + i_received,
992 i_payloadSize + PACKET_HEADER_LEN - i_received,
993 PING_WAIT_TIME - ( mdate() - i_begin_time ) / CLOCK_FREQ,
994 &b_timeout );
995 if ( i_ret < 0 )
996 {
997 if ( errno == EINTR )
998 return true;
999 // An error occured, we give up
1000 msg_Err( m_module, "The connection to the Chromecast died (receiving).");
1001 vlc_mutex_locker locker(&m_lock);
1002 setState( Dead );
1003 return false;
1004 }
1005 else if ( b_timeout == true )
1006 {
1007 // If no commands were queued to be sent, we timed out. Let's ping the chromecast
1008 vlc_mutex_locker locker(&m_lock);
1009 if ( m_pingRetriesLeft == 0 )
1010 {
1011 m_state = Dead;
1012 msg_Warn( m_module, "No PING response from the chromecast" );
1013 return false;
1014 }
1015 --m_pingRetriesLeft;
1016 m_communication->msgPing();
1017 m_communication->msgReceiverGetStatus();
1018 return true;
1019 }
1020 assert( i_ret != 0 );
1021 i_received += i_ret;
1022 if ( i_payloadSize == 0 )
1023 {
1024 i_payloadSize = U32_AT( p_packet );
1025 if ( i_payloadSize > PACKET_MAX_LEN - PACKET_HEADER_LEN )
1026 {
1027 msg_Err( m_module, "Payload size is too long: dropping connection" );
1028 vlc_mutex_locker locker(&m_lock);
1029 m_state = Dead;
1030 return false;
1031 }
1032 continue;
1033 }
1034 assert( i_received <= i_payloadSize + PACKET_HEADER_LEN );
1035 if ( i_received == i_payloadSize + PACKET_HEADER_LEN )
1036 break;
1037 }
1038 castchannel::CastMessage msg;
1039 msg.ParseFromArray(p_packet + PACKET_HEADER_LEN, i_payloadSize);
1040 return processMessage(msg);
1041 }
1042
doStop()1043 void intf_sys_t::doStop()
1044 {
1045 if( !isStatePlaying() )
1046 return;
1047
1048 if ( m_mediaSessionId == 0 )
1049 m_request_stop = true;
1050 else
1051 {
1052 m_last_request_id =
1053 m_communication->msgPlayerStop( m_appTransportId, m_mediaSessionId );
1054 setState( Stopping );
1055 }
1056 }
1057
requestPlayerStop()1058 void intf_sys_t::requestPlayerStop()
1059 {
1060 vlc_mutex_locker locker(&m_lock);
1061
1062 std::queue<QueueableMessages> empty;
1063 std::swap(m_msgQueue, empty);
1064
1065 m_retry_on_fail = false;
1066 m_request_load = false;
1067
1068 if( vlc_killed() )
1069 {
1070 if( !isStatePlaying() )
1071 return;
1072 queueMessage( Stop );
1073 }
1074 else
1075 doStop();
1076 }
1077
state() const1078 States intf_sys_t::state() const
1079 {
1080 vlc_mutex_locker locker( &m_lock );
1081 return m_state;
1082 }
1083
timeCCToVLC(double time)1084 mtime_t intf_sys_t::timeCCToVLC(double time)
1085 {
1086 return mtime_t(time * 1000000.0);
1087 }
1088
timeVLCToCC(mtime_t time)1089 std::string intf_sys_t::timeVLCToCC(mtime_t time)
1090 {
1091 std::stringstream ss;
1092 ss.setf(std::ios_base::fixed, std::ios_base::floatfield);
1093 ss << std::setprecision(6) << (double (time) / 1000000.0);
1094 return ss.str();
1095 }
1096
setOnInputEventCb(on_input_event_itf on_input_event,void * on_input_event_data)1097 void intf_sys_t::setOnInputEventCb(on_input_event_itf on_input_event,
1098 void *on_input_event_data)
1099 {
1100 vlc_mutex_locker locker(&m_lock);
1101 m_on_input_event = on_input_event;
1102 m_on_input_event_data = on_input_event_data;
1103 }
1104
setDemuxEnabled(bool enabled,on_paused_changed_itf on_paused_changed,void * on_paused_changed_data)1105 void intf_sys_t::setDemuxEnabled(bool enabled,
1106 on_paused_changed_itf on_paused_changed,
1107 void *on_paused_changed_data)
1108 {
1109 vlc_mutex_locker locker(&m_lock);
1110 m_on_paused_changed = on_paused_changed;
1111 m_on_paused_changed_data = on_paused_changed_data;
1112
1113 if( enabled )
1114 {
1115 if( m_state == Dead && !vlc_killed() )
1116 reinit();
1117 }
1118 }
1119
setPauseState(bool paused)1120 void intf_sys_t::setPauseState(bool paused)
1121 {
1122 vlc_mutex_locker locker( &m_lock );
1123 if ( m_mediaSessionId == 0 || paused == m_paused || !m_communication )
1124 return;
1125
1126 m_paused = paused;
1127 msg_Info( m_module, "%s state", paused ? "paused" : "playing" );
1128 if ( !paused )
1129 m_last_request_id =
1130 m_communication->msgPlayerPlay( m_appTransportId, m_mediaSessionId );
1131 else if ( m_state != Paused )
1132 m_last_request_id =
1133 m_communication->msgPlayerPause( m_appTransportId, m_mediaSessionId );
1134 }
1135
getHttpStreamPort() const1136 unsigned int intf_sys_t::getHttpStreamPort() const
1137 {
1138 return m_httpd.m_port;
1139 }
1140
getHttpStreamPath() const1141 std::string intf_sys_t::getHttpStreamPath() const
1142 {
1143 return m_httpd.m_root + "/stream";
1144 }
1145
getHttpArtRoot() const1146 std::string intf_sys_t::getHttpArtRoot() const
1147 {
1148 return m_httpd.m_root + "/art";
1149 }
1150
isFinishedPlaying()1151 bool intf_sys_t::isFinishedPlaying()
1152 {
1153 return m_cc_eof || isStateError() || m_state == Stopped;
1154 }
1155
setMeta(vlc_meta_t * p_meta)1156 void intf_sys_t::setMeta(vlc_meta_t *p_meta)
1157 {
1158 vlc_mutex_locker locker(&m_lock);
1159 if (m_meta != NULL)
1160 vlc_meta_Delete(m_meta);
1161 m_meta = p_meta;
1162 }
1163
getPlaybackTimestamp()1164 mtime_t intf_sys_t::getPlaybackTimestamp()
1165 {
1166 vlc_mutex_locker locker( &m_lock );
1167 switch( m_state )
1168 {
1169 case Buffering:
1170 case Paused:
1171 if( !m_played_once )
1172 return VLC_TS_INVALID;
1173 /* fallthrough */
1174 case Playing:
1175 {
1176 assert( m_communication );
1177 mtime_t now = mdate();
1178 if( m_state == Playing && m_last_request_id == 0
1179 && now - m_cc_time_last_request_date > INT64_C(4000000) )
1180 {
1181 m_cc_time_last_request_date = now;
1182 m_last_request_id =
1183 m_communication->msgPlayerGetStatus( m_appTransportId );
1184 }
1185 return m_cc_time + now - m_cc_time_date;
1186 }
1187 default:
1188 return VLC_TS_INVALID;
1189 }
1190 }
1191
setState(States state)1192 void intf_sys_t::setState( States state )
1193 {
1194 if ( m_state != state )
1195 {
1196 #ifndef NDEBUG
1197 msg_Dbg( m_module, "Switching from state %s to %s", StateToStr( m_state ), StateToStr( state ) );
1198 #endif
1199 m_state = state;
1200
1201 switch( m_state )
1202 {
1203 case Connected:
1204 case Ready:
1205 tryLoad();
1206 break;
1207 case Paused:
1208 if (m_played_once && m_on_paused_changed != NULL)
1209 m_on_paused_changed(m_on_paused_changed_data, true);
1210 break;
1211 case Playing:
1212 if (m_played_once && m_on_paused_changed != NULL)
1213 m_on_paused_changed(m_on_paused_changed_data, false);
1214 m_played_once = true;
1215 break;
1216 default:
1217 break;
1218 }
1219 vlc_cond_signal( &m_stateChangedCond );
1220 vlc_cond_signal( &m_pace_cond );
1221 }
1222 }
1223
get_time(void * pt)1224 mtime_t intf_sys_t::get_time(void *pt)
1225 {
1226 intf_sys_t *p_this = static_cast<intf_sys_t*>(pt);
1227 return p_this->getPlaybackTimestamp();
1228 }
1229
set_demux_enabled(void * pt,bool enabled,on_paused_changed_itf itf,void * data)1230 void intf_sys_t::set_demux_enabled(void *pt, bool enabled,
1231 on_paused_changed_itf itf, void *data)
1232 {
1233 intf_sys_t *p_this = static_cast<intf_sys_t*>(pt);
1234 p_this->setDemuxEnabled(enabled, itf, data);
1235 }
1236
pace(void * pt)1237 int intf_sys_t::pace(void *pt)
1238 {
1239 intf_sys_t *p_this = static_cast<intf_sys_t*>(pt);
1240 return p_this->pace();
1241 }
1242
send_input_event(void * pt,enum cc_input_event event,union cc_input_arg arg)1243 void intf_sys_t::send_input_event(void *pt, enum cc_input_event event, union cc_input_arg arg)
1244 {
1245 intf_sys_t *p_this = static_cast<intf_sys_t*>(pt);
1246 return p_this->sendInputEvent(event, arg);
1247 }
1248
set_pause_state(void * pt,bool paused)1249 void intf_sys_t::set_pause_state(void *pt, bool paused)
1250 {
1251 intf_sys_t *p_this = static_cast<intf_sys_t*>(pt);
1252 p_this->setPauseState( paused );
1253 }
1254
set_meta(void * pt,vlc_meta_t * p_meta)1255 void intf_sys_t::set_meta(void *pt, vlc_meta_t *p_meta)
1256 {
1257 intf_sys_t *p_this = static_cast<intf_sys_t*>(pt);
1258 p_this->setMeta( p_meta );
1259 }
1260