Commit a0d58179 authored by Jon Lech Johansen's avatar Jon Lech Johansen

* Bonjour services discovery module using avahi.

parent 29ff734c
......@@ -1607,7 +1607,7 @@ then
VLC_ADD_BUILTINS([mux_ts])
fi
VLC_ADD_CPPFLAGS([mux_ts ts dvb],[-I${real_dvbpsi_tree}/src])
VLC_ADD_LDFLAGS([mux_ts ts dvb],[-L${real_dvbpsi_tree}/src/.libs -ldvbpsi])
VLC_ADD_LDFLAGS([mux_ts ts dvb],[${real_dvbpsi_tree}/src/.libs/libdvbpsi.a])
else
dnl The given libdvbpsi wasn't built
AC_MSG_RESULT(no)
......@@ -4152,6 +4152,21 @@ then
[AC_MSG_WARN(DAAP library not found)])
fi
dnl
dnl Bonjour services discovery
dnl
AC_ARG_ENABLE(bonjour,
[ --enable-bonjour Bonjour services discovery (default enabled)])
if test "${enable_bonjour}" != "no"
then
PKG_CHECK_MODULES(BONJOUR, avahi-client >= 0.3,
[AC_DEFINE(HAVE_AVAHI_CLIENT, [], [Define if you have the avahi-client library])
VLC_ADD_LDFLAGS([bonjour access_output_http],[$BONJOUR_LIBS])
VLC_ADD_CFLAGS([bonjour access_output_http],[$BONJOUR_CFLAGS])
VLC_ADD_PLUGINS([bonjour]) ],
[AC_MSG_WARN(avahi-client library not found)])
fi
dnl
dnl Lirc plugin
dnl
......
SOURCES_access_output_dummy = dummy.c
SOURCES_access_output_file = file.c
SOURCES_access_output_udp = udp.c
SOURCES_access_output_http = http.c
SOURCES_access_output_http = http.c bonjour.c bonjour.h
SOURCES_access_output_shout = shout.c
/*****************************************************************************
* bonjour.c
*****************************************************************************
* Copyright (C) 2005 the VideoLAN team
* $Id$
*
* Authors: Jon Lech Johansen <jon@nanocrew.net>
*
* 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, USA.
*****************************************************************************/
/*****************************************************************************
* Preamble
*****************************************************************************/
#include <stdlib.h>
#include <vlc/vlc.h>
#ifdef HAVE_AVAHI_CLIENT
#include <vlc/intf.h>
#include <vlc/sout.h>
#include <avahi-client/client.h>
#include <avahi-common/alternative.h>
#include <avahi-common/simple-watch.h>
#include <avahi-common/malloc.h>
#include <avahi-common/error.h>
/*****************************************************************************
* Structures
*****************************************************************************/
typedef struct poll_thread_t
{
VLC_COMMON_MEMBERS
AvahiSimplePoll *simple_poll;
} poll_thread_t;
typedef struct bonjour_t
{
vlc_object_t *p_log;
poll_thread_t *poll_thread;
AvahiSimplePoll *simple_poll;
AvahiEntryGroup *group;
AvahiClient *client;
char *psz_stype;
char *psz_name;
int i_port;
char *psz_txt;
} bonjour_t;
/*****************************************************************************
* Prototypes
*****************************************************************************/
static int create_service( bonjour_t * );
/*****************************************************************************
* entry_group_callback
*****************************************************************************/
static void entry_group_callback( AvahiEntryGroup *g,
AvahiEntryGroupState state,
void *userdata )
{
bonjour_t *p_sys = (bonjour_t *)userdata;
if( state == AVAHI_ENTRY_GROUP_ESTABLISHED )
{
msg_Dbg( p_sys->p_log, "service '%s' successfully established",
p_sys->psz_name );
}
else if( state == AVAHI_ENTRY_GROUP_COLLISION )
{
char *n;
n = avahi_alternative_service_name( p_sys->psz_name );
avahi_free( p_sys->psz_name );
p_sys->psz_name = n;
create_service( p_sys );
}
}
/*****************************************************************************
* create_service
*****************************************************************************/
static int create_service( bonjour_t *p_sys )
{
int error;
if( p_sys->group == NULL )
{
p_sys->group = avahi_entry_group_new( p_sys->client,
entry_group_callback,
p_sys );
if( p_sys->group == NULL )
{
msg_Err( p_sys->p_log, "failed to create avahi entry group: %s",
avahi_strerror( avahi_client_errno( p_sys->client ) ) );
return VLC_EGENERIC;
}
}
error = avahi_entry_group_add_service( p_sys->group, AVAHI_IF_UNSPEC,
AVAHI_PROTO_UNSPEC, p_sys->psz_name,
p_sys->psz_stype, NULL, NULL,
p_sys->i_port,
p_sys->psz_txt, NULL );
if( error < 0 )
{
msg_Err( p_sys->p_log, "failed to add %s service: %s",
p_sys->psz_stype, avahi_strerror( error ) );
return VLC_EGENERIC;
}
error = avahi_entry_group_commit( p_sys->group );
if( error < 0 )
{
msg_Err( p_sys->p_log, "failed to commit entry group: %s",
avahi_strerror( error ) );
return VLC_EGENERIC;
}
return VLC_SUCCESS;
}
/*****************************************************************************
* client_callback
*****************************************************************************/
static void client_callback( AvahiClient *c,
AvahiClientState state,
void * userdata )
{
bonjour_t *p_sys = (bonjour_t *)userdata;
if( state == AVAHI_CLIENT_S_RUNNING )
{
p_sys->client = c;
create_service( p_sys );
}
else if( state == AVAHI_CLIENT_S_COLLISION )
{
if( p_sys->group != NULL )
avahi_entry_group_reset( p_sys->group );
}
else if( state == AVAHI_CLIENT_DISCONNECTED )
{
msg_Err( p_sys->p_log, "avahi client disconnected" );
avahi_simple_poll_quit( p_sys->simple_poll );
}
}
/*****************************************************************************
* poll_iterate_thread
*****************************************************************************/
static void poll_iterate_thread( poll_thread_t *p_pt )
{
vlc_thread_ready( p_pt );
while( !p_pt->b_die )
if( avahi_simple_poll_iterate( p_pt->simple_poll, 100 ) != 0 )
break;
}
/*****************************************************************************
* bonjour_start_service
*****************************************************************************/
void *bonjour_start_service( vlc_object_t *p_log, char *psz_stype,
char *psz_name, int i_port, char *psz_txt )
{
int err;
bonjour_t *p_sys;
p_sys = (bonjour_t *)malloc( sizeof(*p_sys) );
if( p_sys == NULL )
{
msg_Err( p_log, "out of memory" );
return NULL;
}
memset( p_sys, 0, sizeof(*p_sys) );
p_sys->p_log = p_log;
p_sys->i_port = i_port;
p_sys->psz_name = avahi_strdup( psz_name );
p_sys->psz_stype = avahi_strdup( psz_stype );
if( p_sys->psz_name == NULL || p_sys->psz_stype == NULL )
{
msg_Err( p_sys->p_log, "out of memory" );
goto error;
}
if( psz_txt != NULL )
{
p_sys->psz_txt = avahi_strdup( psz_txt );
if( p_sys->psz_txt == NULL )
{
msg_Err( p_sys->p_log, "out of memory" );
goto error;
}
}
p_sys->simple_poll = avahi_simple_poll_new();
if( p_sys->simple_poll == NULL )
{
msg_Err( p_sys->p_log, "failed to create avahi simple pool" );
goto error;
}
p_sys->client = avahi_client_new( avahi_simple_poll_get(p_sys->simple_poll),
client_callback, p_sys, &err );
if( p_sys->client == NULL )
{
msg_Err( p_sys->p_log, "failed to create avahi client: %s",
avahi_strerror( err ) );
goto error;
}
p_sys->poll_thread = vlc_object_create( p_sys->p_log,
sizeof(poll_thread_t) );
if( p_sys->poll_thread == NULL )
{
msg_Err( p_sys->p_log, "out of memory" );
goto error;
}
p_sys->poll_thread->simple_poll = p_sys->simple_poll;
if( vlc_thread_create( p_sys->poll_thread, "Avahi Poll Iterate Thread",
poll_iterate_thread,
VLC_THREAD_PRIORITY_HIGHEST, VLC_FALSE ) )
{
msg_Err( p_sys->p_log, "failed to create poll iterate thread" );
goto error;
}
return (void *)p_sys;
error:
if( p_sys->poll_thread != NULL )
vlc_object_destroy( p_sys->poll_thread );
if( p_sys->client != NULL )
avahi_client_free( p_sys->client );
if( p_sys->simple_poll != NULL )
avahi_simple_poll_free( p_sys->simple_poll );
if( p_sys->psz_stype != NULL )
avahi_free( p_sys->psz_stype );
if( p_sys->psz_name != NULL )
avahi_free( p_sys->psz_name );
if( p_sys->psz_txt != NULL )
avahi_free( p_sys->psz_txt );
free( (void *)p_sys );
return NULL;
}
/*****************************************************************************
* bonjour_stop_service
*****************************************************************************/
void bonjour_stop_service( void *_p_sys )
{
bonjour_t *p_sys = (bonjour_t *)_p_sys;
if( p_sys->poll_thread->b_thread )
{
p_sys->poll_thread->b_die = 1;
vlc_thread_join( p_sys->poll_thread );
}
vlc_object_destroy( p_sys->poll_thread );
if( p_sys->group != NULL )
avahi_entry_group_free( p_sys->group );
avahi_client_free( p_sys->client );
avahi_simple_poll_free( p_sys->simple_poll );
if( p_sys->psz_name != NULL )
avahi_free( p_sys->psz_name );
if( p_sys->psz_txt != NULL )
avahi_free( p_sys->psz_txt );
avahi_free( p_sys->psz_stype );
free( _p_sys );
}
#endif /* HAVE_AVAHI_CLIENT */
/*****************************************************************************
* bonjour.h
*****************************************************************************
* Copyright (C) 2005 the VideoLAN team
* $Id$
*
* Authors: Jon Lech Johansen <jon@nanocrew.net>
*
* 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, USA.
*****************************************************************************/
void *bonjour_start_service( vlc_object_t *, char*, char *, int, char * );
void bonjour_stop_service( void * );
/*****************************************************************************
* http.c
*****************************************************************************
* Copyright (C) 2001-2003 the VideoLAN team
* Copyright (C) 2001-2005 the VideoLAN team
* $Id$
*
* Authors: Laurent Aimar <fenrir@via.ecp.fr>
* Jon Lech Johansen <jon@nanocrew.net>
*
* 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
......@@ -29,6 +30,18 @@
#include <vlc/vlc.h>
#include <vlc/sout.h>
#ifdef HAVE_AVAHI_CLIENT
#include <vlc/intf.h>
#include "bonjour.h"
#if defined( WIN32 )
#define DIRECTORY_SEPARATOR '\\'
#else
#define DIRECTORY_SEPARATOR '/'
#endif
#endif
#include "vlc_httpd.h"
#define FREE( p ) if( p ) { free( p); (p) = NULL; }
......@@ -120,6 +133,10 @@ struct sout_access_out_sys_t
int i_header_size;
uint8_t *p_header;
vlc_bool_t b_header_complete;
#ifdef HAVE_AVAHI_CLIENT
void *p_bonjour;
#endif
};
/*****************************************************************************
......@@ -142,6 +159,11 @@ static int Open( vlc_object_t *p_this )
*psz_crl = NULL;
vlc_value_t val;
#ifdef HAVE_AVAHI_CLIENT
playlist_t *p_playlist;
char *psz_txt;
#endif
if( !( p_sys = p_access->p_sys =
malloc( sizeof( sout_access_out_sys_t ) ) ) )
{
......@@ -271,9 +293,45 @@ static int Open( vlc_object_t *p_this )
return VLC_EGENERIC;
}
#ifdef HAVE_AVAHI_CLIENT
asprintf( &psz_txt, "path=%s", psz_file_name );
#endif
free( psz_file_name );
free( psz_name );
#ifdef HAVE_AVAHI_CLIENT
p_playlist = (playlist_t *)vlc_object_find( p_access, VLC_OBJECT_PLAYLIST,
FIND_ANYWHERE );
if( p_playlist == NULL )
{
msg_Err( p_access, "unable to find playlist" );
httpd_HostDelete( p_sys->p_httpd_host );
free( (void *)psz_txt );
free( (void *)p_sys );
return VLC_EGENERIC;
}
psz_name = strrchr( p_playlist->status.p_item->input.psz_uri,
DIRECTORY_SEPARATOR );
if( psz_name != NULL ) psz_name++;
else psz_name = p_playlist->status.p_item->input.psz_uri;
p_sys->p_bonjour = bonjour_start_service( (vlc_object_t *)p_access,
"_vlc-http._tcp",
psz_name, i_bind_port, psz_txt );
free( (void *)psz_txt );
if( p_sys->p_bonjour == NULL )
{
vlc_object_release( p_playlist );
httpd_HostDelete( p_sys->p_httpd_host );
free( (void *)p_sys );
return VLC_EGENERIC;
}
vlc_object_release( p_playlist );
#endif
p_sys->i_header_allocated = 1024;
p_sys->i_header_size = 0;
p_sys->p_header = malloc( p_sys->i_header_allocated );
......@@ -297,6 +355,10 @@ static void Close( vlc_object_t * p_this )
sout_access_out_t *p_access = (sout_access_out_t*)p_this;
sout_access_out_sys_t *p_sys = p_access->p_sys;
#ifdef HAVE_AVAHI_CLIENT
bonjour_stop_service( p_sys->p_bonjour );
#endif
/* update p_sout->i_out_pace_nocontrol */
p_access->p_sout->i_out_pace_nocontrol--;
......
......@@ -3,3 +3,4 @@ SOURCES_hal = hal.c
SOURCES_daap = daap.c
SOURCES_shout = shout.c
SOURCES_upnp = upnp.cpp
SOURCES_bonjour = bonjour.c
/*****************************************************************************
* bonjour.c: Bonjour services discovery module
*****************************************************************************
* Copyright (C) 2005 the VideoLAN team
* $Id$
*
* Authors: Jon Lech Johansen <jon@nanocrew.net>
*
* 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, USA.
*****************************************************************************/
/*****************************************************************************
* Includes
*****************************************************************************/
#include <stdlib.h> /* malloc(), free() */
#include <vlc/vlc.h>
#include <vlc/intf.h>
#include <avahi-client/client.h>
#include <avahi-common/simple-watch.h>
#include <avahi-common/malloc.h>
#include <avahi-common/error.h>
/*****************************************************************************
* Module descriptor
*****************************************************************************/
/* Callbacks */
static int Open ( vlc_object_t * );
static void Close( vlc_object_t * );
vlc_module_begin();
set_shortname( "Bonjour" );
set_description( _("Bonjour services") );
set_category( CAT_PLAYLIST );
set_subcategory( SUBCAT_PLAYLIST_SD );
set_capability( "services_discovery", 0 );
set_callbacks( Open, Close );
vlc_module_end();
/*****************************************************************************
* Local structures
*****************************************************************************/
struct services_discovery_sys_t
{
/* playlist node */
playlist_item_t *p_node;
playlist_t *p_playlist;
AvahiSimplePoll *simple_poll;
AvahiClient *client;
AvahiServiceBrowser *sb;
};
/*****************************************************************************
* Local prototypes
*****************************************************************************/
/* Main functions */
static void Run ( services_discovery_t *p_intf );
/*****************************************************************************
* client_callback
*****************************************************************************/
static void client_callback( AvahiClient *c, AvahiClientState state,
void * userdata )
{
services_discovery_t *p_sd = ( services_discovery_t* )userdata;
services_discovery_sys_t *p_sys = p_sd->p_sys;
if( state == AVAHI_CLIENT_DISCONNECTED )
{
msg_Err( p_sd, "avahi client disconnected" );
avahi_simple_poll_quit( p_sys->simple_poll );
}
}
/*****************************************************************************
* resolve_callback
*****************************************************************************/
static void resolve_callback(
AvahiServiceResolver *r,
AvahiIfIndex interface,
AvahiProtocol protocol,
AvahiResolverEvent event,
const char *name,
const char *type,
const char *domain,
const char *host_name,
const AvahiAddress *address,
uint16_t port,
AvahiStringList *txt,
void* userdata )
{
services_discovery_t *p_sd = ( services_discovery_t* )userdata;
services_discovery_sys_t *p_sys = p_sd->p_sys;
if( event == AVAHI_RESOLVER_TIMEOUT )
{
msg_Err( p_sd,
"failed to resolve service '%s' of type '%s' in domain '%s'",
name, type, domain );
}
else if( event == AVAHI_RESOLVER_FOUND )
{
char a[128];
char *psz_uri = NULL;
AvahiStringList *asl;
playlist_item_t *p_item = NULL;
msg_Dbg( p_sd, "service '%s' of type '%s' in domain '%s'",
name, type, domain );
avahi_address_snprint(a, (sizeof(a)/sizeof(a[0]))-1, address);
asl = avahi_string_list_find( txt, "path" );
if( asl != NULL )
{
size_t size;
char *key = NULL;
char *value = NULL;
if( avahi_string_list_get_pair( asl, &key, &value, &size ) == 0 )
asprintf( &psz_uri, "http://%s:%d%s", a, port, value );
}
else
{
asprintf( &psz_uri, "http://%s:%d", a, port );
}
if( psz_uri != NULL )
p_item = playlist_ItemNew( p_sd, psz_uri, name );
if( p_item != NULL )
{
p_item->i_flags &= ~PLAYLIST_SKIP_FLAG;
playlist_NodeAddItem( p_sys->p_playlist, p_item,
VIEW_CATEGORY, p_sys->p_node,
PLAYLIST_APPEND, PLAYLIST_END );
}
}
avahi_service_resolver_free( r );
}
/*****************************************************************************
* browser_callback
*****************************************************************************/
static void browse_callback(
AvahiServiceBrowser *b,
AvahiIfIndex interface,
AvahiProtocol protocol,
AvahiBrowserEvent event,
const char *name,
const char *type,
const char *domain,
void* userdata )
{
services_discovery_t *p_sd = ( services_discovery_t* )userdata;
services_discovery_sys_t *p_sys = p_sd->p_sys;
if( event == AVAHI_BROWSER_NEW )
{
if( avahi_service_resolver_new( p_sys->client, interface, protocol,
name, type, domain, AVAHI_PROTO_UNSPEC,
resolve_callback, userdata ) == NULL )
{
msg_Err( p_sd, "failed to resolve service '%s': %s", name,
avahi_strerror( avahi_client_errno( p_sys->client ) ) );
}
}
else
{
playlist_item_t *p_item;
p_item = playlist_ChildSearchName( p_sys->p_node, name );
if( p_item == NULL )
{
msg_Err( p_sd, "failed to find service '%s' in playlist", name );
}
else
{
playlist_Delete( p_sys->p_playlist, p_item->input.i_id );
}
}
}
/*****************************************************************************
* Open: initialize and create stuff
*****************************************************************************/
static int Open( vlc_object_t *p_this )
{
services_discovery_t *p_sd = ( services_discovery_t* )p_this;
services_discovery_sys_t *p_sys;
playlist_view_t *p_view;
vlc_value_t val;
int err;
p_sd->p_sys = p_sys = (services_discovery_sys_t *)malloc(
sizeof( services_discovery_sys_t ) );
if( p_sd->p_sys == NULL )
{
msg_Err( p_sd, "out of memory" );
return VLC_EGENERIC;
}
memset( p_sys, 0, sizeof(*p_sys) );
p_sys->simple_poll = avahi_simple_poll_new();
if( p_sys->simple_poll == NULL )
{
msg_Err( p_sd, "failed to create avahi simple poll" );
goto error;
}
p_sys->client = avahi_client_new( avahi_simple_poll_get(p_sys->simple_poll),
client_callback, p_sd, &err );
if( p_sys->client == NULL )
{
msg_Err( p_sd, "failed to create avahi client: %s",
avahi_strerror( err ) );
goto error;
}
p_sys->sb = avahi_service_browser_new( p_sys->client, AVAHI_IF_UNSPEC,
AVAHI_PROTO_UNSPEC,
"_vlc-http._tcp", NULL,
browse_callback, p_sd );
if( p_sys->sb == NULL )
{
msg_Err( p_sd, "failed to create avahi service browser" );
goto error;
}
/* Create our playlist node */
p_sys->p_playlist = (playlist_t *)vlc_object_find( p_sd,
VLC_OBJECT_PLAYLIST,
FIND_ANYWHERE );
if( !p_sys->p_playlist )
{
msg_Warn( p_sd, "unable to find playlist, cancelling");
goto error;
}
p_view = playlist_ViewFind( p_sys->p_playlist, VIEW_CATEGORY );
p_sys->p_node = playlist_NodeCreate( p_sys->p_playlist, VIEW_CATEGORY,
_("Bonjour"), p_view->p_root );