2016-01-17 14:59:16 -05:00
/*
* Copyright ( C ) 2015 - 2016 Espen Jürgensen < espenjurgensen @ gmail . com >
*
* TODO Credits
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA
*/
# ifdef HAVE_CONFIG_H
# include <config.h>
# endif
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <errno.h>
# include <sys/types.h>
# include <sys/socket.h>
# include <arpa/inet.h>
2016-01-31 12:42:32 -05:00
# include <ifaddrs.h>
2016-01-17 14:59:16 -05:00
# include <unistd.h>
2016-01-26 15:49:32 -05:00
# include <fcntl.h>
2016-01-17 14:59:16 -05:00
# include <endian.h>
# include <gnutls/gnutls.h>
2016-01-26 15:49:32 -05:00
# include <json-c/json.h>
2016-01-17 14:59:16 -05:00
# include <event2/event.h>
2016-01-30 17:01:01 -05:00
# include "conffile.h"
2016-01-26 15:49:32 -05:00
# include "mdns.h"
2016-01-17 14:59:16 -05:00
# include "logger.h"
2016-01-26 15:49:32 -05:00
# include "player.h"
# include "outputs.h"
2016-01-17 14:59:16 -05:00
# include "cast_channel.pb-c.h"
// Number of bytes to request from TLS connection
# define MAX_BUF 4096
// CA file location (not very portable...?)
# define CAFILE " / etc / ssl / certs / ca-certificates.crt"
// Namespaces
# define NS_CONNECTION "urn:x-cast:com.google.cast.tp.connection"
# define NS_RECEIVER "urn:x-cast:com.google.cast.receiver"
# define NS_HEARTBEAT "urn:x-cast:com.google.cast.tp.heartbeat"
# define NS_MEDIA "urn:x-cast:com.google.cast.media"
2016-01-26 15:49:32 -05:00
# define USE_TRANSPORT_ID (1 << 1)
# define USE_REQUEST_ID (1 << 2)
# define USE_REQUEST_ID_ONLY (1 << 3)
2016-01-30 17:01:01 -05:00
# define CALLBACK_REGISTER_SIZE 32
2016-01-26 15:49:32 -05:00
union sockaddr_all
{
struct sockaddr_in sin ;
struct sockaddr_in6 sin6 ;
struct sockaddr sa ;
struct sockaddr_storage ss ;
} ;
struct cast_session ;
struct cast_msg_payload ;
typedef void ( * cast_reply_cb ) ( struct cast_session * cs , struct cast_msg_payload * payload ) ;
2016-01-17 14:59:16 -05:00
struct cast_session
{
2016-01-26 15:49:32 -05:00
enum output_device_state state ;
2016-01-17 14:59:16 -05:00
2016-01-26 15:49:32 -05:00
// Connection fd and session, and listener event
int server_fd ;
2016-01-17 14:59:16 -05:00
gnutls_session_t tls_session ;
2016-01-26 15:49:32 -05:00
struct event * ev ;
2016-01-17 14:59:16 -05:00
char * devname ;
char * address ;
unsigned short port ;
2016-01-29 18:36:05 -05:00
float volume ;
2016-01-17 14:59:16 -05:00
2016-01-31 12:42:32 -05:00
// IP address URL of forked-daapd's mp3 stream
char stream_url [ 128 ] ;
2016-01-26 15:49:32 -05:00
// Outgoing request which have the USE_REQUEST_ID flag get a new id, and a
// callback is registered. The callback is called when an incoming message
// from the peer with that request id arrives.
int request_id ;
cast_reply_cb callback_register [ CALLBACK_REGISTER_SIZE ] ;
// Session info from the ChromeCast
char * transport_id ;
char * session_id ;
2016-01-31 12:42:32 -05:00
int media_session_id ;
2016-01-17 14:59:16 -05:00
/* Do not dereference - only passed to the status cb */
2016-01-26 15:49:32 -05:00
struct output_device * device ;
struct output_session * output_session ;
output_status_cb status_cb ;
2016-01-17 14:59:16 -05:00
struct cast_session * next ;
} ;
2016-01-26 15:49:32 -05:00
enum cast_msg_types
2016-01-17 14:59:16 -05:00
{
UNKNOWN ,
PING ,
PONG ,
CONNECT ,
CLOSE ,
GET_STATUS ,
2016-01-26 15:49:32 -05:00
RECEIVER_STATUS ,
2016-01-17 14:59:16 -05:00
LAUNCH ,
MEDIA_CONNECT ,
MEDIA_GET_STATUS ,
2016-01-26 15:49:32 -05:00
MEDIA_STATUS ,
MEDIA_LOAD ,
2016-01-31 12:42:32 -05:00
MEDIA_PLAY ,
MEDIA_PAUSE ,
2016-01-17 14:59:16 -05:00
MEDIA_STOP ,
2016-01-26 15:49:32 -05:00
MEDIA_LOAD_FAILED ,
2016-01-29 18:36:05 -05:00
SET_VOLUME ,
2016-01-26 15:49:32 -05:00
} ;
struct cast_msg_basic
{
enum cast_msg_types type ;
char * tag ; // Used for looking up incoming message type
char * namespace ;
char * payload ;
int flags ;
} ;
struct cast_msg_payload
{
enum cast_msg_types type ;
int request_id ;
const char * session_id ;
const char * transport_id ;
2016-01-31 12:42:32 -05:00
int media_session_id ;
2016-01-17 14:59:16 -05:00
} ;
2016-01-26 15:49:32 -05:00
// Array of the cast messages that we use. Must be in sync with cast_msg_types.
struct cast_msg_basic cast_msg [ ] =
{
{
. type = UNKNOWN ,
. namespace = " " ,
. payload = " " ,
} ,
{
. type = PING ,
. tag = " PING " ,
. namespace = NS_HEARTBEAT ,
. payload = " {'type':'PING'} " ,
} ,
{
. type = PONG ,
. tag = " PONG " ,
. namespace = NS_HEARTBEAT ,
. payload = " {'type':'PONG'} " ,
} ,
{
. type = CONNECT ,
. namespace = NS_CONNECTION ,
. payload = " {'type':'CONNECT'} " ,
// msg.payload_utf8 = "{\"origin\":{},\"userAgent\":\"forked-daapd\",\"type\":\"CONNECT\",\"senderInfo\":{\"browserVersion\":\"44.0.2403.30\",\"version\":\"15.605.1.3\",\"connectionType\":1,\"platform\":4,\"sdkType\":2,\"systemVersion\":\"Macintosh; Intel Mac OS X10_10_3\"}}";
} ,
{
. type = CLOSE ,
. tag = " CLOSE " ,
. namespace = NS_CONNECTION ,
. payload = " {'type':'CLOSE'} " ,
} ,
{
. type = GET_STATUS ,
. namespace = NS_RECEIVER ,
. payload = " {'type':'GET_STATUS','requestId':%d} " ,
. flags = USE_REQUEST_ID_ONLY ,
} ,
{
. type = RECEIVER_STATUS ,
. tag = " RECEIVER_STATUS " ,
} ,
{
. type = LAUNCH ,
. namespace = NS_RECEIVER ,
. payload = " {'type':'LAUNCH','requestId':%d,'appId':'CC1AD845'} " ,
. flags = USE_REQUEST_ID_ONLY ,
} ,
{
. type = MEDIA_CONNECT ,
. namespace = NS_CONNECTION ,
. payload = " {'type':'CONNECT'} " ,
. flags = USE_TRANSPORT_ID ,
} ,
{
. type = MEDIA_GET_STATUS ,
. namespace = NS_MEDIA ,
. payload = " {'type':'GET_STATUS','requestId':%d} " ,
. flags = USE_TRANSPORT_ID | USE_REQUEST_ID_ONLY ,
} ,
{
. type = MEDIA_STATUS ,
. tag = " MEDIA_STATUS " ,
} ,
{
. type = MEDIA_LOAD ,
. namespace = NS_MEDIA ,
. payload = " {'currentTime':0,'media':{'contentId':'%s','streamType':'LIVE','contentType':'audio/mp3'},'customData':{},'sessionId':'%s','requestId':%d,'type':'LOAD','autoplay':1} " ,
. flags = USE_TRANSPORT_ID | USE_REQUEST_ID ,
} ,
2016-01-31 12:42:32 -05:00
{
. type = MEDIA_PLAY ,
. namespace = NS_MEDIA ,
. payload = " {'mediaSessionId':%d,'sessionId':'%s','type':'PLAY','requestId':%d} " ,
. flags = USE_TRANSPORT_ID | USE_REQUEST_ID ,
} ,
{
. type = MEDIA_PAUSE ,
. namespace = NS_MEDIA ,
. payload = " {'mediaSessionId':%d,'sessionId':'%s','type':'PAUSE','requestId':%d} " ,
. flags = USE_TRANSPORT_ID | USE_REQUEST_ID ,
} ,
2016-01-26 15:49:32 -05:00
{
. type = MEDIA_STOP ,
2016-01-31 12:42:32 -05:00
. namespace = NS_MEDIA ,
. payload = " {'mediaSessionId':%d,'sessionId':'%s','type':'STOP','requestId':%d} " ,
2016-01-26 15:49:32 -05:00
. flags = USE_TRANSPORT_ID | USE_REQUEST_ID ,
} ,
{
. type = MEDIA_LOAD_FAILED ,
. tag = " LOAD_FAILED " ,
} ,
2016-01-29 18:36:05 -05:00
{
. type = SET_VOLUME ,
. namespace = NS_RECEIVER ,
. payload = " {'type':'SET_VOLUME','volume':{'level':%.2f,'muted':0},'requestId':%d} " ,
. flags = USE_REQUEST_ID ,
} ,
2016-01-26 15:49:32 -05:00
{
. type = 0 ,
} ,
} ;
2016-01-17 14:59:16 -05:00
/* From player.c */
extern struct event_base * evbase_player ;
/* Globals */
static gnutls_certificate_credentials_t tls_credentials ;
static struct cast_session * sessions ;
2016-01-31 12:42:32 -05:00
static struct event * flush_timer ;
2016-01-17 14:59:16 -05:00
2016-01-26 15:49:32 -05:00
2016-01-17 14:59:16 -05:00
static int
tcp_connect ( const char * address , unsigned int port , int family )
{
union sockaddr_all sa ;
int fd ;
int len ;
int ret ;
2016-01-26 15:49:32 -05:00
// TODO Open non-block right away so we don't block the player while connecting
// and during TLS handshake (we would probably need to introduce a deferredev)
2016-01-17 14:59:16 -05:00
fd = socket ( family , SOCK_STREAM | SOCK_CLOEXEC , 0 ) ;
if ( fd < 0 )
{
DPRINTF ( E_LOG , L_CAST , " Could not create socket: %s \n " , strerror ( errno ) ) ;
return - 1 ;
}
switch ( family )
{
case AF_INET :
sa . sin . sin_port = htons ( port ) ;
ret = inet_pton ( AF_INET , address , & sa . sin . sin_addr ) ;
len = sizeof ( sa . sin ) ;
break ;
case AF_INET6 :
sa . sin6 . sin6_port = htons ( port ) ;
ret = inet_pton ( AF_INET6 , address , & sa . sin6 . sin6_addr ) ;
len = sizeof ( sa . sin6 ) ;
break ;
default :
DPRINTF ( E_WARN , L_CAST , " Unknown family %d \n " , family ) ;
close ( fd ) ;
return - 1 ;
}
if ( ret < = 0 )
{
DPRINTF ( E_LOG , L_CAST , " Device address not valid (%s) \n " , address ) ;
close ( fd ) ;
return - 1 ;
}
sa . ss . ss_family = family ;
ret = connect ( fd , & sa . sa , len ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_CAST , " connect() to [%s]:%u failed: %s \n " , address , port , strerror ( errno ) ) ;
close ( fd ) ;
return - 1 ;
}
return fd ;
}
static void
2016-01-26 15:49:32 -05:00
tcp_close ( int fd )
2016-01-17 14:59:16 -05:00
{
/* no more receptions */
shutdown ( fd , SHUT_RDWR ) ;
close ( fd ) ;
}
2016-01-31 12:42:32 -05:00
static int
stream_url_make ( char * out , size_t len , const char * peer_addr , int family )
{
struct ifaddrs * ifap ;
struct ifaddrs * ifa ;
union sockaddr_all haddr ;
union sockaddr_all hmask ;
union sockaddr_all paddr ;
char host_addr [ 128 ] ;
unsigned short port ;
int found ;
int ret ;
if ( family = = AF_INET )
ret = inet_pton ( AF_INET , peer_addr , & paddr . sin . sin_addr ) ;
else
ret = inet_pton ( AF_INET6 , peer_addr , & paddr . sin6 . sin6_addr ) ;
if ( ret ! = 1 )
return - 1 ;
found = 0 ;
getifaddrs ( & ifap ) ;
for ( ifa = ifap ; ! found & & ifa ; ifa = ifa - > ifa_next )
{
if ( ifa - > ifa_addr - > sa_family ! = family )
continue ;
if ( family = = AF_INET )
{
memcpy ( & haddr . sin , ifa - > ifa_addr , sizeof ( struct sockaddr_in ) ) ;
memcpy ( & hmask . sin , ifa - > ifa_netmask , sizeof ( struct sockaddr_in ) ) ;
found = ( ( haddr . sin . sin_addr . s_addr & hmask . sin . sin_addr . s_addr ) = =
( paddr . sin . sin_addr . s_addr & hmask . sin . sin_addr . s_addr ) ) ;
if ( found )
inet_ntop ( family , & haddr . sin . sin_addr , host_addr , sizeof ( host_addr ) ) ;
}
else if ( family = = AF_INET6 )
{
memcpy ( & haddr . sin6 , ifa - > ifa_addr , sizeof ( struct sockaddr_in6 ) ) ;
found = ( memcmp ( & haddr . sin6 . sin6_addr . s6_addr , & paddr . sin6 . sin6_addr . s6_addr , 8 ) = = 0 ) ;
if ( found )
inet_ntop ( family , & haddr . sin6 . sin6_addr , host_addr , sizeof ( host_addr ) ) ;
}
}
freeifaddrs ( ifap ) ;
if ( ! found )
return - 1 ;
port = cfg_getint ( cfg_getsec ( cfg , " library " ) , " port " ) ;
snprintf ( out , len , " http://%s:%d/stream.mp3 " , host_addr , port ) ;
return 0 ;
}
2016-01-26 15:49:32 -05:00
static char *
squote_to_dquote ( char * buf )
{
char * ptr ;
for ( ptr = buf ; * ptr ! = ' \0 ' ; ptr + + )
if ( * ptr = = ' \' ' )
* ptr = ' " ' ;
return buf ;
}
2016-01-17 14:59:16 -05:00
2016-01-26 15:49:32 -05:00
/*static char *
copy_tag_value ( const char * json , const char * key )
{
char full_key [ MAX_BUF ] ;
char * ptr ;
char * end ;
char * out ;
int len ;
//TODO check return
snprintf ( full_key , sizeof ( full_key ) , " \" %s \" : " , key ) ;
ptr = strstr ( json , full_key ) ;
if ( ! ptr )
return NULL ;
len = strlen ( json ) ;
// Advance ptr from beginning of key to beginning of value (first " after key)
for ( ptr = ptr + strlen ( full_key ) ; ( ptr - json < len - 1 ) & & ( * ptr ! = ' " ' ) & & ( * ptr ! = ' \0 ' ) ; ptr + + ) ;
if ( * ptr ! = ' " ' )
return NULL ;
ptr + + ;
for ( end = ptr + 1 ; ( end - json < len - 1 ) & & ( * end ! = ' " ' ) & & ( * end ! = ' \0 ' ) ; end + + ) ;
if ( * end ! = ' " ' )
return NULL ;
// If value is for instance "test", ptr will now be t and end will be "
len = ( end - ptr ) ;
out = calloc ( 1 , len + 1 ) ;
if ( ! out )
return NULL ;
memcpy ( out , ptr , len ) ;
printf ( " Value is %s \n " , out ) ;
return out ;
} */
static int
cast_msg_send ( struct cast_session * cs , enum cast_msg_types type , cast_reply_cb reply_cb )
{
Extensions__CoreApi__CastChannel__CastMessage msg = EXTENSIONS__CORE_API__CAST_CHANNEL__CAST_MESSAGE__INIT ;
char msg_buf [ MAX_BUF ] ;
uint8_t buf [ MAX_BUF ] ;
uint32_t be ;
size_t len ;
int ret ;
DPRINTF ( E_DBG , L_CAST , " Preparing to send message type %d to %s \n " , type , cs - > devname ) ;
msg . source_id = " sender-0 " ;
msg . namespace_ = cast_msg [ type ] . namespace ;
if ( ( cast_msg [ type ] . flags & USE_TRANSPORT_ID ) & & ! cs - > transport_id )
{
DPRINTF ( E_LOG , L_CAST , " Error, didn't get transportId for message (type %d) to '%s' \n " , type , cs - > devname ) ;
return - 1 ;
}
if ( cast_msg [ type ] . flags & USE_TRANSPORT_ID )
msg . destination_id = cs - > transport_id ;
else
msg . destination_id = " receiver-0 " ;
if ( cast_msg [ type ] . flags & ( USE_REQUEST_ID | USE_REQUEST_ID_ONLY ) )
{
cs - > request_id + + ;
if ( reply_cb )
cs - > callback_register [ cs - > request_id % CALLBACK_REGISTER_SIZE ] = reply_cb ;
}
2016-01-29 18:36:05 -05:00
// Special handling of some message types
2016-01-26 15:49:32 -05:00
if ( cast_msg [ type ] . flags & USE_REQUEST_ID_ONLY )
snprintf ( msg_buf , sizeof ( msg_buf ) , cast_msg [ type ] . payload , cs - > request_id ) ;
else if ( type = = MEDIA_LOAD )
2016-01-31 12:42:32 -05:00
snprintf ( msg_buf , sizeof ( msg_buf ) , cast_msg [ type ] . payload , cs - > stream_url , cs - > session_id , cs - > request_id ) ;
else if ( ( type = = MEDIA_PLAY ) | | ( type = = MEDIA_PAUSE ) | | ( type = = MEDIA_STOP ) )
snprintf ( msg_buf , sizeof ( msg_buf ) , cast_msg [ type ] . payload , cs - > media_session_id , cs - > session_id , cs - > request_id ) ;
2016-01-29 18:36:05 -05:00
else if ( type = = SET_VOLUME )
snprintf ( msg_buf , sizeof ( msg_buf ) , cast_msg [ type ] . payload , cs - > volume , cs - > request_id ) ;
2016-01-26 15:49:32 -05:00
else
snprintf ( msg_buf , sizeof ( msg_buf ) , " %s " , cast_msg [ type ] . payload ) ;
squote_to_dquote ( msg_buf ) ;
msg . payload_utf8 = msg_buf ;
len = extensions__core_api__cast_channel__cast_message__get_packed_size ( & msg ) ;
if ( len < = 0 )
{
DPRINTF ( E_LOG , L_CAST , " Could not send message (type %d), invalid length: %d \n " , type , len ) ;
return - 1 ;
}
// The message must be prefixed with Big-Endian 32 bit length
be = htobe32 ( len ) ;
memcpy ( buf , & be , 4 ) ;
// Now add the packed message and send it
extensions__core_api__cast_channel__cast_message__pack ( & msg , buf + 4 ) ;
ret = gnutls_record_send ( cs - > tls_session , buf , len + 4 ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_CAST , " Could not send message, TLS error \n " ) ;
return - 1 ;
}
else if ( ret ! = len + 4 )
{
DPRINTF ( E_LOG , L_CAST , " BUG! Message partially sent, and we are not able to send the rest \n " ) ;
return - 1 ;
}
DPRINTF ( E_DBG , L_CAST , " TX %d %s %s %s %s \n " , len , msg . source_id , msg . destination_id , msg . namespace_ , msg . payload_utf8 ) ;
return 0 ;
}
static void *
cast_msg_parse ( struct cast_msg_payload * payload , char * s )
{
json_object * haystack ;
json_object * somehay ;
json_object * needle ;
const char * val ;
int i ;
haystack = json_tokener_parse ( s ) ;
if ( ! haystack )
{
DPRINTF ( E_LOG , L_CAST , " JSON parser returned an error \n " ) ;
return NULL ;
}
payload - > type = UNKNOWN ;
if ( json_object_object_get_ex ( haystack , " type " , & needle ) )
{
val = json_object_get_string ( needle ) ;
for ( i = 1 ; cast_msg [ i ] . type ; i + + )
{
if ( cast_msg [ i ] . tag & & ( strcmp ( val , cast_msg [ i ] . tag ) = = 0 ) )
{
payload - > type = cast_msg [ i ] . type ;
break ;
}
}
}
if ( json_object_object_get_ex ( haystack , " requestId " , & needle ) )
payload - > request_id = json_object_get_int ( needle ) ;
// Might be done now
2016-01-31 12:42:32 -05:00
if ( ( payload - > type ! = RECEIVER_STATUS ) & & ( payload - > type ! = MEDIA_STATUS ) )
2016-01-26 15:49:32 -05:00
return haystack ;
// Isn't this marvelous
2016-01-31 12:42:32 -05:00
if ( json_object_object_get_ex ( haystack , " status " , & needle ) & &
( json_object_get_type ( needle ) = = json_type_array ) & &
( somehay = json_object_array_get_idx ( needle , 0 ) ) & &
json_object_object_get_ex ( somehay , " mediaSessionId " , & needle ) & &
( json_object_get_type ( needle ) = = json_type_int ) )
payload - > media_session_id = json_object_get_int ( needle ) ;
2016-01-26 15:49:32 -05:00
if ( ! ( json_object_object_get_ex ( haystack , " status " , & somehay ) & &
json_object_object_get_ex ( somehay , " applications " , & needle ) & &
( json_object_get_type ( needle ) = = json_type_array ) & &
( somehay = json_object_array_get_idx ( needle , 0 ) ) ) )
return haystack ;
if ( json_object_object_get_ex ( somehay , " sessionId " , & needle ) & &
( json_object_get_type ( needle ) = = json_type_string ) )
payload - > session_id = json_object_get_string ( needle ) ;
if ( json_object_object_get_ex ( somehay , " transportId " , & needle ) & &
( json_object_get_type ( needle ) = = json_type_string ) )
payload - > transport_id = json_object_get_string ( needle ) ;
return haystack ;
}
static void
cast_msg_parse_free ( void * haystack )
{
if ( json_object_put ( ( json_object * ) haystack ) ! = 1 )
DPRINTF ( E_LOG , L_CAST , " Memleak: JSON parser did not free object \n " ) ;
}
static void
cast_msg_process ( struct cast_session * cs , const uint8_t * data , size_t len )
2016-01-17 14:59:16 -05:00
{
Extensions__CoreApi__CastChannel__CastMessage * reply ;
2016-01-26 15:49:32 -05:00
struct cast_msg_payload payload = { 0 } ;
void * hdl ;
int i ;
2016-01-17 14:59:16 -05:00
reply = extensions__core_api__cast_channel__cast_message__unpack ( NULL , len , data ) ;
2016-01-26 15:49:32 -05:00
if ( ! reply )
{
DPRINTF ( E_LOG , L_CAST , " Could not unpack message! \n " ) ;
return ;
}
2016-01-17 14:59:16 -05:00
DPRINTF ( E_DBG , L_CAST , " RX %d %s %s %s %s \n " , len , reply - > source_id , reply - > destination_id , reply - > namespace_ , reply - > payload_utf8 ) ;
2016-01-26 15:49:32 -05:00
hdl = cast_msg_parse ( & payload , reply - > payload_utf8 ) ;
if ( ! hdl )
{
DPRINTF ( E_DBG , L_CAST , " Could not parse message: %s \n " , reply - > payload_utf8 ) ;
goto out_free_unpacked ;
}
2016-01-17 14:59:16 -05:00
2016-01-26 15:49:32 -05:00
if ( payload . type = = PING )
2016-01-17 14:59:16 -05:00
{
2016-01-26 15:49:32 -05:00
cast_msg_send ( cs , PONG , NULL ) ;
goto out_free_parsed ;
2016-01-17 14:59:16 -05:00
}
2016-01-26 15:49:32 -05:00
if ( payload . type = = UNKNOWN )
goto out_free_parsed ;
// TODO: UPDATE SESSION STATUS AND READ BROADCASTS
i = payload . request_id % CALLBACK_REGISTER_SIZE ;
2016-01-30 17:01:01 -05:00
if ( payload . request_id & & cs - > callback_register [ i ] )
2016-01-17 14:59:16 -05:00
{
2016-01-26 15:49:32 -05:00
cs - > callback_register [ i ] ( cs , & payload ) ;
cs - > callback_register [ i ] = NULL ;
goto out_free_parsed ;
2016-01-17 14:59:16 -05:00
}
2016-01-26 15:49:32 -05:00
out_free_parsed :
cast_msg_parse_free ( hdl ) ;
out_free_unpacked :
extensions__core_api__cast_channel__cast_message__free_unpacked ( reply , NULL ) ;
2016-01-17 14:59:16 -05:00
}
static void
cast_session_free ( struct cast_session * cs )
{
event_free ( cs - > ev ) ;
2016-01-26 15:49:32 -05:00
// TODO How will this work if device disconnected?
2016-01-17 14:59:16 -05:00
gnutls_bye ( cs - > tls_session , GNUTLS_SHUT_RDWR ) ;
close ( cs - > server_fd ) ;
gnutls_deinit ( cs - > tls_session ) ;
if ( cs - > address )
free ( cs - > address ) ;
if ( cs - > devname )
free ( cs - > devname ) ;
2016-01-26 15:49:32 -05:00
if ( cs - > session_id )
free ( cs - > session_id ) ;
if ( cs - > transport_id )
free ( cs - > transport_id ) ;
free ( cs - > output_session ) ;
2016-01-17 14:59:16 -05:00
free ( cs ) ;
}
static void
cast_session_cleanup ( struct cast_session * cs )
{
struct cast_session * s ;
if ( cs = = sessions )
sessions = sessions - > next ;
else
{
for ( s = sessions ; s & & ( s - > next ! = cs ) ; s = s - > next )
; /* EMPTY */
if ( ! s )
DPRINTF ( E_WARN , L_CAST , " WARNING: struct cast_session not found in list; BUG! \n " ) ;
else
s - > next = cs - > next ;
}
cast_session_free ( cs ) ;
}
static void
cast_session_failure ( struct cast_session * cs )
{
2016-01-26 15:49:32 -05:00
// Session failed, let our user know
cs - > status_cb ( cs - > device , cs - > output_session , OUTPUT_STATE_FAILED ) ;
2016-01-17 14:59:16 -05:00
cast_session_cleanup ( cs ) ;
}
2016-01-26 15:49:32 -05:00
static void
cast_cb_startup_volume ( struct cast_session * cs , struct cast_msg_payload * payload )
{
output_status_cb status_cb ;
cs - > state = OUTPUT_STATE_CONNECTED ;
/* Session startup and setup is done, tell our user */
DPRINTF ( E_DBG , L_CAST , " Session ready \n " ) ;
status_cb = cs - > status_cb ;
cs - > status_cb = NULL ;
status_cb ( cs - > device , cs - > output_session , cs - > state ) ;
}
static void
cast_cb_startup_media ( struct cast_session * cs , struct cast_msg_payload * payload )
{
2016-01-29 18:36:05 -05:00
int ret ;
2016-01-26 15:49:32 -05:00
if ( payload - > type ! = MEDIA_STATUS )
{
DPRINTF ( E_LOG , L_CAST , " No receiver status or transport id? \n " ) ;
cast_msg_send ( cs , CLOSE , NULL ) ;
cast_session_failure ( cs ) ;
return ;
}
// TODO Send a volume message with the cb
2016-01-29 18:36:05 -05:00
ret = cast_msg_send ( cs , SET_VOLUME , cast_cb_startup_volume ) ;
if ( ret < 0 )
{
cast_msg_send ( cs , CLOSE , NULL ) ;
cast_session_failure ( cs ) ;
}
2016-01-26 15:49:32 -05:00
}
static void
cast_cb_startup_launch ( struct cast_session * cs , struct cast_msg_payload * payload )
{
int ret ;
if ( payload - > type ! = RECEIVER_STATUS | | ! payload - > transport_id )
{
DPRINTF ( E_LOG , L_CAST , " No receiver status or transport id? \n " ) ;
cast_msg_send ( cs , CLOSE , NULL ) ;
cast_session_failure ( cs ) ;
return ;
}
if ( cs - > transport_id )
DPRINTF ( E_LOG , L_CAST , " Ooops, memleaking... \n " ) ;
cs - > transport_id = strdup ( payload - > transport_id ) ;
ret = cast_msg_send ( cs , MEDIA_CONNECT , NULL ) ;
if ( ret = = 0 )
ret = cast_msg_send ( cs , MEDIA_GET_STATUS , cast_cb_startup_media ) ;
if ( ret < 0 )
{
cast_msg_send ( cs , CLOSE , NULL ) ;
cast_session_failure ( cs ) ;
}
}
static void
cast_cb_startup_connect ( struct cast_session * cs , struct cast_msg_payload * payload )
{
int ret ;
if ( payload - > type ! = RECEIVER_STATUS | | ! payload - > session_id )
{
DPRINTF ( E_LOG , L_CAST , " No receiver status or session id? (type: %d, id: %s) \n " , payload - > type , payload - > session_id ) ;
cast_msg_send ( cs , CLOSE , NULL ) ;
cast_session_failure ( cs ) ;
return ;
}
if ( cs - > session_id )
DPRINTF ( E_LOG , L_CAST , " Ooops, memleaking... \n " ) ;
cs - > session_id = strdup ( payload - > session_id ) ;
ret = cast_msg_send ( cs , LAUNCH , cast_cb_startup_launch ) ;
if ( ret < 0 )
{
cast_msg_send ( cs , CLOSE , NULL ) ;
cast_session_failure ( cs ) ;
}
}
2016-01-31 12:42:32 -05:00
// TODO Not really a cb right now
2016-01-26 15:49:32 -05:00
static void
cast_cb_close ( struct cast_session * cs , struct cast_msg_payload * payload )
{
2016-01-31 12:42:32 -05:00
output_status_cb status_cb ;
cs - > state = OUTPUT_STATE_STOPPED ;
/* Session shut down, tell our user */
status_cb = cs - > status_cb ;
cs - > status_cb = NULL ;
status_cb ( cs - > device , cs - > output_session , cs - > state ) ;
cast_session_cleanup ( cs ) ;
2016-01-26 15:49:32 -05:00
}
static void
cast_cb_load ( struct cast_session * cs , struct cast_msg_payload * payload )
{
if ( payload - > type = = MEDIA_LOAD_FAILED )
{
DPRINTF ( E_LOG , L_CAST , " The device %s could not start playback \n " , cs - > devname ) ;
cast_msg_send ( cs , CLOSE , NULL ) ;
cast_session_failure ( cs ) ;
return ;
}
2016-01-31 12:42:32 -05:00
if ( ! payload - > media_session_id )
DPRINTF ( E_LOG , L_CAST , " Did not get a media session id? \n " ) ;
else
cs - > media_session_id = payload - > media_session_id ;
2016-01-26 15:49:32 -05:00
cs - > state = OUTPUT_STATE_STREAMING ;
DPRINTF ( E_DBG , L_CAST , " Media loaded \n " ) ;
cs - > status_cb ( cs - > device , cs - > output_session , cs - > state ) ;
}
2016-01-31 12:42:32 -05:00
static void
cast_cb_stop ( struct cast_session * cs , struct cast_msg_payload * payload )
{
output_status_cb status_cb ;
cs - > state = OUTPUT_STATE_CONNECTED ;
status_cb = cs - > status_cb ;
cs - > status_cb = NULL ;
status_cb ( cs - > device , cs - > output_session , cs - > state ) ;
//TODO
}
2016-01-29 18:36:05 -05:00
static void
cast_cb_volume ( struct cast_session * cs , struct cast_msg_payload * payload )
{
output_status_cb status_cb ;
status_cb = cs - > status_cb ;
cs - > status_cb = NULL ;
status_cb ( cs - > device , cs - > output_session , cs - > state ) ;
}
2016-01-31 12:42:32 -05:00
static void
cast_cb_flush ( struct cast_session * cs , struct cast_msg_payload * payload )
{
output_status_cb status_cb ;
DPRINTF ( E_DBG , L_CAST , " Cast CB flush called \n " ) ;
cs - > state = OUTPUT_STATE_CONNECTED ;
/* Let our user know */
status_cb = cs - > status_cb ;
cs - > status_cb = NULL ;
status_cb ( cs - > device , cs - > output_session , cs - > state ) ;
}
2016-01-29 18:36:05 -05:00
2016-01-17 14:59:16 -05:00
static void
cast_listen_cb ( int fd , short what , void * arg )
{
struct cast_session * cs ;
uint8_t buffer [ MAX_BUF + 1 ] ; // Not sure about the +1, but is copied from gnutls examples
uint32_t be ;
size_t len ;
2016-01-30 17:01:01 -05:00
int received ;
2016-01-17 14:59:16 -05:00
int ret ;
cs = ( struct cast_session * ) arg ;
DPRINTF ( E_DBG , L_CAST , " New data from %s \n " , cs - > devname ) ;
2016-01-30 17:01:01 -05:00
received = 0 ;
while ( ( ret = gnutls_record_recv ( cs - > tls_session , buffer + received , MAX_BUF - received ) ) > 0 )
2016-01-17 14:59:16 -05:00
{
DPRINTF ( E_DBG , L_CAST , " Received %d bytes \n " , ret ) ;
if ( ret = = 4 )
{
memcpy ( & be , buffer , 4 ) ;
len = be32toh ( be ) ;
DPRINTF ( E_DBG , L_CAST , " Incoming %d bytes \n " , len ) ;
}
2016-01-26 15:49:32 -05:00
else
2016-01-17 14:59:16 -05:00
{
2016-01-30 17:01:01 -05:00
received + = ret ;
2016-01-29 18:36:05 -05:00
}
2016-01-30 17:01:01 -05:00
if ( received > = MAX_BUF )
2016-01-29 18:36:05 -05:00
{
DPRINTF ( E_LOG , L_CAST , " Receive buffer exhausted! \n " ) ;
cast_session_failure ( cs ) ;
return ;
2016-01-17 14:59:16 -05:00
}
}
2016-01-26 15:49:32 -05:00
if ( ( ret ! = GNUTLS_E_INTERRUPTED ) & & ( ret ! = GNUTLS_E_AGAIN ) )
{
DPRINTF ( E_LOG , L_CAST , " Session error: %s \n " , gnutls_strerror ( ret ) ) ;
cast_session_failure ( cs ) ;
2016-01-29 18:36:05 -05:00
return ;
2016-01-26 15:49:32 -05:00
}
2016-01-29 18:36:05 -05:00
2016-01-30 17:01:01 -05:00
if ( received )
cast_msg_process ( cs , buffer , received ) ;
2016-01-17 14:59:16 -05:00
}
static struct cast_session *
2016-01-26 15:49:32 -05:00
cast_session_make ( struct output_device * device , int family , output_status_cb cb )
2016-01-17 14:59:16 -05:00
{
2016-01-26 15:49:32 -05:00
struct output_session * os ;
2016-01-17 14:59:16 -05:00
struct cast_session * cs ;
const char * proto ;
const char * err ;
char * address ;
unsigned short port ;
2016-01-26 15:49:32 -05:00
int flags ;
2016-01-17 14:59:16 -05:00
int ret ;
switch ( family )
{
case AF_INET :
/* We always have the v4 services, so no need to check */
2016-01-26 15:49:32 -05:00
if ( ! device - > v4_address )
2016-01-17 14:59:16 -05:00
return NULL ;
2016-01-26 15:49:32 -05:00
address = device - > v4_address ;
port = device - > v4_port ;
2016-01-17 14:59:16 -05:00
break ;
case AF_INET6 :
2016-01-26 15:49:32 -05:00
if ( ! device - > v6_address )
2016-01-17 14:59:16 -05:00
return NULL ;
2016-01-26 15:49:32 -05:00
address = device - > v6_address ;
port = device - > v6_port ;
2016-01-17 14:59:16 -05:00
break ;
default :
return NULL ;
}
2016-01-26 15:49:32 -05:00
os = calloc ( 1 , sizeof ( struct output_session ) ) ;
2016-01-17 14:59:16 -05:00
cs = calloc ( 1 , sizeof ( struct cast_session ) ) ;
2016-01-26 15:49:32 -05:00
if ( ! os | | ! cs )
2016-01-17 14:59:16 -05:00
{
DPRINTF ( E_LOG , L_CAST , " Out of memory for TLS session \n " ) ;
return NULL ;
}
2016-01-26 15:49:32 -05:00
os - > session = cs ;
os - > type = device - > type ;
cs - > output_session = os ;
cs - > state = OUTPUT_STATE_STOPPED ;
cs - > device = device ;
2016-01-17 14:59:16 -05:00
cs - > status_cb = cb ;
/* Init TLS session, use default priorities and put the x509 credentials to the current session */
if ( ( ( ret = gnutls_init ( & cs - > tls_session , GNUTLS_CLIENT ) ) ! = GNUTLS_E_SUCCESS ) | |
( ( ret = gnutls_priority_set_direct ( cs - > tls_session , " PERFORMANCE " , & err ) ) ! = GNUTLS_E_SUCCESS ) | |
( ( ret = gnutls_credentials_set ( cs - > tls_session , GNUTLS_CRD_CERTIFICATE , tls_credentials ) ) ! = GNUTLS_E_SUCCESS ) )
{
DPRINTF ( E_LOG , L_CAST , " Could not initialize GNUTLS session: %s \n " , gnutls_strerror ( ret ) ) ;
goto out_free_session ;
}
cs - > server_fd = tcp_connect ( address , port , family ) ;
if ( cs - > server_fd < 0 )
2016-01-31 12:42:32 -05:00
{
DPRINTF ( E_LOG , L_CAST , " Could not connect to %s \n " , device - > name ) ;
goto out_deinit_gnutls ;
}
ret = stream_url_make ( cs - > stream_url , sizeof ( cs - > stream_url ) , address , family ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_CAST , " Bug! Could find a network interface on same subnet as %s \n " , device - > name ) ;
goto out_close_connection ;
}
2016-01-17 14:59:16 -05:00
2016-01-26 15:49:32 -05:00
// TODO Add a timeout to detect connection problems
cs - > ev = event_new ( evbase_player , cs - > server_fd , EV_READ | EV_PERSIST , cast_listen_cb , cs ) ;
2016-01-17 14:59:16 -05:00
if ( ! cs - > ev )
{
DPRINTF ( E_LOG , L_CAST , " Out of memory for listener event \n " ) ;
goto out_close_connection ;
}
gnutls_transport_set_ptr ( cs - > tls_session , ( gnutls_transport_ptr_t ) cs - > server_fd ) ;
ret = gnutls_handshake ( cs - > tls_session ) ;
if ( ret ! = GNUTLS_E_SUCCESS )
{
DPRINTF ( E_LOG , L_CAST , " Could not attach TLS to TCP connection: %s \n " , gnutls_strerror ( ret ) ) ;
goto out_free_ev ;
}
2016-01-26 15:49:32 -05:00
flags = fcntl ( cs - > server_fd , F_GETFL , 0 ) ;
fcntl ( cs - > server_fd , F_SETFL , flags | O_NONBLOCK ) ;
2016-01-17 14:59:16 -05:00
event_add ( cs - > ev , NULL ) ;
2016-01-26 15:49:32 -05:00
cs - > devname = strdup ( device - > name ) ;
2016-01-17 14:59:16 -05:00
cs - > address = strdup ( address ) ;
2016-01-29 18:36:05 -05:00
cs - > volume = 0.01 * device - > volume ;
2016-01-17 14:59:16 -05:00
cs - > next = sessions ;
sessions = cs ;
proto = gnutls_protocol_get_name ( gnutls_protocol_get_version ( cs - > tls_session ) ) ;
DPRINTF ( E_INFO , L_CAST , " Connection to %s established using %s \n " , cs - > devname , proto ) ;
return cs ;
out_free_ev :
event_free ( cs - > ev ) ;
out_close_connection :
tcp_close ( cs - > server_fd ) ;
out_deinit_gnutls :
gnutls_deinit ( cs - > tls_session ) ;
out_free_session :
free ( cs ) ;
return NULL ;
}
2016-01-26 15:49:32 -05:00
static int
cast_device_start ( struct output_device * device , output_status_cb cb , uint64_t rtptime ) ;
2016-01-23 19:14:07 -05:00
static void
cast_device_cb ( const char * name , const char * type , const char * domain , const char * hostname , int family , const char * address , int port , struct keyval * txt )
{
2016-01-26 15:49:32 -05:00
struct output_device * device ;
2016-01-23 19:14:07 -05:00
const char * p ;
uint32_t id ;
p = keyval_get ( txt , " id " ) ;
if ( p )
id = djb_hash ( p , strlen ( p ) ) ;
else
id = 0 ;
if ( ! id )
{
DPRINTF ( E_LOG , L_PLAYER , " Could not extract ChromeCast device ID (%s) \n " , name ) ;
return ;
}
DPRINTF ( E_DBG , L_PLAYER , " Event for Chromecast device %s (port %d, id % " PRIu32 " ) \n " , name , port , id ) ;
2016-01-26 15:49:32 -05:00
device = calloc ( 1 , sizeof ( struct output_device ) ) ;
if ( ! device )
2016-01-23 19:14:07 -05:00
{
DPRINTF ( E_LOG , L_PLAYER , " Out of memory for new Chromecast device \n " ) ;
return ;
}
2016-01-26 15:49:32 -05:00
device - > id = id ;
device - > name = strdup ( name ) ;
device - > type = OUTPUT_TYPE_CAST ;
device - > type_name = outputs_name ( device - > type ) ;
2016-01-23 19:14:07 -05:00
if ( port < 0 )
{
/* Device stopped advertising */
switch ( family )
{
case AF_INET :
2016-01-26 15:49:32 -05:00
device - > v4_port = 1 ;
2016-01-23 19:14:07 -05:00
break ;
case AF_INET6 :
2016-01-26 15:49:32 -05:00
device - > v6_port = 1 ;
2016-01-23 19:14:07 -05:00
break ;
}
2016-01-26 15:49:32 -05:00
player_device_remove ( device ) ;
2016-01-23 19:14:07 -05:00
return ;
}
DPRINTF ( E_INFO , L_PLAYER , " Adding Chromecast device %s \n " , name ) ;
2016-01-26 15:49:32 -05:00
device - > advertised = 1 ;
2016-01-23 19:14:07 -05:00
switch ( family )
{
case AF_INET :
2016-01-26 15:49:32 -05:00
device - > v4_address = strdup ( address ) ;
device - > v4_port = port ;
2016-01-23 19:14:07 -05:00
break ;
case AF_INET6 :
2016-01-26 15:49:32 -05:00
device - > v6_address = strdup ( address ) ;
device - > v6_port = port ;
2016-01-23 19:14:07 -05:00
break ;
}
2016-01-26 15:49:32 -05:00
player_device_add ( device ) ;
2016-01-23 19:14:07 -05:00
}
2016-01-26 15:49:32 -05:00
static int
cast_device_start ( struct output_device * device , output_status_cb cb , uint64_t rtptime )
2016-01-17 14:59:16 -05:00
{
struct cast_session * cs ;
int ret ;
2016-01-26 15:49:32 -05:00
DPRINTF ( E_LOG , L_CAST , " Got start request for %s \n " , device - > name ) ;
cs = cast_session_make ( device , AF_INET6 , cb ) ;
2016-01-17 14:59:16 -05:00
if ( cs )
{
2016-01-26 15:49:32 -05:00
ret = cast_msg_send ( cs , CONNECT , NULL ) ;
2016-01-17 14:59:16 -05:00
if ( ret = = 0 )
2016-01-26 15:49:32 -05:00
{
cast_msg_send ( cs , GET_STATUS , cast_cb_startup_connect ) ;
return 0 ;
}
2016-01-17 14:59:16 -05:00
else
{
2016-01-26 15:49:32 -05:00
DPRINTF ( E_WARN , L_CAST , " Could not send CONNECT request on IPv6 (start) \n " ) ;
2016-01-17 14:59:16 -05:00
2016-01-26 15:49:32 -05:00
cast_session_cleanup ( cs ) ;
2016-01-17 14:59:16 -05:00
}
}
2016-01-26 15:49:32 -05:00
cs = cast_session_make ( device , AF_INET , cb ) ;
2016-01-17 14:59:16 -05:00
if ( ! cs )
return - 1 ;
2016-01-26 15:49:32 -05:00
ret = cast_msg_send ( cs , CONNECT , NULL ) ;
2016-01-17 14:59:16 -05:00
if ( ret < 0 )
{
2016-01-26 15:49:32 -05:00
DPRINTF ( E_LOG , L_CAST , " Could not send CONNECT request on IPv4 (start) \n " ) ;
2016-01-17 14:59:16 -05:00
2016-01-26 15:49:32 -05:00
cast_session_cleanup ( cs ) ;
2016-01-17 14:59:16 -05:00
return - 1 ;
}
2016-01-26 15:49:32 -05:00
// TODO Check return value
cast_msg_send ( cs , GET_STATUS , cast_cb_startup_connect ) ;
2016-01-17 14:59:16 -05:00
return 0 ;
}
2016-01-26 15:49:32 -05:00
static void
cast_device_stop ( struct output_session * session )
{
struct cast_session * cs = session - > session ;
if ( ! ( cs - > state & OUTPUT_STATE_F_CONNECTED ) )
2016-01-31 12:42:32 -05:00
{
cast_session_cleanup ( cs ) ;
return ;
}
cast_msg_send ( cs , CLOSE , NULL ) ;
cast_cb_close ( cs , NULL ) ;
2016-01-26 15:49:32 -05:00
}
2016-01-29 18:36:05 -05:00
static int
cast_volume_set ( struct output_device * device , output_status_cb cb )
{
struct cast_session * cs ;
int ret ;
if ( ! device - > session | | ! device - > session - > session )
return 0 ;
cs = device - > session - > session ;
if ( ! ( cs - > state & OUTPUT_STATE_F_CONNECTED ) )
return 0 ;
cs - > volume = 0.01 * device - > volume ;
ret = cast_msg_send ( cs , SET_VOLUME , cast_cb_volume ) ;
if ( ret < 0 )
{
cast_session_failure ( cs ) ;
return 0 ;
}
cs - > status_cb = cb ;
return 1 ;
}
2016-01-26 15:49:32 -05:00
static void
cast_playback_start ( uint64_t next_pkt , struct timespec * ts )
{
struct cast_session * cs ;
2016-01-31 12:42:32 -05:00
if ( evtimer_pending ( flush_timer , NULL ) )
event_del ( flush_timer ) ;
2016-01-26 15:49:32 -05:00
for ( cs = sessions ; cs ; cs = cs - > next )
{
if ( cs - > state = = OUTPUT_STATE_CONNECTED )
cast_msg_send ( cs , MEDIA_LOAD , cast_cb_load ) ;
}
}
2016-01-31 12:42:32 -05:00
static void
cast_playback_stop ( void )
{
struct cast_session * cs ;
int ret ;
for ( cs = sessions ; cs ; cs = cs - > next )
{
ret = cast_msg_send ( cs , MEDIA_STOP , cast_cb_stop ) ;
if ( ret < 0 )
DPRINTF ( E_LOG , L_CAST , " MEDIA_STOP request failed! \n " ) ;
}
}
static void
cast_flush_timer_cb ( int fd , short what , void * arg )
{
DPRINTF ( E_DBG , L_CAST , " Flush timer expired; tearing down all sessions \n " ) ;
cast_playback_stop ( ) ;
}
static int
cast_flush ( output_status_cb cb , uint64_t rtptime )
{
struct cast_session * cs ;
struct cast_session * next ;
struct timeval tv ;
int pending ;
int ret ;
pending = 0 ;
for ( cs = sessions ; cs ; cs = next )
{
next = cs - > next ;
if ( cs - > state ! = OUTPUT_STATE_STREAMING )
continue ;
ret = cast_msg_send ( cs , MEDIA_PAUSE , cast_cb_flush ) ;
if ( ret < 0 )
{
cast_session_failure ( cs ) ;
continue ;
}
cs - > status_cb = cb ;
pending + + ;
}
if ( pending > 0 )
{
evutil_timerclear ( & tv ) ;
tv . tv_sec = 10 ;
evtimer_add ( flush_timer , & tv ) ;
}
return pending ;
}
2016-01-26 15:49:32 -05:00
static void
cast_set_status_cb ( struct output_session * session , output_status_cb cb )
{
struct cast_session * cs = session - > session ;
cs - > status_cb = cb ;
}
static int
2016-01-17 14:59:16 -05:00
cast_init ( void )
{
2016-01-23 19:14:07 -05:00
int mdns_flags ;
2016-01-26 15:49:32 -05:00
int i ;
2016-01-17 14:59:16 -05:00
int ret ;
2016-01-26 15:49:32 -05:00
// Sanity check
for ( i = 1 ; cast_msg [ i ] . type ; i + + )
2016-01-23 19:14:07 -05:00
{
2016-01-26 15:49:32 -05:00
if ( cast_msg [ i ] . type ! = i )
{
DPRINTF ( E_LOG , L_CAST , " BUG! Cast messages and types are misaligned (type %d!=%d). Could not initialize. \n " , cast_msg [ i ] . type , i ) ;
return - 1 ;
}
2016-01-23 19:14:07 -05:00
}
2016-01-17 14:59:16 -05:00
// TODO Setting the cert file may not be required
if ( ( ( ret = gnutls_global_init ( ) ) ! = GNUTLS_E_SUCCESS ) | |
( ( ret = gnutls_certificate_allocate_credentials ( & tls_credentials ) ) ! = GNUTLS_E_SUCCESS ) | |
( ( ret = gnutls_certificate_set_x509_trust_file ( tls_credentials , CAFILE , GNUTLS_X509_FMT_PEM ) ) < 0 ) )
{
DPRINTF ( E_LOG , L_CAST , " Could not initialize GNUTLS: %s \n " , gnutls_strerror ( ret ) ) ;
return - 1 ;
}
2016-01-31 12:42:32 -05:00
flush_timer = evtimer_new ( evbase_player , cast_flush_timer_cb , NULL ) ;
if ( ! flush_timer )
{
DPRINTF ( E_LOG , L_CAST , " Out of memory for flush timer \n " ) ;
goto out_tls_deinit ;
}
2016-01-26 15:49:32 -05:00
mdns_flags = MDNS_WANT_V4 | MDNS_WANT_V6 | MDNS_WANT_V6LL ;
ret = mdns_browse ( " _googlecast._tcp " , mdns_flags , cast_device_cb ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_PLAYER , " Could not add mDNS browser for Chromecast devices \n " ) ;
2016-01-31 12:42:32 -05:00
goto out_free_flush_timer ;
2016-01-26 15:49:32 -05:00
}
2016-01-17 14:59:16 -05:00
return 0 ;
2016-01-26 15:49:32 -05:00
2016-01-31 12:42:32 -05:00
out_free_flush_timer :
event_free ( flush_timer ) ;
2016-01-26 15:49:32 -05:00
out_tls_deinit :
gnutls_certificate_free_credentials ( tls_credentials ) ;
gnutls_global_deinit ( ) ;
return - 1 ;
2016-01-17 14:59:16 -05:00
}
2016-01-26 15:49:32 -05:00
static void
2016-01-17 14:59:16 -05:00
cast_deinit ( void )
{
struct cast_session * cs ;
for ( cs = sessions ; sessions ; cs = sessions )
{
sessions = cs - > next ;
cast_session_free ( cs ) ;
}
2016-01-31 12:42:32 -05:00
event_free ( flush_timer ) ;
2016-01-17 14:59:16 -05:00
gnutls_certificate_free_credentials ( tls_credentials ) ;
gnutls_global_deinit ( ) ;
}
2016-01-26 15:49:32 -05:00
struct output_definition output_cast =
{
. name = " Chromecast " ,
. type = OUTPUT_TYPE_CAST ,
. priority = 2 ,
. disabled = 0 ,
. init = cast_init ,
. deinit = cast_deinit ,
. device_start = cast_device_start ,
. device_stop = cast_device_stop ,
2016-01-31 12:42:32 -05:00
// .device_probe = cast_device_probe,
// .device_free_extra is unset - nothing to free
2016-01-29 18:36:05 -05:00
. device_volume_set = cast_volume_set ,
2016-01-26 15:49:32 -05:00
. playback_start = cast_playback_start ,
2016-01-31 12:42:32 -05:00
. playback_stop = cast_playback_stop ,
// .write is unset - we don't write, the Chromecast will read our mp3 stream
. flush = cast_flush ,
2016-01-26 15:49:32 -05:00
. status_cb = cast_set_status_cb ,
/* .metadata_prepare = cast_metadata_prepare,
. metadata_send = cast_metadata_send ,
. metadata_purge = cast_metadata_purge ,
. metadata_prune = cast_metadata_prune ,
*/
} ;