[telepathy-ashes/master] Added pseudo media echo by reflecting candidates.

David Laban david.laban at collabora.co.uk
Wed Sep 30 06:39:37 PDT 2009


Basically send the user back their own transport candidates and codecs.
This works only on naive RTP implementations like gtalk and n810.
For some reason, this makes farsight busy-loop, when it should just
recognise looped back packets and fail quickly. In conclusion, a nice
exercise, and uncovered a few bugs, but not useful as a public echo
service.
---
 ashes/tools/bases.py        |   17 ++++-
 ashes/tools/echo_bot.py     |    5 +-
 ashes/tools/media_echoer.py |  150 ++++++++++++++++++++++++++++++++++++++++++-
 3 files changed, 164 insertions(+), 8 deletions(-)

diff --git a/ashes/tools/bases.py b/ashes/tools/bases.py
index 8a6cd46..851997b 100644
--- a/ashes/tools/bases.py
+++ b/ashes/tools/bases.py
@@ -13,6 +13,9 @@ class ObjectListener(object):
     A base class for anything that listens to remote objects
     This implements __eq__ because dbus.Object doesn't do so.
     """
+    # TODO: create a function to get child objects on the same service,
+    # as this is a common pattern.
+    # Also, something to turn this into an InterfaceFactory.
     def __init__(self, dbus_object):
         self.dbus_object = dbus_object
         self.service_name = dbus_object.service_name
@@ -43,8 +46,9 @@ class ObjectListener(object):
         """
         #FIXME: port to dbus.Objets rather than ifacefactories.
         iface = self.dbus_object[iface_name]
-        cb = getattr(self, signal_name,
-                        self._make_printer(iface_name, signal_name))
+        cb = getattr(self, signal_name, None)
+        if not cb:
+            cb = self._make_printer(iface_name, signal_name)
         iface.connect_to_signal(signal_name, cb)
 
     def _make_printer(self, iface_name, function_name):
@@ -59,6 +63,7 @@ class ObjectListener(object):
                     ', '.join('%s=%s' % (k, v) for (k, v) in kwargs.items()))
         return printer
 
+
 class ConnectionListener(ObjectListener):
     """
     Listens to a connection, and prints unhandled events.
@@ -170,12 +175,16 @@ class ChannelListener(ObjectListener):
             ['Closed'],
         'org.freedesktop.Telepathy.Channel.Interface.CallState':
             ['CallStateChanged'],
-        'org.freedesktop.Telepathy.Channel.Interface.MediaSignalling':
-            ['NewSessionHandler'],
         'org.freedesktop.Telepathy.Properties':
             ['PropertiesChanged', 'PropertyFlagsChanged'],
         'org.freedesktop.Telepathy.Channel.Interface.ChatState':
             ['ChatStateChanged'],
+        # I'm not quite sure why this signal is emitted, if gabble
+        # StreamedMedia channels are their own SessionHandlers.
+        'org.freedesktop.Telepathy.Channel.Interface.MediaSignalling':
+            ['NewSessionHandler'],
+        # This isn't actually a type of channel, but gabble StreamedMedia
+        # channels implement this interface to make things simpler.
         'org.freedesktop.Telepathy.Media.SessionHandler':
             ['NewStreamHandler'],
         'org.freedesktop.Telepathy.Channel.Interface.Group':
diff --git a/ashes/tools/echo_bot.py b/ashes/tools/echo_bot.py
index a80f9e6..086c6b3 100644
--- a/ashes/tools/echo_bot.py
+++ b/ashes/tools/echo_bot.py
@@ -15,7 +15,7 @@ from account import connection_from_file
 
 from bases import ObjectListener, ConnectionListener, ChannelListener
 from dispatchers import ChannelDispatcher, connections
-from media_echoer import MediaChannelEchoer
+from media_echoer import MediaChannelEchoer, IceEchoer
 from presence import PresenceEchoer, Onlineifier
 from groups import ContactAcceptor
 from text import TextChannelEchoer
@@ -37,12 +37,13 @@ class EchoBot(  PresenceEchoer,
 
 # FIXME: this should probably be done on echo bot somehow.
 # In case multiple things inheret from ChannelDispatcher.
+# This mess is because I originally did it with class decorators (which don't work in py2.5)
 ChannelDispatcher.handler_for('org.freedesktop.Telepathy.Channel.Type.ContactList',
                                 telepathy.HANDLE_TYPE_LIST)(ContactAcceptor)
 ChannelDispatcher.handler_for('org.freedesktop.Telepathy.Channel.Type.Text',
                                 telepathy.HANDLE_TYPE_CONTACT)(TextChannelEchoer)
 ChannelDispatcher.handler_for('org.freedesktop.Telepathy.Channel.Type.StreamedMedia',
-                                telepathy.HANDLE_TYPE_CONTACT)(MediaChannelEchoer)
+                                telepathy.HANDLE_TYPE_CONTACT)(IceEchoer)
 
 
 
diff --git a/ashes/tools/media_echoer.py b/ashes/tools/media_echoer.py
index ef1a9a3..57d0967 100644
--- a/ashes/tools/media_echoer.py
+++ b/ashes/tools/media_echoer.py
@@ -5,17 +5,20 @@
 # and tpfarsight install to /usr/lib/python2.6/site-packages
 import sys
 
+import dbus
 sys.path.append('/usr/lib/python2.6/site-packages')
 import tpfarsight
 import farsight
 import gst
 import telepathy
 
-from bases import ChannelListener
+from bases import ChannelListener, ObjectListener
 from groups import ContactAcceptor
 from helper_functions import (get_connections, get_property,
         green, red, printer, rpartial)
 
+    
+
 class MediaChannelEchoer(ContactAcceptor):
     """
     Listens to a media channel and echoes everything it hears/sees.
@@ -177,4 +180,147 @@ class MediaChannelEchoer(ContactAcceptor):
     #def stream_created(self, channel, stream):
     #    print green('stream_created(%s)' % ', '.join(map(str, [channel, stream])))
     #def stream_get_codec_config(self, *args):
-    #    print green('stream-get-codec-config(%s)' % ', '.join(map(str, args)))
\ No newline at end of file
+    #    print green('stream-get-codec-config(%s)' % ', '.join(map(str, args)))
+
+
+
+
+
+class IceEchoer(ContactAcceptor):
+    """
+    Avoids using farsight, and just does hackery with signals to make remote
+    clients talk to themselves.
+    """
+    def __init__(self, connection, channel, properties):
+        self.session_handlers = []
+        self.stream_handlers = {}
+        ContactAcceptor.__init__(self, connection, channel, properties)
+        print green('Media Channel Handled.')
+        channel["org.freedesktop.Telepathy.Channel.Interface.MediaSignalling"
+            ].GetSessionHandlers(reply_handler=self.NewSessionHandlers,
+                error_handler=printer)
+
+    def NewSessionHandlers(self, handlers):
+        """Handles the initial reply from GetSessionHandlers()"""
+        for object_path, session_type in handlers:
+            self.NewSessionHandler(object_path, session_type)
+
+    def NewSessionHandler(self, object_path, session_type):
+        """
+        Handles org.freedesktop.Telepathy.Channel.Interface.MediaSignalling.
+        NewSessionHandler()
+        """
+        if object_path == self.dbus_object.object_path:
+            print "Got the session handler on this object."
+            self.channel["org.freedesktop.Telepathy.Media.SessionHandler"].Ready()
+        else:
+            print "Got a new session handler"
+            service_name = self.dbus_object.service_name
+            obj = dbus.Bus().get_object(service_name, object_path)
+            session_handler = telepathy.client.InterfaceFactory(obj,
+                "org.freedesktop.Telepathy.Media.SessionHandler")
+            mirror = SessionMirror(session_handler, media_type, direction)
+            self.session_handlers.append(mirror)
+
+    def NewStreamHandler(self, object_path, id, media_type, direction):
+        """
+        Handles org.freedesktop.Telepathy.Media.SessionHandler.NewStreamHandler
+        """
+        print green("New Stream Handler"), object_path
+        service_name = self.dbus_object.service_name
+        obj = dbus.Bus().get_object(service_name, object_path)
+        stream_handler = telepathy.client.InterfaceFactory(obj,
+                "org.freedesktop.Telepathy.Media.StreamHandler")
+        mirror = IceMirrorStreamHandler(stream_handler, id, media_type, direction)
+        self.stream_handlers[id] = mirror
+
+    def StreamDirectionChanged(self, id, direction, flags):
+        """
+        
+        """
+        print "Stream", id, "is now", ["idle", "sending", "receiving", "bidirectional"][direction]
+        
+
+    def StreamAdded(self, id, handle, type):
+        print "Stream Added: id %s with contact %s of type %s" % (
+            id, handle, ['audio', 'video'][type])
+        
+class SessionMirror(ObjectListener):
+    """For listening to SessionHandler objects."""
+    def __init__(self, session_handler, media_type, direction):
+        pass
+
+class StreamHandlerListener(ObjectListener):
+    """For listening to StreamHandler objects"""
+
+    _signal_names = {
+        "org.freedesktop.Telepathy.Media.StreamHandler":
+            ["AddRemoteCandidate", "Close", "RemoveRemoteCandidate",
+            "SetActiveCandidatePair", "SetRemoteCandidateList",
+            "SetRemoteCodecs", "SetStreamPlaying", "SetStreamSending",
+            "StartTelephonyEvent", "StopTelephonyEvent", "SetStreamHeld"],
+            }
+    def __init__(self, stream_handler, id, media_type, direction):
+        ObjectListener.__init__(self, stream_handler)
+        self._register_callbacks()
+        # This probably should go in the base class but hush.
+        # Also, we could send codecs in here, but we're not.
+        self.dbus_object.Ready([])
+
+    def _register_callbacks(self):
+        """
+        Registers callbacks for signals, or assigns printers as callbacks
+        in the usual way.
+        #TODO: push this into some kind of base class.
+        """
+        for interface_name in self.dbus_object.get_valid_interfaces():
+            if interface_name.startswith('org.freedesktop.Telepathy'):
+                signal_names = self._signal_names[interface_name]
+                for signal_name in signal_names:
+                    self.connect_to_signal(interface_name, signal_name)
+
+class IceMirrorStreamHandler(StreamHandlerListener):
+    """
+    This does magic with media signalling to make remote clients talk to
+    themselves.
+    """
+    # Hopefully we can just use __init__ from StreamHandlerListener.
+    def __init__(self, *args):
+        StreamHandlerListener.__init__(self, *args)
+        local_candidates = {}
+        remote_candidates = {}
+        self.codecs = []
+        self.dbus_object.StreamState(1) # Connecting
+
+    def SetRemoteCodecs(self, codecs):
+        """Signal handler. Don't be fooled by the name."""
+        print "SetRemoteCodecs received:", codecs
+        self.codecs = codecs
+        self.dbus_object.SetLocalCodecs(self.codecs)
+        print "SetLocalCodecs sent"
+        self.dbus_object.SupportedCodecs(self.codecs)
+        print "SupportedCodecs sent"
+
+    def AddRemoteCandidate(self, id, transports):
+        """Signal handler. Don't be fooled by the name."""
+        print "AddRemoteCandidate received:", id, transports
+        new_id = 'F'+id
+        self.dbus_object.NewNativeCandidate(new_id, transports)
+        print "NewNativeCandidate sent:", new_id, transports
+        self.dbus_object.NativeCandidatesPrepared()
+        print "NativeCandidatesPrepared sent"
+        # Can't hurt to say that all candidates are active, right? :P
+        self.dbus_object.NewActiveCandidatePair(new_id, id)
+        print "NewActiveCandidatePair sent:", new_id, id
+        self.dbus_object.StreamState(2) #Connected
+
+    def SetStreamPlaying(self, playing):
+        """Signal handler. Don't be fooled by the name."""
+        if playing:
+            print "Playing. Expect to receive some data"
+        else:
+            pass # Signal is meaningless
+
+    def SetStreamSending(self, sending):
+        """Signal handler. Don't be fooled by the name."""
+        print "SetStreamSending received:", sending
-- 
1.5.6.5




More information about the telepathy-commits mailing list