Bleeding edge UPnP service discovery :
authorRémi Denis-Courmont <rem@videolan.org>
Sun, 17 Jul 2005 09:04:58 +0000 (09:04 +0000)
committerRémi Denis-Courmont <rem@videolan.org>
Sun, 17 Jul 2005 09:04:58 +0000 (09:04 +0000)
 * depends on CyberLink C++ UPnP library available from :
   http://sourceforge.net/project/showfiles.php?group_id=89768
   with iconv compilation patch from :
   http://jserv.sayya.org/upnp/clinkcc170-fix-compilation.diff
 * requires libexpat-devel or whatever you call it,
 * needs review by playlist guru (Zorglub ?),
   especially as regards locking
 * at least, detects UPnP Media server properly
 * I'm looking for a '''working''' UPnP Media Server on Linux for more
   extensive tests. I couldn't get TwonkyVision to accept my medias :(
 * tentatively closes #98 - and probably introduce new bugs

configure.ac
modules/services_discovery/Modules.am
modules/services_discovery/upnp.cpp [new file with mode: 0644]

index bce00d5..d55b97e 100644 (file)
@@ -3437,6 +3437,50 @@ then
   AC_LANG_POP([C++])
 fi
 
+dnl
+dnl  CyberLink for C++ UPnP stack
+dnl
+AC_ARG_ENABLE(cyberlink,
+  [  --enable-cyberlink      CyberLink for C++ UPnP stack (default disabled)])
+if test "${CXX}" != "" -a "${enable_cyberlink}" = "yes" || (test "${enable_cyberlink}" != "no"); then
+  AC_ARG_WITH(cyberlink-tree,
+    [    --with-cyberlink-tree=PATH CyberLink for C++ tree for static linking])
+
+  dnl
+  dnl test for --with-cyberlink-tree
+  dnl
+  if test ! -z "${with_cyberlink_tree}" -a "${CXX}" != ""; then
+    AC_LANG_PUSH(C++)
+    real_cyberlink_tree="`cd ${with_cyberlink_tree} 2>/dev/null && pwd`"
+    if test -z "${real_cyberlink_tree}"
+    then
+      dnl  The given directory can't be found
+      AC_MSG_RESULT(no)
+      AC_MSG_ERROR([cannot cd to ${with_cyberlink_tree}])
+    fi
+    CXXFLAGS_save="${CXXFLAGS}"
+    CXXFLAGS_cyberlink="-I${real_cyberlink_tree}/include"
+    CXXFLAGS="${CXXFLAGS} ${CXXFLAGS_cyberlink}"
+    AC_CHECK_HEADERS([cybergarage/upnp/MediaServer.h],
+      [ VLC_ADD_CXXFLAGS([upnp], [${CXXFLAGS_cyberlink}])
+        VLC_ADD_PLUGINS([upnp]) 
+      ],[
+        AC_MSG_ERROR([cannot find CyberLink for C++ headers])
+      ])
+    AC_MSG_CHECKING(for libclink.a in ${with_cyberlink_tree})
+    if test -f "${real_cyberlink_tree}/lib/unix/libclink.a"
+    then
+      AC_MSG_RESULT(${real_cyberlink_tree}/lib/unix/libclink.a)
+      VLC_ADD_LDFLAGS([upnp], [${real_cyberlink_tree}/lib/unix/libclink.a -lexpat])
+    else
+      AC_MSG_RESULT(no)
+       AC_MSG_ERROR([cannot find ${real_cyberlink_tree}/lib/unix/libclink.a, make sure you compiled CyberLink for C++ in ${with_cyberlink_tree}])
+    fi
+    CXXFLAGS="${CXXFLAGS_save}"
+    AC_LANG_POP([C++])
+  fi
+fi
+
 dnl
 dnl  Interface plugins
 dnl
index d70bbe0..4eca0fd 100644 (file)
@@ -2,4 +2,4 @@ SOURCES_sap = sap.c
 SOURCES_hal = hal.c
 SOURCES_daap = daap.c
 SOURCES_shout = shout.c
-
+SOURCES_upnp = upnp.cpp
diff --git a/modules/services_discovery/upnp.cpp b/modules/services_discovery/upnp.cpp
new file mode 100644 (file)
index 0000000..5d8c724
--- /dev/null
@@ -0,0 +1,332 @@
+/*****************************************************************************
+ * upnp.cpp :  UPnP discovery module
+ *****************************************************************************
+ * Copyright (C) 2004-2005 the VideoLAN team
+ * $Id: sap.c 11664 2005-07-09 06:17:09Z courmisch $
+ *
+ * Authors: Rémi Denis-Courmont <rem # videolan.org>
+ * 
+ * Based on original wxWindows patch for VLC, and dependant on CyberLink
+ * UPnP library from :
+ *          Satoshi Konno <skonno@cybergarage.org>
+ *
+ * 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 <cybergarage/upnp/media/player/MediaPlayer.h>
+
+#undef PACKAGE_NAME
+#include <vlc/vlc.h>
+#include <vlc/intf.h>
+
+/* FIXME: thread-safety ?? */
+/* FIXME: playlist locking */
+
+/************************************************************************
+ * Macros and definitions
+ ************************************************************************/
+using namespace std;
+using namespace CyberLink;
+
+/*****************************************************************************
+ * Module descriptor
+ *****************************************************************************/
+
+/* Callbacks */
+    static int  Open ( vlc_object_t * );
+    static void Close( vlc_object_t * );
+
+vlc_module_begin();
+    set_shortname( _("UPnP"));
+    set_description( _("Universal Plug'n'Play discovery") );
+    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;
+};
+
+/*****************************************************************************
+ * Local prototypes
+ *****************************************************************************/
+
+/* Main functions */
+    static void Run    ( services_discovery_t *p_sd );
+
+/*****************************************************************************
+ * 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  = (services_discovery_sys_t *)
+                                malloc( sizeof( services_discovery_sys_t ) );
+
+    playlist_view_t     *p_view;
+    vlc_value_t         val;
+
+    p_sd->pf_run = Run;
+    p_sd->p_sys  = p_sys;
+
+    /* 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 UPnP listening");
+        return VLC_EGENERIC;
+    }
+
+    p_view = playlist_ViewFind( p_sys->p_playlist, VIEW_CATEGORY );
+    p_sys->p_node = playlist_NodeCreate( p_sys->p_playlist, VIEW_CATEGORY,
+                                         _("UPnP"), p_view->p_root );
+    p_sys->p_node->i_flags |= PLAYLIST_RO_FLAG;
+    p_sys->p_node->i_flags =~ PLAYLIST_SKIP_FLAG;
+    val.b_bool = VLC_TRUE;
+    var_Set( p_sys->p_playlist, "intf-change", val );
+
+    return VLC_SUCCESS;
+}
+
+
+/*****************************************************************************
+ * Close:
+ *****************************************************************************/
+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;
+
+    if( p_sys->p_playlist )
+    {
+        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 UPnP thread
+ *****************************************************************************
+ * Processes UPnP events
+ *****************************************************************************/
+class UPnPHandler : public MediaPlayer, public DeviceChangeListener,
+                    /*public EventListener,*/ public SearchResponseListener
+{
+    private:
+        services_discovery_t *p_sd;
+        services_discovery_sys_t *p_sys;
+
+        Device *GetDeviceFromUSN( const string& usn )
+        {
+            return getDevice( usn.substr( 0, usn.find( "::" ) ).c_str() );
+        }
+
+        playlist_item_t *FindDeviceNode( Device *dev )
+        {
+            return playlist_ChildSearchName( p_sys->p_node, dev->getFriendlyName() );
+        }
+
+        playlist_item_t *FindDeviceNode( const string &usn )
+        {
+            return FindDeviceNode( GetDeviceFromUSN( usn ) );
+        }
+
+        playlist_item_t *AddDevice( Device *dev );
+        void AddDeviceContent( Device *dev );
+        void AddContent( playlist_item_t *p_parent, ContentNode *node );
+        void RemoveDevice( Device *dev );
+
+        /* CyberLink callbacks */
+        virtual void deviceAdded( Device *dev );
+        virtual void deviceRemoved( Device *dev );
+
+        virtual void deviceSearchResponseReceived( SSDPPacket *packet );
+        /*virtual void eventNotifyReceived( const char *uuid, long seq,
+                                          const char *name,
+                                          const char *value );*/
+
+    public:
+        UPnPHandler( services_discovery_t *p_this )
+            : p_sd( p_this ), p_sys( p_this->p_sys )
+        {
+            addDeviceChangeListener( this );
+            addSearchResponseListener( this );
+            //addEventListener( this );
+        }
+
+};
+
+static void Run( services_discovery_t *p_sd )
+{
+    UPnPHandler u( p_sd );
+
+    u.start();
+
+    msg_Dbg( p_sd, "UPnP discovery started" );
+    /* read SAP packets */
+    while( !p_sd->b_die )
+    {
+        msleep( 500 );
+    }
+
+    u.stop();
+    msg_Dbg( p_sd, "UPnP discovery stopped" );
+}
+
+
+playlist_item_t *UPnPHandler::AddDevice( Device *dev )
+{
+    if( dev == NULL )
+        return NULL;
+
+    /* We are not interested in IGD devices or whatever (at the moment) */
+    if ( !dev->isDeviceType( MediaServer::DEVICE_TYPE ) )
+        return NULL;
+
+    playlist_item_t *p_item = FindDeviceNode( dev );
+    if ( p_item != NULL )
+        return p_item;
+
+    /* FIXME:
+     * Maybe one day, VLC API will make sensible use of the const keyword;
+     * That day, you will no longer need this strdup().
+     */
+    char *str = strdup( dev->getFriendlyName( ) );
+
+    p_item = playlist_NodeCreate( p_sys->p_playlist, VIEW_CATEGORY,
+                                  str, p_sys->p_node );
+    p_item->i_flags =~ PLAYLIST_SKIP_FLAG;
+    msg_Dbg( p_sd, "device %s added", str );
+    free( str );
+
+    return p_item;
+}
+
+void UPnPHandler::AddDeviceContent( Device *dev )
+{
+    playlist_item_t *p_devnode = AddDevice( dev );
+
+    if( p_devnode == NULL )
+        return;
+
+    AddContent( p_devnode, getContentDirectory( dev ) );
+}
+
+void UPnPHandler::AddContent( playlist_item_t *p_parent, ContentNode *node )
+{
+    if( node == NULL )
+        return;
+
+    const char *title = node->getTitle();
+    if( title == NULL )
+        return;
+
+    msg_Dbg( p_sd, "title = %s", title );
+    
+    if( !node->isContainerNode() )
+    {
+        msg_Dbg( p_sd, "not a container" );
+        return;
+    }
+
+    ContainerNode *conNode = (ContainerNode *)node;
+    ItemNode *iNode = (ItemNode *)node;
+
+    playlist_item_t *p_item;
+    p_item = playlist_ItemNew( p_sd, iNode->getResource(), title );
+    playlist_NodeAddItem( p_sys->p_playlist, p_item, VIEW_CATEGORY,
+                          p_parent, PLAYLIST_APPEND, PLAYLIST_END );
+
+    /*if( !cnode->hasContainerNodes() )
+        return;*/
+
+    unsigned nContentNodes = conNode->getNContentNodes();
+
+    for( unsigned n = 0; n < nContentNodes; n++ )
+        AddContent( p_item, conNode->getContentNode( n ) );
+}
+
+
+void UPnPHandler::RemoveDevice( Device *dev )
+{
+    playlist_item_t *p_item = FindDeviceNode( dev );
+
+    if( p_item != NULL )
+        playlist_NodeDelete( p_sys->p_playlist, p_item, VLC_TRUE, VLC_TRUE );
+}
+
+
+void UPnPHandler::deviceAdded( Device *dev )
+{
+    msg_Dbg( p_sd, "adding device" );
+    AddDeviceContent( dev );
+}
+
+
+void UPnPHandler::deviceRemoved( Device *dev )
+{
+    msg_Dbg( p_sd, "removing device" );
+    RemoveDevice( dev );
+}
+
+
+void UPnPHandler::deviceSearchResponseReceived( SSDPPacket *packet )
+{
+    if( !packet->isRootDevice() )
+        return;
+
+    string usn, nts, nt, udn;
+
+    packet->getUSN( usn );
+    packet->getNT( nt );
+    packet->getNTS( nts );
+    udn = usn.substr( 0, usn.find( "::" ) );
+
+    Device *dev = GetDeviceFromUSN( usn );
+    if( packet->isByeBye() )
+        RemoveDevice( dev );
+    else
+        AddDeviceContent( dev );
+}
+
+/*void UPnPHandler::eventNotifyReceived( const char *uuid, long seq,
+                                       const char *name, const char *value )
+{
+    msg_Dbg( p_sd, "event notify received" );
+    msg_Dbg( p_sd, "uuid = %s, name = %s, value = %s", uuid, name, value );
+}*/