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