support for content-{add,reject,accept}, new helpers, and other few things
This commit is contained in:
parent
a051d1ec95
commit
77541f3e7f
|
@ -1579,14 +1579,17 @@ class ChatControl(ChatControlBase, ChatCommands):
|
||||||
elif state == 'connecting':
|
elif state == 'connecting':
|
||||||
self.audio_state = self.JINGLE_STATE_CONNECTING
|
self.audio_state = self.JINGLE_STATE_CONNECTING
|
||||||
self.audio_sid = sid
|
self.audio_sid = sid
|
||||||
|
self._audio_button.set_active(True)
|
||||||
elif state == 'connection_received':
|
elif state == 'connection_received':
|
||||||
self.audio_state = self.JINGLE_STATE_CONNECTION_RECEIVED
|
self.audio_state = self.JINGLE_STATE_CONNECTION_RECEIVED
|
||||||
self.audio_sid = sid
|
self.audio_sid = sid
|
||||||
|
self._audio_button.set_active(True)
|
||||||
elif state == 'connected':
|
elif state == 'connected':
|
||||||
self.audio_state = self.JINGLE_STATE_CONNECTED
|
self.audio_state = self.JINGLE_STATE_CONNECTED
|
||||||
elif state == 'stop':
|
elif state == 'stop':
|
||||||
self.audio_state = self.JINGLE_STATE_AVAILABLE
|
self.audio_state = self.JINGLE_STATE_AVAILABLE
|
||||||
self.audio_sid = None
|
self.audio_sid = None
|
||||||
|
self._audio_button.set_active(False)
|
||||||
elif state == 'error':
|
elif state == 'error':
|
||||||
self.audio_state = self.JINGLE_STATE_ERROR
|
self.audio_state = self.JINGLE_STATE_ERROR
|
||||||
self.update_audio()
|
self.update_audio()
|
||||||
|
@ -1605,15 +1608,18 @@ class ChatControl(ChatControlBase, ChatCommands):
|
||||||
self.video_sid = None
|
self.video_sid = None
|
||||||
elif state == 'connecting':
|
elif state == 'connecting':
|
||||||
self.video_state = self.JINGLE_STATE_CONNECTING
|
self.video_state = self.JINGLE_STATE_CONNECTING
|
||||||
|
self._video_button.set_active(True)
|
||||||
self.video_sid = sid
|
self.video_sid = sid
|
||||||
elif state == 'connection_received':
|
elif state == 'connection_received':
|
||||||
self.video_state = self.JINGLE_STATE_CONNECTION_RECEIVED
|
self.video_state = self.JINGLE_STATE_CONNECTION_RECEIVED
|
||||||
|
self._video_button.set_active(True)
|
||||||
self.video_sid = sid
|
self.video_sid = sid
|
||||||
elif state == 'connected':
|
elif state == 'connected':
|
||||||
self.video_state = self.JINGLE_STATE_CONNECTED
|
self.video_state = self.JINGLE_STATE_CONNECTED
|
||||||
elif state == 'stop':
|
elif state == 'stop':
|
||||||
self.video_state = self.JINGLE_STATE_AVAILABLE
|
self.video_state = self.JINGLE_STATE_AVAILABLE
|
||||||
self.video_sid = None
|
self.video_sid = None
|
||||||
|
self._video_button.set_active(False)
|
||||||
elif state == 'error':
|
elif state == 'error':
|
||||||
self.video_state = self.JINGLE_STATE_ERROR
|
self.video_state = self.JINGLE_STATE_ERROR
|
||||||
self.update_video()
|
self.update_video()
|
||||||
|
@ -1825,10 +1831,11 @@ class ChatControl(ChatControlBase, ChatCommands):
|
||||||
self.set_audio_state('connecting', sid)
|
self.set_audio_state('connecting', sid)
|
||||||
else:
|
else:
|
||||||
session = gajim.connections[self.account].get_jingle_session(
|
session = gajim.connections[self.account].get_jingle_session(
|
||||||
self.contact.get_full_jid(), self.audio_sid)
|
self.contact.get_full_jid(), self.video_sid)
|
||||||
if session:
|
if session:
|
||||||
# TODO: end only audio
|
content = session.get_content('audio')
|
||||||
session.end_session()
|
if content:
|
||||||
|
session.remove_content(content.creator, content.name)
|
||||||
|
|
||||||
def on_video_button_toggled(self, widget):
|
def on_video_button_toggled(self, widget):
|
||||||
if widget.get_active():
|
if widget.get_active():
|
||||||
|
@ -1839,8 +1846,9 @@ class ChatControl(ChatControlBase, ChatCommands):
|
||||||
session = gajim.connections[self.account].get_jingle_session(
|
session = gajim.connections[self.account].get_jingle_session(
|
||||||
self.contact.get_full_jid(), self.video_sid)
|
self.contact.get_full_jid(), self.video_sid)
|
||||||
if session:
|
if session:
|
||||||
# TODO: end only video
|
content = session.get_content('video')
|
||||||
session.end_session()
|
if content:
|
||||||
|
session.remove_content(content.creator, content.name)
|
||||||
|
|
||||||
def _toggle_gpg(self):
|
def _toggle_gpg(self):
|
||||||
if not self.gpg_is_active and not self.contact.keyID:
|
if not self.gpg_is_active and not self.contact.keyID:
|
||||||
|
|
|
@ -17,7 +17,10 @@
|
||||||
# - 'senders' attribute of 'content' element
|
# - 'senders' attribute of 'content' element
|
||||||
# - security preconditions
|
# - security preconditions
|
||||||
# * actions:
|
# * actions:
|
||||||
# - content-accept, content-reject, content-add, content-modify
|
# - content-accept: see content-add
|
||||||
|
# - content-reject: sending it ; receiving is ok
|
||||||
|
# - content-add: handling ; sending is ok
|
||||||
|
# - content-modify: both
|
||||||
# - description-info, session-info
|
# - description-info, session-info
|
||||||
# - security-info
|
# - security-info
|
||||||
# - transport-accept, transport-reject
|
# - transport-accept, transport-reject
|
||||||
|
@ -88,7 +91,7 @@ class JingleSession(object):
|
||||||
# our full jid
|
# our full jid
|
||||||
self.ourjid = gajim.get_jid_from_account(self.connection.name) + '/' + \
|
self.ourjid = gajim.get_jid_from_account(self.connection.name) + '/' + \
|
||||||
con.server_resource
|
con.server_resource
|
||||||
self.peerjid = jid # jid we connect to
|
self.peerjid = str(jid) # jid we connect to
|
||||||
# jid we use as the initiator
|
# jid we use as the initiator
|
||||||
self.initiator = weinitiate and self.ourjid or self.peerjid
|
self.initiator = weinitiate and self.ourjid or self.peerjid
|
||||||
# jid we use as the responder
|
# jid we use as the responder
|
||||||
|
@ -107,10 +110,12 @@ class JingleSession(object):
|
||||||
# use .prepend() to add new callbacks, especially when you're going
|
# use .prepend() to add new callbacks, especially when you're going
|
||||||
# to send error instead of ack
|
# to send error instead of ack
|
||||||
self.callbacks = {
|
self.callbacks = {
|
||||||
'content-accept': [self.__contentAcceptCB, self.__defaultCB],
|
'content-accept': [self.__contentAcceptCB, self.__broadcastCB,
|
||||||
'content-add': [self.__defaultCB], #TODO
|
self.__defaultCB],
|
||||||
|
'content-add': [self.__contentAddCB, self.__broadcastCB,
|
||||||
|
self.__defaultCB], #TODO
|
||||||
'content-modify': [self.__defaultCB], #TODO
|
'content-modify': [self.__defaultCB], #TODO
|
||||||
'content-reject': [self.__defaultCB], #TODO
|
'content-reject': [self.__defaultCB, self.__contentRemoveCB], #TODO
|
||||||
'content-remove': [self.__defaultCB, self.__contentRemoveCB],
|
'content-remove': [self.__defaultCB, self.__contentRemoveCB],
|
||||||
'description-info': [self.__defaultCB], #TODO
|
'description-info': [self.__defaultCB], #TODO
|
||||||
'security-info': [self.__defaultCB], #TODO
|
'security-info': [self.__defaultCB], #TODO
|
||||||
|
@ -133,7 +138,6 @@ class JingleSession(object):
|
||||||
def approve_session(self):
|
def approve_session(self):
|
||||||
''' Called when user accepts session in UI (when we aren't the initiator).
|
''' Called when user accepts session in UI (when we aren't the initiator).
|
||||||
'''
|
'''
|
||||||
self.accepted = True
|
|
||||||
self.accept_session()
|
self.accept_session()
|
||||||
|
|
||||||
def decline_session(self):
|
def decline_session(self):
|
||||||
|
@ -154,10 +158,23 @@ class JingleSession(object):
|
||||||
|
|
||||||
''' Middle-level functions to manage contents. Handle local content
|
''' Middle-level functions to manage contents. Handle local content
|
||||||
cache and send change notifications. '''
|
cache and send change notifications. '''
|
||||||
|
def get_content(self, media=None):
|
||||||
|
if media == 'audio':
|
||||||
|
cls = JingleVoIP
|
||||||
|
elif media == 'video':
|
||||||
|
cls = JingleVideo
|
||||||
|
#elif media == None:
|
||||||
|
# cls = JingleContent
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
for content in self.contents.values():
|
||||||
|
if isinstance(content, cls):
|
||||||
|
return content
|
||||||
|
|
||||||
def add_content(self, name, content, creator='we'):
|
def add_content(self, name, content, creator='we'):
|
||||||
''' Add new content to session. If the session is active,
|
''' Add new content to session. If the session is active,
|
||||||
this will send proper stanza to update session.
|
this will send proper stanza to update session.
|
||||||
The protocol prohibits changing that when pending.
|
|
||||||
Creator must be one of ('we', 'peer', 'initiator', 'responder')'''
|
Creator must be one of ('we', 'peer', 'initiator', 'responder')'''
|
||||||
assert creator in ('we', 'peer', 'initiator', 'responder')
|
assert creator in ('we', 'peer', 'initiator', 'responder')
|
||||||
|
|
||||||
|
@ -171,34 +188,53 @@ class JingleSession(object):
|
||||||
content.name = name
|
content.name = name
|
||||||
self.contents[(creator, name)] = content
|
self.contents[(creator, name)] = content
|
||||||
|
|
||||||
if self.state == JingleStates.active:
|
|
||||||
pass # TODO: send proper stanza, shouldn't be needed now
|
|
||||||
|
|
||||||
def remove_content(self, creator, name):
|
def remove_content(self, creator, name):
|
||||||
''' We do not need this now '''
|
''' We do not need this now '''
|
||||||
pass
|
#TODO:
|
||||||
|
if (creator, name) in self.contents:
|
||||||
|
content = self.contents[(creator, name)]
|
||||||
|
if len(self.contents) > 1:
|
||||||
|
self.__content_remove(content)
|
||||||
|
self.contents[(creator, name)].destroy()
|
||||||
|
if len(self.contents) == 0:
|
||||||
|
self.end_session()
|
||||||
|
|
||||||
def modify_content(self, creator, name, *someother):
|
def modify_content(self, creator, name, *someother):
|
||||||
''' We do not need this now '''
|
''' We do not need this now '''
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def accept_session(self):
|
def on_session_state_changed(self, content=None):
|
||||||
''' Check if all contents and user agreed to start session. '''
|
if self.state == JingleStates.active and self.accepted:
|
||||||
if not self.weinitiate and self.accepted and \
|
if not content:
|
||||||
self.state == JingleStates.pending and self.is_ready():
|
return
|
||||||
|
if (content.creator == 'initiator') == self.weinitiate:
|
||||||
|
# We initiated this content. It's a pending content-add.
|
||||||
|
self.__content_add(content)
|
||||||
|
else:
|
||||||
|
# The other side created this content, we accept it.
|
||||||
|
self.__content_accept(content)
|
||||||
|
elif self.is_ready():
|
||||||
|
if not self.weinitiate and self.state == JingleStates.pending:
|
||||||
self.__session_accept()
|
self.__session_accept()
|
||||||
|
elif self.weinitiate and self.state == JingleStates.ended:
|
||||||
|
self.__session_initiate()
|
||||||
|
|
||||||
def is_ready(self):
|
def is_ready(self):
|
||||||
''' Returns True when all codecs and candidates are ready
|
''' Returns True when all codecs and candidates are ready
|
||||||
(for all contents). '''
|
(for all contents). '''
|
||||||
return all((content.is_ready() for content in self.contents.itervalues()))
|
return (all((content.is_ready() for content in self.contents.itervalues()))
|
||||||
|
and self.accepted)
|
||||||
|
|
||||||
''' Middle-level function to do stanza exchange. '''
|
''' Middle-level function to do stanza exchange. '''
|
||||||
|
def accept_session(self):
|
||||||
|
''' Mark the session as accepted. '''
|
||||||
|
self.accepted = True
|
||||||
|
self.on_session_state_changed()
|
||||||
|
|
||||||
def start_session(self):
|
def start_session(self):
|
||||||
''' Start session. '''
|
''' Mark the session as ready to be started. '''
|
||||||
#FIXME: Start only once
|
self.accepted = True
|
||||||
if self.weinitiate and self.state == JingleStates.ended and self.is_ready():
|
self.on_session_state_changed()
|
||||||
self.__session_initiate()
|
|
||||||
|
|
||||||
def send_session_info(self):
|
def send_session_info(self):
|
||||||
pass
|
pass
|
||||||
|
@ -309,10 +345,10 @@ class JingleSession(object):
|
||||||
creator = content['creator']
|
creator = content['creator']
|
||||||
name = content['name']
|
name = content['name']
|
||||||
if (creator, name) in self.contents:
|
if (creator, name) in self.contents:
|
||||||
del self.contents[(creator, name)]
|
self.contents[(creator, name)].destroy()
|
||||||
if len(self.contents) == 0:
|
if len(self.contents) == 0:
|
||||||
reason = xmpp.Node('reason')
|
reason = xmpp.Node('reason')
|
||||||
reason.setTag('success') #FIXME: Is it the good one?
|
reason.setTag('success')
|
||||||
self._session_terminate(reason)
|
self._session_terminate(reason)
|
||||||
|
|
||||||
def __sessionAcceptCB(self, stanza, jingle, error, action):
|
def __sessionAcceptCB(self, stanza, jingle, error, action):
|
||||||
|
@ -328,6 +364,27 @@ class JingleSession(object):
|
||||||
creator = content['creator']
|
creator = content['creator']
|
||||||
name = content['name']#TODO...
|
name = content['name']#TODO...
|
||||||
|
|
||||||
|
def __contentAddCB(self, stanza, jingle, error, action):
|
||||||
|
#TODO: Needs to be rewritten
|
||||||
|
if self.state == JingleStates.ended:
|
||||||
|
raise OutOfOrder
|
||||||
|
for element in jingle.iterTags('content'):
|
||||||
|
# checking what kind of session this will be
|
||||||
|
desc_ns = element.getTag('description').getNamespace()
|
||||||
|
media = element.getTag('description')['media']
|
||||||
|
tran_ns = element.getTag('transport').getNamespace()
|
||||||
|
if desc_ns == xmpp.NS_JINGLE_RTP and media in ('audio', 'video') \
|
||||||
|
and tran_ns == xmpp.NS_JINGLE_ICE_UDP:
|
||||||
|
if media == 'audio':
|
||||||
|
self.add_content(element['name'], JingleVoIP(self), 'peer')
|
||||||
|
else:
|
||||||
|
self.add_content(element['name'], JingleVideo(self), 'peer')
|
||||||
|
else:
|
||||||
|
content = JingleContent()
|
||||||
|
self.add_content(element['name'], content, 'peer')
|
||||||
|
self.__content_reject(content)
|
||||||
|
self.contents[(content.creator, content.name)].destroy()
|
||||||
|
|
||||||
def __sessionInitiateCB(self, stanza, jingle, error, action):
|
def __sessionInitiateCB(self, stanza, jingle, error, action):
|
||||||
''' We got a jingle session request from other entity,
|
''' We got a jingle session request from other entity,
|
||||||
therefore we are the receiver... Unpack the data,
|
therefore we are the receiver... Unpack the data,
|
||||||
|
@ -511,20 +568,40 @@ class JingleSession(object):
|
||||||
text = '%s (%s)' % (reason, text)
|
text = '%s (%s)' % (reason, text)
|
||||||
else:
|
else:
|
||||||
text = reason
|
text = reason
|
||||||
self.connection.dispatch('JINGLE_DISCONNECTED', (self.peerjid, self.sid, text))
|
|
||||||
self.connection.delete_jingle(self)
|
self.connection.delete_jingle(self)
|
||||||
|
self.connection.dispatch('JINGLE_DISCONNECTED', (self.peerjid, self.sid, text))
|
||||||
|
|
||||||
def __content_add(self):
|
def __content_add(self, content):
|
||||||
assert self.state == JingleStates.active
|
#TODO: test
|
||||||
|
|
||||||
def __content_accept(self):
|
|
||||||
assert self.state != JingleStates.ended
|
assert self.state != JingleStates.ended
|
||||||
|
stanza, jingle = self.__make_jingle('content-add')
|
||||||
|
self.__append_content(jingle, content)
|
||||||
|
self.__broadcastCB(stanza, jingle, None, 'content-add-sent')
|
||||||
|
self.connection.connection.send(stanza)
|
||||||
|
|
||||||
|
def __content_accept(self, content):
|
||||||
|
#TODO: test
|
||||||
|
assert self.state != JingleStates.ended
|
||||||
|
stanza, jingle = self.__make_jingle('content-accept')
|
||||||
|
self.__append_content(jingle, content)
|
||||||
|
self.__broadcastCB(stanza, jingle, None, 'content-accept-sent')
|
||||||
|
self.connection.connection.send(stanza)
|
||||||
|
|
||||||
|
def __content_reject(self, content):
|
||||||
|
assert self.state != JingleStates.ended
|
||||||
|
stanza, jingle = self.__make_jingle('content-reject')
|
||||||
|
self.__append_content(jingle, content)
|
||||||
|
self.connection.connection.send(stanza)
|
||||||
|
|
||||||
def __content_modify(self):
|
def __content_modify(self):
|
||||||
assert self.state != JingleStates.ended
|
assert self.state != JingleStates.ended
|
||||||
|
|
||||||
def __content_remove(self):
|
def __content_remove(self, content):
|
||||||
assert self.state != JingleStates.ended
|
assert self.state != JingleStates.ended
|
||||||
|
stanza, jingle = self.__make_jingle('content-remove')
|
||||||
|
self.__append_content(jingle, content)
|
||||||
|
self.connection.connection.send(stanza)
|
||||||
|
#TODO: dispatch something?
|
||||||
|
|
||||||
def content_negociated(self, media):
|
def content_negociated(self, media):
|
||||||
self.connection.dispatch('JINGLE_CONNECTED', (self.peerjid, self.sid,
|
self.connection.dispatch('JINGLE_CONNECTED', (self.peerjid, self.sid,
|
||||||
|
@ -554,8 +631,8 @@ class JingleContent(object):
|
||||||
|
|
||||||
self.callbacks = {
|
self.callbacks = {
|
||||||
# these are called when *we* get stanzas
|
# these are called when *we* get stanzas
|
||||||
'content-accept': [],
|
'content-accept': [self.__transportInfoCB],
|
||||||
'content-add': [],
|
'content-add': [self.__transportInfoCB],
|
||||||
'content-modify': [],
|
'content-modify': [],
|
||||||
'content-remove': [],
|
'content-remove': [],
|
||||||
'session-accept': [self.__transportInfoCB],
|
'session-accept': [self.__transportInfoCB],
|
||||||
|
@ -566,6 +643,8 @@ class JingleContent(object):
|
||||||
'iq-result': [],
|
'iq-result': [],
|
||||||
'iq-error': [],
|
'iq-error': [],
|
||||||
# these are called when *we* sent these stanzas
|
# these are called when *we* sent these stanzas
|
||||||
|
'content-accept-sent': [self.__fillJingleStanza],
|
||||||
|
'content-add-sent': [self.__fillJingleStanza],
|
||||||
'session-initiate-sent': [self.__fillJingleStanza],
|
'session-initiate-sent': [self.__fillJingleStanza],
|
||||||
'session-accept-sent': [self.__fillJingleStanza],
|
'session-accept-sent': [self.__fillJingleStanza],
|
||||||
'session-terminate-sent': [],
|
'session-terminate-sent': [],
|
||||||
|
@ -676,6 +755,11 @@ class JingleContent(object):
|
||||||
content.addChild(xmpp.NS_JINGLE_ICE_UDP + ' transport', attrs=attrs,
|
content.addChild(xmpp.NS_JINGLE_ICE_UDP + ' transport', attrs=attrs,
|
||||||
payload=self.iter_candidates())
|
payload=self.iter_candidates())
|
||||||
|
|
||||||
|
def destroy(self):
|
||||||
|
self.callbacks = None
|
||||||
|
del self.session.contents[(self.creator, self.name)]
|
||||||
|
|
||||||
|
|
||||||
class JingleRTPContent(JingleContent):
|
class JingleRTPContent(JingleContent):
|
||||||
def __init__(self, session, media, node=None):
|
def __init__(self, session, media, node=None):
|
||||||
JingleContent.__init__(self, session, node)
|
JingleContent.__init__(self, session, node)
|
||||||
|
@ -687,6 +771,7 @@ class JingleRTPContent(JingleContent):
|
||||||
self.candidates_ready = False # True when local candidates are prepared
|
self.candidates_ready = False # True when local candidates are prepared
|
||||||
|
|
||||||
self.callbacks['content-accept'] += [self.__getRemoteCodecsCB]
|
self.callbacks['content-accept'] += [self.__getRemoteCodecsCB]
|
||||||
|
self.callbacks['content-add'] += [self.__getRemoteCodecsCB]
|
||||||
self.callbacks['session-accept'] += [self.__getRemoteCodecsCB]
|
self.callbacks['session-accept'] += [self.__getRemoteCodecsCB]
|
||||||
self.callbacks['session-initiate'] += [self.__getRemoteCodecsCB]
|
self.callbacks['session-initiate'] += [self.__getRemoteCodecsCB]
|
||||||
self.callbacks['session-terminate'] += [self.__stop]
|
self.callbacks['session-terminate'] += [self.__stop]
|
||||||
|
@ -718,21 +803,32 @@ class JingleRTPContent(JingleContent):
|
||||||
content.addChild(xmpp.NS_JINGLE_RTP + ' description',
|
content.addChild(xmpp.NS_JINGLE_RTP + ' description',
|
||||||
attrs={'media': self.media}, payload=self.iter_codecs())
|
attrs={'media': self.media}, payload=self.iter_codecs())
|
||||||
|
|
||||||
|
def _setup_funnel(self):
|
||||||
|
self.funnel = gst.element_factory_make('fsfunnel')
|
||||||
|
self.pipeline.add(self.funnel)
|
||||||
|
self.funnel.set_state(gst.STATE_PLAYING)
|
||||||
|
self.sink.set_state(gst.STATE_PLAYING)
|
||||||
|
self.funnel.link(self.sink)
|
||||||
|
|
||||||
|
def _on_src_pad_added(self, stream, pad, codec):
|
||||||
|
if not self.funnel:
|
||||||
|
self._setup_funnel()
|
||||||
|
pad.link(self.funnel.get_pad('sink%d'))
|
||||||
|
|
||||||
def _on_gst_message(self, bus, message):
|
def _on_gst_message(self, bus, message):
|
||||||
if message.type == gst.MESSAGE_ELEMENT:
|
if message.type == gst.MESSAGE_ELEMENT:
|
||||||
name = message.structure.get_name()
|
name = message.structure.get_name()
|
||||||
#print name
|
|
||||||
if name == 'farsight-new-active-candidate-pair':
|
if name == 'farsight-new-active-candidate-pair':
|
||||||
pass
|
pass
|
||||||
elif name == 'farsight-recv-codecs-changed':
|
elif name == 'farsight-recv-codecs-changed':
|
||||||
pass
|
pass
|
||||||
elif name == 'farsight-codecs-changed':
|
elif name == 'farsight-codecs-changed':
|
||||||
self.session.accept_session()
|
if self.is_ready():
|
||||||
self.session.start_session()
|
self.session.on_session_state_changed(self)
|
||||||
elif name == 'farsight-local-candidates-prepared':
|
elif name == 'farsight-local-candidates-prepared':
|
||||||
self.candidates_ready = True
|
self.candidates_ready = True
|
||||||
self.session.accept_session()
|
if self.is_ready():
|
||||||
self.session.start_session()
|
self.session.on_session_state_changed(self)
|
||||||
elif name == 'farsight-new-local-candidate':
|
elif name == 'farsight-new-local-candidate':
|
||||||
candidate = message.structure['candidate']
|
candidate = message.structure['candidate']
|
||||||
self.candidates.append(candidate)
|
self.candidates.append(candidate)
|
||||||
|
@ -751,9 +847,6 @@ class JingleRTPContent(JingleContent):
|
||||||
#TODO: farsight.DIRECTION_BOTH only if senders='both'
|
#TODO: farsight.DIRECTION_BOTH only if senders='both'
|
||||||
self.p2pstream.set_property('direction', farsight.DIRECTION_BOTH)
|
self.p2pstream.set_property('direction', farsight.DIRECTION_BOTH)
|
||||||
self.session.content_negociated(self.media)
|
self.session.content_negociated(self.media)
|
||||||
#if not self.session.weinitiate: #FIXME: one more FIXME...
|
|
||||||
# self.session.send_content_accept(self.__content((xmpp.Node(
|
|
||||||
# 'description', payload=self.iter_codecs()),)))
|
|
||||||
elif name == 'farsight-error':
|
elif name == 'farsight-error':
|
||||||
print 'Farsight error #%d!' % message.structure['error-no']
|
print 'Farsight error #%d!' % message.structure['error-no']
|
||||||
print 'Message: %s' % message.structure['error-msg']
|
print 'Message: %s' % message.structure['error-msg']
|
||||||
|
@ -805,6 +898,11 @@ class JingleRTPContent(JingleContent):
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
self.__stop()
|
self.__stop()
|
||||||
|
|
||||||
|
def destroy(self):
|
||||||
|
JingleContent.destroy(self)
|
||||||
|
self.p2pstream.disconnect_by_func(self._on_src_pad_added)
|
||||||
|
self.pipeline.get_bus().disconnect_by_func(self._on_gst_message)
|
||||||
|
|
||||||
|
|
||||||
class JingleVoIP(JingleRTPContent):
|
class JingleVoIP(JingleRTPContent):
|
||||||
''' Jingle VoIP sessions consist of audio content transported
|
''' Jingle VoIP sessions consist of audio content transported
|
||||||
|
@ -830,8 +928,8 @@ class JingleVoIP(JingleRTPContent):
|
||||||
# the local parts
|
# the local parts
|
||||||
# TODO: use gconfaudiosink?
|
# TODO: use gconfaudiosink?
|
||||||
# sink = get_first_gst_element(['alsasink', 'osssink', 'autoaudiosink'])
|
# sink = get_first_gst_element(['alsasink', 'osssink', 'autoaudiosink'])
|
||||||
sink = gst.element_factory_make('alsasink')
|
self.sink = gst.element_factory_make('alsasink')
|
||||||
sink.set_property('sync', False)
|
self.sink.set_property('sync', False)
|
||||||
#sink.set_property('latency-time', 20000)
|
#sink.set_property('latency-time', 20000)
|
||||||
#sink.set_property('buffer-time', 80000)
|
#sink.set_property('buffer-time', 80000)
|
||||||
|
|
||||||
|
@ -843,21 +941,12 @@ class JingleVoIP(JingleRTPContent):
|
||||||
self.mic_volume.set_property('volume', 1)
|
self.mic_volume.set_property('volume', 1)
|
||||||
|
|
||||||
# link gst elements
|
# link gst elements
|
||||||
self.pipeline.add(sink, src_mic, self.mic_volume)
|
self.pipeline.add(self.sink, src_mic, self.mic_volume)
|
||||||
src_mic.link(self.mic_volume)
|
src_mic.link(self.mic_volume)
|
||||||
|
|
||||||
def src_pad_added (stream, pad, codec):
|
|
||||||
if not self.funnel:
|
|
||||||
self.funnel = gst.element_factory_make('fsfunnel')
|
|
||||||
self.pipeline.add(self.funnel)
|
|
||||||
self.funnel.set_state (gst.STATE_PLAYING)
|
|
||||||
sink.set_state (gst.STATE_PLAYING)
|
|
||||||
self.funnel.link(sink)
|
|
||||||
pad.link(self.funnel.get_pad('sink%d'))
|
|
||||||
|
|
||||||
self.mic_volume.get_pad('src').link(self.p2psession.get_property(
|
self.mic_volume.get_pad('src').link(self.p2psession.get_property(
|
||||||
'sink-pad'))
|
'sink-pad'))
|
||||||
self.p2pstream.connect('src-pad-added', src_pad_added)
|
self.p2pstream.connect('src-pad-added', self._on_src_pad_added)
|
||||||
|
|
||||||
# The following is needed for farsight to process ICE requests:
|
# The following is needed for farsight to process ICE requests:
|
||||||
self.pipeline.set_state(gst.STATE_PLAYING)
|
self.pipeline.set_state(gst.STATE_PLAYING)
|
||||||
|
@ -875,7 +964,7 @@ class JingleVideo(JingleRTPContent):
|
||||||
# sometimes it'll freeze...
|
# sometimes it'll freeze...
|
||||||
JingleRTPContent.setup_stream(self)
|
JingleRTPContent.setup_stream(self)
|
||||||
# the local parts
|
# the local parts
|
||||||
src_vid = gst.element_factory_make('v4l2src')
|
src_vid = gst.element_factory_make('videotestsrc')
|
||||||
videoscale = gst.element_factory_make('videoscale')
|
videoscale = gst.element_factory_make('videoscale')
|
||||||
caps = gst.element_factory_make('capsfilter')
|
caps = gst.element_factory_make('capsfilter')
|
||||||
caps.set_property('caps', gst.caps_from_string('video/x-raw-yuv, width=320, height=240'))
|
caps.set_property('caps', gst.caps_from_string('video/x-raw-yuv, width=320, height=240'))
|
||||||
|
@ -884,19 +973,11 @@ class JingleVideo(JingleRTPContent):
|
||||||
self.pipeline.add(src_vid, videoscale, caps, colorspace)
|
self.pipeline.add(src_vid, videoscale, caps, colorspace)
|
||||||
gst.element_link_many(src_vid, videoscale, caps, colorspace)
|
gst.element_link_many(src_vid, videoscale, caps, colorspace)
|
||||||
|
|
||||||
def src_pad_added (stream, pad, codec):
|
self.sink = gst.element_factory_make('xvimagesink')
|
||||||
if not self.funnel:
|
self.pipeline.add(self.sink)
|
||||||
self.funnel = gst.element_factory_make('fsfunnel')
|
|
||||||
self.pipeline.add(self.funnel)
|
|
||||||
videosink = gst.element_factory_make('xvimagesink')
|
|
||||||
self.pipeline.add(videosink)
|
|
||||||
self.funnel.set_state (gst.STATE_PLAYING)
|
|
||||||
videosink.set_state(gst.STATE_PLAYING)
|
|
||||||
self.funnel.link(videosink)
|
|
||||||
pad.link(self.funnel.get_pad('sink%d'))
|
|
||||||
|
|
||||||
colorspace.get_pad('src').link(self.p2psession.get_property('sink-pad'))
|
colorspace.get_pad('src').link(self.p2psession.get_property('sink-pad'))
|
||||||
self.p2pstream.connect('src-pad-added', src_pad_added)
|
self.p2pstream.connect('src-pad-added', self._on_src_pad_added)
|
||||||
|
|
||||||
# The following is needed for farsight to process ICE requests:
|
# The following is needed for farsight to process ICE requests:
|
||||||
self.pipeline.set_state(gst.STATE_PLAYING)
|
self.pipeline.set_state(gst.STATE_PLAYING)
|
||||||
|
@ -953,6 +1034,8 @@ class ConnectionJingle(object):
|
||||||
raise xmpp.NodeProcessed
|
raise xmpp.NodeProcessed
|
||||||
|
|
||||||
def startVoIP(self, jid):
|
def startVoIP(self, jid):
|
||||||
|
if self.get_jingle_session(jid, media='audio'):
|
||||||
|
return
|
||||||
jingle = self.get_jingle_session(jid, media='video')
|
jingle = self.get_jingle_session(jid, media='video')
|
||||||
if jingle:
|
if jingle:
|
||||||
jingle.add_content('voice', JingleVoIP(jingle))
|
jingle.add_content('voice', JingleVoIP(jingle))
|
||||||
|
@ -964,6 +1047,8 @@ class ConnectionJingle(object):
|
||||||
return jingle.sid
|
return jingle.sid
|
||||||
|
|
||||||
def startVideoIP(self, jid):
|
def startVideoIP(self, jid):
|
||||||
|
if self.get_jingle_session(jid, media='video'):
|
||||||
|
return
|
||||||
jingle = self.get_jingle_session(jid, media='audio')
|
jingle = self.get_jingle_session(jid, media='audio')
|
||||||
if jingle:
|
if jingle:
|
||||||
jingle.add_content('video', JingleVideo(jingle))
|
jingle.add_content('video', JingleVideo(jingle))
|
||||||
|
@ -981,14 +1066,10 @@ class ConnectionJingle(object):
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
elif media:
|
elif media:
|
||||||
if media == 'audio':
|
if media not in ('audio', 'video'):
|
||||||
cls = JingleVoIP
|
|
||||||
elif media == 'video':
|
|
||||||
cls = JingleVideo
|
|
||||||
else:
|
|
||||||
return None
|
return None
|
||||||
for session in self.__sessions.values():
|
for session in self.__sessions.values():
|
||||||
for content in session.contents.values():
|
if session.peerjid == jid and session.get_content(media):
|
||||||
if isinstance(content, cls):
|
|
||||||
return session
|
return session
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
|
@ -32,6 +32,7 @@
|
||||||
import gtk
|
import gtk
|
||||||
import gobject
|
import gobject
|
||||||
import os
|
import os
|
||||||
|
from weakref import WeakValueDictionary
|
||||||
|
|
||||||
import gtkgui_helpers
|
import gtkgui_helpers
|
||||||
import vcard
|
import vcard
|
||||||
|
@ -4442,11 +4443,15 @@ class GPGInfoWindow:
|
||||||
self.window.destroy()
|
self.window.destroy()
|
||||||
|
|
||||||
class VoIPCallReceivedDialog(object):
|
class VoIPCallReceivedDialog(object):
|
||||||
|
instances = WeakValueDictionary()
|
||||||
|
|
||||||
def __init__(self, account, contact_jid, sid):
|
def __init__(self, account, contact_jid, sid):
|
||||||
self.account = account
|
self.account = account
|
||||||
self.fjid = contact_jid
|
self.fjid = contact_jid
|
||||||
self.sid = sid
|
self.sid = sid
|
||||||
|
|
||||||
|
self.instances[(contact_jid, sid)] = self
|
||||||
|
|
||||||
xml = gtkgui_helpers.get_glade('voip_call_received_dialog.glade')
|
xml = gtkgui_helpers.get_glade('voip_call_received_dialog.glade')
|
||||||
xml.signal_autoconnect(self)
|
xml.signal_autoconnect(self)
|
||||||
|
|
||||||
|
@ -4461,9 +4466,17 @@ class VoIPCallReceivedDialog(object):
|
||||||
dialog = xml.get_widget('voip_call_received_messagedialog')
|
dialog = xml.get_widget('voip_call_received_messagedialog')
|
||||||
dialog.set_property('secondary-text',
|
dialog.set_property('secondary-text',
|
||||||
dialog.get_property('secondary-text') % {'contact': contact_text})
|
dialog.get_property('secondary-text') % {'contact': contact_text})
|
||||||
|
self._dialog = dialog
|
||||||
|
|
||||||
dialog.show_all()
|
dialog.show_all()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_dialog(cls, jid, sid):
|
||||||
|
if (jid, sid) in cls.instances:
|
||||||
|
return cls.instances[(jid, sid)]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
def on_voip_call_received_messagedialog_close(self, dialog):
|
def on_voip_call_received_messagedialog_close(self, dialog):
|
||||||
return self.on_voip_call_received_messagedialog_response(dialog,
|
return self.on_voip_call_received_messagedialog_response(dialog,
|
||||||
gtk.RESPONSE_NO)
|
gtk.RESPONSE_NO)
|
||||||
|
@ -4487,7 +4500,10 @@ class VoIPCallReceivedDialog(object):
|
||||||
if not contact:
|
if not contact:
|
||||||
return
|
return
|
||||||
ctrl = gajim.interface.new_chat(contact, self.account)
|
ctrl = gajim.interface.new_chat(contact, self.account)
|
||||||
|
if session.get_content('audio'):
|
||||||
ctrl.set_audio_state('connecting', self.sid)
|
ctrl.set_audio_state('connecting', self.sid)
|
||||||
|
if session.get_content('video'):
|
||||||
|
ctrl.set_video_state('connecting', self.sid)
|
||||||
else: # response==gtk.RESPONSE_NO
|
else: # response==gtk.RESPONSE_NO
|
||||||
session.decline_session()
|
session.decline_session()
|
||||||
|
|
||||||
|
|
12
src/gajim.py
12
src/gajim.py
|
@ -2119,7 +2119,10 @@ class Interface:
|
||||||
if not ctrl:
|
if not ctrl:
|
||||||
ctrl = self.msg_win_mgr.get_control(jid, account)
|
ctrl = self.msg_win_mgr.get_control(jid, account)
|
||||||
if ctrl:
|
if ctrl:
|
||||||
|
if 'audio' in content_types:
|
||||||
ctrl.set_audio_state('connection_received', sid)
|
ctrl.set_audio_state('connection_received', sid)
|
||||||
|
if 'video' in content_types:
|
||||||
|
ctrl.set_video_state('connection_received', sid)
|
||||||
|
|
||||||
if helpers.allow_popup_window(account):
|
if helpers.allow_popup_window(account):
|
||||||
dialogs.VoIPCallReceivedDialog(account, peerjid, sid)
|
dialogs.VoIPCallReceivedDialog(account, peerjid, sid)
|
||||||
|
@ -2141,14 +2144,17 @@ class Interface:
|
||||||
def handle_event_jingle_connected(self, account, data):
|
def handle_event_jingle_connected(self, account, data):
|
||||||
# ('JINGLE_CONNECTED', account, (peerjid, sid, media))
|
# ('JINGLE_CONNECTED', account, (peerjid, sid, media))
|
||||||
peerjid, sid, media = data
|
peerjid, sid, media = data
|
||||||
if media == 'audio':
|
if media in ('audio', 'video'):
|
||||||
jid = gajim.get_jid_without_resource(peerjid)
|
jid = gajim.get_jid_without_resource(peerjid)
|
||||||
resource = gajim.get_resource_from_jid(peerjid)
|
resource = gajim.get_resource_from_jid(peerjid)
|
||||||
ctrl = self.msg_win_mgr.get_control(peerjid, account)
|
ctrl = self.msg_win_mgr.get_control(peerjid, account)
|
||||||
if not ctrl:
|
if not ctrl:
|
||||||
ctrl = self.msg_win_mgr.get_control(jid, account)
|
ctrl = self.msg_win_mgr.get_control(jid, account)
|
||||||
if ctrl:
|
if ctrl:
|
||||||
|
if media == 'audio':
|
||||||
ctrl.set_audio_state('connected', sid)
|
ctrl.set_audio_state('connected', sid)
|
||||||
|
else:
|
||||||
|
ctrl.set_video_state('connected', sid)
|
||||||
|
|
||||||
def handle_event_jingle_disconnected(self, account, data):
|
def handle_event_jingle_disconnected(self, account, data):
|
||||||
# ('JINGLE_DISCONNECTED', account, (peerjid, sid, reason))
|
# ('JINGLE_DISCONNECTED', account, (peerjid, sid, reason))
|
||||||
|
@ -2160,6 +2166,10 @@ class Interface:
|
||||||
ctrl = self.msg_win_mgr.get_control(jid, account)
|
ctrl = self.msg_win_mgr.get_control(jid, account)
|
||||||
if ctrl:
|
if ctrl:
|
||||||
ctrl.set_audio_state('stop', sid=sid, reason=reason)
|
ctrl.set_audio_state('stop', sid=sid, reason=reason)
|
||||||
|
ctrl.set_video_state('stop', sid=sid, reason=reason)
|
||||||
|
dialog = dialogs.VoIPCallReceivedDialog.get_dialog(peerjid, sid)
|
||||||
|
if dialog:
|
||||||
|
dialog._dialog.destroy()
|
||||||
|
|
||||||
def handle_event_jingle_error(self, account, data):
|
def handle_event_jingle_error(self, account, data):
|
||||||
# ('JINGLE_ERROR', account, (peerjid, sid, reason))
|
# ('JINGLE_ERROR', account, (peerjid, sid, reason))
|
||||||
|
|
Loading…
Reference in New Issue