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