]> git.sesse.net Git - vlc/commitdiff
* Bonjour services discovery module using avahi.
authorJon Lech Johansen <jlj@videolan.org>
Fri, 9 Sep 2005 02:42:40 +0000 (02:42 +0000)
committerJon Lech Johansen <jlj@videolan.org>
Fri, 9 Sep 2005 02:42:40 +0000 (02:42 +0000)
configure.ac
modules/access_output/Modules.am
modules/access_output/bonjour.c [new file with mode: 0644]
modules/access_output/bonjour.h [new file with mode: 0644]
modules/access_output/http.c
modules/services_discovery/Modules.am
modules/services_discovery/bonjour.c [new file with mode: 0644]

index c66a647b14476f63a427828a80350927cfc83181..71197e82cb17a3130111a419a8a9fe9ceef9cf95 100644 (file)
@@ -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
index c9581a4e2c8e042c75118438fc84ae4179f37eea..5ea101a7368b0e33b25c99716d98a2788c3a4c76 100644 (file)
@@ -1,5 +1,5 @@
 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
diff --git a/modules/access_output/bonjour.c b/modules/access_output/bonjour.c
new file mode 100644 (file)
index 0000000..40374ab
--- /dev/null
@@ -0,0 +1,302 @@
+/*****************************************************************************
+ * 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 */
diff --git a/modules/access_output/bonjour.h b/modules/access_output/bonjour.h
new file mode 100644 (file)
index 0000000..79f9158
--- /dev/null
@@ -0,0 +1,25 @@
+/*****************************************************************************
+ * 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 * );
index 32ba5c1b22b6b178ba0e2e6e22fc5eaef314e006..e2cc24aa0f4141c9a64e35b66735d1a709c9a499 100644 (file)
@@ -1,10 +1,11 @@
 /*****************************************************************************
  * 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
 #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--;
 
index 4eca0fd43b462a7ac33b4323a31bd6ad942ed0c2..172e2f2b969927f9d1ef1fec01998c97111e10a3 100644 (file)
@@ -3,3 +3,4 @@ SOURCES_hal = hal.c
 SOURCES_daap = daap.c
 SOURCES_shout = shout.c
 SOURCES_upnp = upnp.cpp
+SOURCES_bonjour = bonjour.c
diff --git a/modules/services_discovery/bonjour.c b/modules/services_discovery/bonjour.c
new file mode 100644 (file)
index 0000000..5475008
--- /dev/null
@@ -0,0 +1,318 @@
+/*****************************************************************************
+ * 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 );
+
+    p_sys->p_node->i_flags |= PLAYLIST_RO_FLAG;
+    val.b_bool = VLC_TRUE;
+    var_Set( p_sys->p_playlist, "intf-change", val );
+
+    p_sd->pf_run = Run;
+
+    return VLC_SUCCESS;
+
+error:
+    if( p_sys->p_playlist != NULL )
+        vlc_object_release( p_sys->p_playlist );
+    if( p_sys->sb != NULL )
+        avahi_service_browser_free( p_sys->sb );
+    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 );
+
+    free( (void *)p_sys );
+
+    return VLC_EGENERIC;
+}
+
+/*****************************************************************************
+ * Close: cleanup
+ *****************************************************************************/
+static void Close( vlc_object_t *p_this )
+{
+    services_discovery_t *p_sd = ( services_discovery_t* )p_this;
+    services_discovery_sys_t *p_sys = p_sd->p_sys;
+
+    avahi_service_browser_free( p_sys->sb );
+    avahi_client_free( p_sys->client );
+    avahi_simple_poll_free( p_sys->simple_poll );
+
+    playlist_NodeDelete( p_sys->p_playlist, p_sys->p_node, VLC_TRUE, VLC_TRUE );
+    vlc_object_release( p_sys->p_playlist );
+
+    free( p_sys );
+}
+
+/*****************************************************************************
+ * Run: main thread
+ *****************************************************************************/
+static void Run( services_discovery_t *p_sd )
+{
+    services_discovery_sys_t *p_sys = p_sd->p_sys;
+
+    while( !p_sd->b_die )
+    {
+        if( avahi_simple_poll_iterate( p_sys->simple_poll, 100 ) != 0 )
+        {
+            msg_Err( p_sd, "poll iterate failed" );
+            break;
+        }
+    }
+}