[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