chromecast: add isStateError() method
[vlc.git] / modules / stream_out / chromecast / chromecast_ctrl.cpp
blobd9ebb771a14628635a79f1566110edbddb579704
1 /*****************************************************************************
2 * chromecast_ctrl.cpp: Chromecast module for vlc
3 *****************************************************************************
4 * Copyright © 2014-2015 VideoLAN
6 * Authors: Adrien Maglo <magsoft@videolan.org>
7 * Jean-Baptiste Kempf <jb@videolan.org>
8 * Steve Lhomme <robux4@videolabs.io>
9 * Hugo Beauzée-Luyssen <hugo@beauzee.fr>
11 * This program is free software; you can redistribute it and/or modify it
12 * under the terms of the GNU Lesser General Public License as published by
13 * the Free Software Foundation; either version 2.1 of the License, or
14 * (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU Lesser General Public License for more details.
21 * You should have received a copy of the GNU Lesser General Public License
22 * along with this program; if not, write to the Free Software Foundation,
23 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
24 *****************************************************************************/
26 /*****************************************************************************
27 * Preamble
28 *****************************************************************************/
30 #ifdef HAVE_CONFIG_H
31 # include "config.h"
32 #endif
34 #include "chromecast.h"
36 #include <cassert>
37 #include <cerrno>
38 #include <iomanip>
40 #include <vlc_stream.h>
42 #include "../../misc/webservices/json.h"
44 /* deadline regarding pings sent from receiver */
45 #define PING_WAIT_TIME 6000
46 #define PING_WAIT_RETRIES 1
48 static int httpd_file_fill_cb( httpd_file_sys_t *data, httpd_file_t *http_file,
49 uint8_t *psz_request, uint8_t **pp_data, int *pi_data );
51 static const char* StateToStr( States s )
53 switch (s )
55 case Authenticating:
56 return "Authenticating";
57 case Connecting:
58 return "Connecting";
59 case Connected:
60 return "Connected";
61 case Launching:
62 return "Lauching";
63 case Ready:
64 return "Ready";
65 case LoadFailed:
66 return "LoadFailed";
67 case Loading:
68 return "Loading";
69 case Buffering:
70 return "Buffering";
71 case Playing:
72 return "Playing";
73 case Paused:
74 return "Paused";
75 case Seeking:
76 return "Seeking";
77 case Stopping:
78 return "Stopping";
79 case Dead:
80 return "Dead";
81 case TakenOver:
82 return "TakenOver";
84 vlc_assert_unreachable();
87 /*****************************************************************************
88 * intf_sys_t: class definition
89 *****************************************************************************/
90 intf_sys_t::intf_sys_t(vlc_object_t * const p_this, int port, std::string device_addr,
91 int device_port, httpd_host_t *httpd_host)
92 : m_module(p_this)
93 , m_streaming_port(port)
94 , m_mediaSessionId( 0 )
95 , m_on_seek_done( NULL )
96 , m_on_seek_done_data( NULL )
97 , m_on_paused_changed( NULL )
98 , m_on_paused_changed_data( NULL )
99 , m_communication( p_this, device_addr.c_str(), device_port )
100 , m_state( Authenticating )
101 , m_played_once( false )
102 , m_request_stop( false )
103 , m_request_load( false )
104 , m_eof( false )
105 , m_pace( false )
106 , m_meta( NULL )
107 , m_httpd_host(httpd_host)
108 , m_httpd_file(NULL)
109 , m_art_url(NULL)
110 , m_art_idx(0)
111 , m_time_playback_started( VLC_TS_INVALID )
112 , m_ts_local_start( VLC_TS_INVALID )
113 , m_length( VLC_TS_INVALID )
114 , m_pingRetriesLeft( PING_WAIT_RETRIES )
116 m_ctl_thread_interrupt = vlc_interrupt_create();
117 if( unlikely(m_ctl_thread_interrupt == NULL) )
118 throw std::runtime_error( "error creating interrupt context" );
120 vlc_mutex_init(&m_lock);
121 vlc_cond_init( &m_stateChangedCond );
122 vlc_cond_init( &m_pace_cond );
124 std::stringstream ss;
125 ss << "http://" << m_communication.getServerIp() << ":" << port;
126 m_art_http_ip = ss.str();
128 m_common.p_opaque = this;
129 m_common.pf_set_on_paused_changed_cb = set_on_paused_changed_cb;
130 m_common.pf_get_position = get_position;
131 m_common.pf_get_time = get_time;
132 m_common.pf_set_length = set_length;
133 m_common.pf_set_initial_time = set_initial_time;
134 m_common.pf_pace = pace;
135 m_common.pf_set_pause_state = set_pause_state;
136 m_common.pf_set_meta = set_meta;
138 assert( var_Type( m_module->obj.parent->obj.parent, CC_SHARED_VAR_NAME) == 0 );
139 if (var_Create( m_module->obj.parent->obj.parent, CC_SHARED_VAR_NAME, VLC_VAR_ADDRESS ) == VLC_SUCCESS )
140 var_SetAddress( m_module->obj.parent->obj.parent, CC_SHARED_VAR_NAME, &m_common );
142 // Start the Chromecast event thread.
143 if (vlc_clone(&m_chromecastThread, ChromecastThread, this,
144 VLC_THREAD_PRIORITY_LOW))
146 vlc_interrupt_destroy( m_ctl_thread_interrupt );
147 vlc_cond_destroy( &m_stateChangedCond );
148 vlc_cond_destroy( &m_pace_cond );
149 var_SetAddress( m_module->obj.parent->obj.parent, CC_SHARED_VAR_NAME, NULL );
150 throw std::runtime_error( "error creating cc thread" );
154 intf_sys_t::~intf_sys_t()
156 var_Destroy( m_module->obj.parent->obj.parent, CC_SHARED_VAR_NAME );
158 vlc_mutex_lock(&m_lock);
159 switch ( m_state )
161 case Ready:
162 case Loading:
163 case Buffering:
164 case Playing:
165 case Paused:
166 case Seeking:
167 case Stopping:
168 // Generate the close messages.
169 m_communication.msgReceiverClose( m_appTransportId );
170 /* fallthrough */
171 case Connecting:
172 case Connected:
173 case Launching:
174 m_communication.msgReceiverClose(DEFAULT_CHOMECAST_RECEIVER);
175 /* fallthrough */
176 default:
177 break;
179 vlc_mutex_unlock(&m_lock);
181 vlc_interrupt_kill( m_ctl_thread_interrupt );
183 vlc_join(m_chromecastThread, NULL);
185 vlc_interrupt_destroy( m_ctl_thread_interrupt );
187 if (m_meta != NULL)
188 vlc_meta_Delete(m_meta);
190 if( m_httpd_file )
191 httpd_FileDelete( m_httpd_file );
193 free( m_art_url );
195 vlc_cond_destroy(&m_stateChangedCond);
196 vlc_cond_destroy(&m_pace_cond);
197 vlc_mutex_destroy(&m_lock);
200 int intf_sys_t::httpd_file_fill( uint8_t *psz_request, uint8_t **pp_data, int *pi_data )
202 (void) psz_request;
204 vlc_mutex_lock( &m_lock );
205 if( !m_art_url )
207 vlc_mutex_unlock( &m_lock );
208 return VLC_EGENERIC;
210 char *psz_art = strdup( m_art_url );
211 vlc_mutex_unlock( &m_lock );
213 stream_t *s = vlc_stream_NewURL( m_module, psz_art );
214 free( psz_art );
215 if( !s )
216 return VLC_EGENERIC;
218 uint64_t size;
219 if( vlc_stream_GetSize( s, &size ) != VLC_SUCCESS
220 || size > INT64_C( 10000000 ) )
222 msg_Warn( m_module, "art stream is too big or invalid" );
223 vlc_stream_Delete( s );
224 return VLC_EGENERIC;
227 *pp_data = (uint8_t *)malloc( size );
228 if( !*pp_data )
230 vlc_stream_Delete( s );
231 return VLC_EGENERIC;
234 ssize_t read = vlc_stream_Read( s, *pp_data, size );
235 vlc_stream_Delete( s );
237 if( read < 0 || (size_t)read != size )
239 free( *pp_data );
240 *pp_data = NULL;
241 return VLC_EGENERIC;
243 *pi_data = size;
245 return VLC_SUCCESS;
248 static int httpd_file_fill_cb( httpd_file_sys_t *data, httpd_file_t *http_file,
249 uint8_t *psz_request, uint8_t **pp_data, int *pi_data )
251 (void) http_file;
252 intf_sys_t *p_sys = static_cast<intf_sys_t*>((void *)data);
253 return p_sys->httpd_file_fill( psz_request, pp_data, pi_data );
256 void intf_sys_t::prepareHttpArtwork()
258 const char *psz_art = m_meta ? vlc_meta_Get( m_meta, vlc_meta_ArtworkURL ) : NULL;
259 /* Abort if there is no art or if the art is already served */
260 if( !psz_art || strncmp( psz_art, "http", 4) == 0 )
261 return;
263 std::stringstream ss_art_idx;
265 if( m_art_url && strcmp( m_art_url, psz_art ) == 0 )
267 /* Same art: use the previous cached artwork url */
268 assert( m_art_idx != 0 );
269 ss_art_idx << "/art" << (m_art_idx - 1);
271 else
273 /* New art: create a new httpd file instance with a new url. The
274 * artwork has to be different since the CC will cache the content. */
276 ss_art_idx << "/art" << m_art_idx;
277 m_art_idx++;
279 vlc_mutex_unlock( &m_lock );
281 if( m_httpd_file )
282 httpd_FileDelete( m_httpd_file );
283 m_httpd_file = httpd_FileNew( m_httpd_host, ss_art_idx.str().c_str(),
284 "application/octet-stream", NULL, NULL,
285 httpd_file_fill_cb, (httpd_file_sys_t *) this );
287 vlc_mutex_lock( &m_lock );
288 if( !m_httpd_file )
289 return;
291 free( m_art_url );
292 m_art_url = strdup( psz_art );
295 std::stringstream ss;
296 ss << m_art_http_ip << ss_art_idx.str();
297 vlc_meta_Set( m_meta, vlc_meta_ArtworkURL, ss.str().c_str() );
300 void intf_sys_t::tryLoad()
302 if( !m_request_load )
303 return;
305 if ( !isStateReady() )
307 if ( m_state == Dead )
309 msg_Warn( m_module, "no Chromecast hook possible");
310 m_request_load = false;
312 else if( m_state == Connected )
314 msg_Dbg( m_module, "Starting the media receiver application" );
315 // Don't use setState as we don't want to signal the condition in this case.
316 m_state = Launching;
317 m_communication.msgReceiverLaunchApp();
319 return;
322 m_request_load = false;
324 // We should now be in the ready state, and therefor have a valid transportId
325 assert( m_appTransportId.empty() == false );
326 // Reset the mediaSessionID to allow the new session to become the current one.
327 // we cannot start a new load when the last one is still processing
328 m_communication.msgPlayerLoad( m_appTransportId, m_streaming_port, m_mime, m_meta );
329 m_state = Loading;
332 void intf_sys_t::setHasInput( const std::string mime_type )
334 vlc_mutex_locker locker(&m_lock);
335 msg_Dbg( m_module, "Loading content" );
337 m_request_stop = false;
339 this->m_mime = mime_type;
341 /* new input: clear message queue */
342 std::queue<QueueableMessages> empty;
343 std::swap(m_msgQueue, empty);
345 prepareHttpArtwork();
347 m_played_once = false;
348 m_eof = false;
349 m_mediaSessionId = 0;
350 m_request_load = true;
352 tryLoad();
353 vlc_cond_signal( &m_stateChangedCond );
356 bool intf_sys_t::isStateError() const
358 switch( m_state )
360 case LoadFailed:
361 case Dead:
362 case TakenOver:
363 return true;
364 default:
365 return false;
369 bool intf_sys_t::isStatePlaying() const
371 switch( m_state )
373 case Loading:
374 case Buffering:
375 case Playing:
376 case Paused:
377 case Seeking:
378 return true;
379 default:
380 return false;
384 bool intf_sys_t::isStateReady() const
386 switch( m_state )
388 case Connected:
389 case Launching:
390 case Authenticating:
391 case Connecting:
392 case Stopping:
393 case Dead:
394 return false;
395 default:
396 return true;
400 void intf_sys_t::setPacing(bool do_pace)
402 vlc_mutex_lock( &m_lock );
403 if( m_pace == do_pace )
405 vlc_mutex_unlock( &m_lock );
406 return;
408 m_pace = do_pace;
409 vlc_mutex_unlock( &m_lock );
410 vlc_cond_signal( &m_pace_cond );
413 static void interrupt_wake_up_cb( void *data )
415 intf_sys_t *p_sys = static_cast<intf_sys_t*>((void *)data);
416 p_sys->interrupt_wake_up();
419 void intf_sys_t::interrupt_wake_up()
421 vlc_mutex_locker locker( &m_lock );
422 m_interrupted = true;
423 vlc_cond_signal( &m_pace_cond );
426 bool intf_sys_t::pace()
428 vlc_mutex_locker locker(&m_lock);
429 if( !m_pace )
430 return true;
432 m_interrupted = false;
433 vlc_interrupt_register( interrupt_wake_up_cb, this );
435 int ret = 0;
436 mtime_t deadline = mdate() + INT64_C(500000);
437 while( m_pace && !m_interrupted && ret == 0 )
438 ret = vlc_cond_timedwait( &m_pace_cond, &m_lock, deadline );
439 vlc_interrupt_unregister();
441 return ret == 0;
445 * @brief Process a message received from the Chromecast
446 * @param msg the CastMessage to process
447 * @return 0 if the message has been successfuly processed else -1
449 void intf_sys_t::processMessage(const castchannel::CastMessage &msg)
451 const std::string & namespace_ = msg.namespace_();
453 #ifndef NDEBUG
454 msg_Dbg( m_module, "processMessage: %s->%s %s", namespace_.c_str(), msg.destination_id().c_str(), msg.payload_utf8().c_str());
455 #endif
457 if (namespace_ == NAMESPACE_DEVICEAUTH)
458 processAuthMessage( msg );
459 else if (namespace_ == NAMESPACE_HEARTBEAT)
460 processHeartBeatMessage( msg );
461 else if (namespace_ == NAMESPACE_RECEIVER)
462 processReceiverMessage( msg );
463 else if (namespace_ == NAMESPACE_MEDIA)
464 processMediaMessage( msg );
465 else if (namespace_ == NAMESPACE_CONNECTION)
466 processConnectionMessage( msg );
467 else
469 msg_Err( m_module, "Unknown namespace: %s", msg.namespace_().c_str());
473 void intf_sys_t::queueMessage( QueueableMessages msg )
475 // Assume lock is held by the called
476 m_msgQueue.push( msg );
477 vlc_interrupt_raise( m_ctl_thread_interrupt );
480 /*****************************************************************************
481 * Chromecast thread
482 *****************************************************************************/
483 void* intf_sys_t::ChromecastThread(void* p_data)
485 intf_sys_t *p_sys = static_cast<intf_sys_t*>(p_data);
486 p_sys->mainLoop();
487 return NULL;
490 void intf_sys_t::mainLoop()
492 vlc_savecancel();
493 vlc_interrupt_set( m_ctl_thread_interrupt );
495 // State was already initialized as Authenticating
496 m_communication.msgAuth();
498 while ( !vlc_killed() )
500 if ( !handleMessages() )
501 break;
502 // Reset the interrupt state to avoid commands not being sent (since
503 // the context is still flagged as interrupted)
504 vlc_interrupt_unregister();
505 vlc_mutex_locker lock( &m_lock );
506 while ( m_msgQueue.empty() == false )
508 QueueableMessages msg = m_msgQueue.front();
509 switch ( msg )
511 case Stop:
512 doStop();
513 break;
515 m_msgQueue.pop();
520 void intf_sys_t::processAuthMessage( const castchannel::CastMessage& msg )
522 castchannel::DeviceAuthMessage authMessage;
523 if ( authMessage.ParseFromString(msg.payload_binary()) == false )
525 msg_Warn( m_module, "Failed to parse the payload" );
526 return;
529 if (authMessage.has_error())
531 msg_Err( m_module, "Authentification error: %d", authMessage.error().error_type());
533 else if (!authMessage.has_response())
535 msg_Err( m_module, "Authentification message has no response field");
537 else
539 vlc_mutex_locker locker(&m_lock);
540 setState( Connecting );
541 m_communication.msgConnect(DEFAULT_CHOMECAST_RECEIVER);
542 m_communication.msgReceiverGetStatus();
546 void intf_sys_t::processHeartBeatMessage( const castchannel::CastMessage& msg )
548 json_value *p_data = json_parse(msg.payload_utf8().c_str());
549 std::string type((*p_data)["type"]);
551 if (type == "PING")
553 msg_Dbg( m_module, "PING received from the Chromecast");
554 m_communication.msgPong();
556 else if (type == "PONG")
558 msg_Dbg( m_module, "PONG received from the Chromecast");
559 m_pingRetriesLeft = PING_WAIT_RETRIES;
561 else
563 msg_Warn( m_module, "Heartbeat command not supported: %s", type.c_str());
566 json_value_free(p_data);
569 void intf_sys_t::processReceiverMessage( const castchannel::CastMessage& msg )
571 json_value *p_data = json_parse(msg.payload_utf8().c_str());
572 std::string type((*p_data)["type"]);
574 if (type == "RECEIVER_STATUS")
576 json_value applications = (*p_data)["status"]["applications"];
577 const json_value *p_app = NULL;
579 for (unsigned i = 0; i < applications.u.array.length; ++i)
581 if ( strcmp( applications[i]["appId"], APP_ID ) == 0 )
583 if ( (const char*)applications[i]["transportId"] != NULL)
585 p_app = &applications[i];
586 break;
591 vlc_mutex_locker locker(&m_lock);
593 switch ( m_state )
595 case Connecting:
596 // We were connecting & fetching the current status.
597 // The media receiver app is running, we are ready to proceed
598 if ( p_app != NULL )
600 msg_Dbg( m_module, "Media receiver application was already running" );
601 m_appTransportId = (const char*)(*p_app)["transportId"];
602 m_communication.msgConnect( m_appTransportId );
603 setState( Ready );
605 else
607 setState( Connected );
609 break;
610 case Launching:
611 // We already asked for the media receiver application to start
612 if ( p_app != NULL )
614 msg_Dbg( m_module, "Media receiver application has been started." );
615 m_appTransportId = (const char*)(*p_app)["transportId"];
616 m_communication.msgConnect( m_appTransportId );
617 setState( Ready );
619 break;
620 case Loading:
621 case Playing:
622 case Paused:
623 case Seeking:
624 case Ready:
625 case TakenOver:
626 case Dead:
627 if ( p_app == NULL )
629 msg_Warn( m_module, "Media receiver application got closed." );
630 setState( Connected );
631 m_appTransportId = "";
632 m_mediaSessionId = 0;
634 break;
635 case Connected:
636 // We might receive a RECEIVER_STATUS while being connected, when pinging/asking the status
637 if ( p_app == NULL )
638 break;
639 // else: fall through and warn
640 default:
641 msg_Warn( m_module, "Unexpected RECEIVER_STATUS with state %s. "
642 "Checking media status",
643 StateToStr( m_state ) );
644 // This is likely because the chromecast refused the playback, but
645 // let's check by explicitely probing the media status
646 m_communication.msgPlayerGetStatus( m_appTransportId );
647 break;
650 else if (type == "LAUNCH_ERROR")
652 json_value reason = (*p_data)["reason"];
653 msg_Err( m_module, "Failed to start the MediaPlayer: %s",
654 (const char *)reason);
655 vlc_mutex_locker locker(&m_lock);
656 m_appTransportId = "";
657 m_mediaSessionId = 0;
658 setState( Dead );
660 else
662 msg_Warn( m_module, "Receiver command not supported: %s",
663 msg.payload_utf8().c_str());
666 json_value_free(p_data);
669 void intf_sys_t::processMediaMessage( const castchannel::CastMessage& msg )
671 json_value *p_data = json_parse(msg.payload_utf8().c_str());
672 std::string type((*p_data)["type"]);
674 vlc_mutex_locker locker( &m_lock );
676 if (type == "MEDIA_STATUS")
678 json_value status = (*p_data)["status"];
680 int64_t sessionId = (json_int_t) status[0]["mediaSessionId"];
681 if (m_mediaSessionId != sessionId && m_mediaSessionId != 0 )
683 msg_Dbg( m_module, "Ignoring message for a different media session" );
684 json_value_free(p_data);
685 return;
688 msg_Dbg( m_module, "Player state: %s sessionId: %" PRId64,
689 status[0]["playerState"].operator const char *(),
690 sessionId );
692 std::string newPlayerState = (const char*)status[0]["playerState"];
693 std::string idleReason = (const char*)status[0]["idleReason"];
696 if (newPlayerState == "IDLE" || newPlayerState.empty() == true )
698 /* Idle state is expected when the media receiver application is
699 * started. In case the state is still Buffering, it denotes an error.
700 * In most case, we'd receive a RECEIVER_STATUS message, which causes
701 * use to ask for the MEDIA_STATUS before assuming an error occured.
702 * If the chromecast silently gave up on playing our stream, we also
703 * might have an empty status array.
704 * If the media load indeed failed, we need to try another
705 * transcode/remux configuration, or give up.
706 * In case we are now loading, we might also receive an INTERRUPTED
707 * state for the previous session, which we wouldn't ignore earlier
708 * since our mediaSessionID was reset to 0.
709 * In this case, don't assume we're being taken over, as we are
710 * actually doing the take over.
712 if ( m_state != Ready && m_state != LoadFailed && m_state != Loading )
714 // The playback stopped
715 m_time_playback_started = VLC_TS_INVALID;
716 if ( idleReason == "INTERRUPTED" )
718 setState( TakenOver );
719 // Do not reset the mediaSessionId to ensure we refuse all
720 // other MEDIA_STATUS from the new session.
722 else if ( m_state == Buffering )
723 setState( LoadFailed );
724 else
726 if (idleReason == "FINISHED")
727 m_eof = true;
728 setState( Ready );
732 else
734 if ( m_mediaSessionId == 0 )
736 m_mediaSessionId = sessionId;
737 msg_Dbg( m_module, "New mediaSessionId: %" PRId64, m_mediaSessionId );
740 if (m_request_stop)
742 m_request_stop = false;
743 m_communication.msgPlayerStop( m_appTransportId, m_mediaSessionId );
744 setState( Stopping );
746 else if (newPlayerState == "PLAYING")
748 msg_Dbg( m_module, "Playback started now:%" PRId64 " i_ts_local_start:%" PRId64,
749 m_time_playback_started, m_ts_local_start);
750 if ( m_state != Playing )
752 /* TODO reset demux PCR ? */
753 m_time_playback_started = mdate();
754 setState( Playing );
757 else if (newPlayerState == "BUFFERING")
759 if ( m_state != Buffering )
761 /* EOF when state goes from Playing to Buffering. There can
762 * be a lot of false positives (when seeking or when the cc
763 * request more input) but this state is fetched only when
764 * the input has reached EOF. */
766 m_time_playback_started = VLC_TS_INVALID;
767 setState( Buffering );
770 else if (newPlayerState == "PAUSED")
772 if ( m_state != Paused )
774 #ifndef NDEBUG
775 msg_Dbg( m_module, "Playback paused: date_play_start: %" PRId64, m_time_playback_started);
776 #endif
778 if ( m_time_playback_started != VLC_TS_INVALID && m_state == Playing )
780 /* this is a pause generated remotely, adjust the playback time */
781 m_ts_local_start += mdate() - m_time_playback_started;
782 #ifndef NDEBUG
783 msg_Dbg( m_module, "updated i_ts_local_start:%" PRId64, m_ts_local_start);
784 #endif
786 m_time_playback_started = VLC_TS_INVALID;
787 setState( Paused );
790 else if ( newPlayerState == "LOADING" )
792 if ( m_state != Loading )
794 msg_Dbg( m_module, "Chromecast is loading the stream" );
795 setState( Loading );
798 else
799 msg_Warn( m_module, "Unknown Chromecast MEDIA_STATUS state %s", newPlayerState.c_str());
802 else if (type == "LOAD_FAILED")
804 msg_Err( m_module, "Media load failed");
805 setState( LoadFailed );
807 else if (type == "LOAD_CANCELLED")
809 msg_Dbg( m_module, "LOAD canceled by another command");
811 else if (type == "INVALID_REQUEST")
813 msg_Dbg( m_module, "We sent an invalid request reason:%s", (const char*)(*p_data)["reason"] );
815 else
817 msg_Warn( m_module, "Media command not supported: %s",
818 msg.payload_utf8().c_str());
821 json_value_free(p_data);
824 void intf_sys_t::processConnectionMessage( const castchannel::CastMessage& msg )
826 json_value *p_data = json_parse(msg.payload_utf8().c_str());
827 std::string type((*p_data)["type"]);
828 json_value_free(p_data);
830 if ( type == "CLOSE" )
832 // Close message indicates an application is being closed, not the connection.
833 // From this point on, we need to relaunch the media receiver app
834 vlc_mutex_locker locker(&m_lock);
835 m_appTransportId = "";
836 m_mediaSessionId = 0;
837 setState( Connected );
839 else
841 msg_Warn( m_module, "Connection command not supported: %s",
842 type.c_str());
846 bool intf_sys_t::handleMessages()
848 uint8_t p_packet[PACKET_MAX_LEN];
849 size_t i_payloadSize = 0;
850 size_t i_received = 0;
851 bool b_timeout = false;
852 mtime_t i_begin_time = mdate();
854 /* Packet structure:
855 * +------------------------------------+------------------------------+
856 * | Payload size (uint32_t big endian) | Payload data |
857 * +------------------------------------+------------------------------+
859 while ( true )
861 // If we haven't received the payload size yet, let's wait for it. Otherwise, we know
862 // how many bytes to read
863 ssize_t i_ret = m_communication.receive( p_packet + i_received,
864 i_payloadSize + PACKET_HEADER_LEN - i_received,
865 PING_WAIT_TIME - ( mdate() - i_begin_time ) / CLOCK_FREQ,
866 &b_timeout );
867 if ( i_ret < 0 )
869 if ( errno == EINTR )
870 return true;
871 // An error occured, we give up
872 msg_Err( m_module, "The connection to the Chromecast died (receiving).");
873 vlc_mutex_locker locker(&m_lock);
874 setState( Dead );
875 return false;
877 else if ( b_timeout == true )
879 // If no commands were queued to be sent, we timed out. Let's ping the chromecast
880 vlc_mutex_locker locker(&m_lock);
881 if ( m_pingRetriesLeft == 0 )
883 m_state = Dead;
884 msg_Warn( m_module, "No PING response from the chromecast" );
885 return false;
887 --m_pingRetriesLeft;
888 m_communication.msgPing();
889 m_communication.msgReceiverGetStatus();
890 return true;
892 assert( i_ret != 0 );
893 i_received += i_ret;
894 if ( i_payloadSize == 0 )
896 i_payloadSize = U32_AT( p_packet );
897 if ( i_payloadSize > PACKET_MAX_LEN - PACKET_HEADER_LEN )
899 msg_Err( m_module, "Payload size is too long: dropping connection" );
900 vlc_mutex_locker locker(&m_lock);
901 m_state = Dead;
902 return false;
904 continue;
906 assert( i_received <= i_payloadSize + PACKET_HEADER_LEN );
907 if ( i_received == i_payloadSize + PACKET_HEADER_LEN )
908 break;
910 castchannel::CastMessage msg;
911 msg.ParseFromArray(p_packet + PACKET_HEADER_LEN, i_payloadSize);
912 processMessage(msg);
913 return true;
916 void intf_sys_t::doStop()
918 if( !isStatePlaying() )
919 return;
921 if ( m_mediaSessionId == 0 )
922 m_request_stop = true;
923 else
925 m_communication.msgPlayerStop( m_appTransportId, m_mediaSessionId );
926 setState( Stopping );
930 void intf_sys_t::requestPlayerStop()
932 vlc_mutex_locker locker(&m_lock);
934 std::queue<QueueableMessages> empty;
935 std::swap(m_msgQueue, empty);
937 m_request_load = false;
939 if( vlc_killed() )
941 if( !isStatePlaying() )
942 return;
943 queueMessage( Stop );
945 else
946 doStop();
949 States intf_sys_t::state() const
951 vlc_mutex_locker locker( &m_lock );
952 return m_state;
955 std::string intf_sys_t::timeVLCToCC(mtime_t time)
957 std::stringstream ss;
958 ss.setf(std::ios_base::fixed, std::ios_base::floatfield);
959 ss << std::setprecision(6) << (double (time) / 1000000.0);
960 return ss.str();
963 bool intf_sys_t::requestPlayerSeek(mtime_t pos)
965 vlc_mutex_locker locker(&m_lock);
966 if( !isStatePlaying() || m_mediaSessionId == 0 )
967 return false;
969 unsigned ret = m_communication.msgPlayerSeek( m_appTransportId, m_mediaSessionId,
970 timeVLCToCC( pos ) );
971 if( ret != ChromecastCommunication::kInvalidId )
973 setState( Seeking );
974 return true;
976 return false;
979 void intf_sys_t::setOnSeekDoneCb(on_seek_done_itf on_seek_done, void *on_seek_done_data)
981 vlc_mutex_locker locker(&m_lock);
982 m_on_seek_done = on_seek_done;
983 m_on_seek_done_data = on_seek_done_data;
986 void intf_sys_t::setOnPausedChangedCb(on_paused_changed_itf on_paused_changed,
987 void *on_paused_changed_data)
989 vlc_mutex_locker locker(&m_lock);
990 m_on_paused_changed = on_paused_changed;
991 m_on_paused_changed_data = on_paused_changed_data;
994 void intf_sys_t::setPauseState(bool paused)
996 vlc_mutex_locker locker( &m_lock );
997 if ( m_mediaSessionId == 0 )
998 return;
1000 msg_Dbg( m_module, "%s state", paused ? "paused" : "playing" );
1001 if ( !paused )
1002 m_communication.msgPlayerPlay( m_appTransportId, m_mediaSessionId );
1003 else if ( m_state != Paused )
1004 m_communication.msgPlayerPause( m_appTransportId, m_mediaSessionId );
1007 bool intf_sys_t::isFinishedPlaying()
1009 vlc_mutex_locker locker(&m_lock);
1010 return m_state == LoadFailed || m_state == Dead || m_eof;
1013 void intf_sys_t::setMeta(vlc_meta_t *p_meta)
1015 vlc_mutex_locker locker(&m_lock);
1016 if (m_meta != NULL)
1017 vlc_meta_Delete(m_meta);
1018 m_meta = p_meta;
1021 mtime_t intf_sys_t::getPlaybackTimestamp() const
1023 switch( m_state )
1025 case Playing:
1026 return ( mdate() - m_time_playback_started ) + m_ts_local_start;
1027 #ifdef CHROMECAST_VERBOSE
1028 case Ready:
1029 msg_Dbg(m_module, "receiver idle using buffering time %" PRId64, m_ts_local_start);
1030 break;
1031 case Buffering:
1032 msg_Dbg(m_module, "receiver buffering using buffering time %" PRId64, m_ts_local_start);
1033 break;
1034 case Paused:
1035 msg_Dbg(m_module, "receiver paused using buffering time %" PRId64, m_ts_local_start);
1036 break;
1037 #endif
1038 default:
1039 break;
1041 return m_ts_local_start;
1044 double intf_sys_t::getPlaybackPosition() const
1046 if( m_length > 0 )
1047 return (double) getPlaybackTimestamp() / (double)( m_length );
1048 return 0.0;
1051 void intf_sys_t::setInitialTime(mtime_t time)
1053 if( time )
1054 m_ts_local_start = time;
1057 void intf_sys_t::setState( States state )
1059 if ( m_state != state )
1061 #ifndef NDEBUG
1062 msg_Dbg( m_module, "Switching from state %s to %s", StateToStr( m_state ), StateToStr( state ) );
1063 #endif
1064 if (m_state == Seeking)
1065 if (m_on_seek_done != NULL)
1066 m_on_seek_done(m_on_seek_done_data);
1068 m_state = state;
1070 switch( m_state )
1072 case Connected:
1073 case Ready:
1074 tryLoad();
1075 break;
1076 case Paused:
1077 if (m_played_once && m_on_paused_changed != NULL)
1078 m_on_paused_changed(m_on_paused_changed_data, true);
1079 break;
1080 case Playing:
1081 if (m_played_once && m_on_paused_changed != NULL)
1082 m_on_paused_changed(m_on_paused_changed_data, false);
1083 m_played_once = true;
1084 break;
1085 default:
1086 break;
1088 vlc_cond_signal( &m_stateChangedCond );
1092 mtime_t intf_sys_t::get_time(void *pt)
1094 intf_sys_t *p_this = static_cast<intf_sys_t*>(pt);
1095 vlc_mutex_locker locker( &p_this->m_lock );
1096 return p_this->getPlaybackTimestamp();
1099 double intf_sys_t::get_position(void *pt)
1101 intf_sys_t *p_this = static_cast<intf_sys_t*>(pt);
1102 vlc_mutex_locker locker( &p_this->m_lock );
1103 return p_this->getPlaybackPosition();
1106 void intf_sys_t::set_initial_time(void *pt, mtime_t time )
1108 intf_sys_t *p_this = static_cast<intf_sys_t*>(pt);
1109 vlc_mutex_locker locker( &p_this->m_lock );
1110 return p_this->setInitialTime( time );
1113 void intf_sys_t::set_on_paused_changed_cb(void *pt,
1114 on_paused_changed_itf itf, void *data)
1116 intf_sys_t *p_this = static_cast<intf_sys_t*>(pt);
1117 p_this->setOnPausedChangedCb(itf, data);
1120 void intf_sys_t::set_length(void *pt, mtime_t length)
1122 intf_sys_t *p_this = static_cast<intf_sys_t*>(pt);
1123 p_this->m_length = length;
1126 bool intf_sys_t::pace(void *pt)
1128 intf_sys_t *p_this = static_cast<intf_sys_t*>(pt);
1129 return p_this->pace();
1132 void intf_sys_t::set_pause_state(void *pt, bool paused)
1134 intf_sys_t *p_this = static_cast<intf_sys_t*>(pt);
1135 p_this->setPauseState( paused );
1138 void intf_sys_t::set_meta(void *pt, vlc_meta_t *p_meta)
1140 intf_sys_t *p_this = static_cast<intf_sys_t*>(pt);
1141 p_this->setMeta( p_meta );