Changeset 8558 for branches/jingle
- Timestamp:
- 08/24/07 01:42:31 (16 months ago)
- Location:
- branches/jingle
- Files:
-
- 1 added
- 4 modified
-
data/glade/voip_call_received_dialog.glade (added)
-
src/common/farsight/test-jingle.py (modified) (1 diff)
-
src/common/jingle.py (modified) (22 diffs)
-
src/dialogs.py (modified) (1 diff)
-
src/gajim.py (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
branches/jingle/src/common/farsight/test-jingle.py
r8557 r8558 53 53 print "WW: stream.signal_native_candidates_prepared()" 54 54 print "WW: stream.start()" 55 exit()56 55 stream.signal_native_candidates_prepared() 57 56 stream.start() -
branches/jingle/src/common/jingle.py
r8532 r8558 13 13 ''' Handles the jingle signalling protocol. ''' 14 14 15 # note: if there will be more types of sessions (possibly file transfer, 16 # video...), split this file 17 15 18 import gajim 19 import gobject 16 20 import xmpp 17 21 18 # ugly hack 22 # ugly hack, fixed in farsight 0.1.24 19 23 import sys, dl, gst 20 24 sys.setdlopenflags(dl.RTLD_NOW | dl.RTLD_GLOBAL) 21 25 import farsight 22 26 sys.setdlopenflags(dl.RTLD_NOW | dl.RTLD_LOCAL) 27 28 def timeout_add_and_call(timeout, callable, *args, **kwargs): 29 ''' Call a callback once. If it returns True, add a timeout handler to call it more times. 30 Helper function. ''' 31 if callable(*args, **kwargs): 32 return gobject.timeout_add(timeout, callable, *args, **kwargs) 33 return -1 # gobject.source_remove will not object 23 34 24 35 class JingleStates(object): … … 28 39 active=2 29 40 30 class E xception(object): pass31 class WrongState(E xception): pass32 class No CommonCodec(Exception): pass41 class Error(Exception): pass 42 class WrongState(Error): pass 43 class NoSuchSession(Error): pass 33 44 34 45 class JingleSession(object): … … 55 66 self.sid=sid # sessionid 56 67 68 self.accepted=True # is this session accepted by user 69 57 70 # callbacks to call on proper contents 58 71 # use .prepend() to add new callbacks, especially when you're going … … 76 89 self.p2psession.connect('error', self.on_p2psession_error) 77 90 91 ''' Interaction with user ''' 92 def approveSession(self): 93 ''' Called when user accepts session in UI (when we aren't the initiator).''' 94 self.accepted=True 95 self.acceptSession() 96 97 def declineSession(self): 98 ''' Called when user declines session in UI (when we aren't the initiator, 99 or when the user wants to stop session completly. ''' 100 self.__sessionTerminate() 101 78 102 ''' Middle-level functions to manage contents. Handle local content 79 103 cache and send change notifications. ''' … … 83 107 The protocol prohibits changing that when pending. 84 108 Creator must be one of ('we', 'peer', 'initiator', 'responder')''' 109 assert creator in ('we', 'peer', 'initiator', 'responder') 110 85 111 if self.state==JingleStates.pending: 86 112 raise WrongState … … 105 131 pass 106 132 133 def acceptSession(self): 134 ''' Check if all contents and user agreed to start session. ''' 135 if not self.weinitiate and \ 136 self.accepted and \ 137 all(c.negotiated for c in self.contents.itervalues()): 138 self.__sessionAccept() 139 else: 107 140 ''' Middle-level function to do stanza exchange. ''' 108 141 def startSession(self): … … 112 145 def sendSessionInfo(self): pass 113 146 114 ''' Callbacks. '''147 ''' Session callbacks. ''' 115 148 def stanzaCB(self, stanza): 116 149 ''' A callback for ConnectionJingle. It gets stanza, then … … 153 186 def __sessionInitiateCB(self, stanza, jingle, error, action): 154 187 ''' We got a jingle session request from other entity, 155 therefore we are the receiver... Unpack the data. ''' 188 therefore we are the receiver... Unpack the data, 189 inform the user. ''' 156 190 self.initiator = jingle['initiator'] 157 191 self.responder = self.ourjid 158 192 self.peerjid = self.initiator 159 193 self.accepted = False # user did not accept this session yet 194 195 # TODO: If the initiator is unknown to the receiver (e.g., via presence 196 # subscription) and the receiver has a policy of not communicating via 197 # Jingle with unknown entities, it SHOULD return a <service-unavailable/> 198 # error. 199 200 # Lets check what kind of jingle session does the peer want 160 201 fail = True 202 contents = [] 161 203 for element in jingle.iterTags('content'): 162 204 # checking what kind of session this will be … … 166 208 # we've got voip content 167 209 self.addContent(element['name'], JingleVoiP(self), 'peer') 210 contents.append(('VOIP',)) 168 211 fail = False 169 212 213 # If there's no content we understand... 170 214 if fail: 171 215 # TODO: we should send <unsupported-content/> inside too 216 # TODO: delete this instance 172 217 self.connection.connection.send( 173 218 xmpp.Error(stanza, xmpp.NS_STANZAS + 'feature-not-implemented')) … … 176 221 177 222 self.state = JingleStates.pending 223 224 # Send event about starting a session 225 self.connection.dispatch('JINGLE_INCOMING', (self.initiator, self.sid, contents)) 178 226 179 227 def __broadcastCB(self, stanza, jingle, error, action): … … 199 247 return stanza, jingle 200 248 201 def __appendContent(self, jingle, content , full=True):249 def __appendContent(self, jingle, content): 202 250 ''' Append <content/> element to <jingle/> element, 203 251 with (full=True) or without (full=False) <content/> 204 252 children. ''' 205 if full: 206 jingle.addChild(node=content.toXML()) 207 else: 208 jingle.addChild('content', 209 attrs={'name': content.name, 'creator': content.creator}) 210 211 def __appendContents(self, jingle, full=True): 253 jingle.addChild('content', 254 attrs={'name': content.name, 'creator': content.creator}) 255 256 def __appendContents(self, jingle): 212 257 ''' Append all <content/> elements to <jingle/>.''' 213 258 # TODO: integrate with __appendContent? 214 259 # TODO: parameters 'name', 'content'? 215 260 for content in self.contents.values(): 216 self.__appendContent(jingle, content , full=full)261 self.__appendContent(jingle, content) 217 262 218 263 def __sessionInitiate(self): … … 220 265 stanza, jingle = self.__makeJingle('session-initiate') 221 266 self.__appendContents(jingle) 267 self.__broadcastCB(stanza, jingle, None, 'session-initiate-sent') 222 268 self.connection.connection.send(stanza) 223 269 224 270 def __sessionAccept(self): 225 271 assert self.state==JingleStates.pending 226 stanza, jingle = self.__jingle('session-accept') 227 self.__appendContents(jingle, False) 272 stanza, jingle = self.__makeJingle('session-accept') 273 self.__appendContents(jingle) 274 self.__broadcastCB(stanza, jingle, None, 'session-accept-sent') 228 275 self.connection.connection.send(stanza) 229 276 self.state=JingleStates.active … … 231 278 def __sessionInfo(self, payload=None): 232 279 assert self.state!=JingleStates.ended 233 stanza, jingle = self.__ jingle('session-info')280 stanza, jingle = self.__makeJingle('session-info') 234 281 if payload: 235 282 jingle.addChild(node=payload) … … 238 285 def __sessionTerminate(self): 239 286 assert self.state!=JingleStates.ended 240 stanza, jingle = self.__ jingle('session-terminate')287 stanza, jingle = self.__makeJingle('session-terminate') 241 288 self.connection.connection.send(stanza) 289 self.__broadcastCB(stanza, jingle, None, 'session-terminate-sent') 242 290 243 291 def __contentAdd(self): … … 252 300 def __contentRemove(self): 253 301 assert self.state!=JingleStates.ended 302 303 def sendContentAccept(self, content): 304 assert self.state!=JingleStates.ended 305 stanza, jingle = self.__makeJingle('content-accept') 306 jingle.addChild(node=content) 307 self.connection.connection.send(stanza) 254 308 255 309 def sendTransportInfo(self, content): … … 271 325 #self.creator = None 272 326 #self.name = None 327 self.negotiated = False # is this content already negotiated? 273 328 274 329 class JingleVoiP(JingleContent): … … 289 344 ''' Called when something related to our content was sent by peer. ''' 290 345 callbacks = { 346 # these are called when *we* get stanzas 291 347 'content-accept': [self.__getRemoteCodecsCB], 292 348 'content-add': [], 293 349 'content-modify': [], 294 350 'content-remove': [], 295 'session-accept': [self.__getRemoteCodecsCB ],351 'session-accept': [self.__getRemoteCodecsCB, self.__startMic], 296 352 'session-info': [], 297 353 'session-initiate': [self.__getRemoteCodecsCB], 298 'session-terminate': [ ],354 'session-terminate': [self.__stop], 299 355 'transport-info': [self.__transportInfoCB], 300 356 'iq-result': [], 301 357 'iq-error': [], 358 # these are called when *we* sent these stanzas 359 'session-initiate-sent': [self.__sessionInitiateSentCB], 360 'session-accept-sent': [self.__startMic], 361 'session-terminate-sent': [self.__stop], 302 362 }[action] 303 363 for callback in callbacks: 304 364 callback(stanza, content, error, action) 305 365 366 def __sessionInitiateSentCB(self, stanza, content, error, action): 367 ''' Add our things to session-initiate stanza. ''' 368 content.setAttr('profile', 'RTP/AVP') 369 content.addChild(xmpp.NS_JINGLE_AUDIO+' description', payload=self.iterCodecs()) 370 content.addChild(xmpp.NS_JINGLE_ICE_UDP+' transport') 371 306 372 def __getRemoteCodecsCB(self, stanza, content, error, action): 373 ''' Get peer codecs from what we get from peer. ''' 307 374 if self.got_codecs: return 308 375 … … 363 430 payload=payload) 364 431 365 def setupStream(self):366 self.p2pstream = self.session.p2psession.create_stream(367 farsight.MEDIA_TYPE_AUDIO, farsight.STREAM_DIRECTION_BOTH)368 self.p2pstream.set_property('transmitter', 'libjingle')369 self.p2pstream.connect('error', self.on_p2pstream_error)370 self.p2pstream.connect('new-active-candidate-pair', self.on_p2pstream_new_active_candidate_pair)371 self.p2pstream.connect('codec-changed', self.on_p2pstream_codec_changed)372 self.p2pstream.connect('native-candidates-prepared', self.on_p2pstream_native_candidates_prepared)373 self.p2pstream.connect('state-changed', self.on_p2pstream_state_changed)374 self.p2pstream.connect('new-native-candidate', self.on_p2pstream_new_native_candidate)375 376 self.p2pstream.set_remote_codecs(self.p2pstream.get_local_codecs())377 378 self.p2pstream.prepare_transports()379 380 self.p2pstream.set_active_codec(8) #???381 382 sink = gst.element_factory_make('alsasink')383 sink.set_property('sync', False)384 sink.set_property('latency-time', 20000)385 sink.set_property('buffer-time', 80000)386 387 src = gst.element_factory_make('audiotestsrc')388 src.set_property('blocksize', 320)389 #src.set_property('latency-time', 20000)390 src.set_property('is-live', True)391 392 self.p2pstream.set_sink(sink)393 self.p2pstream.set_source(src)394 395 432 def on_p2pstream_error(self, *whatever): pass 396 433 def on_p2pstream_new_active_candidate_pair(self, stream, native, remote): pass 397 434 def on_p2pstream_codec_changed(self, stream, codecid): pass 398 435 def on_p2pstream_native_candidates_prepared(self, *whatever): 399 for candidate in self.p2pstream.get_native_candidate_list():400 self.send_candidate(candidate) 436 pass 437 401 438 def on_p2pstream_state_changed(self, stream, state, dir): 402 439 if state==farsight.STREAM_STATE_CONNECTED: 403 440 stream.signal_native_candidates_prepared() 404 441 stream.start() 442 self.pipeline.set_state(gst.STATE_PLAYING) 443 444 self.negotiated = True 445 if not self.session.weinitiate: 446 self.session.sendContentAccept(self.__content((xmpp.Node('description', payload=self.iterCodecs()),))) 447 self.session.acceptSession() 448 405 449 def on_p2pstream_new_native_candidate(self, p2pstream, candidate_id): 406 450 candidates = p2pstream.get_native_candidate(candidate_id) … … 408 452 for candidate in candidates: 409 453 self.send_candidate(candidate) 454 410 455 def send_candidate(self, candidate): 411 456 attrs={ … … 443 488 yield xmpp.Node('payload-type', a, p) 444 489 490 ''' Things to control the gstreamer's pipeline ''' 491 def setupStream(self): 492 # the pipeline 493 self.pipeline = gst.Pipeline() 494 495 # the network part 496 self.p2pstream = self.session.p2psession.create_stream( 497 farsight.MEDIA_TYPE_AUDIO, farsight.STREAM_DIRECTION_BOTH) 498 self.p2pstream.set_pipeline(self.pipeline) 499 self.p2pstream.set_property('transmitter', 'libjingle') 500 self.p2pstream.connect('error', self.on_p2pstream_error) 501 self.p2pstream.connect('new-active-candidate-pair', self.on_p2pstream_new_active_candidate_pair) 502 self.p2pstream.connect('codec-changed', self.on_p2pstream_codec_changed) 503 self.p2pstream.connect('native-candidates-prepared', self.on_p2pstream_native_candidates_prepared) 504 self.p2pstream.connect('state-changed', self.on_p2pstream_state_changed) 505 self.p2pstream.connect('new-native-candidate', self.on_p2pstream_new_native_candidate) 506 507 self.p2pstream.set_remote_codecs(self.p2pstream.get_local_codecs()) 508 509 self.p2pstream.prepare_transports() 510 511 self.p2pstream.set_active_codec(8) #??? 512 513 # the local parts 514 # TODO: use gconfaudiosink? 515 sink = gst.element_factory_make('alsasink') 516 sink.set_property('sync', False) 517 sink.set_property('latency-time', 20000) 518 sink.set_property('buffer-time', 80000) 519 self.pipeline.add(sink) 520 521 self.src_signal = gst.element_factory_make('audiotestsrc') 522 self.src_signal.set_property('blocksize', 320) 523 self.src_signal.set_property('freq', 440) 524 self.pipeline.add(self.src_signal) 525 526 # TODO: use gconfaudiosrc? 527 self.src_mic = gst.element_factory_make('alsasrc') 528 self.src_mic.set_property('blocksize', 320) 529 self.pipeline.add(self.src_mic) 530 531 self.mic_volume = gst.element_factory_make('volume') 532 self.mic_volume.set_property('volume', 0) 533 self.pipeline.add(self.mic_volume) 534 535 self.adder = gst.element_factory_make('adder') 536 self.pipeline.add(self.adder) 537 538 # link gst elements 539 self.src_signal.link(self.adder) 540 self.src_mic.link(self.mic_volume) 541 self.mic_volume.link(self.adder) 542 543 # this will actually start before the pipeline will be started. 544 # no worries, though; it's only a ringing sound 545 def signal(): 546 while True: 547 self.src_signal.set_property('volume', 0.5) 548 yield True # wait 750 ms 549 yield True # wait 750 ms 550 self.src_signal.set_property('volume', 0) 551 yield True # wait 750 ms 552 self.signal_cb_id = timeout_add_and_call(750, signal().__iter__().next) 553 554 self.p2pstream.set_sink(sink) 555 self.p2pstream.set_source(self.adder) 556 557 def __startMic(self, *things): 558 gobject.source_remove(self.signal_cb_id) 559 self.src_signal.set_property('volume', 0) 560 self.mic_volume.set_property('volume', 1) 561 562 def __stop(self, *things): 563 self.pipeline.set_state(gst.STATE_NULL) 564 gobject.source_remove(self.signal_cb_id) 565 566 def __del__(self): 567 self.__stop() 568 445 569 class ConnectionJingle(object): 446 570 ''' This object depends on that it is a part of Connection class. ''' … … 485 609 # do we need to create a new jingle object 486 610 if (jid, sid) not in self.__sessions: 487 # TODO: we should check its type here...488 611 newjingle = JingleSession(con=self, weinitiate=False, jid=jid, sid=sid) 489 612 self.addJingle(newjingle) … … 502 625 jingle.addContent('voice', JingleVoiP(jingle)) 503 626 jingle.startSession() 627 def getJingleSession(self, jid, sid): 628 try: 629 return self.__sessions[(jid, sid)] 630 except KeyError: 631 raise NoSuchSession -
branches/jingle/src/dialogs.py
r8402 r8558 3299 3299 def on_close_window(self, widget): 3300 3300 self.window.destroy() 3301 3302 class VoIPCallReceivedDialog(object): 3303 def __init__(self, account, contact_jid, sid): 3304 self.account = account 3305 self.jid = contact_jid 3306 self.sid = sid 3307 3308 xml = gtkgui_helpers.get_glade('voip_call_received_dialog.glade') 3309 xml.signal_autoconnect(self) 3310 3311 contact = gajim.contacts.get_first_contact_from_jid(account, contact_jid) 3312 if contact and contact.name: 3313 contact_text = '%s (%s)' % (contact.name, contact_jid) 3314 else: 3315 contact_text = contact_jid 3316 3317 # do the substitution 3318 dialog = xml.get_widget('voip_call_received_messagedialog') 3319 dialog.set_property('secondary-text', 3320 dialog.get_property('secondary-text') % {'contact': contact_text}) 3321 3322 dialog.show_all() 3323 3324 def on_voip_call_received_messagedialog_close(self, dialog): 3325 return self.on_voip_call_received_messagedialog_response(dialog, gtk.RESPONSE_NO) 3326 def on_voip_call_received_messagedialog_response(self, dialog, response): 3327 # we've got response from user, either stop connecting or accept the call 3328 session = gajim.connections[self.account].getJingleSession(self.jid, self.sid) 3329 if response==gtk.RESPONSE_YES: 3330 session.approveSession() 3331 else: # response==gtk.RESPONSE_NO 3332 session.declineSession() 3333 3334 dialog.destroy() -
branches/jingle/src/gajim.py
r8495 r8558 1865 1865 is_modal = False, ok_handler = on_ok) 1866 1866 1867 def handle_event_jingle_incoming(self, account, data): 1868 # ('JINGLE_INCOMING', account, peer jid, sid, tuple-of-contents==(type, data...)) 1869 # TODO: conditional blocking if peer is not in roster 1870 1871 # unpack data 1872 peerjid, sid, contents = data 1873 content_types = set(c[0] for c in contents) 1874 1875 # check type of jingle session 1876 if 'VOIP' in content_types: 1877 # a voip session... 1878 # we now handle only voip, so the only thing we will do here is 1879 # not to return from function 1880 pass 1881 else: 1882 # unknown session type... it should be declined in common/jingle.py 1883 return 1884 1885 if helpers.allow_popup_window(account): 1886 dialogs.VoIPCallReceivedDialog(account, peerjid, sid) 1887 1888 # TODO: not checked 1889 self.add_event(account, peerjid, 'jingle-session', (sid, contents)) 1890 1891 if helpers.allow_showing_notification(account): 1892 # TODO: we should use another pixmap ;-) 1893 img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', 1894 'ft_request.png') 1895 txt = _('%s wants to start a jingle session.') % gajim.get_name_from_jid( 1896 account, jid) 1897 path = gtkgui_helpers.get_path_to_generic_or_avatar(img) 1898 event_type = _('Jingle Session Request') 1899 notify.popup(event_type, jid, account, 'jingle-request', 1900 path_to_image = path, title = event_type, text = txt) 1901 1867 1902 def read_sleepy(self): 1868 1903 '''Check idle status and change that status if needed''' … … 2193 2228 'SEARCH_RESULT': self.handle_event_search_result, 2194 2229 'RESOURCE_CONFLICT': self.handle_event_resource_conflict, 2230 'JINGLE_INCOMING': self.handle_event_jingle_incoming, 2195 2231 } 2196 2232 gajim.handlers = self.handlers
