blob: c12d477ffcf7069b17ea0a20a40f2a1cc56e669d [file] [log] [blame]
Benny Prijono9c461142008-07-10 22:41:20 +00001# $Id:$
2#
3# Object oriented PJSUA wrapper.
4#
5# Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
6#
7
8"""Multimedia communication client library based on SIP protocol.
9
10This implements a fully featured multimedia communication client
11library based on PJSIP stack (http://www.pjsip.org)
12
13
14FEATURES
15
16 - Session Initiation Protocol (SIP:
17 - Basic registration and call
18 - Multiple accounts
19 - Call hold, attended and unattended call transfer
20 - Presence
21 - Instant messaging
22 - Media stack:
23 - Audio
24 - Conferencing
25 - Narrowband and wideband
26 - Codecs: PCMA, PCMU, GSM, iLBC, Speex, G.722, L16
27 - RTP/RTCP
28 - Secure RTP (SRTP
29 - NAT traversal features
30 - Symmetric RTP
31 - STUN
32 - TURN
33 - ICE
34
35
36"""
37import _pjsua
38import thread
39
40class Error:
41 "Error exception class"
42 op_name = ""
43 obj = None
44 err_code = -1
45 _err_msg = ""
46
47 def __init__(self, op_name, obj, err_code, err_msg=""):
48 self.op_name = op_name
49 self.obj = obj
50 self.err_code = err_code
51 self._err_msg = err_msg
52
53 def err_msg(self):
54 "Retrieve the description of the error."
55 if self._err_msg != "":
56 return self._err_msg
57 self._err_msg = Lib.strerror(self.err_code)
58 return self._err_msg
59
60 def __str__(self):
61 return "Object: " + str(self.obj) + ", operation=" + self.op_name + \
62 ", error=" + self.err_msg()
63
64#
65# Constants
66#
67
68class TransportType:
69 "SIP transport type constants"
70 UNSPECIFIED = 0
71 UDP = 1
72 TCP = 2
73 TLS = 3
74 IPV6 = 128
75 UDP_IPV6 = UDP + IPV6
76 TCP_IPV6 = TCP + IPV6
77
78class TransportFlag:
79 "Transport flags"
80 RELIABLE = 1
81 SECURE = 2
82 DATAGRAM = 4
83
84class CallRole:
85 "Call role constants"
86 CALLER = 0
87 CALLEE = 1
88
89class CallState:
90 "Call state constants"
91 NULL = 0
92 CALLING = 1
93 INCOMING = 2
94 EARLY = 3
95 CONNECTING = 4
96 CONFIRMED = 5
97 DISCONNECTED = 6
98
99
100class MediaState:
101 "Call media state constants"
102 NONE = 0
103 ACTIVE = 1
104 LOCAL_HOLD = 2
105 REMOTE_HOLD = 3
106 ERROR = 4
107
108
109class MediaDir:
110 "Media direction constants"
111 NONE = 0
112 ENCODING = 1
113 DECODING = 2
114 ENCODING_DECODING = 3
115
116
117class PresenceActivity:
118 "Presence activities constants"
119 UNKNOWN = 0
120 AWAY = 1
121 BUSY = 2
122
123class TURNConnType:
124 "TURN connection type constants"
125 UDP = 17
126 TCP = 6
127 TLS = 255
128
129
130class UAConfig:
131 "User agent configuration class"
132 max_calls = 4
133 nameserver = []
134 stun_domain = ""
135 stun_host = ""
136 user_agent = "pjsip python"
137
138 def _cvt_from_pjsua(self, cfg):
139 self.max_calls = cfg.max_calls
140 self.thread_cnt = cfg.thread_cnt
141 self.nameserver = cfg.nameserver
142 self.stun_domain = cfg.stun_domain
143 self.stun_host = cfg.stun_host
144 self.user_agent = cfg.user_agent
145
146 def _cvt_to_pjsua(self):
147 cfg = _pjsua.config_default()
148 cfg.max_calls = self.max_calls
149 cfg.thread_cnt = 0
150 cfg.nameserver = self.nameserver
151 cfg.stun_domain = self.stun_domain
152 cfg.stun_host = self.stun_host
153 cfg.user_agent = self.user_agent
154 return cfg
155
156
157class LogConfig:
158 "Logging configuration class."
159 msg_logging = True
160 level = 5
161 console_level = 5
162 decor = 0
163 filename = ""
164 callback = None
165
166 def __init__(self, level=-1, filename="", callback=None,
167 console_level=-1):
168 self._cvt_from_pjsua(_pjsua.logging_config_default())
169 if level != -1:
170 self.level = level
171 if filename != "":
172 self.filename = filename
173 if callback != None:
174 self.callback = callback
175 if console_level != -1:
176 self.console_level = console_level
177
178 def _cvt_from_pjsua(self, cfg):
179 self.msg_logging = cfg.msg_logging
180 self.level = cfg.level
181 self.console_level = cfg.console_level
182 self.decor = cfg.decor
183 self.filename = cfg.log_filename
184 self.callback = cfg.cb
185
186 def _cvt_to_pjsua(self):
187 cfg = _pjsua.logging_config_default()
188 cfg.msg_logging = self.msg_logging
189 cfg.level = self.level
190 cfg.console_level = self.console_level
191 cfg.decor = self.decor
192 cfg.log_filename = self.filename
193 cfg.cb = self.callback
194 return cfg
195
196
197class MediaConfig:
198 "Media configuration class."
199 clock_rate = 16000
200 snd_clock_rate = 0
201 snd_auto_close_time = 5
202 channel_count = 1
203 audio_frame_ptime = 20
204 max_media_ports = 32
205 quality = 6
206 ptime = 0
207 no_vad = False
208 ilbc_mode = 30
209 tx_drop_pct = 0
210 rx_drop_pct = 0
211 ec_options = 0
212 ec_tail_len = 256
213 jb_min = -1
214 jb_max = -1
215 enable_ice = True
216 enable_turn = False
217 turn_server = ""
218 turn_conn_type = TURNConnType.UDP
219 turn_cred = None
220
221 def __init__(self):
222 default = _pjsua.media_config_default()
223 self._cvt_from_pjsua(default)
224
225 def _cvt_from_pjsua(self, cfg):
226 self.clock_rate = cfg.clock_rate
227 self.snd_clock_rate = cfg.snd_clock_rate
228 self.snd_auto_close_time = cfg.snd_auto_close_time
229 self.channel_count = cfg.channel_count
230 self.audio_frame_ptime = cfg.audio_frame_ptime
231 self.max_media_ports = cfg.max_media_ports
232 self.quality = cfg.quality
233 self.ptime = cfg.ptime
234 self.no_vad = cfg.no_vad
235 self.ilbc_mode = cfg.ilbc_mode
236 self.tx_drop_pct = cfg.tx_drop_pct
237 self.rx_drop_pct = cfg.rx_drop_pct
238 self.ec_options = cfg.ec_options
239 self.ec_tail_len = cfg.ec_tail_len
240 self.jb_min = cfg.jb_min
241 self.jb_max = cfg.jb_max
242 self.enable_ice = cfg.enable_ice
243 self.enable_turn = cfg.enable_turn
244 self.turn_server = cfg.turn_server
245 self.turn_conn_type = cfg.turn_conn_type
246 if cfg.turn_username:
247 self.turn_cred = AuthCred(cfg.turn_realm, cfg.turn_username,
248 cfg.turn_passwd, cfg.turn_passwd_type)
249 else:
250 self.turn_cred = None
251
252 def _cvt_to_pjsua(self):
253 cfg = _pjsua.media_config_default()
254 cfg.clock_rate = self.clock_rate
255 cfg.snd_clock_rate = self.snd_clock_rate
256 cfg.snd_auto_close_time = self.snd_auto_close_time
257 cfg.channel_count = self.channel_count
258 cfg.audio_frame_ptime = self.audio_frame_ptime
259 cfg.max_media_ports = self.max_media_ports
260 cfg.quality = self.quality
261 cfg.ptime = self.ptime
262 cfg.no_vad = self.no_vad
263 cfg.ilbc_mode = self.ilbc_mode
264 cfg.tx_drop_pct = self.tx_drop_pct
265 cfg.rx_drop_pct = self.rx_drop_pct
266 cfg.ec_options = self.ec_options
267 cfg.ec_tail_len = self.ec_tail_len
268 cfg.jb_min = self.jb_min
269 cfg.jb_max = self.jb_max
270 cfg.enable_ice = self.enable_ice
271 cfg.enable_turn = self.enable_turn
272 cfg.turn_server = self.turn_server
273 cfg.turn_conn_type = self.turn_conn_type
274 if self.turn_cred:
275 cfg.turn_realm = self.turn_cred.realm
276 cfg.turn_username = self.turn_cred.username
277 cfg.turn_passwd_type = self.turn_cred.passwd_type
278 cfg.turn_passwd = self.turn_cred.passwd
279 return cfg
280
281
282class TransportConfig:
283 "SIP transport configuration class."
284 port = 0
285 bound_addr = ""
286 public_addr = ""
287
288 def __init__(self, port=5060,
289 bound_addr="", public_addr=""):
290 self.port = port
291 self.bound_addr = bound_addr
292 self.public_addr = public_addr
293
294 def _cvt_to_pjsua(self):
295 cfg = _pjsua.transport_config_default()
296 cfg.port = self.port
297 cfg.bound_addr = self.bound_addr
298 cfg.public_addr = self.public_addr
299 return cfg
300
301
302class TransportInfo:
303 """SIP transport info.
304 """
305 type = ""
306 description = ""
307 is_reliable = False
308 is_secure = False
309 is_datagram = False
310 host = ""
311 port = 0
312 ref_cnt = 0
313
314 def __init__(self, ti):
315 self.type = ti.type_name
316 self.description = ti.info
317 self.is_reliable = (ti.flag & TransportFlag.RELIABLE)
318 self.is_secure = (ti.flag & TransportFlag.SECURE)
319 self.is_datagram = (ti.flag & TransportFlag.DATAGRAM)
320 self.host = ti.addr
321 self.port = ti.port
322 self.ref_cnt = ti.usage_count
323
324
325class Transport:
326 "SIP transport class."
327 _id = -1
328 _lib = None
329 _obj_name = ""
330
331 def __init__(self, lib, id):
332 self._lib = lib
333 self._id = id
334 self._obj_name = "Transport " + self.info().description
335
336 def __str__(self):
337 return self._obj_name
338
339 def info(self):
340 """Get TransportInfo.
341 """
342 ti = _pjsua.transport_get_info(self._id)
343 if not ti:
344 self._lib._err_check("info()", self, -1, "Invalid transport")
345 return TransportInfo(ti)
346
347 def enable(self):
348 err = _pjsua.transport_set_enable(self._id, True)
349 self._lib._err_check("enable()", self, err)
350
351 def disable(self):
352 err = _pjsua.transport_set_enable(self._id, 0)
353 self._lib._err_check("disable()", self, err)
354
355 def close(self, force=False):
356 err = _pjsua.transport_close(self._id, force)
357 self._lib._err_check("close()", self, err)
358
359
360class SIPUri:
361 scheme = ""
362 user = ""
363 host = ""
364 port = 0
365 transport = ""
366
367 def __init__(self, uri):
368 self.decode(uri)
369
370 def decode(self, uri):
371 self.scheme, self.user, self.host, self.port, self.transport = \
372 _pjsua.parse_simple_uri(uri)
373
374 def encode(self):
375 output = self.scheme + ":"
376 if self.user and len(self.user):
377 output = output + self.user + "@"
378 output = output + self.host
379 if self.port:
380 output = output + ":" + output(self.port)
381 if self.transport:
382 output = output + ";transport=" + self.transport
383 return output
384
385class AuthCred:
386 "Authentication credential."
387 scheme = "Digest"
388 realm = "*"
389 username = ""
390 passwd_type = 0
391 passwd = ""
392
393 def __init__(self, realm, username, passwd, scheme="Digest", passwd_type=0):
394 self.scheme = scheme
395 self.realm = realm
396 self.username = username
397 self.passwd_type = passwd_type
398 self.passwd = passwd
399
400
401class AccountConfig:
402 """ This describes account configuration.
403 """
404 priority = 0
405 id = ""
406 force_contact = ""
407 reg_uri = ""
408 reg_timeout = 0
409 require_100rel = False
410 publish_enabled = False
411 pidf_tuple_id = ""
412 proxy = []
413 auth_cred = []
414 auth_initial_send = False
415 auth_initial_algorithm = ""
416 transport_id = -1
417 allow_contact_rewrite = True
418 ka_interval = 15
419 ka_data = "\r\n"
420 use_srtp = 0
421 srtp_secure_signaling = 1
422
423 def __init__(self, domain="", username="", password="",
424 display="", registrar="", proxy=""):
425 """
426 Construct account config. If domain argument is specified,
427 a typical configuration will be built.
428
429 Keyword arguments:
430 domain -- domain name of the server.
431 username -- user name.
432 password -- plain-text password.
433 display -- optional display name for the user name.
434 registrar -- the registrar URI. If domain name is specified
435 and this argument is empty, the registrar URI
436 will be constructed from the domain name.
437 proxy -- the proxy URI. If domain name is specified
438 and this argument is empty, the proxy URI
439 will be constructed from the domain name.
440
441 """
442 default = _pjsua.acc_config_default()
443 self._cvt_from_pjsua(default)
444 if domain!="":
445 self.build_config(domain, username, password,
446 display, registrar, proxy)
447
448 def build_config(self, domain, username, password, display="",
449 registrar="", proxy=""):
450 """
451 Construct account config. If domain argument is specified,
452 a typical configuration will be built.
453
454 Keyword arguments:
455 domain -- domain name of the server.
456 username -- user name.
457 password -- plain-text password.
458 display -- optional display name for the user name.
459 registrar -- the registrar URI. If domain name is specified
460 and this argument is empty, the registrar URI
461 will be constructed from the domain name.
462 proxy -- the proxy URI. If domain name is specified
463 and this argument is empty, the proxy URI
464 will be constructed from the domain name.
465
466 """
467 if display != "":
468 display = display + " "
469 userpart = username
470 if userpart != "":
471 userpart = userpart + "@"
472 self.id = display + "<sip:" + userpart + domain + ">"
473 self.reg_uri = registrar
474 if self.reg_uri == "":
475 self.reg_uri = "sip:" + domain
476 if proxy == "":
477 proxy = "sip:" + domain + ";lr"
478 if proxy.find(";lr") == -1:
479 proxy = proxy + ";lr"
480 self.proxy.append(proxy)
481 if username != "":
482 self.auth_cred.append(AuthCred("*", username, password))
483
484 def _cvt_from_pjsua(self, cfg):
485 self.priority = cfg.priority
486 self.id = cfg.id
487 self.force_contact = cfg.force_contact
488 self.reg_uri = cfg.reg_uri
489 self.reg_timeout = cfg.reg_timeout
490 self.require_100rel = cfg.require_100rel
491 self.publish_enabled = cfg.publish_enabled
492 self.pidf_tuple_id = cfg.pidf_tuple_id
493 self.proxy = cfg.proxy
494 for cred in cfg.cred_info:
495 self.auth_cred.append(AuthCred(cred.realm, cred.username,
496 cred.data, cred.scheme,
497 cred.data_type))
498 self.auth_initial_send = cfg.auth_initial_send
499 self.auth_initial_algorithm = cfg.auth_initial_algorithm
500 self.transport_id = cfg.transport_id
501 self.allow_contact_rewrite = cfg.allow_contact_rewrite
502 self.ka_interval = cfg.ka_interval
503 self.ka_data = cfg.ka_data
504 self.use_srtp = cfg.use_srtp
505 self.srtp_secure_signaling = cfg.srtp_secure_signaling
506
507 def _cvt_to_pjsua(self):
508 cfg = _pjsua.acc_config_default()
509 cfg.priority = self.priority
510 cfg.id = self.id
511 cfg.force_contact = self.force_contact
512 cfg.reg_uri = self.reg_uri
513 cfg.reg_timeout = self.reg_timeout
514 cfg.require_100rel = self.require_100rel
515 cfg.publish_enabled = self.publish_enabled
516 cfg.pidf_tuple_id = self.pidf_tuple_id
517 cfg.proxy = self.proxy
518 for cred in self.auth_cred:
519 c = _pjsua.Pjsip_Cred_Info()
520 c.realm = cred.realm
521 c.scheme = cred.scheme
522 c.username = cred.username
523 c.data_type = cred.passwd_type
524 c.data = cred.passwd
525 cfg.cred_info.append(c)
526 cfg.auth_initial_send = self.auth_initial_send
527 cfg.auth_initial_algorithm = self.auth_initial_algorithm
528 cfg.transport_id = self.transport_id
529 cfg.allow_contact_rewrite = self.allow_contact_rewrite
530 cfg.ka_interval = self.ka_interval
531 cfg.ka_data = self.ka_data
532 cfg.use_srtp = self.use_srtp
533 cfg.srtp_secure_signaling = self.srtp_secure_signaling
534 return cfg
535
536
537# Account information
538class AccountInfo:
539 """This describes Account info. Application retrives account info
540 with Account.info().
541
542 """
543 is_default = False
544 uri = ""
545 reg_active = False
546 reg_expires = -1
547 reg_status = 0
548 reg_reason = ""
549 online_status = False
550 online_text = ""
551
552 def __init__(self, ai):
553 self.is_default = ai.is_default
554 self.uri = ai.acc_uri
555 self.reg_active = ai.has_registration
556 self.reg_expires = ai.expires
557 self.reg_status = ai.status
558 self.reg_reason = ai.status_text
559 self.online_status = ai.online_status
560 self.online_text = ai.online_status_text
561
562# Account callback
563class AccountCallback:
564 """Class to receive notifications on account's events.
565
566 Derive a class from this class and register it to the Account object
567 using Account.set_callback() to start receiving events from the Account
568 object.
569 """
570 account = None
571
572 def __init__(self, account):
573 self.account = account
574
575 def on_reg_state(self):
576 """Notification that the registration status has changed.
577 """
578 pass
579
580 def on_incoming_call(self, call):
581 """Notification about incoming call.
582
583 Unless this callback is implemented, the default behavior is to
584 reject the call with default status code.
585
586 Keyword arguments:
587 call -- the new incoming call
588 """
589 call.hangup()
590
591 def on_pager(self, from_uri, contact, mime_type, body):
592 """
593 Notification that incoming instant message is received on
594 this account.
595
596 Keyword arguments:
597 from_uri -- sender's URI
598 contact -- sender's Contact URI
599 mime_type -- MIME type of the instant message body
600 body -- the instant message body
601
602 """
603 pass
604
605 def on_pager_status(self, to_uri, body, im_id, code, reason):
606 """
607 Notification about the delivery status of previously sent
608 instant message.
609
610 Keyword arguments:
611 to_uri -- the destination URI of the message
612 body -- the message body
613 im_id -- message ID
614 code -- SIP status code
615 reason -- SIP reason phrase
616
617 """
618 pass
619
620 def on_typing(self, from_uri, contact, is_typing):
621 """
622 Notification that remote is typing or stop typing.
623
624 Keyword arguments:
625 buddy -- Buddy object for the sender, if found. Otherwise
626 this will be None
627 from_uri -- sender's URI of the indication
628 contact -- sender's contact URI
629 is_typing -- boolean to indicate whether remote is currently
630 typing an instant message.
631
632 """
633 pass
634
635
636
637class Account:
638 """This describes SIP account class.
639
640 PJSUA accounts provide identity (or identities) of the user who is
641 currently using the application. In SIP terms, the identity is used
642 as the From header in outgoing requests.
643
644 Account may or may not have client registration associated with it.
645 An account is also associated with route set and some authentication
646 credentials, which are used when sending SIP request messages using
647 the account. An account also has presence's online status, which
648 will be reported to remote peer when they subscribe to the account's
649 presence, or which is published to a presence server if presence
650 publication is enabled for the account.
651
652 Account is created with Lib.create_account(). At least one account
653 MUST be created. If no user association is required, application can
654 create a userless account by calling Lib.create_account_for_transport().
655 A userless account identifies local endpoint instead of a particular
656 user, and it correspond with a particular transport instance.
657
658 Also one account must be set as the default account, which is used as
659 the account to use when PJSUA fails to match a request with any other
660 accounts.
661
662 """
663 _id = -1
664 _lib = None
665 _cb = AccountCallback(None)
666 _obj_name = ""
667
668 def __init__(self, lib, id):
669 """Construct this class. This is normally called by Lib class and
670 not by application.
671
672 Keyword arguments:
673 lib -- the Lib instance.
674 id -- the pjsua account ID.
675 """
676 _cb = AccountCallback(self)
677 self._id = id
678 self._lib = lib
679 self._lib._associate_account(self._id, self)
680 self._obj_name = "Account " + self.info().uri
681
682 def __del__(self):
683 self._lib._disassociate_account(self._id, self)
684
685 def __str__(self):
686 return self._obj_name
687
688 def info(self):
689 """Retrieve AccountInfo for this account.
690 """
691 ai = _pjsua.acc_get_info(self._id)
692 if ai==None:
693 self._lib._err_check("info()", self, -1, "Invalid account")
694 return AccountInfo(ai)
695
696 def is_valid(self):
697 """
698 Check if this account is still valid.
699
700 """
701 return _pjsua.acc_is_valid(self._id)
702
703 def set_callback(self, cb):
704 """Register callback to receive notifications from this object.
705
706 Keyword argument:
707 cb -- AccountCallback instance.
708
709 """
710 if cb:
711 self._cb = cb
712 else:
713 self._cb = AccountCallback(self)
714
715 def set_default(self):
716 """ Set this account as default account to send outgoing requests
717 and as the account to receive incoming requests when more exact
718 matching criteria fails.
719 """
720 err = _pjsua.acc_set_default(self._id)
721 self._lib._err_check("set_default()", self, err)
722
723 def is_default(self):
724 """ Check if this account is the default account.
725
726 """
727 def_id = _pjsua.acc_get_default()
728 return self.is_valid() and def_id==self._id
729
730 def delete(self):
731 """ Delete this account.
732
733 """
734 err = _pjsua.acc_del(self._id)
735 self._lib._err_check("delete()", self, err)
736
737 def set_basic_status(self, is_online):
738 """ Set basic presence status of this account.
739
740 Keyword argument:
741 is_online -- boolean to indicate basic presence availability.
742
743 """
744 err = _pjsua.acc_set_online_status(self._id, is_online)
745 self._lib._err_check("set_basic_status()", self, err)
746
747 def set_presence_status(self, is_online,
748 activity=PresenceActivity.UNKNOWN,
749 pres_text="", rpid_id=""):
750 """ Set presence status of this account.
751
752 Keyword arguments:
753 is_online -- boolean to indicate basic presence availability
754 activity -- value from PresenceActivity
755 pres_text -- optional string to convey additional information about
756 the activity (such as "On the phone")
757 rpid_id -- optional string to be placed as RPID ID.
758
759 """
760 err = _pjsua.acc_set_online_status2(self._id, is_online, activity,
761 pres_text, rpid_id)
762 self._lib._err_check("set_presence_status()", self, err)
763
764 def set_registration(self, renew):
765 """Manually renew registration or unregister from the server.
766
767 Keyword argument:
768 renew -- boolean to indicate whether registration is renewed.
769 Setting this value for False will trigger unregistration.
770
771 """
772 err = _pjsua.acc_set_registration(self._id, renew)
773 self._lib._err_check("set_registration()", self, err)
774
775 def has_registration(self):
776 """Returns True if registration is active for this account.
777
778 """
779 acc_info = _pjsua.acc_get_info(self._id)
780 if not acc_info:
781 self._lib._err_check("has_registration()", self, -1,
782 "invalid account")
783 return acc_info.has_registration
784
785 def set_transport(self, transport):
786 """Set this account to only use the specified transport to send
787 outgoing requests.
788
789 Keyword argument:
790 transport -- Transport object.
791
792 """
793 err = _pjsua.acc_set_transport(self._id, transport._id)
794 self._lib._err_check("set_transport()", self, err)
795
796 def make_call(self, dst_uri, hdr_list=None):
797 """Make outgoing call to the specified URI.
798
799 Keyword arguments:
800 dst_uri -- Destination SIP URI.
801 hdr_list -- Optional list of headers to be sent with outgoing
802 INVITE
803
804 """
805 err, cid = _pjsua.call_make_call(self._id, dst_uri, 0,
806 0, Lib._create_msg_data(hdr_list))
807 self._lib._err_check("make_call()", self, err)
808 return Call(self._lib, cid)
809
810 def add_buddy(self, uri):
811 """Add new buddy.
812
813 Keyword argument:
814 uri -- SIP URI of the buddy
815
816 Return:
817 Buddy object
818 """
819 buddy_cfg = _pjsua.buddy_config_default()
820 buddy_cfg.uri = uri
821 buddy_cfg.subscribe = False
822 err, buddy_id = _pjsua.buddy_add(buddy_cfg)
823 self._lib._err_check("add_buddy()", self, err)
824 return Buddy(self._lib, buddy_id, self)
825
826
827class CallCallback:
828 """Class to receive event notification from Call objects.
829
830 Use Call.set_callback() method to install instance of this callback
831 class to receive event notifications from the call object.
832 """
833 call = None
834
835 def __init__(self, call):
836 self.call = call
837
838 def on_state(self):
839 """Notification that the call's state has changed.
840
841 """
842 pass
843
844 def on_media_state(self):
845 """Notification that the call's media state has changed.
846
847 """
848 pass
849
850 def on_dtmf_digit(self, digits):
851 """Notification on incoming DTMF digits.
852
853 Keyword argument:
854 digits -- string containing the received digits.
855
856 """
857 pass
858
859 def on_transfer_request(self, dst, code):
860 """Notification that call is being transfered by remote party.
861
862 Application can decide to accept/reject transfer request by returning
863 code greater than or equal to 500. The default behavior is to accept
864 the transfer by returning 202.
865
866 Keyword arguments:
867 dst -- string containing the destination URI
868 code -- the suggested status code to return to accept the request.
869
870 Return:
871 the callback should return 202 to accept the request, or 300-699 to
872 reject the request.
873
874 """
875 return code
876
877 def on_transfer_status(self, code, reason, final, cont):
878 """
879 Notification about the status of previous call transfer request.
880
881 Keyword arguments:
882 code -- SIP status code to indicate completion status.
883 text -- SIP status reason phrase.
884 final -- if True then this is a final status and no further
885 notifications will be sent for this call transfer
886 status.
887 cont -- suggested return value.
888
889 Return:
890 If the callback returns false then no further notification will
891 be sent for the transfer request for this call.
892
893 """
894 return cont
895
896 def on_replace_request(self, code, reason):
897 """Notification when incoming INVITE with Replaces header is received.
898
899 Application may reject the request by returning value greather than
900 or equal to 500. The default behavior is to accept the request.
901
902 Keyword arguments:
903 code -- default status code to return
904 reason -- default reason phrase to return
905
906 Return:
907 The callback should return (code, reason) tuple.
908
909 """
910 return code, reason
911
912 def on_replaced(self, new_call):
913 """
914 Notification that this call will be replaced with new_call.
915 After this callback is called, this call will be disconnected.
916
917 Keyword arguments:
918 new_call -- the new call that will replace this call.
919 """
920 pass
921
922 def on_pager(self, mime_type, body):
923 """
924 Notification that incoming instant message is received on
925 this call.
926
927 Keyword arguments:
928 mime_type -- MIME type of the instant message body.
929 body -- the instant message body.
930
931 """
932 pass
933
934 def on_pager_status(self, body, im_id, code, reason):
935 """
936 Notification about the delivery status of previously sent
937 instant message.
938
939 Keyword arguments:
940 body -- message body
941 im_id -- message ID
942 code -- SIP status code
943 reason -- SIP reason phrase
944
945 """
946 pass
947
948 def on_typing(self, is_typing):
949 """
950 Notification that remote is typing or stop typing.
951
952 Keyword arguments:
953 is_typing -- boolean to indicate whether remote is currently
954 typing an instant message.
955
956 """
957 pass
958
959
960class CallInfo:
961 """This structure contains various information about Call.
962
963 Application may retrieve this information with Call.info().
964 """
965 role = CallRole.CALLER
966 account = None
967 uri = ""
968 contact = ""
969 remote_uri = ""
970 remote_contact = ""
971 sip_call_id = ""
972 state = CallState.NULL
973 state_text = ""
974 last_code = 0
975 last_reason = ""
976 media_state = MediaState.NONE
977 media_dir = MediaDir.NONE
978 conf_slot = -1
979 call_time = 0
980 total_time = 0
981
982 def __init__(self, lib=None, ci=None):
983 if lib and ci:
984 self._cvt_from_pjsua(lib, ci)
985
986 def _cvt_from_pjsua(self, lib, ci):
987 self.role = ci.role
988 self.account = lib._lookup_account(ci.acc_id)
989 self.uri = ci.local_info
990 self.contact = ci.local_contact
991 self.remote_uri = ci.remote_info
992 self.remote_contact = ci.remote_contact
993 self.sip_call_id = ci.call_id
994 self.state = ci.state
995 self.state_text = ci.state_text
996 self.last_code = ci.last_status
997 self.last_reason = ci.last_status_text
998 self.media_state = ci.media_status
999 self.media_dir = ci.media_dir
1000 self.conf_slot = ci.conf_slot
1001 self.call_time = ci.connect_duration.sec
1002 self.total_time = ci.total_duration.sec
1003
1004
1005class Call:
1006 """This class represents SIP call.
1007
1008 Application initiates outgoing call with Account.make_call(), and
1009 incoming calls are reported in AccountCallback.on_incoming_call().
1010 """
1011 _id = -1
1012 _cb = None
1013 _lib = None
1014 _obj_name = ""
1015
1016 def __init__(self, lib, call_id):
1017 self._cb = CallCallback(self)
1018 self._id = call_id
1019 self._lib = lib
1020 self._lib._associate_call(call_id, self)
1021 self._obj_name = "Call " + self.info().remote_uri
1022
1023 def __del__(self):
1024 self._lib._disassociate_call(self._id, self)
1025
1026 def __str__(self):
1027 return self._obj_name
1028
1029 def set_callback(self, cb):
1030 """
1031 Set callback object to retrieve event notifications from this call.
1032
1033 Keyword arguments:
1034 cb -- CallCallback instance.
1035 """
1036 if cb:
1037 self._cb = cb
1038 else:
1039 self._cb = CallCallback(self)
1040
1041 def info(self):
1042 """
1043 Get the CallInfo.
1044 """
1045 ci = _pjsua.call_get_info(self._id)
1046 if not ci:
1047 self._lib._err_check("info", self, -1, "Invalid call")
1048 return CallInfo(self._lib, ci)
1049
1050 def is_valid(self):
1051 """
1052 Check if this call is still valid.
1053 """
1054 return _pjsua.call_is_active(self._id)
1055
1056 def dump_status(self, with_media=True, indent="", max_len=1024):
1057 """
1058 Dump the call status.
1059 """
1060 return _pjsua.call_dump(self._id, with_media, max_len, indent)
1061
1062 def answer(self, code=200, reason="", hdr_list=None):
1063 """
1064 Send provisional or final response to incoming call.
1065
1066 Keyword arguments:
1067 code -- SIP status code.
1068 reason -- Reason phrase. Put empty to send default reason
1069 phrase for the status code.
1070 hdr_list -- Optional list of headers to be sent with the
1071 INVITE response.
1072
1073 """
1074 err = _pjsua.call_answer(self._id, code, reason,
1075 Lib._create_msg_data(hdr_list))
1076 self._lib._err_check("answer()", self, err)
1077
1078 def hangup(self, code=603, reason="", hdr_list=None):
1079 """
1080 Terminate the call.
1081
1082 Keyword arguments:
1083 code -- SIP status code.
1084 reason -- Reason phrase. Put empty to send default reason
1085 phrase for the status code.
1086 hdr_list -- Optional list of headers to be sent with the
1087 message.
1088
1089 """
1090 err = _pjsua.call_hangup(self._id, code, reason,
1091 Lib._create_msg_data(hdr_list))
1092 self._lib._err_check("hangup()", self, err)
1093
1094 def hold(self, hdr_list=None):
1095 """
1096 Put the call on hold.
1097
1098 Keyword arguments:
1099 hdr_list -- Optional list of headers to be sent with the
1100 message.
1101 """
1102 err = _pjsua.call_set_hold(self._id, Lib._create_msg_data(hdr_list))
1103 self._lib._err_check("hold()", self, err)
1104
1105 def unhold(self, hdr_list=None):
1106 """
1107 Release the call from hold.
1108
1109 Keyword arguments:
1110 hdr_list -- Optional list of headers to be sent with the
1111 message.
1112
1113 """
1114 err = _pjsua.call_reinvite(self._id, True,
1115 Lib._create_msg_data(hdr_list))
1116 self._lib._err_check("unhold()", self, err)
1117
1118 def reinvite(self, hdr_list=None):
1119 """
1120 Send re-INVITE and optionally offer new codecs to use.
1121
1122 Keyword arguments:
1123 hdr_list -- Optional list of headers to be sent with the
1124 message.
1125
1126 """
1127 err = _pjsua.call_reinvite(self._id, True,
1128 Lib._create_msg_data(hdr_list))
1129 self._lib._err_check("reinvite()", self, err)
1130
1131 def update(self, hdr_list=None, options=0):
1132 """
1133 Send UPDATE and optionally offer new codecs to use.
1134
1135 Keyword arguments:
1136 hdr_list -- Optional list of headers to be sent with the
1137 message.
1138 options -- Must be zero for now.
1139
1140 """
1141 err = _pjsua.call_update(self._id, options,
1142 Lib._create_msg_data(hdr_list))
1143 self._lib._err_check("update()", self, err)
1144
1145 def transfer(self, dest_uri, hdr_list=None):
1146 """
1147 Transfer the call to new destination.
1148
1149 Keyword arguments:
1150 dest_uri -- Specify the SIP URI to transfer the call to.
1151 hdr_list -- Optional list of headers to be sent with the
1152 message.
1153
1154 """
1155 err = _pjsua.call_xfer(self._id, dest_uri,
1156 Lib._create_msg_data(hdr_list))
1157 self._lib._err_check("transfer()", self, err)
1158
1159 def transfer_to_call(self, call, hdr_list=None, options=0):
1160 """
1161 Attended call transfer.
1162
1163 Keyword arguments:
1164 call -- The Call object to transfer call to.
1165 hdr_list -- Optional list of headers to be sent with the
1166 message.
1167 options -- Must be zero for now.
1168
1169 """
1170 err = _pjsua.call_xfer_replaces(self._id, call._id, options,
1171 Lib._create_msg_data(hdr_list))
1172 self._lib._err_check("transfer_to_call()", self, err)
1173
1174 def dial_dtmf(self, digits):
1175 """
1176 Send DTMF digits with RTP event package.
1177
1178 Keyword arguments:
1179 digits -- DTMF digit string.
1180
1181 """
1182 err = _pjsua.call_dial_dtmf(self._id, digits)
1183 self._lib._err_check("dial_dtmf()", self, err)
1184
1185 def send_request(self, method, hdr_list=None, content_type=None,
1186 body=None):
1187 """
1188 Send arbitrary request to remote call.
1189
1190 This is useful for example to send INFO request. Note that this
1191 function should not be used to send request that will change the
1192 call state such as CANCEL or BYE.
1193
1194 Keyword arguments:
1195 method -- SIP method name.
1196 hdr_list -- Optional header list to be sent with the request.
1197 content_type -- Content type to describe the body, if the body
1198 is present
1199 body -- Optional SIP message body.
1200
1201 """
1202 if hdr_list and body:
1203 msg_data = _pjsua.Msg_Data()
1204 if hdr_list:
1205 msg_data.hdr_list = hdr_list
1206 if content_type:
1207 msg_data.content_type = content_type
1208 if body:
1209 msg_data.msg_body = body
1210 else:
1211 msg_data = None
1212
1213 err = _pjsua.call_send_request(self._id, method, msg_data)
1214 self._lib._err_check("send_request()", self, err)
1215
1216
1217class BuddyInfo:
1218 """This class contains information about Buddy. Application may
1219 retrieve this information by calling Buddy.info().
1220 """
1221 uri = ""
1222 contact = ""
1223 online_status = 0
1224 online_text = ""
1225 activity = PresenceActivity.UNKNOWN
1226 subscribed = False
1227
1228 def __init__(self, pjsua_bi=None):
1229 if pjsua_bi:
1230 self._cvt_from_pjsua(pjsua_bi)
1231
1232 def _cvt_from_pjsua(self, inf):
1233 self.uri = inf.uri
1234 self.contact = inf.contact
1235 self.online_status = inf.status
1236 self.online_text = inf.status_text
1237 self.activity = inf.activity
1238 self.subscribed = inf.monitor_pres
1239
1240
1241class BuddyCallback:
1242 """This class can be used to receive notifications about Buddy's
1243 presence status change. Application needs to derive a class from
1244 this class, and register the instance with Buddy.set_callback().
1245 """
1246 buddy = None
1247
1248 def __init__(self, buddy):
1249 self.buddy = buddy
1250
1251 def on_state(self):
1252 """
1253 Notification that buddy's presence state has changed. Application
1254 may then retrieve the new status with Buddy.info() function.
1255 """
1256 pass
1257
1258 def on_pager(self, mime_type, body):
1259 """Notification that incoming instant message is received from
1260 this buddy.
1261
1262 Keyword arguments:
1263 mime_type -- MIME type of the instant message body
1264 body -- the instant message body
1265
1266 """
1267 pass
1268
1269 def on_pager_status(self, body, im_id, code, reason):
1270 """Notification about the delivery status of previously sent
1271 instant message.
1272
1273 Keyword arguments:
1274 body -- the message body
1275 im_id -- message ID
1276 code -- SIP status code
1277 reason -- SIP reason phrase
1278
1279 """
1280 pass
1281
1282 def on_typing(self, is_typing):
1283 """Notification that remote is typing or stop typing.
1284
1285 Keyword arguments:
1286 is_typing -- boolean to indicate whether remote is currently
1287 typing an instant message.
1288
1289 """
1290 pass
1291
1292
1293class Buddy:
1294 """A Buddy represents person or remote agent.
1295
1296 This class provides functions to subscribe to buddy's presence and
1297 to send or receive instant messages from the buddy.
1298 """
1299 _id = -1
1300 _lib = None
1301 _cb = None
1302 _obj_name = ""
1303 _acc = None
1304
1305 def __init__(self, lib, id, account):
1306 self._cb = BuddyCallback(self)
1307 self._lib = lib
1308 self._id = id
1309 self._acc = account
1310 lib._associate_buddy(self._id, self)
1311 self._obj_name = "Buddy " + self.info().uri
1312
1313 def __del__(self):
1314 self._lib._disassociate_buddy(self)
1315
1316 def __str__(self):
1317 return self._obj_name
1318
1319 def info(self):
1320 """
1321 Get buddy info as BuddyInfo.
1322 """
1323 return BuddyInfo(_pjsua.buddy_get_info(self._id))
1324
1325 def set_callback(self, cb):
1326 """Install callback to receive notifications from this object.
1327
1328 Keyword argument:
1329 cb -- BuddyCallback instance.
1330 """
1331 if cb:
1332 self._cb = cb
1333 else:
1334 self._cb = BuddyCallback(self)
1335
1336 def subscribe(self):
1337 """
1338 Subscribe to buddy's presence status notification.
1339 """
1340 err = _pjsua.buddy_subscribe_pres(self._id, True)
1341 self._lib._err_check("subscribe()", self, err)
1342
1343 def unsubscribe(self):
1344 """
1345 Unsubscribe from buddy's presence status notification.
1346 """
1347 err = _pjsua.buddy_subscribe_pres(self._id, False)
1348 self._lib._err_check("unsubscribe()", self, err)
1349
1350 def delete(self):
1351 """
1352 Remove this buddy from the buddy list.
1353 """
1354 err = _pjsua.buddy_del(self._id)
1355 self._lib._err_check("delete()", self, err)
1356
1357 def send_pager(self, text, im_id=0, content_type="text/plain", \
1358 hdr_list=None):
1359 """Send instant message to remote buddy.
1360
1361 Keyword arguments:
1362 text -- Instant message to be sent
1363 im_id -- Optional instant message ID to identify this
1364 instant message when delivery status callback
1365 is called.
1366 content_type -- MIME type identifying the instant message
1367 hdr_list -- Optional list of headers to be sent with the
1368 request.
1369
1370 """
1371 err = _pjsua.im_send(self._acc._id, self.info().uri, \
1372 content_type, text, \
1373 Lib._create_msg_data(hdr_list), \
1374 im_id)
1375 self._lib._err_check("send_pager()", self, err)
1376
1377 def send_typing_ind(self, is_typing=True, hdr_list=None):
1378 """Send typing indication to remote buddy.
1379
1380 Keyword argument:
1381 is_typing -- boolean to indicate wheter user is typing.
1382 hdr_list -- Optional list of headers to be sent with the
1383 request.
1384
1385 """
1386 err = _pjsua.im_typing(self._acc._id, self.info().uri, \
1387 is_typing, Lib._create_msg_data(hdr_list))
1388 self._lib._err_check("send_typing_ind()", self, err)
1389
1390
1391
1392# Sound device info
1393class SoundDeviceInfo:
1394 name = ""
1395 input_channels = 0
1396 output_output_channels = 0
1397 default_clock_rate = 0
1398
1399 def __init__(self, sdi):
1400 self.name = sdi.name
1401 self.input_channels = sdi.input_count
1402 self.output_channels = sdi.output_count
1403 self.default_clock_rate = sdi.default_samples_per_sec
1404
1405
1406# Codec info
1407class CodecInfo:
1408 name = ""
1409 priority = 0
1410 clock_rate = 0
1411 channel_count = 0
1412 avg_bps = 0
1413 frm_ptime = 0
1414 ptime = 0
1415 pt = 0
1416 vad_enabled = False
1417 plc_enabled = False
1418
1419 def __init__(self, codec_info, codec_param):
1420 self.name = codec_info.id
1421 self.priority = codec_info.priority
1422 self.clock_rate = codec_param.info.clock_rate
1423 self.channel_count = codec_param.info.channel_count
1424 self.avg_bps = codec_param.info.avg_bps
1425 self.frm_ptime = codec_param.info.frm_ptime
1426 self.ptime = codec_param.info.frm_ptime * \
1427 codec_param.setting.frm_per_pkt
1428 self.ptime = codec_param.info.pt
1429 self.vad_enabled = codec_param.setting.vad
1430 self.plc_enabled = codec_param.setting.plc
1431
1432 def _cvt_to_pjsua(self):
1433 ci = _pjsua.Codec_Info()
1434 ci.id = self.name
1435 ci.priority = self.priority
1436 return ci
1437
1438
1439# Codec parameter
1440class CodecParameter:
1441 ptime = 0
1442 vad_enabled = False
1443 plc_enabled = False
1444 _codec_param = None
1445
1446 def __init__(self, codec_param):
1447 self.ptime = codec_param.info.frm_ptime * \
1448 codec_param.setting.frm_per_pkt
1449 self.vad_enabled = codec_param.setting.vad
1450 self.plc_enabled = codec_param.setting.plc
1451 self._codec_param = codec_param
1452
1453 def _cvt_to_pjsua(self):
1454 self._codec_param.setting.frm_per_pkt = self.ptime / \
1455 self._codec_param.info.frm_ptime
1456 self._codec_param.setting.vad = self.vad_enabled
1457 self._codec_param.setting.plc = self.plc_enabled
1458 return self._codec_param
1459
1460
1461# PJSUA Library
1462_lib = None
1463class Lib:
1464 """Library instance.
1465
1466 """
1467 call = {}
1468 account = {}
1469 buddy = {}
1470 buddy_by_uri = {}
1471 buddy_by_contact = {}
1472 _quit = False
1473 _has_thread = False
1474
1475 def __init__(self):
1476 global _lib
1477 if _lib:
1478 raise Error("__init()__", None, -1,
1479 "Library instance already exist")
1480
1481 err = _pjsua.create()
1482 self._err_check("_pjsua.create()", None, err)
1483 _lib = self
1484
1485 def __del__(self):
1486 _pjsua.destroy()
1487
1488 def __str__(self):
1489 return "Lib"
1490
1491 @staticmethod
1492 def instance():
1493 """Return singleton instance of Lib.
1494 """
1495 return _lib
1496
1497 def init(self, ua_cfg=None, log_cfg=None, media_cfg=None):
1498 """
1499 Initialize pjsua with the specified configurations.
1500
1501 Keyword arguments:
1502 ua_cfg -- optional UAConfig instance
1503 log_cfg -- optional LogConfig instance
1504 media_cfg -- optional MediaConfig instance
1505
1506 """
1507 if not ua_cfg: ua_cfg = UAConfig()
1508 if not log_cfg: log_cfg = LogConfig()
1509 if not media_cfg: media_cfg = MediaConfig()
1510
1511 py_ua_cfg = ua_cfg._cvt_to_pjsua()
1512 py_ua_cfg.cb.on_call_state = _cb_on_call_state
1513 py_ua_cfg.cb.on_incoming_call = _cb_on_incoming_call
1514 py_ua_cfg.cb.on_call_media_state = _cb_on_call_media_state
1515 py_ua_cfg.cb.on_dtmf_digit = _cb_on_dtmf_digit
1516 py_ua_cfg.cb.on_call_transfer_request = _cb_on_call_transfer_request
1517 py_ua_cfg.cb.on_call_transfer_status = _cb_on_call_transfer_status
1518 py_ua_cfg.cb.on_call_replace_request = _cb_on_call_replace_request
1519 py_ua_cfg.cb.on_call_replaced = _cb_on_call_replaced
1520 py_ua_cfg.cb.on_reg_state = _cb_on_reg_state
1521 py_ua_cfg.cb.on_buddy_state = _cb_on_buddy_state
1522 py_ua_cfg.cb.on_pager = _cb_on_pager
1523 py_ua_cfg.cb.on_pager_status = _cb_on_pager_status
1524 py_ua_cfg.cb.on_typing = _cb_on_typing
1525
1526 err = _pjsua.init(py_ua_cfg, log_cfg._cvt_to_pjsua(),
1527 media_cfg._cvt_to_pjsua())
1528 self._err_check("init()", self, err)
1529
1530 def destroy(self):
1531 """Destroy the library, and pjsua."""
1532 global _lib
1533 if self._has_thread:
1534 self._quit = 1
1535 loop = 0
1536 while self._quit != 2 and loop < 400:
1537 _pjsua.handle_events(50)
1538 loop = loop + 1
1539 _pjsua.destroy()
1540 _lib = None
1541
1542 def start(self, with_thread=True):
1543 """Start the library.
1544
1545 Keyword argument:
1546 with_thread -- specify whether the module should create worker
1547 thread.
1548
1549 """
1550 err = _pjsua.start()
1551 self._err_check("start()", self, err)
1552 self._has_thread = with_thread
1553 if self._has_thread:
1554 thread.start_new(_worker_thread_main, (0,))
1555
1556 def handle_events(self, timeout=50):
1557 """Poll the events from underlying pjsua library.
1558
1559 Application must poll the stack periodically if worker thread
1560 is disable when starting the library.
1561
1562 Keyword argument:
1563 timeout -- in milliseconds.
1564
1565 """
1566 return _pjsua.handle_events(timeout)
1567
1568 def verify_sip_url(self, sip_url):
1569 """Verify that the specified string is a valid URI.
1570
1571 Keyword argument:
1572 sip_url -- the URL string.
1573
1574 Return:
1575 0 is the the URI is valid, otherwise the appropriate error
1576 code is returned.
1577
1578 """
1579 return _pjsua.verify_sip_url(sip_url)
1580
1581 def create_transport(self, type, cfg=None):
1582 """Create SIP transport instance of the specified type.
1583
1584 Keyword arguments:
1585 type -- transport type from TransportType constant.
1586 cfg -- TransportConfig instance
1587
1588 Return:
1589 Transport object
1590
1591 """
1592 if not cfg: cfg=TransportConfig(type)
1593 err, tp_id = _pjsua.transport_create(type, cfg._cvt_to_pjsua())
1594 self._err_check("create_transport()", self, err)
1595 return Transport(self, tp_id)
1596
1597 def create_account(self, acc_config, set_default=True):
1598 """
1599 Create a new local pjsua account using the specified configuration.
1600
1601 Keyword arguments:
1602 acc_config -- AccountConfig
1603 set_default -- boolean to specify whether to use this as the
1604 default account.
1605
1606 Return:
1607 Account instance
1608
1609 """
1610 err, acc_id = _pjsua.acc_add(acc_config._cvt_to_pjsua(), set_default)
1611 self._err_check("create_account()", self, err)
1612 return Account(self, acc_id)
1613
1614 def create_account_for_transport(self, transport, set_default=True):
1615 """Create a new local pjsua transport for the specified transport.
1616
1617 Keyword arguments:
1618 transport -- the Transport instance.
1619 set_default -- boolean to specify whether to use this as the
1620 default account.
1621
1622 Return:
1623 Account instance
1624
1625 """
1626 err, acc_id = _pjsua.acc_add_local(transport._id, set_default)
1627 self._err_check("create_account_for_transport()", self, err)
1628 return Account(self, acc_id)
1629
1630 def hangup_all(self):
1631 """Hangup all calls.
1632
1633 """
1634 _pjsua.call_hangup_all()
1635
1636 # Sound device API
1637
1638 def enum_snd_dev(self):
1639 """Enumerate sound devices in the system.
1640
1641 Return:
1642 array of SoundDeviceInfo. The index of the element specifies
1643 the device ID for the device.
1644 """
1645 sdi_array = _pjsua.enum_snd_devs()
1646 info = []
1647 for sdi in sdi_array:
1648 info.append(SoundDeviceInfo(sdi))
1649 return info
1650
1651 def get_snd_dev(self):
1652 """Get the device IDs of current sound devices used by pjsua.
1653
1654 Return:
1655 (capture_dev_id, playback_dev_id) tuple
1656 """
1657 return _pjsua.get_snd_dev()
1658
1659 def set_snd_dev(self, capture_dev, playback_dev):
1660 """Change the current sound devices.
1661
1662 Keyword arguments:
1663 capture_dev -- the device ID of capture device to be used
1664 playback_dev -- the device ID of playback device to be used.
1665
1666 """
1667 err = _pjsua.set_snd_dev(capture_dev, playback_dev)
1668 self._err_check("set_current_sound_devices()", self, err)
1669
1670 def set_null_snd_dev(self):
1671 """Disable the sound devices. This is useful if the system
1672 does not have sound device installed.
1673
1674 """
1675 err = _pjsua.set_null_snd_dev()
1676 self._err_check("set_null_snd_dev()", self, err)
1677
1678
1679 # Conference bridge
1680
1681 def conf_get_max_ports(self):
1682 """Get the conference bridge capacity.
1683
1684 Return:
1685 conference bridge capacity.
1686
1687 """
1688 return _pjsua.conf_get_max_ports()
1689
1690 def conf_connect(self, src_slot, dst_slot):
1691 """Establish unidirectional media flow from souce to sink.
1692
1693 One source may transmit to multiple destinations/sink. And if
1694 multiple sources are transmitting to the same sink, the media
1695 will be mixed together. Source and sink may refer to the same ID,
1696 effectively looping the media.
1697
1698 If bidirectional media flow is desired, application needs to call
1699 this function twice, with the second one having the arguments
1700 reversed.
1701
1702 Keyword arguments:
1703 src_slot -- integer to identify the conference slot number of
1704 the source/transmitter.
1705 dst_slot -- integer to identify the conference slot number of
1706 the destination/receiver.
1707
1708 """
1709 err = _pjsua.conf_connect(src_slot, dst_slot)
1710 self._err_check("conf_connect()", self, err)
1711
1712 def conf_disconnect(self, src_slot, dst_slot):
1713 """Disconnect media flow from the source to destination port.
1714
1715 Keyword arguments:
1716 src_slot -- integer to identify the conference slot number of
1717 the source/transmitter.
1718 dst_slot -- integer to identify the conference slot number of
1719 the destination/receiver.
1720
1721 """
1722 err = _pjsua.conf_disconnect(src_slot, dst_slot)
1723 self._err_check("conf_disconnect()", self, err)
1724
1725 # Codecs API
1726
1727 def enum_codecs(self):
1728 """Return list of codecs supported by pjsua.
1729
1730 Return:
1731 array of CodecInfo
1732
1733 """
1734 ci_array = _pjsua.enum_codecs()
1735 codec_info = []
1736 for ci in ci_array:
1737 cp = _pjsua.codec_get_param(ci.id)
1738 if cp:
1739 codec_info.append(CodecInfo(ci, cp))
1740 return codec_info
1741
1742 def set_codec_priority(self, name, priority):
1743 """Change the codec priority.
1744
1745 Keyword arguments:
1746 name -- Codec name
1747 priority -- Codec priority, which range is 0-255.
1748
1749 """
1750 err = _pjsua.codec_set_priority(name, priority)
1751 self._err_check("set_codec_priority()", self, err)
1752
1753 def get_codec_parameter(self, name):
1754 """Get codec parameter for the specified codec.
1755
1756 Keyword arguments:
1757 name -- codec name.
1758
1759 """
1760 cp = _pjsua.codec_get_param(name)
1761 if not cp:
1762 self._err_check("get_codec_parameter()", self, -1,
1763 "Invalid codec name")
1764 return CodecParameter(cp)
1765
1766 def set_codec_parameter(self, name, param):
1767 """Modify codec parameter for the specified codec.
1768
1769 Keyword arguments:
1770 name -- codec name
1771 param -- codec parameter.
1772
1773 """
1774 err = _pjsua.codec_set_param(name, param._cvt_to_pjsua())
1775 self._err_check("set_codec_parameter()", self, err)
1776
1777 # WAV playback and recording
1778
1779 def create_player(self, filename, loop=False):
1780 """Create WAV file player.
1781
1782 Keyword arguments
1783 filename -- WAV file name
1784 loop -- boolean to specify wheter playback shoudl
1785 automatically restart
1786 Return:
1787 WAV player ID
1788
1789 """
1790 opt = 0
1791 if not loop:
1792 opt = opt + 1
1793 err, player_id = _pjsua.player_create(filename, opt)
1794 self._err_check("create_player()", self, err)
1795 return player_id
1796
1797 def player_get_slot(self, player_id):
1798 """Get the conference port ID for the specified player.
1799
1800 Keyword arguments:
1801 player_id -- the WAV player ID
1802
1803 Return:
1804 Conference slot number for the player
1805
1806 """
1807 slot = _pjsua.player_get_conf_port(player_id)
1808 self._err_check("player_get_slot()", self, -1, "Invalid player id")
1809 return slot
1810
1811 def player_set_pos(self, player_id, pos):
1812 """Set WAV playback position.
1813
1814 Keyword arguments:
1815 player_id -- WAV player ID
1816 pos -- playback position, in samples
1817
1818 """
1819 err = _pjsua.player_set_pos(player_id, pos)
1820 self._err_check("player_set_pos()", self, err)
1821
1822 def player_destroy(self, player_id):
1823 """Destroy the WAV player.
1824
1825 Keyword arguments:
1826 player_id -- the WAV player ID.
1827
1828 """
1829 err = _pjsua.player_destroy(player_id)
1830 self._err_check("player_destroy()", self, err)
1831
1832 def create_recorder(self, filename):
1833 """Create WAV file recorder.
1834
1835 Keyword arguments
1836 filename -- WAV file name
1837
1838 Return:
1839 WAV recorder ID
1840
1841 """
1842 err, rec_id = _pjsua.recorder_create(filename, 0, None, -1, 0)
1843 self._err_check("create_recorder()", self, err)
1844 return rec_id
1845
1846 def recorder_get_slot(self, rec_id):
1847 """Get the conference port ID for the specified recorder.
1848
1849 Keyword arguments:
1850 rec_id -- the WAV recorder ID
1851
1852 Return:
1853 Conference slot number for the recorder
1854
1855 """
1856 slot = _pjsua.recorder_get_conf_port(rec_id)
1857 self._err_check("recorder_get_slot()", self, -1, "Invalid recorder id")
1858 return slot
1859
1860 def recorder_destroy(self, rec_id):
1861 """Destroy the WAV recorder.
1862
1863 Keyword arguments:
1864 rec_id -- the WAV recorder ID.
1865
1866 """
1867 err = _pjsua.recorder_destroy(rec_id)
1868 self._err_check("recorder_destroy()", self, err)
1869
1870
1871 # Internal functions
1872
1873 @staticmethod
1874 def strerror(err):
1875 return _pjsua.strerror(err)
1876
1877 def _err_check(self, op_name, obj, err_code, err_msg=""):
1878 if err_code != 0:
1879 raise Error(op_name, obj, err_code, err_msg)
1880
1881 @staticmethod
1882 def _create_msg_data(hdr_list):
1883 if not hdr_list:
1884 return None
1885 msg_data = _pjsua.Msg_Data()
1886 msg_data.hdr_list = hdr_list
1887 return msg_data
1888
1889 # Internal dictionary manipulation for calls, accounts, and buddies
1890
1891 def _associate_call(self, call_id, call):
1892 self.call[call_id] = call
1893
1894 def _lookup_call(self, call_id):
1895 return self.call.has_key(call_id) and self.call[call_id] or None
1896
1897 def _disassociate_call(self, call):
1898 if self._lookup_call(call._id)==call:
1899 del self.call[call._id]
1900
1901 def _associate_account(self, acc_id, account):
1902 self.account[acc_id] = account
1903
1904 def _lookup_account(self, acc_id):
1905 return self.account.has_key(acc_id) and self.account[acc_id] or None
1906
1907 def _disassociate_account(self, account):
1908 if self._lookup_account(account._id)==account:
1909 del self.account[account._id]
1910
1911 def _associate_buddy(self, buddy_id, buddy):
1912 self.buddy[buddy_id] = buddy
1913 uri = SIPUri(buddy.info().uri)
1914 self.buddy_by_uri[(uri.user, uri.host)] = buddy
1915
1916 def _lookup_buddy(self, buddy_id, uri=None):
1917 print "lookup_buddy, id=", buddy_id
1918 buddy = self.buddy.has_key(buddy_id) and self.buddy[buddy_id] or None
1919 if uri and not buddy:
1920 sip_uri = SIPUri(uri)
1921 print "lookup_buddy, uri=", sip_uri.user, sip_uri.host
1922 buddy = self.buddy_by_uri.has_key( (sip_uri.user, sip_uri.host) ) \
1923 and self.buddy_by_uri[(sip_uri.user, sip_uri.host)] or \
1924 None
1925 return buddy
1926
1927 def _disassociate_buddy(self, buddy):
1928 if self._lookup_buddy(buddy._id)==buddy:
1929 del self.buddy[buddy._id]
1930 if self.buddy_by_uri.has_key(buddy.info().uri):
1931 del self.buddy_by_uri[buddy.info().uri]
1932
1933 # Account allbacks
1934
1935 def _cb_on_reg_state(self, acc_id):
1936 acc = self._lookup_account(acc_id)
1937 if acc:
1938 acc._cb.on_reg_state()
1939
1940 def _cb_on_incoming_call(self, acc_id, call_id, rdata):
1941 acc = self._lookup_account(acc_id)
1942 if acc:
1943 acc._cb.on_incoming_call( Call(self, call_id) )
1944 else:
1945 _pjsua.call_hangup(call_id, 603, None, None)
1946
1947 # Call callbacks
1948
1949 def _cb_on_call_state(self, call_id):
1950 call = self._lookup_call(call_id)
1951 if call:
1952 call._cb.on_state()
1953
1954 def _cb_on_call_media_state(self, call_id):
1955 call = self._lookup_call(call_id)
1956 if call:
1957 call._cb.on_media_state()
1958
1959 def _cb_on_dtmf_digit(self, call_id, digits):
1960 call = self._lookup_call(call_id)
1961 if call:
1962 call._cb.on_dtmf_digit(digits)
1963
1964 def _cb_on_call_transfer_request(self, call_id, dst, code):
1965 call = self._lookup_call(call_id)
1966 if call:
1967 return call._cb.on_transfer_request(dst, code)
1968 else:
1969 return 603
1970
1971 def _cb_on_call_transfer_status(self, call_id, code, text, final, cont):
1972 call = self._lookup_call(call_id)
1973 if call:
1974 return call._cb.on_transfer_status(code, text, final, cont)
1975 else:
1976 return cont
1977
1978 def _cb_on_call_replace_request(self, call_id, rdata, code, reason):
1979 call = self._lookup_call(call_id)
1980 if call:
1981 return call._cb.on_replace_request(code, reason)
1982 else:
1983 return code, reason
1984
1985 def _cb_on_call_replaced(self, old_call_id, new_call_id):
1986 old_call = self._lookup_call(old_call_id)
1987 new_call = self._lookup_call(new_call_id)
1988 if old_call and new_call:
1989 old_call._cb.on_replaced(new_call)
1990
1991 def _cb_on_pager(self, call_id, from_uri, to_uri, contact, mime_type,
1992 body, acc_id):
1993 call = None
1994 if call_id == -1:
1995 call = self._lookup_call(call_id)
1996 if call:
1997 call._cb.on_pager(mime_type, body)
1998 else:
1999 acc = self._lookup_account(acc_id)
2000 buddy = self._lookup_buddy(-1, from_uri)
2001 if buddy:
2002 buddy._cb.on_pager(mime_type, body)
2003 else:
2004 acc._cb.on_pager(from_uri, contact, mime_type, body)
2005
2006 def _cb_on_pager_status(self, call_id, to_uri, body, user_data,
2007 code, reason, acc_id):
2008 call = None
2009 if call_id == -1:
2010 call = self._lookup_call(call_id)
2011 if call:
2012 call._cb.on_pager_status(body, user_data, code, reason)
2013 else:
2014 acc = self._lookup_account(acc_id)
2015 buddy = self._lookup_buddy(-1, to_uri)
2016 if buddy:
2017 buddy._cb.on_pager_status(body, user_data, code, reason)
2018 else:
2019 acc._cb.on_pager_status(to_uri, body, user_data, code, reason)
2020
2021 def _cb_on_typing(self, call_id, from_uri, to_uri, contact, is_typing,
2022 acc_id):
2023 call = None
2024 if call_id == -1:
2025 call = self._lookup_call(call_id)
2026 if call:
2027 call._cb.on_typing(is_typing)
2028 else:
2029 acc = self._lookup_account(acc_id)
2030 buddy = self._lookup_buddy(-1, from_uri)
2031 if buddy:
2032 buddy._cb.on_typing(is_typing)
2033 else:
2034 acc._cb.on_typing(from_uri, contact, is_typing)
2035
2036 def _cb_on_buddy_state(self, buddy_id):
2037 buddy = self._lookup_buddy(buddy_id)
2038 if buddy:
2039 buddy._cb.on_state()
2040
2041
2042
2043
2044#
2045# Internal
2046#
2047
2048def _cb_on_call_state(call_id, e):
2049 _lib._cb_on_call_state(call_id)
2050
2051def _cb_on_incoming_call(acc_id, call_id, rdata):
2052 _lib._cb_on_incoming_call(acc_id, call_id, rdata)
2053
2054def _cb_on_call_media_state(call_id):
2055 _lib._cb_on_call_media_state(call_id)
2056
2057def _cb_on_dtmf_digit(call_id, digits):
2058 _lib._cb_on_dtmf_digit(call_id, digits)
2059
2060def _cb_on_call_transfer_request(call_id, dst, code):
2061 return _lib._cb_on_call_transfer_request(call_id, dst, code)
2062
2063def _cb_on_call_transfer_status(call_id, code, reason, final, cont):
2064 return _lib._cb_on_call_transfer_status(call_id, code, reason,
2065 final, cont)
2066def _cb_on_call_replace_request(call_id, rdata, code, reason):
2067 return _lib._cb_on_call_replace_request(call_id, rdata, code, reason)
2068
2069def _cb_on_call_replaced(old_call_id, new_call_id):
2070 _lib._cb_on_call_replaced(old_call_id, new_call_id)
2071
2072def _cb_on_reg_state(acc_id):
2073 _lib._cb_on_reg_state(acc_id)
2074
2075def _cb_on_buddy_state(buddy_id):
2076 _lib._cb_on_buddy_state(buddy_id)
2077
2078def _cb_on_pager(call_id, from_uri, to, contact, mime_type, body, acc_id):
2079 _lib._cb_on_pager(call_id, from_uri, to, contact, mime_type, body, acc_id)
2080
2081def _cb_on_pager_status(call_id, to, body, user_data, status, reason, acc_id):
2082 _lib._cb_on_pager_status(call_id, to, body, user_data,
2083 status, reason, acc_id)
2084
2085def _cb_on_typing(call_id, from_uri, to, contact, is_typing, acc_id):
2086 _lib._cb_on_typing(call_id, from_uri, to, contact, is_typing, acc_id)
2087
2088
2089# Worker thread
2090def _worker_thread_main(arg):
2091 global _lib
2092 thread_desc = 0;
2093 err = _pjsua.thread_register("python worker", thread_desc)
2094 _lib._err_check("thread_register()", _lib, err)
2095 while _lib._quit == 0:
2096 _pjsua.handle_events(50)
2097 _lib._quit = 2
2098
2099