[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