[telepathy-butterfly/master] Added initial support for streamed media channels

Louis-Francis Ratté-Boulianne louis-francis.ratte-boulianne at collabora.co.uk
Thu Sep 10 06:48:09 PDT 2009


Signed-off-by: Louis-Francis Ratté-Boulianne <louis-francis.ratte-boulianne at collabora.co.uk>
---
 butterfly/channel/media.py         |  129 ++++++++++++++++++++++
 butterfly/channel_manager.py       |   25 +++++
 butterfly/connection.py            |   11 ++
 butterfly/media/__init__.py        |   20 ++++
 butterfly/media/session_handler.py |   89 +++++++++++++++
 butterfly/media/stream_handler.py  |  211 ++++++++++++++++++++++++++++++++++++
 butterfly/wscript                  |    2 +-
 7 files changed, 486 insertions(+), 1 deletions(-)
 create mode 100644 butterfly/channel/media.py
 create mode 100644 butterfly/media/__init__.py
 create mode 100644 butterfly/media/session_handler.py
 create mode 100644 butterfly/media/stream_handler.py

diff --git a/butterfly/channel/media.py b/butterfly/channel/media.py
new file mode 100644
index 0000000..c1c2cae
--- /dev/null
+++ b/butterfly/channel/media.py
@@ -0,0 +1,129 @@
+# telepathy-butterfly - an MSN connection manager for Telepathy
+#
+# Copyright (C) 2009 Collabora Ltd.
+#
+# 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
+
+import logging
+import weakref
+import dbus
+
+import telepathy
+import papyon
+import papyon.event
+
+from butterfly.util.decorator import async
+from butterfly.handle import ButterflyHandleFactory
+from butterfly.media import ButterflySessionHandler
+
+__all__ = ['ButterflyMediaChannel']
+
+logger = logging.getLogger('Butterfly.MediaChannel')
+
+
+class ButterflyMediaChannel(
+        telepathy.server.ChannelTypeStreamedMedia,
+        telepathy.server.ChannelInterfaceCallState,
+        telepathy.server.ChannelInterfaceGroup,
+        telepathy.server.ChannelInterfaceMediaSignalling,
+        papyon.event.CallEventInterface,
+        papyon.event.MediaSessionEventInterface):
+
+    def __init__(self, conn, manager, call, handle, props):
+        telepathy.server.ChannelTypeStreamedMedia.__init__(self, conn, manager, props)
+        telepathy.server.ChannelInterfaceCallState.__init__(self)
+        telepathy.server.ChannelInterfaceGroup.__init__(self)
+        telepathy.server.ChannelInterfaceMediaSignalling.__init__(self)
+        papyon.event.CallEventInterface.__init__(self, call)
+        papyon.event.MediaSessionEventInterface.__init__(self, call.media_session)
+
+        self._call = call
+        self._handle = handle
+
+        self._session_handler = ButterflySessionHandler(self._conn, self,
+                call.media_session, handle)
+        self.NewSessionHandler(self._session_handler, self._session_handler.type)
+
+        self.GroupFlagsChanged(telepathy.CHANNEL_GROUP_FLAG_CAN_ADD, 0)
+        self.__add_initial_participants()
+
+    def Close(self):
+        telepathy.server.ChannelTypeStreamedMedia.Close(self)
+        self.remove_from_connection()
+
+    def GetSessionHandlers(self):
+        return [(self._session_handler, self._session_handler.type)]
+
+    def ListStreams(self):
+        print "ListStreams"
+        streams = dbus.Array([], signature="a(uuuuuu)")
+        for handler in self._session_handler.ListStreams():
+            streams.append((handler.id, self._handle, handler.type,
+                handler.state, handler.direction, handler.pending_send))
+        return streams
+
+    def RequestStreams(self, handle, types):
+        print "RequestStreams %r %r %r" % (handle, self._handle, types)
+        if self._handle.get_id() == 0:
+            self._handle = self._conn.handle(telepathy.HANDLE_TYPE_CONTACT, handle)
+
+        streams = dbus.Array([], signature="a(uuuuuu)")
+        for type in types:
+            handler = self._session_handler.CreateStream(type)
+            streams.append((handler.id, self._handle, handler.type,
+                handler.state, handler.direction, handler.pending_send))
+        return streams
+
+    def RequestStreamDirection(self, id, direction):
+        print "RequestStreamDirection %r %r" % (id, direction)
+        self._session_handler.GetStream(id).direction = direction
+
+    def RemoveStreams(self, streams):
+        print "RemoveStreams %r" % streams
+        for id in streams:
+            self._session_handler.RemoveStream(id)
+
+    #papyon.event.call.CallEventInterface
+    def on_call_incoming(self):
+        self._call.accept()
+
+    #papyon.event.call.CallEventInterface
+    def on_call_ringing(self):
+        pass
+
+    #papyon.event.call.CallEventInterface
+    def on_call_accepted(self):
+        pass
+
+    #papyon.event.call.CallEventInterface
+    def on_call_rejected(self):
+        pass
+
+    #papyon.event.call.CallEventInterface
+    def on_call_ended(self):
+        pass
+
+    #papyon.event.media.MediaSessionEventInterface
+    def on_stream_created(self, stream):
+        print "Media Stream created"
+        handler = self._session_handler.AddStream(stream)
+        self.StreamAdded(handler.id, self._handle, handler.type)
+        self.StreamDirectionChanged(handler.id, handler.direction,
+                handler.pending_send)
+
+    @async
+    def __add_initial_participants(self):
+        self.MembersChanged('', [self._handle], [], [], [],
+                0, telepathy.CHANNEL_GROUP_CHANGE_REASON_NONE)
diff --git a/butterfly/channel_manager.py b/butterfly/channel_manager.py
index e8bc8e1..6e9a1e7 100644
--- a/butterfly/channel_manager.py
+++ b/butterfly/channel_manager.py
@@ -26,6 +26,7 @@ import papyon
 from butterfly.channel.contact_list import ButterflyContactListChannelFactory
 from butterfly.channel.group import ButterflyGroupChannel
 from butterfly.channel.text import ButterflyTextChannel
+from butterfly.channel.media import ButterflyMediaChannel
 from butterfly.handle import ButterflyHandleFactory
 
 __all__ = ['ButterflyChannelManager']
@@ -45,6 +46,11 @@ class ButterflyChannelManager(telepathy.server.ChannelManager):
         self._implement_channel_class(telepathy.CHANNEL_TYPE_CONTACT_LIST,
             self._get_list_channel, fixed, [])
 
+        fixed = {telepathy.CHANNEL_INTERFACE + '.ChannelType': telepathy.CHANNEL_TYPE_STREAMED_MEDIA,
+            telepathy.CHANNEL_INTERFACE + '.TargetHandleType': dbus.UInt32(telepathy.HANDLE_TYPE_CONTACT)}
+        self._implement_channel_class(telepathy.CHANNEL_TYPE_STREAMED_MEDIA,
+            self._get_media_channel, fixed, [telepathy.CHANNEL_INTERFACE + '.TargetHandle'])
+
     def _get_list_channel(self, props):
         _, surpress_handler, handle = self._get_type_requested_handle(props)
 
@@ -74,3 +80,22 @@ class ButterflyChannelManager(telepathy.server.ChannelManager):
             conversation = papyon.Conversation(client, [contact])
         channel = ButterflyTextChannel(self._conn, self, conversation, props)
         return channel
+
+    def _get_media_channel(self, props, call=None):
+        _, surpress_handler, handle = self._get_type_requested_handle(props)
+
+        if handle.get_type() != telepathy.HANDLE_TYPE_CONTACT:
+            raise telepathy.NotImplemented('Only contacts are allowed')
+
+        contact = handle.contact
+
+        if contact.presence == papyon.Presence.OFFLINE:
+            raise telepathy.NotAvailable('Contact not available')
+
+        logger.debug('New media channel')
+
+        if call is None:
+            client = self._conn.msn_client
+            call = client.call_manager.create_call(contact)
+
+        return ButterflyMediaChannel(self._conn, self, call, handle, props)
diff --git a/butterfly/connection.py b/butterfly/connection.py
index a0e6402..fe82400 100644
--- a/butterfly/connection.py
+++ b/butterfly/connection.py
@@ -280,6 +280,17 @@ class ButterflyConnection(telepathy.server.Connection,
         channel = self._channel_manager.channel_for_props(props,
             signal=True, conversation=conversation)
 
+    # papyon.event.InviteEventInterface
+    def on_invite_conference(self, call):
+        logger.debug("Call invite")
+        handle = ButterflyHandleFactory(self, 'contact', call.contact.account,
+                call.contact.network_id)
+
+        props = self._generate_props(telepathy.CHANNEL_TYPE_STREAMED_MEDIA,
+                handle, False)
+        channel = self._channel_manager.channel_for_props(props,
+                signal=True, call=call)
+
     def _advertise_disconnected(self):
         self._manager.disconnected(self)
 
diff --git a/butterfly/media/__init__.py b/butterfly/media/__init__.py
new file mode 100644
index 0000000..98755aa
--- /dev/null
+++ b/butterfly/media/__init__.py
@@ -0,0 +1,20 @@
+# telepathy-butterfly - an MSN connection manager for Telepathy
+#
+# Copyright (C) 2009 Collabora Ltd.
+#
+# 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
+
+from stream_handler import ButterflyStreamHandler
+from session_handler import ButterflySessionHandler
diff --git a/butterfly/media/session_handler.py b/butterfly/media/session_handler.py
new file mode 100644
index 0000000..23e95a4
--- /dev/null
+++ b/butterfly/media/session_handler.py
@@ -0,0 +1,89 @@
+# telepathy-butterfly - an MSN connection manager for Telepathy
+#
+# Copyright (C) 2009 Collabora Ltd.
+#
+# 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
+
+import logging
+import weakref
+import dbus
+
+import telepathy
+import papyon
+import papyon.event
+
+from butterfly.util.decorator import async
+from butterfly.handle import ButterflyHandleFactory
+from butterfly.media import ButterflyStreamHandler
+
+__all__ = ['ButterflySessionHandler']
+
+class ButterflySessionHandler (telepathy.server.MediaSessionHandler):
+    def __init__(self, connection, channel, session, handle):
+        self._conn = connection
+        self._session = session
+        self._handle = handle
+        self._stream_handlers = {}
+        self._next_stream_id = 0
+
+        path = channel._object_path + "/sessionhandler1"
+        telepathy.server.MediaSessionHandler.__init__(self, connection._name, path)
+
+    @property
+    def next_stream_id(self):
+        self._next_stream_id += 1
+        return self._next_stream_id
+
+    @property
+    def type(self):
+        return "rtp"
+
+    def get_stream_path(self, id):
+        return "%s/stream%d" % (self._object_path, id)
+
+    def Ready(self):
+        print "Session ready"
+        for handler in self._stream_handlers.values():
+            path = self.get_stream_path(handler.id)
+            self.NewStreamHandler(path, handler.id, handler.type,
+                    handler.direction)
+
+    def Error(self, code, message):
+        print "Session error", code, message
+
+    def GetStream(self, id):
+        return self._stream_handlers[id]
+
+    def ListStreams(self):
+        return self._stream_handlers.values()
+
+    def AddStream(self, stream):
+        handler = ButterflyStreamHandler(self._conn, self, stream)
+        self._stream_handlers[handler.id] = handler
+        path = self.get_stream_path(handler.id)
+        self.NewStreamHandler(path, handler.id, handler.type, handler.direction)
+        return handler
+
+    def CreateStream(self, type):
+        if type == 0:
+            media_type = "audio"
+        else:
+            media_type = "video"
+        stream = self._session.create_stream(media_type)
+        handler = ButterflyStreamHandler(self._conn, self, stream)
+        return handler
+
+    def RemoveStream(self, id):
+        pass
diff --git a/butterfly/media/stream_handler.py b/butterfly/media/stream_handler.py
new file mode 100644
index 0000000..1580d64
--- /dev/null
+++ b/butterfly/media/stream_handler.py
@@ -0,0 +1,211 @@
+# telepathy-butterfly - an MSN connection manager for Telepathy
+#
+# Copyright (C) 2009 Collabora Ltd.
+#
+# 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
+
+import base64
+
+import dbus
+
+import telepathy
+import papyon
+import papyon.event
+
+__all__ = ['ButterflyStreamHandler']
+
+StreamTypes = {
+    "audio": 0,
+    "video": 1
+}
+
+class ButterflyStreamHandler (
+        telepathy.server.DBusProperties,
+        telepathy.server.MediaStreamHandler,
+        papyon.event.MediaStreamEventInterface):
+
+    def __init__(self, connection, session, stream):
+        self._id = session.next_stream_id
+        path = session.get_stream_path(self._id)
+
+        self._conn = connection
+        self._session = session
+        self._stream = stream
+        self._interfaces = set()
+
+        self._state = 1
+        self._direction = 3
+        self._pending_send = 1
+        self._type = StreamTypes[stream.name]
+
+        self._remote_candidates = None
+        self._remote_codecs = None
+
+        telepathy.server.DBusProperties.__init__(self)
+        telepathy.server.MediaStreamHandler.__init__(self, connection._name, path)
+        papyon.event.MediaStreamEventInterface.__init__(self, stream)
+
+        self._implement_property_get(telepathy.interfaces.MEDIA_STREAM_HANDLER,
+            {'CreatedLocally': lambda: self._stream.controlling,
+             'NATTraversal': lambda: self.nat_traversal,
+             'STUNServers': lambda: self.stun_servers,
+             'RelayInfo': lambda: self.relay_info})
+
+    @property
+    def id(self):
+        return self._id
+
+    @property
+    def type(self):
+        return self._type
+
+    @property
+    def direction(self):
+        return self._direction
+
+    @property
+    def pending_send(self):
+        return self._pending_send
+
+    @property
+    def state(self):
+        return self._state
+
+    @property
+    def nat_traversal(self):
+        return "wlm-2009"
+
+    @property
+    def relay_info(self):
+        return dbus.Array([], signature="aa{sv}")
+
+    @property
+    def stun_servers(self):
+        return [("64.14.48.28", dbus.UInt32(3478))]
+
+    def Ready(self, Codecs):
+        if self._remote_candidates is not None:
+            self.SetRemoteCandidateList(self._remote_candidates)
+        if self._remote_codecs is not None:
+            self.SetRemoteCodecs(self._remote_codecs)
+        self.SetLocalCodecs(codecs)
+        #if self._session is None:
+        #    self._session = ButterflyWebcamSession(self._conn, self._handle.contact)
+
+    def StreamState(self, State):
+        print "StreamState : ", State
+
+    def Error(self, code, message):
+        print "StreamError - %i - %s" % (code, message)
+
+    def NewNativeCandidate(self, id, transports):
+        candidates = []
+        for transport in transports:
+            candidates.append(self.convert_tp_candidate(id, transport))
+        for candidate in candidates:
+            self._stream.new_local_candidate(candidate)
+
+    def NativeCandidatesPrepared(self):
+        self._stream.local_candidates_prepared()
+
+    def NewActiveCandidatePair(self, native_id, remote_id):
+        self._stream.new_active_candidate_pair(native_id, remote_id)
+
+    def SetLocalCodecs(self, Codecs):
+        list = self.convert_tp_codecs(codecs)
+        self._stream.set_local_codecs(list)
+
+    def SupportedCodecs(self, Codecs):
+        print "SupportedCodecs: ", Codecs
+
+    def CodecChoice(self, Codec_ID):
+        print "CodecChoice :", Codec_ID
+
+    def CodecsUpdated(self, Codecs):
+        print "CodecsUpdated: ", Codecs
+
+    #papyon.event.MediaStreamEventInterface
+    def on_remote_candidates_received(self, candidates):
+        print "REMOTE CANDIDATES"
+        list = self.convert_ice_candidates(candidates)
+        self._remote_candidates = list
+
+    #papyon.event.MediaStreamEventInterface
+    def on_remote_codecs_received(self, codecs):
+        list = []
+        for codec in codecs:
+            list.append(self.convert_sdp_codec(codec))
+        self._remote_codecs = list
+
+    #papyon.event.MediaStreamEventInterface
+    def on_stream_direction_changed(self):
+        self._session.StreamDirectionChanged(self.id, self.direction,
+                self.flag_send)
+
+    #papyon.event.MediaStreamEventInterface
+    def on_stream_closed(self):
+        self.Close()
+
+    def convert_sdp_codec(self, codec):
+        return (codec.payload, codec.encoding, self._type, codec.bitrate, 1, {})
+
+    def convert_tp_codecs(self, codecs):
+        list = []
+        for codec in codecs:
+            c = papyon.sip.sdp.SDPCodec(codec[0], codec[1], codec[3])
+            list.append(c)
+        return list
+
+    def convert_ice_candidates(self, candidates):
+        array = {}
+        for c in candidates:
+            if c.transport.lower() == "udp":
+                proto = 0
+            else:
+                proto = 1
+            if c.type == "host":
+                type = 0
+            elif c.type == "srflx" or c.type == "prflx":
+                type = 1
+            elif c.type == "relay":
+                type = 2
+            else:
+                print "TYPE", c.type
+                type = 0
+            while True:
+                try:
+                    base64.b64decode(c.username)
+                    break
+                except:
+                    c.username += "="
+            while True:
+                try:
+                    base64.b64decode(c.password)
+                    break
+                except:
+                    c.password += "="
+            preference = float(c.priority) / 65536.0
+            transport = (c.component_id, c.ip, c.port, proto, "RTP", "AVP",
+                    preference, type, c.username, c.password)
+            array.setdefault(c.foundation, []).append(transport)
+        return array.items()
+
+    def convert_tp_candidate(self, id, transport):
+        proto = "UDP"
+        priority = int(transport[6] * 65536)
+        type = "host"
+        return papyon.sip.ice.ICECandidate(19, id, int(transport[0]), proto, priority,
+                transport[8], transport[9], type, transport[1],
+                int(transport[2]))
diff --git a/butterfly/wscript b/butterfly/wscript
index 625734d..218fa42 100644
--- a/butterfly/wscript
+++ b/butterfly/wscript
@@ -3,7 +3,7 @@
 import os.path
 
 def build(bld):
-    src_loc = ['.', 'util', 'channel']
+    src_loc = ['.', 'util', 'channel', 'media']
     for loc in src_loc:
         obj = bld.create_obj('py')
         obj.find_sources_in_dirs(loc)
-- 
1.5.6.5




More information about the telepathy-commits mailing list