chromecast: rework seek
[vlc.git] / modules / stream_out / chromecast / chromecast_ctrl.cpp
blob83faa09d510db8cb061fead08de09abdbce317b9
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>
39 #include <vlc_stream.h>
41 #include "../../misc/webservices/json.h"
43 /* deadline regarding pings sent from receiver */
44 #define PING_WAIT_TIME 6000
45 #define PING_WAIT_RETRIES 1
47 static int httpd_file_fill_cb( httpd_file_sys_t *data, httpd_file_t *http_file,
48 uint8_t *psz_request, uint8_t **pp_data, int *pi_data );
50 static const char* StateToStr( States s )
52 switch (s )
54 case Authenticating:
55 return "Authenticating";
56 case Connecting:
57 return "Connecting";
58 case Connected:
59 return "Connected";
60 case Launching:
61 return "Lauching";
62 case Ready:
63 return "Ready";
64 case LoadFailed:
65 return "LoadFailed";
66 case Loading:
67 return "Loading";
68 case Buffering:
69 return "Buffering";
70 case Playing:
71 return "Playing";
72 case Paused:
73 return "Paused";
74 case Seeking:
75 return "Seeking";
76 case Stopping:
77 return "Stopping";
78 case Dead:
79 return "Dead";
80 case TakenOver:
81 return "TakenOver";
83 vlc_assert_unreachable();
86 /*****************************************************************************
87 * intf_sys_t: class definition
88 *****************************************************************************/
89 intf_sys_t::intf_sys_t(vlc_object_t * const p_this, int port, std::string device_addr,
90 int device_port, vlc_interrupt_t *p_interrupt, httpd_host_t *httpd_host)
91 : m_module(p_this)
92 , m_streaming_port(port)
93 , m_mediaSessionId( 0 )
94 , m_communication( p_this, device_addr.c_str(), device_port )
95 , m_state( Authenticating )
96 , m_request_stop( false )
97 , m_request_load( false )
98 , m_eof( false )
99 , m_pace( false )
100 , m_meta( NULL )
101 , m_ctl_thread_interrupt(p_interrupt)
102 , m_httpd_host(httpd_host)
103 , m_art_url(NULL)
104 , m_time_playback_started( VLC_TS_INVALID )
105 , m_ts_local_start( VLC_TS_INVALID )
106 , m_ts_seek( VLC_TS_INVALID )
107 , m_length( VLC_TS_INVALID )
108 , m_pingRetriesLeft( PING_WAIT_RETRIES )
110 vlc_mutex_init(&m_lock);
111 vlc_cond_init( &m_stateChangedCond );
112 vlc_cond_init( &m_pace_cond );
114 const char *psz_artmime = "application/octet-stream";
115 m_httpd_file = httpd_FileNew( m_httpd_host, "/art", psz_artmime, NULL, NULL,
116 httpd_file_fill_cb, (httpd_file_sys_t *) this );
118 std::stringstream ss;
119 ss << "http://" << m_communication.getServerIp() << ":" << port;
120 m_art_http_ip = ss.str();
122 m_common.p_opaque = this;
123 m_common.pf_get_position = get_position;
124 m_common.pf_get_time = get_time;
125 m_common.pf_set_length = set_length;
126 m_common.pf_set_initial_time = set_initial_time;
127 m_common.pf_pace = pace;
128 m_common.pf_set_pause_state = set_pause_state;
129 m_common.pf_set_meta = set_meta;
131 assert( var_Type( m_module->obj.parent->obj.parent, CC_SHARED_VAR_NAME) == 0 );
132 if (var_Create( m_module->obj.parent->obj.parent, CC_SHARED_VAR_NAME, VLC_VAR_ADDRESS ) == VLC_SUCCESS )
133 var_SetAddress( m_module->obj.parent->obj.parent, CC_SHARED_VAR_NAME, &m_common );
135 // Start the Chromecast event thread.
136 if (vlc_clone(&m_chromecastThread, ChromecastThread, this,
137 VLC_THREAD_PRIORITY_LOW))
138 throw std::runtime_error( "error creating cc thread" );
141 intf_sys_t::~intf_sys_t()
143 var_Destroy( m_module->obj.parent->obj.parent, CC_SHARED_VAR_NAME );
145 vlc_mutex_lock(&m_lock);
146 switch ( m_state )
148 case Ready:
149 case Loading:
150 case Buffering:
151 case Playing:
152 case Paused:
153 case Seeking:
154 case Stopping:
155 // Generate the close messages.
156 m_communication.msgReceiverClose( m_appTransportId );
157 /* fallthrough */
158 case Connecting:
159 case Connected:
160 case Launching:
161 m_communication.msgReceiverClose(DEFAULT_CHOMECAST_RECEIVER);
162 /* fallthrough */
163 default:
164 break;
166 vlc_mutex_unlock(&m_lock);
168 vlc_interrupt_kill( m_ctl_thread_interrupt );
170 vlc_join(m_chromecastThread, NULL);
172 vlc_interrupt_destroy( m_ctl_thread_interrupt );
174 if (m_meta != NULL)
175 vlc_meta_Delete(m_meta);
177 if( m_httpd_file )
178 httpd_FileDelete( m_httpd_file );
180 free( m_art_url );
182 vlc_cond_destroy(&m_stateChangedCond);
183 vlc_cond_destroy(&m_pace_cond);
184 vlc_mutex_destroy(&m_lock);
187 int intf_sys_t::httpd_file_fill( uint8_t *psz_request, uint8_t **pp_data, int *pi_data )
189 (void) psz_request;
191 vlc_mutex_lock( &m_lock );
192 if( !m_art_url )
194 vlc_mutex_unlock( &m_lock );
195 return VLC_EGENERIC;
197 char *psz_art = strdup( m_art_url );
198 vlc_mutex_unlock( &m_lock );
200 stream_t *s = vlc_stream_NewURL( m_module, psz_art );
201 free( psz_art );
202 if( !s )
203 return VLC_EGENERIC;
205 uint64_t size;
206 if( vlc_stream_GetSize( s, &size ) != VLC_SUCCESS
207 || size > INT64_C( 10000000 ) )
209 msg_Warn( m_module, "art stream is too big or invalid" );
210 vlc_stream_Delete( s );
211 return VLC_EGENERIC;
214 *pp_data = (uint8_t *)malloc( size );
215 if( !*pp_data )
217 vlc_stream_Delete( s );
218 return VLC_EGENERIC;
221 ssize_t read = vlc_stream_Read( s, *pp_data, size );
222 vlc_stream_Delete( s );
224 if( read < 0 || (size_t)read != size )
226 free( *pp_data );
227 *pp_data = NULL;
228 return VLC_EGENERIC;
230 *pi_data = size;
232 return VLC_SUCCESS;
235 static int httpd_file_fill_cb( httpd_file_sys_t *data, httpd_file_t *http_file,
236 uint8_t *psz_request, uint8_t **pp_data, int *pi_data )
238 (void) http_file;
239 intf_sys_t *p_sys = static_cast<intf_sys_t*>((void *)data);
240 return p_sys->httpd_file_fill( psz_request, pp_data, pi_data );
243 void intf_sys_t::prepareHttpArtwork()
245 if( !m_httpd_file )
246 return;
248 const char *psz_art = m_meta ? vlc_meta_Get( m_meta, vlc_meta_ArtworkURL ) : NULL;
249 /* Abort if there is no art or if the art is already served */
250 if( !psz_art || strncmp( psz_art, "http", 4) == 0 )
251 return;
253 free( m_art_url );
254 m_art_url = strdup( psz_art );
256 std::stringstream ss;
257 ss << m_art_http_ip << "/art";
258 vlc_meta_Set( m_meta, vlc_meta_ArtworkURL, ss.str().c_str() );
261 void intf_sys_t::tryLoad()
263 if( !m_request_load )
264 return;
266 if ( !isStateReady() )
268 if ( m_state == Dead )
270 msg_Warn( m_module, "no Chromecast hook possible");
271 m_request_load = false;
273 else if( m_state == Connected )
275 msg_Dbg( m_module, "Starting the media receiver application" );
276 // Don't use setState as we don't want to signal the condition in this case.
277 m_state = Launching;
278 m_communication.msgReceiverLaunchApp();
280 return;
283 m_request_load = false;
285 // We should now be in the ready state, and therefor have a valid transportId
286 assert( m_appTransportId.empty() == false );
287 // Reset the mediaSessionID to allow the new session to become the current one.
288 // we cannot start a new load when the last one is still processing
289 m_communication.msgPlayerLoad( m_appTransportId, m_streaming_port, m_mime, m_meta );
290 m_state = Loading;
293 void intf_sys_t::setHasInput( const std::string mime_type )
295 vlc_mutex_locker locker(&m_lock);
296 msg_Dbg( m_module, "Loading content" );
298 m_request_stop = false;
300 this->m_mime = mime_type;
302 /* new input: clear message queue */
303 std::queue<QueueableMessages> empty;
304 std::swap(m_msgQueue, empty);
306 prepareHttpArtwork();
308 m_eof = false;
309 m_mediaSessionId = 0;
310 m_request_load = true;
312 tryLoad();
313 vlc_cond_signal( &m_stateChangedCond );
316 bool intf_sys_t::isStatePlaying() const
318 switch( m_state )
320 case Loading:
321 case Buffering:
322 case Playing:
323 case Paused:
324 case Seeking:
325 return true;
326 default:
327 return false;
331 bool intf_sys_t::isStateReady() const
333 switch( m_state )
335 case Connected:
336 case Launching:
337 case Authenticating:
338 case Connecting:
339 case Stopping:
340 case Dead:
341 return false;
342 default:
343 return true;
347 void intf_sys_t::setPacing(bool do_pace)
349 vlc_mutex_lock( &m_lock );
350 if( m_pace == do_pace )
352 vlc_mutex_unlock( &m_lock );
353 return;
355 m_pace = do_pace;
356 vlc_mutex_unlock( &m_lock );
357 vlc_cond_signal( &m_pace_cond );
360 static void interrupt_wake_up_cb( void *data )
362 intf_sys_t *p_sys = static_cast<intf_sys_t*>((void *)data);
363 p_sys->interrupt_wake_up();
366 void intf_sys_t::interrupt_wake_up()
368 vlc_mutex_locker locker( &m_lock );
369 m_interrupted = true;
370 vlc_cond_signal( &m_pace_cond );
373 void intf_sys_t::pace()
375 vlc_mutex_locker locker(&m_lock);
376 if( !m_pace )
377 return;
379 m_interrupted = false;
380 vlc_interrupt_register( interrupt_wake_up_cb, this );
382 while( m_pace && !m_interrupted )
383 vlc_cond_wait( &m_pace_cond, &m_lock );
384 vlc_interrupt_unregister();
388 * @brief Process a message received from the Chromecast
389 * @param msg the CastMessage to process
390 * @return 0 if the message has been successfuly processed else -1
392 void intf_sys_t::processMessage(const castchannel::CastMessage &msg)
394 const std::string & namespace_ = msg.namespace_();
396 #ifndef NDEBUG
397 msg_Dbg( m_module, "processMessage: %s->%s %s", namespace_.c_str(), msg.destination_id().c_str(), msg.payload_utf8().c_str());
398 #endif
400 if (namespace_ == NAMESPACE_DEVICEAUTH)
401 processAuthMessage( msg );
402 else if (namespace_ == NAMESPACE_HEARTBEAT)
403 processHeartBeatMessage( msg );
404 else if (namespace_ == NAMESPACE_RECEIVER)
405 processReceiverMessage( msg );
406 else if (namespace_ == NAMESPACE_MEDIA)
407 processMediaMessage( msg );
408 else if (namespace_ == NAMESPACE_CONNECTION)
409 processConnectionMessage( msg );
410 else
412 msg_Err( m_module, "Unknown namespace: %s", msg.namespace_().c_str());
416 void intf_sys_t::queueMessage( QueueableMessages msg )
418 // Assume lock is held by the called
419 m_msgQueue.push( msg );
420 vlc_interrupt_raise( m_ctl_thread_interrupt );
425 /*****************************************************************************
426 * Chromecast thread
427 *****************************************************************************/
428 void* intf_sys_t::ChromecastThread(void* p_data)
430 intf_sys_t *p_sys = static_cast<intf_sys_t*>(p_data);
431 p_sys->mainLoop();
432 return NULL;
435 void intf_sys_t::mainLoop()
437 vlc_savecancel();
438 vlc_interrupt_set( m_ctl_thread_interrupt );
440 // State was already initialized as Authenticating
441 m_communication.msgAuth();
443 while ( !vlc_killed() )
445 if ( !handleMessages() )
446 break;
447 // Reset the interrupt state to avoid commands not being sent (since
448 // the context is still flagged as interrupted)
449 vlc_interrupt_unregister();
450 vlc_mutex_locker lock( &m_lock );
451 while ( m_msgQueue.empty() == false )
453 QueueableMessages msg = m_msgQueue.front();
454 switch ( msg )
456 case Stop:
457 if( isStatePlaying() )
459 if ( m_mediaSessionId == 0 )
460 m_request_stop = true;
461 else
463 m_communication.msgPlayerStop( m_appTransportId, m_mediaSessionId );
464 setState( Stopping );
467 break;
468 case Seek:
470 if( !isStatePlaying() || m_mediaSessionId == 0 || m_ts_seek == VLC_TS_INVALID )
471 break;
472 char current_time[32];
473 if( snprintf( current_time, sizeof(current_time), "%.3f",
474 double( m_ts_seek ) / 1000000.0 ) >= (int)sizeof(current_time) )
476 msg_Err( m_module, "snprintf() truncated string for mediaSessionId" );
477 current_time[sizeof(current_time) - 1] = '\0';
479 m_ts_seek = VLC_TS_INVALID;
480 /* send a fake time to seek to, to make sure the device flushes its buffers */
481 m_communication.msgPlayerSeek( m_appTransportId, m_mediaSessionId, current_time );
482 setState( Seeking );
483 break;
486 m_msgQueue.pop();
491 void intf_sys_t::processAuthMessage( const castchannel::CastMessage& msg )
493 castchannel::DeviceAuthMessage authMessage;
494 if ( authMessage.ParseFromString(msg.payload_binary()) == false )
496 msg_Warn( m_module, "Failed to parse the payload" );
497 return;
500 if (authMessage.has_error())
502 msg_Err( m_module, "Authentification error: %d", authMessage.error().error_type());
504 else if (!authMessage.has_response())
506 msg_Err( m_module, "Authentification message has no response field");
508 else
510 vlc_mutex_locker locker(&m_lock);
511 setState( Connecting );
512 m_communication.msgConnect(DEFAULT_CHOMECAST_RECEIVER);
513 m_communication.msgReceiverGetStatus();
517 void intf_sys_t::processHeartBeatMessage( const castchannel::CastMessage& msg )
519 json_value *p_data = json_parse(msg.payload_utf8().c_str());
520 std::string type((*p_data)["type"]);
522 if (type == "PING")
524 msg_Dbg( m_module, "PING received from the Chromecast");
525 m_communication.msgPong();
527 else if (type == "PONG")
529 msg_Dbg( m_module, "PONG received from the Chromecast");
530 m_pingRetriesLeft = PING_WAIT_RETRIES;
532 else
534 msg_Warn( m_module, "Heartbeat command not supported: %s", type.c_str());
537 json_value_free(p_data);
540 void intf_sys_t::processReceiverMessage( const castchannel::CastMessage& msg )
542 json_value *p_data = json_parse(msg.payload_utf8().c_str());
543 std::string type((*p_data)["type"]);
545 if (type == "RECEIVER_STATUS")
547 json_value applications = (*p_data)["status"]["applications"];
548 const json_value *p_app = NULL;
550 for (unsigned i = 0; i < applications.u.array.length; ++i)
552 if ( strcmp( applications[i]["appId"], APP_ID ) == 0 )
554 if ( (const char*)applications[i]["transportId"] != NULL)
556 p_app = &applications[i];
557 break;
562 vlc_mutex_locker locker(&m_lock);
564 switch ( m_state )
566 case Connecting:
567 // We were connecting & fetching the current status.
568 // The media receiver app is running, we are ready to proceed
569 if ( p_app != NULL )
571 msg_Dbg( m_module, "Media receiver application was already running" );
572 m_appTransportId = (const char*)(*p_app)["transportId"];
573 m_communication.msgConnect( m_appTransportId );
574 setState( Ready );
576 else
578 setState( Connected );
580 break;
581 case Launching:
582 // We already asked for the media receiver application to start
583 if ( p_app != NULL )
585 msg_Dbg( m_module, "Media receiver application has been started." );
586 m_appTransportId = (const char*)(*p_app)["transportId"];
587 m_communication.msgConnect( m_appTransportId );
588 setState( Ready );
590 break;
591 case Loading:
592 case Playing:
593 case Paused:
594 case Seeking:
595 case Ready:
596 case TakenOver:
597 case Dead:
598 if ( p_app == NULL )
600 msg_Warn( m_module, "Media receiver application got closed." );
601 setState( Connected );
602 m_appTransportId = "";
603 m_mediaSessionId = 0;
605 break;
606 case Connected:
607 // We might receive a RECEIVER_STATUS while being connected, when pinging/asking the status
608 if ( p_app == NULL )
609 break;
610 // else: fall through and warn
611 default:
612 msg_Warn( m_module, "Unexpected RECEIVER_STATUS with state %s. "
613 "Checking media status",
614 StateToStr( m_state ) );
615 // This is likely because the chromecast refused the playback, but
616 // let's check by explicitely probing the media status
617 m_communication.msgPlayerGetStatus( m_appTransportId );
618 break;
621 else if (type == "LAUNCH_ERROR")
623 json_value reason = (*p_data)["reason"];
624 msg_Err( m_module, "Failed to start the MediaPlayer: %s",
625 (const char *)reason);
626 vlc_mutex_locker locker(&m_lock);
627 m_appTransportId = "";
628 m_mediaSessionId = 0;
629 setState( Dead );
631 else
633 msg_Warn( m_module, "Receiver command not supported: %s",
634 msg.payload_utf8().c_str());
637 json_value_free(p_data);
640 void intf_sys_t::processMediaMessage( const castchannel::CastMessage& msg )
642 json_value *p_data = json_parse(msg.payload_utf8().c_str());
643 std::string type((*p_data)["type"]);
645 if (type == "MEDIA_STATUS")
647 json_value status = (*p_data)["status"];
649 int64_t sessionId = (json_int_t) status[0]["mediaSessionId"];
650 if (m_mediaSessionId != sessionId && m_mediaSessionId != 0 )
652 msg_Dbg( m_module, "Ignoring message for a different media session" );
653 json_value_free(p_data);
654 return;
657 msg_Dbg( m_module, "Player state: %s sessionId: %" PRId64,
658 status[0]["playerState"].operator const char *(),
659 sessionId );
661 std::string newPlayerState = (const char*)status[0]["playerState"];
662 std::string idleReason = (const char*)status[0]["idleReason"];
664 vlc_mutex_locker locker( &m_lock );
666 if (newPlayerState == "IDLE" || newPlayerState.empty() == true )
668 /* Idle state is expected when the media receiver application is
669 * started. In case the state is still Buffering, it denotes an error.
670 * In most case, we'd receive a RECEIVER_STATUS message, which causes
671 * use to ask for the MEDIA_STATUS before assuming an error occured.
672 * If the chromecast silently gave up on playing our stream, we also
673 * might have an empty status array.
674 * If the media load indeed failed, we need to try another
675 * transcode/remux configuration, or give up.
676 * In case we are now loading, we might also receive an INTERRUPTED
677 * state for the previous session, which we wouldn't ignore earlier
678 * since our mediaSessionID was reset to 0.
679 * In this case, don't assume we're being taken over, as we are
680 * actually doing the take over.
682 if ( m_state != Ready && m_state != LoadFailed && m_state != Loading )
684 // The playback stopped
685 m_time_playback_started = VLC_TS_INVALID;
686 if ( idleReason == "INTERRUPTED" )
688 setState( TakenOver );
689 // Do not reset the mediaSessionId to ensure we refuse all
690 // other MEDIA_STATUS from the new session.
692 else if ( m_state == Buffering )
693 setState( LoadFailed );
694 else
696 if (idleReason == "FINISHED")
697 m_eof = true;
698 setState( Ready );
702 else
704 if ( m_mediaSessionId == 0 )
706 m_mediaSessionId = sessionId;
707 msg_Dbg( m_module, "New mediaSessionId: %" PRId64, m_mediaSessionId );
710 if (m_request_stop)
712 m_request_stop = false;
713 m_communication.msgPlayerStop( m_appTransportId, m_mediaSessionId );
714 setState( Stopping );
716 else if (newPlayerState == "PLAYING")
718 msg_Dbg( m_module, "Playback started now:%" PRId64 " i_ts_local_start:%" PRId64,
719 m_time_playback_started, m_ts_local_start);
720 if ( m_state != Playing )
722 /* TODO reset demux PCR ? */
723 m_time_playback_started = mdate();
724 setState( Playing );
727 else if (newPlayerState == "BUFFERING")
729 if ( m_state != Buffering )
731 /* EOF when state goes from Playing to Buffering. There can
732 * be a lot of false positives (when seeking or when the cc
733 * request more input) but this state is fetched only when
734 * the input has reached EOF. */
736 m_time_playback_started = VLC_TS_INVALID;
737 setState( Buffering );
740 else if (newPlayerState == "PAUSED")
742 if ( m_state != Paused )
744 #ifndef NDEBUG
745 msg_Dbg( m_module, "Playback paused: date_play_start: %" PRId64, m_time_playback_started);
746 #endif
748 if ( m_time_playback_started != VLC_TS_INVALID && m_state == Playing )
750 /* this is a pause generated remotely, adjust the playback time */
751 m_ts_local_start += mdate() - m_time_playback_started;
752 #ifndef NDEBUG
753 msg_Dbg( m_module, "updated i_ts_local_start:%" PRId64, m_ts_local_start);
754 #endif
756 m_time_playback_started = VLC_TS_INVALID;
757 setState( Paused );
760 else if ( newPlayerState == "LOADING" )
762 if ( m_state != Loading )
764 msg_Dbg( m_module, "Chromecast is loading the stream" );
765 setState( Loading );
768 else
769 msg_Warn( m_module, "Unknown Chromecast MEDIA_STATUS state %s", newPlayerState.c_str());
772 else if (type == "LOAD_FAILED")
774 msg_Err( m_module, "Media load failed");
775 vlc_mutex_locker locker(&m_lock);
776 setState( LoadFailed );
778 else if (type == "LOAD_CANCELLED")
780 msg_Dbg( m_module, "LOAD canceled by another command");
782 else if (type == "INVALID_REQUEST")
784 msg_Dbg( m_module, "We sent an invalid request reason:%s", (const char*)(*p_data)["reason"] );
786 else
788 msg_Warn( m_module, "Media command not supported: %s",
789 msg.payload_utf8().c_str());
792 json_value_free(p_data);
795 void intf_sys_t::processConnectionMessage( const castchannel::CastMessage& msg )
797 json_value *p_data = json_parse(msg.payload_utf8().c_str());
798 std::string type((*p_data)["type"]);
799 json_value_free(p_data);
801 if ( type == "CLOSE" )
803 // Close message indicates an application is being closed, not the connection.
804 // From this point on, we need to relaunch the media receiver app
805 vlc_mutex_locker locker(&m_lock);
806 m_appTransportId = "";
807 m_mediaSessionId = 0;
808 setState( Connected );
810 else
812 msg_Warn( m_module, "Connection command not supported: %s",
813 type.c_str());
817 bool intf_sys_t::handleMessages()
819 uint8_t p_packet[PACKET_MAX_LEN];
820 size_t i_payloadSize = 0;
821 size_t i_received = 0;
822 bool b_timeout = false;
823 mtime_t i_begin_time = mdate();
825 /* Packet structure:
826 * +------------------------------------+------------------------------+
827 * | Payload size (uint32_t big endian) | Payload data |
828 * +------------------------------------+------------------------------+
830 while ( true )
832 // If we haven't received the payload size yet, let's wait for it. Otherwise, we know
833 // how many bytes to read
834 ssize_t i_ret = m_communication.receive( p_packet + i_received,
835 i_payloadSize + PACKET_HEADER_LEN - i_received,
836 PING_WAIT_TIME - ( mdate() - i_begin_time ) / CLOCK_FREQ,
837 &b_timeout );
838 if ( i_ret < 0 )
840 if ( errno == EINTR )
841 return true;
842 // An error occured, we give up
843 msg_Err( m_module, "The connection to the Chromecast died (receiving).");
844 vlc_mutex_locker locker(&m_lock);
845 setState( Dead );
846 return false;
848 else if ( b_timeout == true )
850 // If no commands were queued to be sent, we timed out. Let's ping the chromecast
851 if ( m_pingRetriesLeft == 0 )
853 vlc_mutex_locker locker(&m_lock);
854 m_state = Dead;
855 msg_Warn( m_module, "No PING response from the chromecast" );
856 return false;
858 --m_pingRetriesLeft;
859 m_communication.msgPing();
860 m_communication.msgReceiverGetStatus();
861 return true;
863 assert( i_ret != 0 );
864 i_received += i_ret;
865 if ( i_payloadSize == 0 )
867 i_payloadSize = U32_AT( p_packet );
868 if ( i_payloadSize > PACKET_MAX_LEN - PACKET_HEADER_LEN )
870 msg_Err( m_module, "Payload size is too long: dropping connection" );
871 vlc_mutex_locker locker(&m_lock);
872 m_state = Dead;
873 return false;
875 continue;
877 assert( i_received <= i_payloadSize + PACKET_HEADER_LEN );
878 if ( i_received == i_payloadSize + PACKET_HEADER_LEN )
879 break;
881 castchannel::CastMessage msg;
882 msg.ParseFromArray(p_packet + PACKET_HEADER_LEN, i_payloadSize);
883 processMessage(msg);
884 return true;
887 void intf_sys_t::requestPlayerStop()
889 vlc_mutex_locker locker(&m_lock);
891 m_request_load = false;
892 m_ts_seek = VLC_TS_INVALID;
894 if( !isStatePlaying() )
895 return;
897 queueMessage( Stop );
900 States intf_sys_t::state() const
902 vlc_mutex_locker locker( &m_lock );
903 return m_state;
906 void intf_sys_t::requestPlayerSeek(mtime_t pos)
908 vlc_mutex_locker locker(&m_lock);
909 if( !isStatePlaying() || m_mediaSessionId == 0 )
910 return;
911 m_ts_seek = pos;
912 queueMessage( Seek );
915 void intf_sys_t::setOnSeekDoneCb(on_seek_done_itf on_seek_done, void *on_seek_done_data)
917 vlc_mutex_locker locker(&m_lock);
918 m_on_seek_done = on_seek_done;
919 m_on_seek_done_data = on_seek_done_data;
922 void intf_sys_t::setPauseState(bool paused)
924 msg_Dbg( m_module, "%s state", paused ? "paused" : "playing" );
925 vlc_mutex_locker locker( &m_lock );
926 if ( !paused )
928 if ( m_mediaSessionId != 0 )
930 m_communication.msgPlayerPlay( m_appTransportId, m_mediaSessionId );
933 else
935 if ( m_mediaSessionId != 0 && m_state != Paused )
937 m_communication.msgPlayerPause( m_appTransportId, m_mediaSessionId );
942 bool intf_sys_t::isFinishedPlaying()
944 vlc_mutex_locker locker(&m_lock);
945 return m_state == LoadFailed || m_state == Dead || m_eof;
948 void intf_sys_t::setMeta(vlc_meta_t *p_meta)
950 vlc_mutex_locker locker(&m_lock);
951 if (m_meta != NULL)
952 vlc_meta_Delete(m_meta);
953 m_meta = p_meta;
956 mtime_t intf_sys_t::getPlaybackTimestamp() const
958 switch( m_state )
960 case Playing:
961 return ( mdate() - m_time_playback_started ) + m_ts_local_start;
962 #ifdef CHROMECAST_VERBOSE
963 case Ready:
964 msg_Dbg(m_module, "receiver idle using buffering time %" PRId64, m_ts_local_start);
965 break;
966 case Buffering:
967 msg_Dbg(m_module, "receiver buffering using buffering time %" PRId64, m_ts_local_start);
968 break;
969 case Paused:
970 msg_Dbg(m_module, "receiver paused using buffering time %" PRId64, m_ts_local_start);
971 break;
972 #endif
973 default:
974 break;
976 return m_ts_local_start;
979 double intf_sys_t::getPlaybackPosition() const
981 if( m_length > 0 )
982 return (double) getPlaybackTimestamp() / (double)( m_length );
983 return 0.0;
986 void intf_sys_t::setInitialTime(mtime_t time)
988 if( time )
989 m_ts_local_start = time;
992 void intf_sys_t::setState( States state )
994 if ( m_state != state )
996 #ifndef NDEBUG
997 msg_Dbg( m_module, "Switching from state %s to %s", StateToStr( m_state ), StateToStr( state ) );
998 #endif
999 if (state == Seeking)
1000 if (m_on_seek_done != NULL)
1001 m_on_seek_done(m_on_seek_done_data);
1002 m_state = state;
1004 switch( m_state )
1006 case Connected:
1007 case Ready:
1008 tryLoad();
1009 break;
1010 default:
1011 break;
1013 vlc_cond_signal( &m_stateChangedCond );
1017 mtime_t intf_sys_t::get_time(void *pt)
1019 intf_sys_t *p_this = static_cast<intf_sys_t*>(pt);
1020 vlc_mutex_locker locker( &p_this->m_lock );
1021 return p_this->getPlaybackTimestamp();
1024 double intf_sys_t::get_position(void *pt)
1026 intf_sys_t *p_this = static_cast<intf_sys_t*>(pt);
1027 vlc_mutex_locker locker( &p_this->m_lock );
1028 return p_this->getPlaybackPosition();
1031 void intf_sys_t::set_initial_time(void *pt, mtime_t time )
1033 intf_sys_t *p_this = static_cast<intf_sys_t*>(pt);
1034 vlc_mutex_locker locker( &p_this->m_lock );
1035 return p_this->setInitialTime( time );
1038 void intf_sys_t::set_length(void *pt, mtime_t length)
1040 intf_sys_t *p_this = static_cast<intf_sys_t*>(pt);
1041 p_this->m_length = length;
1044 void intf_sys_t::pace(void *pt)
1046 intf_sys_t *p_this = static_cast<intf_sys_t*>(pt);
1047 p_this->pace();
1050 void intf_sys_t::set_pause_state(void *pt, bool paused)
1052 intf_sys_t *p_this = static_cast<intf_sys_t*>(pt);
1053 p_this->setPauseState( paused );
1056 void intf_sys_t::set_meta(void *pt, vlc_meta_t *p_meta)
1058 intf_sys_t *p_this = static_cast<intf_sys_t*>(pt);
1059 p_this->setMeta( p_meta );