[Telepathy-commits] [mingle/master] Add support for google video (answering only for now)

Sjoerd Simons sjoerd at luon.net
Thu Nov 13 02:55:30 PST 2008


---
 jingle.py |  298 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 293 insertions(+), 5 deletions(-)

diff --git a/jingle.py b/jingle.py
index e47df7c..d1166e1 100644
--- a/jingle.py
+++ b/jingle.py
@@ -154,6 +154,60 @@ class JingleRtpDescription(JingleBaseDescription):
 
         self.add_payloads(description)
 
+class GoogleBaseDescription(JingleBaseDescription):
+    def __init__(self, content, type):
+        JingleBaseDescription.__init__(self, content, type)
+
+    def content_to_codecs(self, description):
+        # We actually get the description
+        payloads = \
+            xpath.queryForNodes(
+                '/description/payload-type[@xmlns="%s"]' %
+                    self.namespace, description)
+
+        codecs = []
+        for p in payloads:
+            if p.hasAttribute("rate"):
+                rate = int(p["rate"])
+            elif p.hasAttribute("clockrate"):
+                rate = int(p["clockrate"])
+            else:
+                rate = 90000
+
+            if "H264" in p["name"]:
+                name = "H264"
+            else:
+                name = p["name"]
+
+
+            codec = farsight.Codec(int(p["id"]), name, self.type, rate)
+
+            params = xpath.queryForNodes("/payload-type/parameter", p)
+            if params !=None:
+                optional_params = []
+                for param in params:
+                    optional_params.append(
+                        (str(param['name']), str(param['value'])))
+                codec.optional_params = optional_params
+
+            codecs.append(codec)
+        return codecs
+
+    def add_description(self, parent):
+        self.add_payloads(parent, self.namespace)
+
+class GooglePhoneDescription(GoogleBaseDescription):
+    def __init__(self, content):
+        GoogleBaseDescription.__init__(self, content,
+            farsight.MEDIA_TYPE_AUDIO)
+        self.namespace = ns.GOOGLE_S_SESSION_PHONE
+
+class GoogleVideoDescription(GoogleBaseDescription):
+    def __init__(self, content):
+        GoogleBaseDescription.__init__(self, content,
+            farsight.MEDIA_TYPE_VIDEO)
+        self.namespace = ns.GOOGLE_S_SESSION_VIDEO
+
 def get_available_transports():
     """ Get the list of available tranports (by namespace) depending on the
         available farsight transmitters
@@ -196,7 +250,9 @@ class JingleBaseTransport:
           'port': ('port', int),
           'protocol': ('proto', {'udp': 0, 'tcp': 1}.__getitem__),
           'name': ('component_id', {'rtp': farsight.COMPONENT_RTP,
-             'rtcp': farsight.COMPONENT_RTCP}.__getitem__),
+             'rtcp': farsight.COMPONENT_RTCP,
+             'video_rtcp' : farsight.COMPONENT_RTCP,
+             'video_rtp': farsight.COMPONENT_RTP}.__getitem__),
         }
 
     def got_stream(self, stream):
@@ -335,6 +391,58 @@ class JingleGoogleTransport(JingleBaseTransport):
               farsight.CANDIDATE_TYPE_MULTICAST: "multicast"
             }[fscandidate.type]
 
+class GoogleTransport(JingleGoogleTransport):
+    TYPE_AUDIO = 0
+    TYPE_VIDEO = 1
+
+    def __init__(self, content, type = TYPE_AUDIO):
+        JingleGoogleTransport.__init__(self, content)
+        self.namespace = ns.GOOGLE_TRANSPORT_P2P
+        self.type = type
+
+    def transport_info(self, candidate):
+        # content is a content stanza with transport info for us, aka a
+        # candidate
+        fscandidate = self.candidate_to_fscandidate(candidate)
+        if fscandidate != None:
+          self.fsstream.add_remote_candidate(fscandidate)
+
+    def prepare(self, fssession, contact):
+        # FIXME hack alert ?
+        participant = \
+          self.content.session.conference.get_participant (contact)
+        stream = self.content.session.conference.create_stream (fssession,
+            participant, self.namespace)
+        self.got_stream (stream.fsstream)
+
+        return defer.succeed(self)
+
+    def send_candidate(self, fscandidate):
+        candidate = domish.Element((None, 'candidate'))
+        self.fscandidate_to_candidate(candidate, fscandidate)
+        self.content.send_transport_info(candidate)
+
+    def candidate_to_fscandidate(self, candidate):
+        if candidate.hasAttribute("name"):
+            if self.type == GoogleTransport.TYPE_AUDIO:
+                if "video" in candidate["name"]:
+                    return
+
+            if self.type == GoogleTransport.TYPE_VIDEO:
+                if not "video" in candidate["name"]:
+                    return
+
+        return JingleGoogleTransport.candidate_to_fscandidate(self, candidate)
+
+    def fscandidate_to_candidate(self, candidate, fscandidate):
+        JingleGoogleTransport.fscandidate_to_candidate(self, \
+            candidate, fscandidate)
+        if self.type == GoogleTransport.TYPE_VIDEO:
+             candidate['name'] = "video_" + candidate['name']
+
+    def add_transport(self, parent):
+        pass
+
 class JingleRawudpTransport(JingleBaseTransport):
     def __init__(self, content):
         JingleBaseTransport.__init__(self, content)
@@ -568,6 +676,52 @@ class JingleContent:
         # Hrm, slightly dodgy
         self.description.initiate(self.transport.fsstream)
 
+
+class GoogleBaseContent(JingleContent):
+    def __init__(self, session, name, type, initiator = True):
+       JingleContent.__init__(self, session, name, type, initiator)
+
+    def send_transport_info(self, candidate):
+       self.session.send_transport_info(candidate)
+
+    def add_description(self, parent):
+        self.add_payloads(description)
+
+    def add_content(self, parent):
+        # Add the content
+        self.description.add_description(parent)
+        self.transport.add_transport(parent)
+
+    def base_got_initiate (self, description, contact):
+        # FIXME handle the case where we can't find a description or transport
+        # class
+        self.fssession = self.session.conference.get_session(
+            self.name, self.type)
+
+        stream = self.transport.got_initiate(self.fssession, contact,
+            description)
+        self.description.got_initiate(self.fssession, stream, description)
+
+class GoogleVideoContent(GoogleBaseContent):
+    def __init__(self, session, initiator = True):
+       GoogleBaseContent.__init__(self, session, "video",
+            farsight.MEDIA_TYPE_VIDEO, initiator)
+
+    def got_initiate(self, description, contact):
+        self.description = GoogleVideoDescription(self)
+        self.transport =  GoogleTransport(self, GoogleTransport.TYPE_VIDEO)
+        self.base_got_initiate(description, contact)
+
+class GooglePhoneContent(GoogleBaseContent):
+    def __init__(self, session, initiator = True):
+       GoogleBaseContent.__init__(self, session, "audio",
+            farsight.MEDIA_TYPE_AUDIO, initiator)
+
+    def got_initiate(self, description, contact):
+        self.description = GooglePhoneDescription(self)
+        self.transport =  GoogleTransport(self)
+        self.base_got_initiate(description, contact)
+
 class JingleBaseSession:
     def __init__(self, client, conference = None):
         self.client = client
@@ -773,6 +927,108 @@ class JingleSession(JingleBaseSession):
         JingleBaseSession.__init__(self, client, conference)
         self.namespace = ns.JINGLE
 
+class GoogleSession(JingleBaseSession):
+    SESSION_TYPE_PHONE = 0
+    SESSION_TYPE_VIDEO = 1
+
+    def __init__(self, client, conference = None):
+        JingleBaseSession.__init__(self, client, conference)
+        self.namespace = ns.GOOGLE_S_SESSION
+        self.type  = self.SESSION_TYPE_PHONE
+
+        self.action_dispatch = {
+            'candidates': self.action_candidates,
+        }
+
+    def contents_ready_for_accept(self, *args):
+        iq = IQ(self.client.stream, 'set')
+        iq['to'] = self.remote_jid.full()
+        session = self.add_google_session_stanza(iq, 'accept')
+
+        if self.type == GoogleSession.SESSION_TYPE_VIDEO:
+            description = session.addElement ((ns.GOOGLE_S_SESSION_VIDEO,
+                "description"))
+        else:
+            description = session.addElement ((ns.GOOGLE_S_SESSION_PHONE,
+                "description"))
+
+        for c in self.contents.itervalues():
+            c.add_content(description)
+            c.accepted()
+        iq.send()
+
+    def handle_session_initiate(self, iq, contact):
+        session = xpath.queryForNodes(
+            '/iq/session[@xmlns="%s"]' % self.namespace, iq)[0]
+
+        self.remote_jid = contact.jid
+        self.initiator = JID(session['initiator'])
+        self.sid = session['id']
+
+        description = xpath.queryForNodes(
+            '/iq/session[@xmlns="%s"]/description' % self.namespace, iq)[0]
+
+        if description.uri == ns.GOOGLE_S_SESSION_VIDEO:
+            self.type  = self.SESSION_TYPE_VIDEO
+
+            contents = [
+                GooglePhoneContent (self, initiator = False),
+                GoogleVideoContent (self, initiator = False)
+            ]
+        elif description.uri == ns.GOOGLE_S_SESSION_PHONE:
+            contents = [ GooglePhoneContent (self, initiator = False) ]
+
+        for c in contents:
+            self.contents[c.name] = c
+            c.got_initiate(description, contact)
+
+        self.iq_handler_id = self.client.add_iq_handler('set',
+            self.namespace, 'session', self.got_google_iq)
+
+        dlist = []
+        for c in self.contents.itervalues():
+            dlist.append(c.ready_for_accept())
+
+        d = defer.DeferredList(dlist)
+        d.addCallback(self.contents_ready_for_accept)
+
+        # Now wait on the contents to be finished with transport and codecs
+        return make_iq_result(iq)
+
+    def action_candidates(self, iq, session):
+        candidates = xpath.queryForNodes('/session/candidate', session)
+        for cand in candidates:
+            for c in self.contents.itervalues():
+                c.transport_info(cand)
+
+        return make_iq_result(iq)
+
+    def add_google_session_stanza(self, iq, type):
+        session = iq.addElement((self.namespace, 'session'))
+        session['type'] = type
+        session['initiator'] = self.initiator.full()
+        session['id'] = self.sid;
+        return session
+
+    def send_transport_info(self, content):
+        iq = IQ(self.client.stream, 'set')
+        iq['to'] = self.remote_jid.full()
+        session = self.add_google_session_stanza(iq, 'candidates')
+        session.addChild(content)
+        iq.send()
+
+    def got_google_iq(self, iq):
+        session = xpath.queryForNodes('/iq/session[@xmlns="%s"]' %
+            self.namespace, iq)[0]
+
+        if session['id'] != self.sid:
+            return None
+
+        if self.action_dispatch.has_key(session['type']):
+            return self.action_dispatch[session['type']](iq, session)
+        print iq
+
+
 class JingleStream:
     def __init__(self, fsstream, namespace):
         self.fsstream = fsstream
@@ -825,8 +1081,13 @@ class JingleConference(fs2.Conference):
 
 
     def create_stream(self, session, participant, namespace):
+        # Using stun.ekiga.net as a stunserver
         transmitters = {
-            ns.GOOGLE_TRANSPORT_P2P: ("nice", { 'compatibility-mode':  1 }),
+            ns.GOOGLE_TRANSPORT_P2P: ("nice",
+                { 'compatibility-mode':  1,
+                  'stun-ip': "69.0.208.27",
+                  'stun-port': 3478
+                }),
             ns.JINGLE_TRANSPORT_RAW_UDP: ("rawudp", {} )
         }
 
@@ -869,6 +1130,9 @@ class JingleConference(fs2.Conference):
         participant = self.get_participant(contact)
 
         if not self.streams[session].has_key(participant):
+            if namespace != None:
+                self.create_stream (session, participant, namespace)
+
             if not self.stream_deferreds[session].has_key(participant):
                 d = defer.Deferred()
                 self.stream_deferreds[session][participant] = d
@@ -887,7 +1151,9 @@ class JingleConference(fs2.Conference):
 
 def get_session_for_ns(client, namespace):
     ns2type = { ns.OLD_JINGLE: JingleOldSession,
-                ns.JINGLE: JingleSession }
+                ns.JINGLE: JingleSession,
+                ns.GOOGLE_S_SESSION: GoogleSession
+              }
 
     if ns2type.has_key(namespace):
         return ns2type[namespace](client)
@@ -914,11 +1180,28 @@ def jingle_iq_received(client, iq):
     jingle = xpath.queryForNodes('/iq/jingle', iq)[0]
     if jingle['action'] != 'session-initiate':
         return None
+
     session = get_session_for_ns(client, jingle.uri)
 
     contact = client.get_contact(JID(iq['from']))
-    if contact != None:
-        print "Ignoring call from unknown contact"
+    if contact == None:
+        print "Ignoring jingle call from unknown contact"
+        return
+
+    return session.handle_session_initiate(iq, contact)
+
+def google_session_iq_received(client, iq):
+    session = xpath.queryForNodes('/iq/session', iq)[0]
+
+    if session['type'] != 'initiate':
+        return None
+
+    session = get_session_for_ns(client, session.uri)
+
+    contact = client.get_contact(JID(iq['from']))
+    if contact == None:
+        print "Ignoring google call from unknown contact %s" % iq['from']
+        return
 
     return session.handle_session_initiate(iq, contact)
 
@@ -931,6 +1214,9 @@ def enable_incoming_calls(client):
         ns.OLD_JINGLE,
         ns.OLD_JINGLE_DESC_AUDIO,
         ns.OLD_JINGLE_DESC_VIDEO,
+        ns.GOOGLE_SESSION,
+        ns.GOOGLE_VOICE_V1,
+        ns.GOOGLE_VIDEO_V1
         ] + transports)
     # Send presence updated with our new capabilities
     client.send_presence()
@@ -939,3 +1225,5 @@ def enable_incoming_calls(client):
         lambda iq: jingle_iq_received(client, iq))
     client.add_iq_handler('set', ns.JINGLE, 'jingle',
         lambda iq: jingle_iq_received(client, iq))
+    client.add_iq_handler('set', ns.GOOGLE_S_SESSION, 'session',
+        lambda iq: google_session_iq_received(client, iq))
-- 
1.5.6.5




More information about the Telepathy-commits mailing list