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 /*****************************************************************************
28 *****************************************************************************/
34 #include "chromecast.h"
39 #include "../../misc/webservices/json.h"
41 /* deadline regarding pings sent from receiver */
42 #define PING_WAIT_TIME 6000
43 #define PING_WAIT_RETRIES 1
45 static const mtime_t SEEK_FORWARD_OFFSET
= 1000000;
47 static const char* StateToStr( States s
)
52 return "Authenticating";
74 vlc_assert_unreachable();
77 /*****************************************************************************
78 * intf_sys_t: class definition
79 *****************************************************************************/
80 intf_sys_t::intf_sys_t(vlc_object_t
* const p_this
, int port
, std::string device_addr
, int device_port
, vlc_interrupt_t
*p_interrupt
)
82 , m_streaming_port(port
)
83 , m_communication( p_this
, device_addr
.c_str(), device_port
)
84 , m_state( Authenticating
)
85 , m_ctl_thread_interrupt(p_interrupt
)
86 , m_time_playback_started( VLC_TS_INVALID
)
87 , m_ts_local_start( VLC_TS_INVALID
)
88 , m_length( VLC_TS_INVALID
)
89 , m_chromecast_start_time( VLC_TS_INVALID
)
90 , m_pingRetriesLeft( PING_WAIT_RETRIES
)
92 vlc_mutex_init(&m_lock
);
93 vlc_cond_init( &m_stateChangedCond
);
95 m_common
.p_opaque
= this;
96 m_common
.pf_get_position
= get_position
;
97 m_common
.pf_get_time
= get_time
;
98 m_common
.pf_set_length
= set_length
;
99 m_common
.pf_wait_app_started
= wait_app_started
;
100 m_common
.pf_request_seek
= request_seek
;
101 m_common
.pf_wait_seek_done
= wait_seek_done
;
102 m_common
.pf_set_pause_state
= set_pause_state
;
103 m_common
.pf_set_artwork
= set_artwork
;
104 m_common
.pf_set_title
= set_title
;
106 assert( var_Type( m_module
->obj
.parent
->obj
.parent
, CC_SHARED_VAR_NAME
) == 0 );
107 if (var_Create( m_module
->obj
.parent
->obj
.parent
, CC_SHARED_VAR_NAME
, VLC_VAR_ADDRESS
) == VLC_SUCCESS
)
108 var_SetAddress( m_module
->obj
.parent
->obj
.parent
, CC_SHARED_VAR_NAME
, &m_common
);
110 // Start the Chromecast event thread.
111 if (vlc_clone(&m_chromecastThread
, ChromecastThread
, this,
112 VLC_THREAD_PRIORITY_LOW
))
114 msg_Err( m_module
, "Could not start the Chromecast talking thread");
118 intf_sys_t::~intf_sys_t()
120 var_Destroy( m_module
->obj
.parent
->obj
.parent
, CC_SHARED_VAR_NAME
);
130 // Generate the close messages.
131 m_communication
.msgReceiverClose( m_appTransportId
);
136 m_communication
.msgReceiverClose(DEFAULT_CHOMECAST_RECEIVER
);
142 vlc_interrupt_kill( m_ctl_thread_interrupt
);
144 vlc_join(m_chromecastThread
, NULL
);
146 vlc_interrupt_destroy( m_ctl_thread_interrupt
);
148 vlc_cond_destroy(&m_stateChangedCond
);
149 vlc_mutex_destroy(&m_lock
);
152 void intf_sys_t::setHasInput( const std::string mime_type
)
154 vlc_mutex_locker
locker(&m_lock
);
155 msg_Dbg( m_module
, "Loading content for session:%s", m_mediaSessionId
.c_str() );
157 this->m_mime
= mime_type
;
160 if ( m_state
== Dead
)
162 msg_Warn( m_module
, "no Chromecast hook possible");
165 // We should now be in the ready state, and therefor have a valid transportId
166 assert( m_state
== Ready
&& m_appTransportId
.empty() == false );
167 // we cannot start a new load when the last one is still processing
168 m_ts_local_start
= VLC_TS_0
;
169 m_communication
.msgPlayerLoad( m_appTransportId
, m_streaming_port
, m_title
, m_artwork
, mime_type
);
174 * @brief Process a message received from the Chromecast
175 * @param msg the CastMessage to process
176 * @return 0 if the message has been successfuly processed else -1
178 void intf_sys_t::processMessage(const castchannel::CastMessage
&msg
)
180 const std::string
& namespace_
= msg
.namespace_();
183 msg_Dbg( m_module
, "processMessage: %s->%s %s", namespace_
.c_str(), msg
.destination_id().c_str(), msg
.payload_utf8().c_str());
186 if (namespace_
== NAMESPACE_DEVICEAUTH
)
187 processAuthMessage( msg
);
188 else if (namespace_
== NAMESPACE_HEARTBEAT
)
189 processHeartBeatMessage( msg
);
190 else if (namespace_
== NAMESPACE_RECEIVER
)
191 processReceiverMessage( msg
);
192 else if (namespace_
== NAMESPACE_MEDIA
)
193 processMediaMessage( msg
);
194 else if (namespace_
== NAMESPACE_CONNECTION
)
195 processConnectionMessage( msg
);
198 msg_Err( m_module
, "Unknown namespace: %s", msg
.namespace_().c_str());
204 /*****************************************************************************
206 *****************************************************************************/
207 void* intf_sys_t::ChromecastThread(void* p_data
)
209 intf_sys_t
*p_sys
= static_cast<intf_sys_t
*>(p_data
);
214 void intf_sys_t::mainLoop()
217 vlc_interrupt_set( m_ctl_thread_interrupt
);
219 // State was already initialized as Authenticating
220 m_communication
.msgAuth();
222 while ( !vlc_killed() && handleMessages() )
226 void intf_sys_t::processAuthMessage( const castchannel::CastMessage
& msg
)
228 castchannel::DeviceAuthMessage authMessage
;
229 if ( authMessage
.ParseFromString(msg
.payload_binary()) == false )
231 msg_Warn( m_module
, "Failed to parse the payload" );
235 if (authMessage
.has_error())
237 msg_Err( m_module
, "Authentification error: %d", authMessage
.error().error_type());
239 else if (!authMessage
.has_response())
241 msg_Err( m_module
, "Authentification message has no response field");
245 vlc_mutex_locker
locker(&m_lock
);
246 setState( Connecting
);
247 m_communication
.msgConnect(DEFAULT_CHOMECAST_RECEIVER
);
248 m_communication
.msgReceiverGetStatus();
252 void intf_sys_t::processHeartBeatMessage( const castchannel::CastMessage
& msg
)
254 json_value
*p_data
= json_parse(msg
.payload_utf8().c_str());
255 std::string
type((*p_data
)["type"]);
259 msg_Dbg( m_module
, "PING received from the Chromecast");
260 m_communication
.msgPong();
262 else if (type
== "PONG")
264 msg_Dbg( m_module
, "PONG received from the Chromecast");
265 m_pingRetriesLeft
= PING_WAIT_RETRIES
;
269 msg_Warn( m_module
, "Heartbeat command not supported: %s", type
.c_str());
272 json_value_free(p_data
);
275 void intf_sys_t::processReceiverMessage( const castchannel::CastMessage
& msg
)
277 json_value
*p_data
= json_parse(msg
.payload_utf8().c_str());
278 std::string
type((*p_data
)["type"]);
280 if (type
== "RECEIVER_STATUS")
282 json_value applications
= (*p_data
)["status"]["applications"];
283 const json_value
*p_app
= NULL
;
285 for (unsigned i
= 0; i
< applications
.u
.array
.length
; ++i
)
287 if ( strcmp( applications
[i
]["appId"], APP_ID
) == 0 )
289 if ( (const char*)applications
[i
]["transportId"] != NULL
)
291 p_app
= &applications
[i
];
297 vlc_mutex_locker
locker(&m_lock
);
302 // We were connecting & fetching the current status.
303 // The media receiver app is running, we are ready to proceed
306 msg_Dbg( m_module
, "Media receiver application was already running" );
307 m_appTransportId
= (const char*)(*p_app
)["transportId"];
309 m_communication
.msgConnect( m_appTransportId
);
313 setState( Connected
);
317 // We already asked for the media receiver application to start
320 msg_Dbg( m_module
, "Media receiver application has been started." );
322 m_appTransportId
= (const char*)(*p_app
)["transportId"];
323 m_communication
.msgConnect( m_appTransportId
);
333 msg_Warn( m_module
, "Media receiver application got closed." );
334 setState( Connected
);
335 m_appTransportId
= "";
336 m_mediaSessionId
= "";
340 // We might receive a RECEIVER_STATUS while being connected, when pinging/asking the status
343 // else: fall through and warn
345 msg_Warn( m_module
, "Unexpected RECEIVER_STATUS with state %d", m_state
);
349 else if (type
== "LAUNCH_ERROR")
351 json_value reason
= (*p_data
)["reason"];
352 msg_Err( m_module
, "Failed to start the MediaPlayer: %s",
353 (const char *)reason
);
354 vlc_mutex_locker
locker(&m_lock
);
359 msg_Warn( m_module
, "Receiver command not supported: %s",
360 msg
.payload_utf8().c_str());
363 json_value_free(p_data
);
366 void intf_sys_t::processMediaMessage( const castchannel::CastMessage
& msg
)
368 json_value
*p_data
= json_parse(msg
.payload_utf8().c_str());
369 std::string
type((*p_data
)["type"]);
371 if (type
== "MEDIA_STATUS")
373 json_value status
= (*p_data
)["status"];
374 msg_Dbg( m_module
, "Player state: %s sessionId:%d",
375 status
[0]["playerState"].operator const char *(),
376 (int)(json_int_t
) status
[0]["mediaSessionId"]);
378 std::string newPlayerState
= (const char*)status
[0]["playerState"];
379 std::string idleReason
= (const char*)status
[0]["idleReason"];
381 vlc_mutex_locker
locker( &m_lock
);
383 if (newPlayerState
== "IDLE")
385 if ( m_state
!= Ready
)
387 // The playback stopped
388 m_mediaSessionId
= "";
389 m_time_playback_started
= VLC_TS_INVALID
;
396 if( snprintf( session_id
, sizeof(session_id
), "%" PRId64
, (json_int_t
) status
[0]["mediaSessionId"] ) >= (int)sizeof(session_id
) )
398 msg_Err( m_module
, "snprintf() truncated string for mediaSessionId" );
399 session_id
[sizeof(session_id
) - 1] = '\0';
401 if (session_id
[0] && m_mediaSessionId
!= session_id
) {
402 if (!m_mediaSessionId
.empty())
403 msg_Warn( m_module
, "different mediaSessionId detected %s was %s", session_id
, this->m_mediaSessionId
.c_str());
404 m_mediaSessionId
= session_id
;
407 if (newPlayerState
== "PLAYING")
409 msg_Dbg( m_module
, "Playback started with an offset of %" PRId64
" now:%" PRId64
" i_ts_local_start:%" PRId64
,
410 m_chromecast_start_time
, m_time_playback_started
, m_ts_local_start
);
411 if ( m_state
!= Playing
)
413 /* TODO reset demux PCR ? */
414 if (unlikely(m_chromecast_start_time
== VLC_TS_INVALID
)) {
415 msg_Warn( m_module
, "start playing without buffering" );
416 m_chromecast_start_time
= (1 + mtime_t( double( status
[0]["currentTime"] ) ) ) * 1000000L;
418 m_time_playback_started
= mdate();
422 else if (newPlayerState
== "BUFFERING")
424 if ( m_state
!= Buffering
)
426 if ( double(status
[0]["currentTime"]) == 0.0 )
428 msg_Dbg( m_module
, "Invalid buffering time, keep current state");
432 m_chromecast_start_time
= (1 + mtime_t( double( status
[0]["currentTime"] ) ) ) * 1000000L;
433 msg_Dbg( m_module
, "Playback pending with an offset of %" PRId64
, m_chromecast_start_time
);
434 m_time_playback_started
= VLC_TS_INVALID
;
435 setState( Buffering
);
439 else if (newPlayerState
== "PAUSED")
441 if ( m_state
!= Paused
)
443 m_chromecast_start_time
= (1 + mtime_t( double( status
[0]["currentTime"] ) ) ) * 1000000L;
445 msg_Dbg( m_module
, "Playback paused with an offset of %" PRId64
" date_play_start:%" PRId64
, m_chromecast_start_time
, m_time_playback_started
);
448 if ( m_time_playback_started
!= VLC_TS_INVALID
&& m_state
== Playing
)
450 /* this is a pause generated remotely, adjust the playback time */
451 m_ts_local_start
+= mdate() - m_time_playback_started
;
453 msg_Dbg( m_module
, "updated i_ts_local_start:%" PRId64
, m_ts_local_start
);
456 m_time_playback_started
= VLC_TS_INVALID
;
460 else if ( newPlayerState
== "LOADING" )
462 if ( m_state
!= Loading
)
464 msg_Dbg( m_module
, "Chromecast is loading the stream" );
468 else if (!newPlayerState
.empty())
469 msg_Warn( m_module
, "Unknown Chromecast MEDIA_STATUS state %s", newPlayerState
.c_str());
472 else if (type
== "LOAD_FAILED")
474 msg_Err( m_module
, "Media load failed");
475 vlc_mutex_locker
locker(&m_lock
);
476 /* close the app to restart it */
477 if ( m_state
== Launching
)
478 m_communication
.msgReceiverClose(m_appTransportId
);
480 m_communication
.msgReceiverGetStatus();
482 else if (type
== "LOAD_CANCELLED")
484 msg_Dbg( m_module
, "LOAD canceled by another command");
486 else if (type
== "INVALID_REQUEST")
488 msg_Dbg( m_module
, "We sent an invalid request reason:%s", (const char*)(*p_data
)["reason"] );
492 msg_Warn( m_module
, "Media command not supported: %s",
493 msg
.payload_utf8().c_str());
496 json_value_free(p_data
);
499 void intf_sys_t::processConnectionMessage( const castchannel::CastMessage
& msg
)
501 json_value
*p_data
= json_parse(msg
.payload_utf8().c_str());
502 std::string
type((*p_data
)["type"]);
503 json_value_free(p_data
);
505 if ( type
== "CLOSE" )
507 // Close message indicates an application is being closed, not the connection.
508 // From this point on, we need to relaunch the media receiver app
509 vlc_mutex_locker
locker(&m_lock
);
510 m_appTransportId
= "";
511 m_mediaSessionId
= "";
512 setState( Connected
);
516 msg_Warn( m_module
, "Connection command not supported: %s",
521 bool intf_sys_t::handleMessages()
523 uint8_t p_packet
[PACKET_MAX_LEN
];
524 size_t i_payloadSize
= 0;
525 size_t i_received
= 0;
526 bool b_timeout
= false;
527 mtime_t i_begin_time
= mdate();
530 * +------------------------------------+------------------------------+
531 * | Payload size (uint32_t big endian) | Payload data |
532 * +------------------------------------+------------------------------+
536 // If we haven't received the payload size yet, let's wait for it. Otherwise, we know
537 // how many bytes to read
538 ssize_t i_ret
= m_communication
.receive( p_packet
+ i_received
,
539 i_payloadSize
+ PACKET_HEADER_LEN
- i_received
,
540 PING_WAIT_TIME
- ( mdate() - i_begin_time
) / CLOCK_FREQ
,
544 if ( errno
== EINTR
)
546 // An error occured, we give up
547 msg_Err( m_module
, "The connection to the Chromecast died (receiving).");
548 vlc_mutex_locker
locker(&m_lock
);
552 else if ( b_timeout
== true )
554 // If no commands were queued to be sent, we timed out. Let's ping the chromecast
555 if ( m_pingRetriesLeft
== 0 )
557 vlc_mutex_locker
locker(&m_lock
);
559 msg_Warn( m_module
, "No PING response from the chromecast" );
563 m_communication
.msgPing();
564 m_communication
.msgReceiverGetStatus();
567 assert( i_ret
!= 0 );
569 if ( i_payloadSize
== 0 )
571 i_payloadSize
= U32_AT( p_packet
);
572 if ( i_payloadSize
> PACKET_MAX_LEN
- PACKET_HEADER_LEN
)
574 msg_Err( m_module
, "Payload size is too long: dropping conection" );
575 vlc_mutex_locker
locker(&m_lock
);
581 assert( i_received
<= i_payloadSize
+ PACKET_HEADER_LEN
);
582 if ( i_received
== i_payloadSize
+ PACKET_HEADER_LEN
)
585 castchannel::CastMessage msg
;
586 msg
.ParseFromArray(p_packet
+ PACKET_HEADER_LEN
, i_payloadSize
);
591 void intf_sys_t::requestPlayerStop()
593 vlc_mutex_locker
locker(&m_lock
);
594 if ( m_mediaSessionId
.empty() == true )
596 m_communication
.msgPlayerStop( m_appTransportId
, m_mediaSessionId
);
599 void intf_sys_t::requestPlayerSeek(mtime_t pos
)
601 vlc_mutex_locker
locker(&m_lock
);
602 if ( m_mediaSessionId
.empty() == true )
604 if ( pos
!= VLC_TS_INVALID
)
605 m_ts_local_start
= pos
;
606 char current_time
[32];
607 mtime_t seek_request_time
= mdate() + SEEK_FORWARD_OFFSET
;
608 if( snprintf( current_time
, sizeof(current_time
), "%.3f", double( seek_request_time
) / 1000000.0 ) >= (int)sizeof(current_time
) )
610 msg_Err( m_module
, "snprintf() truncated string for mediaSessionId" );
611 current_time
[sizeof(current_time
) - 1] = '\0';
613 /* send a fake time to seek to, to make sure the device flushes its buffers */
614 m_communication
.msgPlayerSeek( m_appTransportId
, m_mediaSessionId
, current_time
);
618 void intf_sys_t::setPauseState(bool paused
)
620 msg_Dbg( m_module
, "%s state for %s", paused
? "paused" : "playing", m_title
.c_str() );
621 vlc_mutex_locker
locker( &m_lock
);
624 if ( !m_mediaSessionId
.empty() )
626 m_communication
.msgPlayerPlay( m_appTransportId
, m_mediaSessionId
);
631 if ( !m_mediaSessionId
.empty() && m_state
!= Paused
)
633 m_communication
.msgPlayerPause( m_appTransportId
, m_mediaSessionId
);
638 void intf_sys_t::waitAppStarted()
640 while ( m_state
!= Ready
&& m_state
!= Dead
)
642 if ( m_state
== Connected
)
644 msg_Dbg( m_module
, "Starting the media receiver application" );
645 // Don't use setState as we don't want to signal the condition in this case.
647 m_communication
.msgReceiverLaunchApp();
649 msg_Dbg( m_module
, "Waiting for Chromecast media receiver app to be ready" );
650 vlc_cond_wait(&m_stateChangedCond
, &m_lock
);
652 msg_Dbg( m_module
, "Done waiting for application. transportId: %s", m_appTransportId
.c_str() );
655 void intf_sys_t::waitSeekDone()
657 vlc_mutex_locker
locker(&m_lock
);
658 while ( m_state
== Seeking
)
661 msg_Dbg( m_module
, "waiting for Chromecast seek" );
663 vlc_cond_wait(&m_stateChangedCond
, &m_lock
);
665 msg_Dbg( m_module
, "finished waiting for Chromecast seek" );
670 bool intf_sys_t::isFinishedPlaying()
672 vlc_mutex_locker
locker(&m_lock
);
673 return m_state
== Ready
;
676 void intf_sys_t::setTitle(const char* psz_title
)
684 void intf_sys_t::setArtwork(const char* psz_artwork
)
687 m_artwork
= psz_artwork
;
692 mtime_t
intf_sys_t::getPlaybackTimestamp() const
697 return ( mdate() - m_time_playback_started
) + m_ts_local_start
;
699 msg_Dbg(m_module
, "receiver idle using buffering time %" PRId64
, m_ts_local_start
);
702 msg_Dbg(m_module
, "receiver buffering using buffering time %" PRId64
, m_ts_local_start
);
705 msg_Dbg(m_module
, "receiver paused using buffering time %" PRId64
, m_ts_local_start
);
710 return m_ts_local_start
;
713 double intf_sys_t::getPlaybackPosition() const
716 return (double) getPlaybackTimestamp() / (double)( m_length
);
720 void intf_sys_t::setState( States state
)
722 if ( m_state
!= state
)
725 msg_Dbg( m_module
, "Switching from state %s to %s", StateToStr( m_state
), StateToStr( state
) );
728 vlc_cond_signal( &m_stateChangedCond
);
732 mtime_t
intf_sys_t::get_time(void *pt
)
734 intf_sys_t
*p_this
= static_cast<intf_sys_t
*>(pt
);
735 vlc_mutex_locker
locker( &p_this
->m_lock
);
736 return p_this
->getPlaybackTimestamp();
739 double intf_sys_t::get_position(void *pt
)
741 intf_sys_t
*p_this
= static_cast<intf_sys_t
*>(pt
);
742 vlc_mutex_locker
locker( &p_this
->m_lock
);
743 return p_this
->getPlaybackPosition();
746 void intf_sys_t::set_length(void *pt
, mtime_t length
)
748 intf_sys_t
*p_this
= static_cast<intf_sys_t
*>(pt
);
749 p_this
->m_length
= length
;
752 void intf_sys_t::wait_app_started(void *pt
)
754 intf_sys_t
*p_this
= static_cast<intf_sys_t
*>(pt
);
755 vlc_mutex_locker
locker( &p_this
->m_lock
);
756 p_this
->waitAppStarted();
759 void intf_sys_t::request_seek(void *pt
, mtime_t pos
)
761 intf_sys_t
*p_this
= static_cast<intf_sys_t
*>(pt
);
762 p_this
->requestPlayerSeek(pos
);
765 void intf_sys_t::wait_seek_done(void *pt
)
767 intf_sys_t
*p_this
= static_cast<intf_sys_t
*>(pt
);
768 p_this
->waitSeekDone();
771 void intf_sys_t::set_pause_state(void *pt
, bool paused
)
773 intf_sys_t
*p_this
= static_cast<intf_sys_t
*>(pt
);
774 p_this
->setPauseState( paused
);
777 void intf_sys_t::set_title(void *pt
, const char *psz_title
)
779 intf_sys_t
*p_this
= static_cast<intf_sys_t
*>(pt
);
780 p_this
->setTitle( psz_title
);
783 void intf_sys_t::set_artwork(void *pt
, const char *psz_artwork
)
785 intf_sys_t
*p_this
= static_cast<intf_sys_t
*>(pt
);
786 p_this
->setArtwork( psz_artwork
);