summaryrefslogtreecommitdiff
path: root/pjsip-apps/src/python/pjsua.py
diff options
context:
space:
mode:
Diffstat (limited to 'pjsip-apps/src/python/pjsua.py')
-rw-r--r--pjsip-apps/src/python/pjsua.py2862
1 files changed, 2862 insertions, 0 deletions
diff --git a/pjsip-apps/src/python/pjsua.py b/pjsip-apps/src/python/pjsua.py
new file mode 100644
index 0000000..183ce0e
--- /dev/null
+++ b/pjsip-apps/src/python/pjsua.py
@@ -0,0 +1,2862 @@
+# $Id: pjsua.py 2976 2009-10-29 08:16:46Z bennylp $
+#
+# Object oriented PJSUA wrapper.
+#
+# Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+
+"""Multimedia communication client library based on SIP protocol.
+
+This implements a fully featured multimedia communication client
+library based on PJSIP stack (http://www.pjsip.org)
+
+
+1. FEATURES
+
+ - Session Initiation Protocol (SIP) features:
+ - Basic registration and call
+ - Multiple accounts
+ - Call hold, attended and unattended call transfer
+ - Presence
+ - Instant messaging
+ - Multiple SIP accounts
+ - Media features:
+ - Audio
+ - Conferencing
+ - Narrowband and wideband
+ - Codecs: PCMA, PCMU, GSM, iLBC, Speex, G.722, L16
+ - RTP/RTCP
+ - Secure RTP (SRTP)
+ - WAV playback, recording, and playlist
+ - NAT traversal features
+ - Symmetric RTP
+ - STUN
+ - TURN
+ - ICE
+
+
+2. USING
+
+See http://www.pjsip.org/trac/wiki/Python_SIP_Tutorial for a more thorough
+tutorial. The paragraphs below explain basic tasks on using this module.
+
+
+"""
+import _pjsua
+import thread
+import threading
+import weakref
+import time
+
+class Error:
+ """Error exception class.
+
+ Member documentation:
+
+ op_name -- name of the operation that generated this error.
+ obj -- the object that generated this error.
+ err_code -- the error code.
+
+ """
+ op_name = ""
+ obj = None
+ err_code = -1
+ _err_msg = ""
+
+ def __init__(self, op_name, obj, err_code, err_msg=""):
+ self.op_name = op_name
+ self.obj = obj
+ self.err_code = err_code
+ self._err_msg = err_msg
+
+ def err_msg(self):
+ "Retrieve the description of the error."
+ if self._err_msg != "":
+ return self._err_msg
+ self._err_msg = Lib.strerror(self.err_code)
+ return self._err_msg
+
+ def __str__(self):
+ return "Object: " + str(self.obj) + ", operation=" + self.op_name + \
+ ", error=" + self.err_msg()
+
+#
+# Constants
+#
+
+class TransportType:
+ """SIP transport type constants.
+
+ Member documentation:
+ UNSPECIFIED -- transport type is unknown or unspecified
+ UDP -- UDP transport
+ TCP -- TCP transport
+ TLS -- TLS transport
+ IPV6 -- this is not a transport type but rather a flag
+ to select the IPv6 version of a transport
+ UDP_IPV6 -- IPv6 UDP transport
+ TCP_IPV6 -- IPv6 TCP transport
+ """
+ UNSPECIFIED = 0
+ UDP = 1
+ TCP = 2
+ TLS = 3
+ IPV6 = 128
+ UDP_IPV6 = UDP + IPV6
+ TCP_IPV6 = TCP + IPV6
+
+class TransportFlag:
+ """Transport flags to indicate the characteristics of the transport.
+
+ Member documentation:
+
+ RELIABLE -- transport is reliable.
+ SECURE -- transport is secure.
+ DATAGRAM -- transport is datagram based.
+
+ """
+ RELIABLE = 1
+ SECURE = 2
+ DATAGRAM = 4
+
+class CallRole:
+ """Call role constants.
+
+ Member documentation:
+
+ CALLER -- role is caller
+ CALLEE -- role is callee
+
+ """
+ CALLER = 0
+ CALLEE = 1
+
+class CallState:
+ """Call state constants.
+
+ Member documentation:
+
+ NULL -- call is not initialized.
+ CALLING -- initial INVITE is sent.
+ INCOMING -- initial INVITE is received.
+ EARLY -- provisional response has been sent or received.
+ CONNECTING -- 200/OK response has been sent or received.
+ CONFIRMED -- ACK has been sent or received.
+ DISCONNECTED -- call is disconnected.
+ """
+ NULL = 0
+ CALLING = 1
+ INCOMING = 2
+ EARLY = 3
+ CONNECTING = 4
+ CONFIRMED = 5
+ DISCONNECTED = 6
+
+
+class MediaState:
+ """Call media state constants.
+
+ Member documentation:
+
+ NULL -- media is not available.
+ ACTIVE -- media is active.
+ LOCAL_HOLD -- media is put on-hold by local party.
+ REMOTE_HOLD -- media is put on-hold by remote party.
+ ERROR -- media error (e.g. ICE negotiation failure).
+ """
+ NULL = 0
+ ACTIVE = 1
+ LOCAL_HOLD = 2
+ REMOTE_HOLD = 3
+ ERROR = 4
+
+
+class MediaDir:
+ """Media direction constants.
+
+ Member documentation:
+
+ NULL -- media is not active
+ ENCODING -- media is active in transmit/encoding direction only.
+ DECODING -- media is active in receive/decoding direction only
+ ENCODING_DECODING -- media is active in both directions.
+ """
+ NULL = 0
+ ENCODING = 1
+ DECODING = 2
+ ENCODING_DECODING = 3
+
+
+class PresenceActivity:
+ """Presence activities constants.
+
+ Member documentation:
+
+ UNKNOWN -- the person activity is unknown
+ AWAY -- the person is currently away
+ BUSY -- the person is currently engaging in other activity
+ """
+ UNKNOWN = 0
+ AWAY = 1
+ BUSY = 2
+
+
+class SubscriptionState:
+ """Presence subscription state constants.
+
+ """
+ NULL = 0
+ SENT = 1
+ ACCEPTED = 2
+ PENDING = 3
+ ACTIVE = 4
+ TERMINATED = 5
+ UNKNOWN = 6
+
+
+class TURNConnType:
+ """These constants specifies the connection type to TURN server.
+
+ Member documentation:
+ UDP -- use UDP transport.
+ TCP -- use TCP transport.
+ TLS -- use TLS transport.
+ """
+ UDP = 17
+ TCP = 6
+ TLS = 255
+
+
+class UAConfig:
+ """User agent configuration to be specified in Lib.init().
+
+ Member documentation:
+
+ max_calls -- maximum number of calls to be supported.
+ nameserver -- list of nameserver hostnames or IP addresses. Nameserver
+ must be configured if DNS SRV resolution is desired.
+ stun_domain -- if nameserver is configured, this can be used to query
+ the STUN server with DNS SRV.
+ stun_host -- the hostname or IP address of the STUN server. This will
+ also be used if DNS SRV resolution for stun_domain fails.
+ user_agent -- Optionally specify the user agent name.
+ """
+ max_calls = 4
+ nameserver = []
+ stun_domain = ""
+ stun_host = ""
+ user_agent = "pjsip python"
+
+ def _cvt_from_pjsua(self, cfg):
+ self.max_calls = cfg.max_calls
+ self.thread_cnt = cfg.thread_cnt
+ self.nameserver = cfg.nameserver
+ self.stun_domain = cfg.stun_domain
+ self.stun_host = cfg.stun_host
+ self.user_agent = cfg.user_agent
+
+ def _cvt_to_pjsua(self):
+ cfg = _pjsua.config_default()
+ cfg.max_calls = self.max_calls
+ cfg.thread_cnt = 0
+ cfg.nameserver = self.nameserver
+ cfg.stun_domain = self.stun_domain
+ cfg.stun_host = self.stun_host
+ cfg.user_agent = self.user_agent
+ return cfg
+
+
+class LogConfig:
+ """Logging configuration to be specified in Lib.init().
+
+ Member documentation:
+
+ msg_logging -- specify if SIP messages should be logged. Set to
+ True.
+ level -- specify the input verbosity level.
+ console_level -- specify the output verbosity level.
+ decor -- specify log decoration.
+ filename -- specify the log filename.
+ callback -- specify callback to be called to write the logging
+ messages. Sample function:
+
+ def log_cb(level, str, len):
+ print str,
+
+ """
+ msg_logging = True
+ level = 5
+ console_level = 5
+ decor = 0
+ filename = ""
+ callback = None
+
+ def __init__(self, level=-1, filename="", callback=None,
+ console_level=-1):
+ self._cvt_from_pjsua(_pjsua.logging_config_default())
+ if level != -1:
+ self.level = level
+ if filename != "":
+ self.filename = filename
+ if callback != None:
+ self.callback = callback
+ if console_level != -1:
+ self.console_level = console_level
+
+ def _cvt_from_pjsua(self, cfg):
+ self.msg_logging = cfg.msg_logging
+ self.level = cfg.level
+ self.console_level = cfg.console_level
+ self.decor = cfg.decor
+ self.filename = cfg.log_filename
+ self.callback = cfg.cb
+
+ def _cvt_to_pjsua(self):
+ cfg = _pjsua.logging_config_default()
+ cfg.msg_logging = self.msg_logging
+ cfg.level = self.level
+ cfg.console_level = self.console_level
+ cfg.decor = self.decor
+ cfg.log_filename = self.filename
+ cfg.cb = self.callback
+ return cfg
+
+
+class MediaConfig:
+ """Media configuration to be specified in Lib.init().
+
+ Member documentation:
+
+ clock_rate -- specify the core clock rate of the audio,
+ most notably the conference bridge.
+ snd_clock_rate -- optionally specify different clock rate for
+ the sound device.
+ snd_auto_close_time -- specify the duration in seconds when the
+ sound device should be closed after inactivity
+ period.
+ channel_count -- specify the number of channels to open the sound
+ device and the conference bridge.
+ audio_frame_ptime -- specify the length of audio frames in millisecond.
+ max_media_ports -- specify maximum number of audio ports to be
+ supported by the conference bridge.
+ quality -- specify the audio quality setting (1-10)
+ ptime -- specify the audio packet length of transmitted
+ RTP packet.
+ no_vad -- disable Voice Activity Detector (VAD) or Silence
+ Detector (SD)
+ ilbc_mode -- specify iLBC codec mode (must be 30 for now)
+ tx_drop_pct -- randomly drop transmitted RTP packets (for
+ simulation). Number is in percent.
+ rx_drop_pct -- randomly drop received RTP packets (for
+ simulation). Number is in percent.
+ ec_options -- Echo Canceller option (specify zero).
+ ec_tail_len -- specify Echo Canceller tail length in milliseconds.
+ Value zero will disable the echo canceller.
+ jb_min -- specify the minimum jitter buffer size in
+ milliseconds. Put -1 for default.
+ jb_max -- specify the maximum jitter buffer size in
+ milliseconds. Put -1 for default.
+ enable_ice -- enable Interactive Connectivity Establishment (ICE)
+ enable_turn -- enable TURN relay. TURN server settings must also
+ be configured.
+ turn_server -- specify the domain or hostname or IP address of
+ the TURN server, in "host[:port]" format.
+ turn_conn_type -- specify connection type to the TURN server, from
+ the TURNConnType constant.
+ turn_cred -- specify AuthCred for the TURN credential.
+ """
+ clock_rate = 16000
+ snd_clock_rate = 0
+ snd_auto_close_time = 5
+ channel_count = 1
+ audio_frame_ptime = 20
+ max_media_ports = 32
+ quality = 6
+ ptime = 0
+ no_vad = False
+ ilbc_mode = 30
+ tx_drop_pct = 0
+ rx_drop_pct = 0
+ ec_options = 0
+ ec_tail_len = 256
+ jb_min = -1
+ jb_max = -1
+ enable_ice = True
+ enable_turn = False
+ turn_server = ""
+ turn_conn_type = TURNConnType.UDP
+ turn_cred = None
+
+ def __init__(self):
+ default = _pjsua.media_config_default()
+ self._cvt_from_pjsua(default)
+
+ def _cvt_from_pjsua(self, cfg):
+ self.clock_rate = cfg.clock_rate
+ self.snd_clock_rate = cfg.snd_clock_rate
+ self.snd_auto_close_time = cfg.snd_auto_close_time
+ self.channel_count = cfg.channel_count
+ self.audio_frame_ptime = cfg.audio_frame_ptime
+ self.max_media_ports = cfg.max_media_ports
+ self.quality = cfg.quality
+ self.ptime = cfg.ptime
+ self.no_vad = cfg.no_vad
+ self.ilbc_mode = cfg.ilbc_mode
+ self.tx_drop_pct = cfg.tx_drop_pct
+ self.rx_drop_pct = cfg.rx_drop_pct
+ self.ec_options = cfg.ec_options
+ self.ec_tail_len = cfg.ec_tail_len
+ self.jb_min = cfg.jb_min
+ self.jb_max = cfg.jb_max
+ self.enable_ice = cfg.enable_ice
+ self.enable_turn = cfg.enable_turn
+ self.turn_server = cfg.turn_server
+ self.turn_conn_type = cfg.turn_conn_type
+ if cfg.turn_username:
+ self.turn_cred = AuthCred(cfg.turn_realm, cfg.turn_username,
+ cfg.turn_passwd, cfg.turn_passwd_type)
+ else:
+ self.turn_cred = None
+
+ def _cvt_to_pjsua(self):
+ cfg = _pjsua.media_config_default()
+ cfg.clock_rate = self.clock_rate
+ cfg.snd_clock_rate = self.snd_clock_rate
+ cfg.snd_auto_close_time = self.snd_auto_close_time
+ cfg.channel_count = self.channel_count
+ cfg.audio_frame_ptime = self.audio_frame_ptime
+ cfg.max_media_ports = self.max_media_ports
+ cfg.quality = self.quality
+ cfg.ptime = self.ptime
+ cfg.no_vad = self.no_vad
+ cfg.ilbc_mode = self.ilbc_mode
+ cfg.tx_drop_pct = self.tx_drop_pct
+ cfg.rx_drop_pct = self.rx_drop_pct
+ cfg.ec_options = self.ec_options
+ cfg.ec_tail_len = self.ec_tail_len
+ cfg.jb_min = self.jb_min
+ cfg.jb_max = self.jb_max
+ cfg.enable_ice = self.enable_ice
+ cfg.enable_turn = self.enable_turn
+ cfg.turn_server = self.turn_server
+ cfg.turn_conn_type = self.turn_conn_type
+ if self.turn_cred:
+ cfg.turn_realm = self.turn_cred.realm
+ cfg.turn_username = self.turn_cred.username
+ cfg.turn_passwd_type = self.turn_cred.passwd_type
+ cfg.turn_passwd = self.turn_cred.passwd
+ return cfg
+
+
+class TransportConfig:
+ """SIP transport configuration class.
+
+ Member configuration:
+
+ port -- port number.
+ bound_addr -- optionally specify the address to bind the socket to.
+ Default is empty to bind to INADDR_ANY.
+ public_addr -- optionally override the published address for this
+ transport. If empty, the default behavior is to get
+ the public address from STUN or from the selected
+ local interface. Format is "host:port".
+ """
+ port = 0
+ bound_addr = ""
+ public_addr = ""
+
+ def __init__(self, port=0,
+ bound_addr="", public_addr=""):
+ self.port = port
+ self.bound_addr = bound_addr
+ self.public_addr = public_addr
+
+ def _cvt_to_pjsua(self):
+ cfg = _pjsua.transport_config_default()
+ cfg.port = self.port
+ cfg.bound_addr = self.bound_addr
+ cfg.public_addr = self.public_addr
+ return cfg
+
+
+class TransportInfo:
+ """SIP transport info.
+
+ Member documentation:
+
+ type -- transport type, from TransportType constants.
+ description -- longer description for this transport.
+ is_reliable -- True if transport is reliable.
+ is_secure -- True if transport is secure.
+ is_datagram -- True if transport is datagram based.
+ host -- the IP address of this transport.
+ port -- the port number.
+ ref_cnt -- number of objects referencing this transport.
+ """
+ type = ""
+ description = ""
+ is_reliable = False
+ is_secure = False
+ is_datagram = False
+ host = ""
+ port = 0
+ ref_cnt = 0
+
+ def __init__(self, ti):
+ self.type = ti.type_name
+ self.description = ti.info
+ self.is_reliable = (ti.flag & TransportFlag.RELIABLE)
+ self.is_secure = (ti.flag & TransportFlag.SECURE)
+ self.is_datagram = (ti.flag & TransportFlag.DATAGRAM)
+ self.host = ti.addr
+ self.port = ti.port
+ self.ref_cnt = ti.usage_count
+
+
+class Transport:
+ "SIP transport class."
+ _id = -1
+ _lib = None
+ _obj_name = ""
+
+ def __init__(self, lib, id):
+ self._lib = weakref.proxy(lib)
+ self._id = id
+ self._obj_name = "{Transport " + self.info().description + "}"
+ _Trace((self, 'created'))
+
+ def __del__(self):
+ _Trace((self, 'destroyed'))
+
+ def __str__(self):
+ return self._obj_name
+
+ def info(self):
+ """Get TransportInfo.
+ """
+ lck = self._lib.auto_lock()
+ ti = _pjsua.transport_get_info(self._id)
+ if not ti:
+ self._lib._err_check("info()", self, -1, "Invalid transport")
+ return TransportInfo(ti)
+
+ def enable(self):
+ """Enable this transport."""
+ lck = self._lib.auto_lock()
+ err = _pjsua.transport_set_enable(self._id, True)
+ self._lib._err_check("enable()", self, err)
+
+ def disable(self):
+ """Disable this transport."""
+ lck = self._lib.auto_lock()
+ err = _pjsua.transport_set_enable(self._id, 0)
+ self._lib._err_check("disable()", self, err)
+
+ def close(self, force=False):
+ """Close and destroy this transport.
+
+ Keyword argument:
+ force -- force deletion of this transport (not recommended).
+ """
+ lck = self._lib.auto_lock()
+ err = _pjsua.transport_close(self._id, force)
+ self._lib._err_check("close()", self, err)
+
+
+class SIPUri:
+ """Helper class to parse the most important components of SIP URI.
+
+ Member documentation:
+
+ scheme -- URI scheme ("sip" or "sips")
+ user -- user part of the URI (may be empty)
+ host -- host name part
+ port -- optional port number (zero if port is not specified).
+ transport -- transport parameter, or empty if transport is not
+ specified.
+
+ """
+ scheme = ""
+ user = ""
+ host = ""
+ port = 0
+ transport = ""
+
+ def __init__(self, uri=None):
+ if uri:
+ self.decode(uri)
+
+ def decode(self, uri):
+ """Parse SIP URL.
+
+ Keyword argument:
+ uri -- the URI string.
+
+ """
+ self.scheme, self.user, self.host, self.port, self.transport = \
+ _pjsua.parse_simple_uri(uri)
+
+ def encode(self):
+ """Encode this object into SIP URI string.
+
+ Return:
+ URI string.
+
+ """
+ output = self.scheme + ":"
+ if self.user and len(self.user):
+ output = output + self.user + "@"
+ output = output + self.host
+ if self.port:
+ output = output + ":" + output(self.port)
+ if self.transport:
+ output = output + ";transport=" + self.transport
+ return output
+
+
+class AuthCred:
+ """Authentication credential for SIP or TURN account.
+
+ Member documentation:
+
+ scheme -- authentication scheme (default is "Digest")
+ realm -- realm
+ username -- username
+ passwd_type -- password encoding (zero for plain-text)
+ passwd -- the password
+ """
+ scheme = "Digest"
+ realm = "*"
+ username = ""
+ passwd_type = 0
+ passwd = ""
+
+ def __init__(self, realm, username, passwd, scheme="Digest", passwd_type=0):
+ self.scheme = scheme
+ self.realm = realm
+ self.username = username
+ self.passwd_type = passwd_type
+ self.passwd = passwd
+
+
+class AccountConfig:
+ """ This describes account configuration to create an account.
+
+ Member documentation:
+
+ priority -- account priority for matching incoming
+ messages.
+ id -- SIP URI of this account. This setting is
+ mandatory.
+ force_contact -- force to use this URI as Contact URI. Setting
+ this value is generally not recommended.
+ reg_uri -- specify the registrar URI. Mandatory if
+ registration is required.
+ reg_timeout -- specify the SIP registration refresh interval
+ in seconds.
+ require_100rel -- specify if reliable provisional response is
+ to be enforced (with Require header).
+ publish_enabled -- specify if PUBLISH should be used. When
+ enabled, the PUBLISH will be sent to the
+ registrar.
+ pidf_tuple_id -- optionally specify the tuple ID in outgoing
+ PIDF document.
+ proxy -- list of proxy URI.
+ auth_cred -- list of AuthCred containing credentials to
+ authenticate against the registrars and
+ the proxies.
+ auth_initial_send -- specify if empty Authorization header should be
+ sent. May be needed for IMS.
+ auth_initial_algorithm -- when auth_initial_send is enabled, optionally
+ specify the authentication algorithm to use.
+ Valid values are "md5", "akav1-md5", or
+ "akav2-md5".
+ transport_id -- optionally specify the transport ID to be used
+ by this account. Shouldn't be needed unless
+ for specific requirements (e.g. in multi-homed
+ scenario).
+ allow_contact_rewrite -- specify whether the account should learn its
+ Contact address from REGISTER response and
+ update the registration accordingly. Default is
+ True.
+ ka_interval -- specify the interval to send NAT keep-alive
+ packet.
+ ka_data -- specify the NAT keep-alive packet contents.
+ use_srtp -- specify the SRTP usage policy. Valid values
+ are: 0=disable, 1=optional, 2=mandatory.
+ Default is 0.
+ srtp_secure_signaling -- specify the signaling security level required
+ by SRTP. Valid values are: 0=no secure
+ transport is required, 1=hop-by-hop secure
+ transport such as TLS is required, 2=end-to-
+ end secure transport is required (i.e. "sips").
+ """
+ priority = 0
+ id = ""
+ force_contact = ""
+ reg_uri = ""
+ reg_timeout = 0
+ require_100rel = False
+ publish_enabled = False
+ pidf_tuple_id = ""
+ proxy = []
+ auth_cred = []
+ auth_initial_send = False
+ auth_initial_algorithm = ""
+ transport_id = -1
+ allow_contact_rewrite = True
+ ka_interval = 15
+ ka_data = "\r\n"
+ use_srtp = 0
+ srtp_secure_signaling = 1
+
+ def __init__(self, domain="", username="", password="",
+ display="", registrar="", proxy=""):
+ """
+ Construct account config. If domain argument is specified,
+ a typical configuration will be built.
+
+ Keyword arguments:
+ domain -- domain name of the server.
+ username -- user name.
+ password -- plain-text password.
+ display -- optional display name for the user name.
+ registrar -- the registrar URI. If domain name is specified
+ and this argument is empty, the registrar URI
+ will be constructed from the domain name.
+ proxy -- the proxy URI. If domain name is specified
+ and this argument is empty, the proxy URI
+ will be constructed from the domain name.
+
+ """
+ default = _pjsua.acc_config_default()
+ self._cvt_from_pjsua(default)
+ if domain!="":
+ self.build_config(domain, username, password,
+ display, registrar, proxy)
+
+ def build_config(self, domain, username, password, display="",
+ registrar="", proxy=""):
+ """
+ Construct account config. If domain argument is specified,
+ a typical configuration will be built.
+
+ Keyword arguments:
+ domain -- domain name of the server.
+ username -- user name.
+ password -- plain-text password.
+ display -- optional display name for the user name.
+ registrar -- the registrar URI. If domain name is specified
+ and this argument is empty, the registrar URI
+ will be constructed from the domain name.
+ proxy -- the proxy URI. If domain name is specified
+ and this argument is empty, the proxy URI
+ will be constructed from the domain name.
+
+ """
+ if display != "":
+ display = display + " "
+ userpart = username
+ if userpart != "":
+ userpart = userpart + "@"
+ self.id = display + "<sip:" + userpart + domain + ">"
+ self.reg_uri = registrar
+ if self.reg_uri == "":
+ self.reg_uri = "sip:" + domain
+ if proxy == "":
+ proxy = "sip:" + domain + ";lr"
+ if proxy.find(";lr") == -1:
+ proxy = proxy + ";lr"
+ self.proxy.append(proxy)
+ if username != "":
+ self.auth_cred.append(AuthCred("*", username, password))
+
+ def _cvt_from_pjsua(self, cfg):
+ self.priority = cfg.priority
+ self.id = cfg.id
+ self.force_contact = cfg.force_contact
+ self.reg_uri = cfg.reg_uri
+ self.reg_timeout = cfg.reg_timeout
+ self.require_100rel = cfg.require_100rel
+ self.publish_enabled = cfg.publish_enabled
+ self.pidf_tuple_id = cfg.pidf_tuple_id
+ self.proxy = cfg.proxy
+ for cred in cfg.cred_info:
+ self.auth_cred.append(AuthCred(cred.realm, cred.username,
+ cred.data, cred.scheme,
+ cred.data_type))
+ self.auth_initial_send = cfg.auth_initial_send
+ self.auth_initial_algorithm = cfg.auth_initial_algorithm
+ self.transport_id = cfg.transport_id
+ self.allow_contact_rewrite = cfg.allow_contact_rewrite
+ self.ka_interval = cfg.ka_interval
+ self.ka_data = cfg.ka_data
+ self.use_srtp = cfg.use_srtp
+ self.srtp_secure_signaling = cfg.srtp_secure_signaling
+
+ def _cvt_to_pjsua(self):
+ cfg = _pjsua.acc_config_default()
+ cfg.priority = self.priority
+ cfg.id = self.id
+ cfg.force_contact = self.force_contact
+ cfg.reg_uri = self.reg_uri
+ cfg.reg_timeout = self.reg_timeout
+ cfg.require_100rel = self.require_100rel
+ cfg.publish_enabled = self.publish_enabled
+ cfg.pidf_tuple_id = self.pidf_tuple_id
+ cfg.proxy = self.proxy
+ for cred in self.auth_cred:
+ c = _pjsua.Pjsip_Cred_Info()
+ c.realm = cred.realm
+ c.scheme = cred.scheme
+ c.username = cred.username
+ c.data_type = cred.passwd_type
+ c.data = cred.passwd
+ cfg.cred_info.append(c)
+ cfg.auth_initial_send = self.auth_initial_send
+ cfg.auth_initial_algorithm = self.auth_initial_algorithm
+ cfg.transport_id = self.transport_id
+ cfg.allow_contact_rewrite = self.allow_contact_rewrite
+ cfg.ka_interval = self.ka_interval
+ cfg.ka_data = self.ka_data
+ cfg.use_srtp = self.use_srtp
+ cfg.srtp_secure_signaling = self.srtp_secure_signaling
+ return cfg
+
+
+# Account information
+class AccountInfo:
+ """This describes Account info. Application retrives account info
+ with Account.info().
+
+ Member documentation:
+
+ is_default -- True if this is the default account.
+ uri -- the account URI.
+ reg_active -- True if registration is active for this account.
+ reg_expires -- contains the current registration expiration value,
+ in seconds.
+ reg_status -- the registration status. If the value is less than
+ 700, it specifies SIP status code. Value greater than
+ this specifies the error code.
+ reg_reason -- contains the registration status text (e.g. the
+ error message).
+ online_status -- the account's presence online status, True if it's
+ publishing itself as online.
+ online_text -- the account's presence status text.
+
+ """
+ is_default = False
+ uri = ""
+ reg_active = False
+ reg_expires = -1
+ reg_status = 0
+ reg_reason = ""
+ online_status = False
+ online_text = ""
+
+ def __init__(self, ai):
+ self.is_default = ai.is_default
+ self.uri = ai.acc_uri
+ self.reg_active = ai.has_registration
+ self.reg_expires = ai.expires
+ self.reg_status = ai.status
+ self.reg_reason = ai.status_text
+ self.online_status = ai.online_status
+ self.online_text = ai.online_status_text
+
+# Account callback
+class AccountCallback:
+ """Class to receive notifications on account's events.
+
+ Derive a class from this class and register it to the Account object
+ using Account.set_callback() to start receiving events from the Account
+ object.
+
+ Member documentation:
+
+ account -- the Account object.
+
+ """
+ account = None
+
+ def __init__(self, account=None):
+ self._set_account(account)
+
+ def __del__(self):
+ pass
+
+ def _set_account(self, account):
+ if account:
+ self.account = weakref.proxy(account)
+ else:
+ self.account = None
+
+ def on_reg_state(self):
+ """Notification that the registration status has changed.
+ """
+ pass
+
+ def on_incoming_call(self, call):
+ """Notification about incoming call.
+
+ Unless this callback is implemented, the default behavior is to
+ reject the call with default status code.
+
+ Keyword arguments:
+ call -- the new incoming call
+ """
+ call.hangup()
+
+ def on_incoming_subscribe(self, buddy, from_uri, contact_uri, pres_obj):
+ """Notification when incoming SUBSCRIBE request is received.
+
+ Application may use this callback to authorize the incoming
+ subscribe request (e.g. ask user permission if the request
+ should be granted)
+
+ Keyword arguments:
+ buddy -- The buddy object, if buddy is found. Otherwise
+ the value is None.
+ from_uri -- The URI string of the sender.
+ pres_obj -- Opaque presence subscription object, which is
+ needed by Account.pres_notify()
+
+ Return:
+ Tuple (code, reason), where:
+ code: The status code. If code is >= 300, the
+ request is rejected. If code is 200, the
+ request is accepted and NOTIFY will be sent
+ automatically. If code is 202, application
+ must accept or reject the request later with
+ Account.press_notify().
+ reason: Optional reason phrase, or None to use the
+ default reasoh phrase for the status code.
+ """
+ return (200, None)
+
+ def on_pager(self, from_uri, contact, mime_type, body):
+ """
+ Notification that incoming instant message is received on
+ this account.
+
+ Keyword arguments:
+ from_uri -- sender's URI
+ contact -- sender's Contact URI
+ mime_type -- MIME type of the instant message body
+ body -- the instant message body
+
+ """
+ pass
+
+ def on_pager_status(self, to_uri, body, im_id, code, reason):
+ """
+ Notification about the delivery status of previously sent
+ instant message.
+
+ Keyword arguments:
+ to_uri -- the destination URI of the message
+ body -- the message body
+ im_id -- message ID
+ code -- SIP status code
+ reason -- SIP reason phrase
+
+ """
+ pass
+
+ def on_typing(self, from_uri, contact, is_typing):
+ """
+ Notification that remote is typing or stop typing.
+
+ Keyword arguments:
+ buddy -- Buddy object for the sender, if found. Otherwise
+ this will be None
+ from_uri -- sender's URI of the indication
+ contact -- sender's contact URI
+ is_typing -- boolean to indicate whether remote is currently
+ typing an instant message.
+
+ """
+ pass
+
+ def on_mwi_info(self, body):
+ """
+ Notification about change in Message Summary / Message Waiting
+ Indication (RFC 3842) status. MWI subscription must be enabled
+ in the account config to receive this notification.
+
+ Keyword arguments:
+ body -- String containing message body as received in the
+ NOTIFY request.
+
+ """
+ pass
+
+
+
+class Account:
+ """This describes SIP account class.
+
+ PJSUA accounts provide identity (or identities) of the user who is
+ currently using the application. In SIP terms, the identity is used
+ as the From header in outgoing requests.
+
+ Account may or may not have client registration associated with it.
+ An account is also associated with route set and some authentication
+ credentials, which are used when sending SIP request messages using
+ the account. An account also has presence's online status, which
+ will be reported to remote peer when they subscribe to the account's
+ presence, or which is published to a presence server if presence
+ publication is enabled for the account.
+
+ Account is created with Lib.create_account(). At least one account
+ MUST be created. If no user association is required, application can
+ create a userless account by calling Lib.create_account_for_transport().
+ A userless account identifies local endpoint instead of a particular
+ user, and it correspond with a particular transport instance.
+
+ Also one account must be set as the default account, which is used as
+ the account to use when PJSUA fails to match a request with any other
+ accounts.
+
+ """
+ _id = -1
+ _lib = None
+ _cb = AccountCallback(None)
+ _obj_name = ""
+
+ def __init__(self, lib, id, cb=None):
+ """Construct this class. This is normally called by Lib class and
+ not by application.
+
+ Keyword arguments:
+ lib -- the Lib instance.
+ id -- the pjsua account ID.
+ cb -- AccountCallback instance to receive events from this Account.
+ If callback is not specified here, it must be set later
+ using set_callback().
+ """
+ self._id = id
+ self._lib = weakref.ref(lib)
+ self._obj_name = "{Account " + self.info().uri + "}"
+ self.set_callback(cb)
+ _pjsua.acc_set_user_data(self._id, self)
+ _Trace((self, 'created'))
+
+ def __del__(self):
+ if self._id != -1:
+ _pjsua.acc_set_user_data(self._id, 0)
+ _Trace((self, 'destroyed'))
+
+ def __str__(self):
+ return self._obj_name
+
+ def info(self):
+ """Retrieve AccountInfo for this account.
+ """
+ lck = self._lib().auto_lock()
+ ai = _pjsua.acc_get_info(self._id)
+ if ai==None:
+ self._lib()._err_check("info()", self, -1, "Invalid account")
+ return AccountInfo(ai)
+
+ def is_valid(self):
+ """
+ Check if this account is still valid.
+
+ """
+ lck = self._lib().auto_lock()
+ return _pjsua.acc_is_valid(self._id)
+
+ def set_callback(self, cb):
+ """Register callback to receive notifications from this object.
+
+ Keyword argument:
+ cb -- AccountCallback instance.
+
+ """
+ if cb:
+ self._cb = cb
+ else:
+ self._cb = AccountCallback(self)
+ self._cb._set_account(self)
+
+ def set_default(self):
+ """ Set this account as default account to send outgoing requests
+ and as the account to receive incoming requests when more exact
+ matching criteria fails.
+ """
+ lck = self._lib().auto_lock()
+ err = _pjsua.acc_set_default(self._id)
+ self._lib()._err_check("set_default()", self, err)
+
+ def is_default(self):
+ """ Check if this account is the default account.
+
+ """
+ lck = self._lib().auto_lock()
+ def_id = _pjsua.acc_get_default()
+ return self.is_valid() and def_id==self._id
+
+ def delete(self):
+ """ Delete this account.
+
+ """
+ lck = self._lib().auto_lock()
+ err = _pjsua.acc_set_user_data(self._id, 0)
+ self._lib()._err_check("delete()", self, err)
+ err = _pjsua.acc_del(self._id)
+ self._lib()._err_check("delete()", self, err)
+ self._id = -1
+
+ def set_basic_status(self, is_online):
+ """ Set basic presence status of this account.
+
+ Keyword argument:
+ is_online -- boolean to indicate basic presence availability.
+
+ """
+ lck = self._lib().auto_lock()
+ err = _pjsua.acc_set_online_status(self._id, is_online)
+ self._lib()._err_check("set_basic_status()", self, err)
+
+ def set_presence_status(self, is_online,
+ activity=PresenceActivity.UNKNOWN,
+ pres_text="", rpid_id=""):
+ """ Set presence status of this account.
+
+ Keyword arguments:
+ is_online -- boolean to indicate basic presence availability
+ activity -- value from PresenceActivity
+ pres_text -- optional string to convey additional information about
+ the activity (such as "On the phone")
+ rpid_id -- optional string to be placed as RPID ID.
+
+ """
+ lck = self._lib().auto_lock()
+ err = _pjsua.acc_set_online_status2(self._id, is_online, activity,
+ pres_text, rpid_id)
+ self._lib()._err_check("set_presence_status()", self, err)
+
+ def set_registration(self, renew):
+ """Manually renew registration or unregister from the server.
+
+ Keyword argument:
+ renew -- boolean to indicate whether registration is renewed.
+ Setting this value for False will trigger unregistration.
+
+ """
+ lck = self._lib().auto_lock()
+ err = _pjsua.acc_set_registration(self._id, renew)
+ self._lib()._err_check("set_registration()", self, err)
+
+ def set_transport(self, transport):
+ """Set this account to only use the specified transport to send
+ outgoing requests.
+
+ Keyword argument:
+ transport -- Transport object.
+
+ """
+ lck = self._lib().auto_lock()
+ err = _pjsua.acc_set_transport(self._id, transport._id)
+ self._lib()._err_check("set_transport()", self, err)
+
+ def make_call(self, dst_uri, cb=None, hdr_list=None):
+ """Make outgoing call to the specified URI.
+
+ Keyword arguments:
+ dst_uri -- Destination SIP URI.
+ cb -- CallCallback instance to be installed to the newly
+ created Call object. If this CallCallback is not
+ specified (i.e. None is given), it must be installed
+ later using call.set_callback().
+ hdr_list -- Optional list of headers to be sent with outgoing
+ INVITE
+
+ Return:
+ Call instance.
+ """
+ lck = self._lib().auto_lock()
+ call = Call(self._lib(), -1, cb)
+ err, cid = _pjsua.call_make_call(self._id, dst_uri, 0,
+ call, Lib._create_msg_data(hdr_list))
+ self._lib()._err_check("make_call()", self, err)
+ call.attach_to_id(cid)
+ return call
+
+ def add_buddy(self, uri, cb=None):
+ """Add new buddy.
+
+ Keyword argument:
+ uri -- SIP URI of the buddy
+ cb -- BuddyCallback instance to be installed to the newly
+ created Buddy object. If this callback is not specified
+ (i.e. None is given), it must be installed later using
+ buddy.set_callback().
+
+ Return:
+ Buddy object
+ """
+ lck = self._lib().auto_lock()
+ buddy_cfg = _pjsua.buddy_config_default()
+ buddy_cfg.uri = uri
+ buddy_cfg.subscribe = False
+ err, buddy_id = _pjsua.buddy_add(buddy_cfg)
+ self._lib()._err_check("add_buddy()", self, err)
+ buddy = Buddy(self._lib(), buddy_id, self, cb)
+ return buddy
+
+ def pres_notify(self, pres_obj, state, reason="", hdr_list=None):
+ """Send NOTIFY to inform account presence status or to terminate
+ server side presence subscription.
+
+ Keyword arguments:
+ pres_obj -- The subscription object from on_incoming_subscribe()
+ callback
+ state -- Subscription state, from SubscriptionState
+ reason -- Optional reason phrase.
+ hdr_list -- Optional header list.
+ """
+ lck = self._lib().auto_lock()
+ _pjsua.acc_pres_notify(self._id, pres_obj, state, reason,
+ Lib._create_msg_data(hdr_list))
+
+ def send_pager(self, uri, text, im_id=0, content_type="text/plain", \
+ hdr_list=None):
+ """Send instant message to arbitrary URI.
+
+ Keyword arguments:
+ text -- Instant message to be sent
+ uri -- URI to send the Instant Message to.
+ im_id -- Optional instant message ID to identify this
+ instant message when delivery status callback
+ is called.
+ content_type -- MIME type identifying the instant message
+ hdr_list -- Optional list of headers to be sent with the
+ request.
+
+ """
+ lck = self._lib().auto_lock()
+ err = _pjsua.im_send(self._id, uri, \
+ content_type, text, \
+ Lib._create_msg_data(hdr_list), \
+ im_id)
+ self._lib()._err_check("send_pager()", self, err)
+
+class CallCallback:
+ """Class to receive event notification from Call objects.
+
+ Use Call.set_callback() method to install instance of this callback
+ class to receive event notifications from the call object.
+
+ Member documentation:
+
+ call -- the Call object.
+
+ """
+ call = None
+
+ def __init__(self, call=None):
+ self._set_call(call)
+
+ def __del__(self):
+ pass
+
+ def _set_call(self, call):
+ if call:
+ self.call = weakref.proxy(call)
+ else:
+ self.call = None
+
+ def on_state(self):
+ """Notification that the call's state has changed.
+
+ """
+ pass
+
+ def on_media_state(self):
+ """Notification that the call's media state has changed.
+
+ """
+ pass
+
+ def on_dtmf_digit(self, digits):
+ """Notification on incoming DTMF digits.
+
+ Keyword argument:
+ digits -- string containing the received digits.
+
+ """
+ pass
+
+ def on_transfer_request(self, dst, code):
+ """Notification that call is being transfered by remote party.
+
+ Application can decide to accept/reject transfer request by returning
+ code greater than or equal to 500. The default behavior is to accept
+ the transfer by returning 202.
+
+ Keyword arguments:
+ dst -- string containing the destination URI
+ code -- the suggested status code to return to accept the request.
+
+ Return:
+ the callback should return 202 to accept the request, or 300-699 to
+ reject the request.
+
+ """
+ return code
+
+ def on_transfer_status(self, code, reason, final, cont):
+ """
+ Notification about the status of previous call transfer request.
+
+ Keyword arguments:
+ code -- SIP status code to indicate completion status.
+ text -- SIP status reason phrase.
+ final -- if True then this is a final status and no further
+ notifications will be sent for this call transfer
+ status.
+ cont -- suggested return value.
+
+ Return:
+ If the callback returns false then no further notification will
+ be sent for the transfer request for this call.
+
+ """
+ return cont
+
+ def on_replace_request(self, code, reason):
+ """Notification when incoming INVITE with Replaces header is received.
+
+ Application may reject the request by returning value greather than
+ or equal to 500. The default behavior is to accept the request.
+
+ Keyword arguments:
+ code -- default status code to return
+ reason -- default reason phrase to return
+
+ Return:
+ The callback should return (code, reason) tuple.
+
+ """
+ return code, reason
+
+ def on_replaced(self, new_call):
+ """
+ Notification that this call will be replaced with new_call.
+ After this callback is called, this call will be disconnected.
+
+ Keyword arguments:
+ new_call -- the new call that will replace this call.
+ """
+ pass
+
+ def on_pager(self, mime_type, body):
+ """
+ Notification that incoming instant message is received on
+ this call.
+
+ Keyword arguments:
+ mime_type -- MIME type of the instant message body.
+ body -- the instant message body.
+
+ """
+ pass
+
+ def on_pager_status(self, body, im_id, code, reason):
+ """
+ Notification about the delivery status of previously sent
+ instant message.
+
+ Keyword arguments:
+ body -- message body
+ im_id -- message ID
+ code -- SIP status code
+ reason -- SIP reason phrase
+
+ """
+ pass
+
+ def on_typing(self, is_typing):
+ """
+ Notification that remote is typing or stop typing.
+
+ Keyword arguments:
+ is_typing -- boolean to indicate whether remote is currently
+ typing an instant message.
+
+ """
+ pass
+
+
+class CallInfo:
+ """This structure contains various information about Call.
+
+ Application may retrieve this information with Call.info().
+
+ Member documentation:
+
+ role -- CallRole
+ account -- Account object.
+ uri -- SIP URI of local account.
+ contact -- local Contact URI.
+ remote_uri -- remote SIP URI.
+ remote_contact -- remote Contact URI
+ sip_call_id -- call's Call-ID identification
+ state -- CallState
+ state_text -- state text.
+ last_code -- last SIP status code
+ last_reason -- text phrase for last_code
+ media_state -- MediaState
+ media_dir -- MediaDir
+ conf_slot -- conference slot number for this call.
+ call_time -- call's connected duration in seconds.
+ total_time -- total call duration in seconds.
+ """
+ role = CallRole.CALLER
+ account = None
+ uri = ""
+ contact = ""
+ remote_uri = ""
+ remote_contact = ""
+ sip_call_id = ""
+ state = CallState.NULL
+ state_text = ""
+ last_code = 0
+ last_reason = ""
+ media_state = MediaState.NULL
+ media_dir = MediaDir.NULL
+ conf_slot = -1
+ call_time = 0
+ total_time = 0
+
+ def __init__(self, lib=None, ci=None):
+ if lib and ci:
+ self._cvt_from_pjsua(lib, ci)
+
+ def _cvt_from_pjsua(self, lib, ci):
+ self.role = ci.role
+ self.account = lib._lookup_account(ci.acc_id)
+ self.uri = ci.local_info
+ self.contact = ci.local_contact
+ self.remote_uri = ci.remote_info
+ self.remote_contact = ci.remote_contact
+ self.sip_call_id = ci.call_id
+ self.state = ci.state
+ self.state_text = ci.state_text
+ self.last_code = ci.last_status
+ self.last_reason = ci.last_status_text
+ self.media_state = ci.media_status
+ self.media_dir = ci.media_dir
+ self.conf_slot = ci.conf_slot
+ self.call_time = ci.connect_duration / 1000
+ self.total_time = ci.total_duration / 1000
+
+
+class Call:
+ """This class represents SIP call.
+
+ Application initiates outgoing call with Account.make_call(), and
+ incoming calls are reported in AccountCallback.on_incoming_call().
+ """
+ _id = -1
+ _cb = None
+ _lib = None
+ _obj_name = ""
+
+ def __init__(self, lib, call_id, cb=None):
+ self._lib = weakref.ref(lib)
+ self.set_callback(cb)
+ self.attach_to_id(call_id)
+ _Trace((self, 'created'))
+
+ def __del__(self):
+ if self._id != -1:
+ _pjsua.call_set_user_data(self._id, 0)
+ _Trace((self, 'destroyed'))
+
+ def __str__(self):
+ return self._obj_name
+
+ def attach_to_id(self, call_id):
+ lck = self._lib().auto_lock()
+ if self._id != -1:
+ _pjsua.call_set_user_data(self._id, 0)
+ self._id = call_id
+ if self._id != -1:
+ _pjsua.call_set_user_data(self._id, self)
+ self._obj_name = "{Call " + self.info().remote_uri + "}"
+ else:
+ self._obj_name = "{Call object}"
+
+ def set_callback(self, cb):
+ """
+ Set callback object to retrieve event notifications from this call.
+
+ Keyword arguments:
+ cb -- CallCallback instance.
+ """
+ if cb:
+ self._cb = cb
+ else:
+ self._cb = CallCallback(self)
+ self._cb._set_call(self)
+
+ def info(self):
+ """
+ Get the CallInfo.
+ """
+ lck = self._lib().auto_lock()
+ ci = _pjsua.call_get_info(self._id)
+ if not ci:
+ self._lib()._err_check("info", self, -1, "Invalid call")
+ call_info = CallInfo(self._lib(), ci)
+ return call_info
+
+ def is_valid(self):
+ """
+ Check if this call is still valid.
+ """
+ lck = self._lib().auto_lock()
+ return _pjsua.call_is_active(self._id)
+
+ def dump_status(self, with_media=True, indent="", max_len=1024):
+ """
+ Dump the call status.
+ """
+ lck = self._lib().auto_lock()
+ return _pjsua.call_dump(self._id, with_media, max_len, indent)
+
+ def answer(self, code=200, reason="", hdr_list=None):
+ """
+ Send provisional or final response to incoming call.
+
+ Keyword arguments:
+ code -- SIP status code.
+ reason -- Reason phrase. Put empty to send default reason
+ phrase for the status code.
+ hdr_list -- Optional list of headers to be sent with the
+ INVITE response.
+
+ """
+ lck = self._lib().auto_lock()
+ err = _pjsua.call_answer(self._id, code, reason,
+ Lib._create_msg_data(hdr_list))
+ self._lib()._err_check("answer()", self, err)
+
+ def hangup(self, code=603, reason="", hdr_list=None):
+ """
+ Terminate the call.
+
+ Keyword arguments:
+ code -- SIP status code.
+ reason -- Reason phrase. Put empty to send default reason
+ phrase for the status code.
+ hdr_list -- Optional list of headers to be sent with the
+ message.
+
+ """
+ lck = self._lib().auto_lock()
+ err = _pjsua.call_hangup(self._id, code, reason,
+ Lib._create_msg_data(hdr_list))
+ self._lib()._err_check("hangup()", self, err)
+
+ def hold(self, hdr_list=None):
+ """
+ Put the call on hold.
+
+ Keyword arguments:
+ hdr_list -- Optional list of headers to be sent with the
+ message.
+ """
+ lck = self._lib().auto_lock()
+ err = _pjsua.call_set_hold(self._id, Lib._create_msg_data(hdr_list))
+ self._lib()._err_check("hold()", self, err)
+
+ def unhold(self, hdr_list=None):
+ """
+ Release the call from hold.
+
+ Keyword arguments:
+ hdr_list -- Optional list of headers to be sent with the
+ message.
+
+ """
+ lck = self._lib().auto_lock()
+ err = _pjsua.call_reinvite(self._id, True,
+ Lib._create_msg_data(hdr_list))
+ self._lib()._err_check("unhold()", self, err)
+
+ def reinvite(self, hdr_list=None):
+ """
+ Send re-INVITE and optionally offer new codecs to use.
+
+ Keyword arguments:
+ hdr_list -- Optional list of headers to be sent with the
+ message.
+
+ """
+ lck = self._lib().auto_lock()
+ err = _pjsua.call_reinvite(self._id, True,
+ Lib._create_msg_data(hdr_list))
+ self._lib()._err_check("reinvite()", self, err)
+
+ def update(self, hdr_list=None, options=0):
+ """
+ Send UPDATE and optionally offer new codecs to use.
+
+ Keyword arguments:
+ hdr_list -- Optional list of headers to be sent with the
+ message.
+ options -- Must be zero for now.
+
+ """
+ lck = self._lib().auto_lock()
+ err = _pjsua.call_update(self._id, options,
+ Lib._create_msg_data(hdr_list))
+ self._lib()._err_check("update()", self, err)
+
+ def transfer(self, dest_uri, hdr_list=None):
+ """
+ Transfer the call to new destination.
+
+ Keyword arguments:
+ dest_uri -- Specify the SIP URI to transfer the call to.
+ hdr_list -- Optional list of headers to be sent with the
+ message.
+
+ """
+ lck = self._lib().auto_lock()
+ err = _pjsua.call_xfer(self._id, dest_uri,
+ Lib._create_msg_data(hdr_list))
+ self._lib()._err_check("transfer()", self, err)
+
+ def transfer_to_call(self, call, hdr_list=None, options=0):
+ """
+ Attended call transfer.
+
+ Keyword arguments:
+ call -- The Call object to transfer call to.
+ hdr_list -- Optional list of headers to be sent with the
+ message.
+ options -- Must be zero for now.
+
+ """
+ lck = self._lib().auto_lock()
+ err = _pjsua.call_xfer_replaces(self._id, call._id, options,
+ Lib._create_msg_data(hdr_list))
+ self._lib()._err_check("transfer_to_call()", self, err)
+
+ def dial_dtmf(self, digits):
+ """
+ Send DTMF digits with RTP event package.
+
+ Keyword arguments:
+ digits -- DTMF digit string.
+
+ """
+ lck = self._lib().auto_lock()
+ err = _pjsua.call_dial_dtmf(self._id, digits)
+ self._lib()._err_check("dial_dtmf()", self, err)
+
+ def send_request(self, method, hdr_list=None, content_type=None,
+ body=None):
+ """
+ Send arbitrary request to remote call.
+
+ This is useful for example to send INFO request. Note that this
+ function should not be used to send request that will change the
+ call state such as CANCEL or BYE.
+
+ Keyword arguments:
+ method -- SIP method name.
+ hdr_list -- Optional header list to be sent with the request.
+ content_type -- Content type to describe the body, if the body
+ is present
+ body -- Optional SIP message body.
+
+ """
+ lck = self._lib().auto_lock()
+ if hdr_list or body:
+ msg_data = _pjsua.Msg_Data()
+ if hdr_list:
+ msg_data.hdr_list = hdr_list
+ if content_type:
+ msg_data.content_type = content_type
+ if body:
+ msg_data.msg_body = body
+ else:
+ msg_data = None
+
+ err = _pjsua.call_send_request(self._id, method, msg_data)
+ self._lib()._err_check("send_request()", self, err)
+
+ def send_pager(self, text, im_id=0, content_type="text/plain",
+ hdr_list=None):
+ """Send instant message inside a call.
+
+ Keyword arguments:
+ text -- Instant message to be sent
+ im_id -- Optional instant message ID to identify this
+ instant message when delivery status callback
+ is called.
+ content_type -- MIME type identifying the instant message
+ hdr_list -- Optional list of headers to be sent with the
+ request.
+
+ """
+ lck = self._lib().auto_lock()
+ err = _pjsua.call_send_im(self._id, \
+ content_type, text, \
+ Lib._create_msg_data(hdr_list), \
+ im_id)
+ self._lib()._err_check("send_pager()", self, err)
+
+
+class BuddyInfo:
+ """This class contains information about Buddy. Application may
+ retrieve this information by calling Buddy.info().
+
+ Member documentation:
+
+ uri -- the Buddy URI.
+ contact -- the Buddy Contact URI, if available.
+ online_status -- the presence online status.
+ online_text -- the presence online status text.
+ activity -- the PresenceActivity
+ subscribed -- specify whether buddy's presence status is currently
+ being subscribed.
+ sub_state -- SubscriptionState
+ sub_term_reason -- The termination reason string of the last presence
+ subscription to this buddy, if any.
+ """
+ uri = ""
+ contact = ""
+ online_status = 0
+ online_text = ""
+ activity = PresenceActivity.UNKNOWN
+ subscribed = False
+ sub_state = SubscriptionState.NULL
+ sub_term_reason = ""
+
+ def __init__(self, pjsua_bi=None):
+ if pjsua_bi:
+ self._cvt_from_pjsua(pjsua_bi)
+
+ def _cvt_from_pjsua(self, inf):
+ self.uri = inf.uri
+ self.contact = inf.contact
+ self.online_status = inf.status
+ self.online_text = inf.status_text
+ self.activity = inf.activity
+ self.subscribed = inf.monitor_pres
+ self.sub_state = inf.sub_state
+ self.sub_term_reason = inf.sub_term_reason
+
+
+class BuddyCallback:
+ """This class can be used to receive notifications about Buddy's
+ presence status change. Application needs to derive a class from
+ this class, and register the instance with Buddy.set_callback().
+
+ Member documentation:
+
+ buddy -- the Buddy object.
+ """
+ buddy = None
+
+ def __init__(self, buddy=None):
+ self._set_buddy(buddy)
+
+ def _set_buddy(self, buddy):
+ if buddy:
+ self.buddy = weakref.proxy(buddy)
+ else:
+ self.buddy = None
+
+ def on_state(self):
+ """
+ Notification that buddy's presence state has changed. Application
+ may then retrieve the new status with Buddy.info() function.
+ """
+ pass
+
+ def on_pager(self, mime_type, body):
+ """Notification that incoming instant message is received from
+ this buddy.
+
+ Keyword arguments:
+ mime_type -- MIME type of the instant message body
+ body -- the instant message body
+
+ """
+ pass
+
+ def on_pager_status(self, body, im_id, code, reason):
+ """Notification about the delivery status of previously sent
+ instant message.
+
+ Keyword arguments:
+ body -- the message body
+ im_id -- message ID
+ code -- SIP status code
+ reason -- SIP reason phrase
+
+ """
+ pass
+
+ def on_typing(self, is_typing):
+ """Notification that remote is typing or stop typing.
+
+ Keyword arguments:
+ is_typing -- boolean to indicate whether remote is currently
+ typing an instant message.
+
+ """
+ pass
+
+
+class Buddy:
+ """A Buddy represents person or remote agent.
+
+ This class provides functions to subscribe to buddy's presence and
+ to send or receive instant messages from the buddy.
+ """
+ _id = -1
+ _lib = None
+ _cb = None
+ _obj_name = ""
+ _acc = None
+
+ def __init__(self, lib, id, account, cb):
+ self._id = id
+ self._lib = weakref.ref(lib)
+ self._acc = weakref.ref(account)
+ self._obj_name = "{Buddy " + self.info().uri + "}"
+ self.set_callback(cb)
+ _pjsua.buddy_set_user_data(self._id, self)
+ _Trace((self, 'created'))
+
+ def __del__(self):
+ if self._id != -1:
+ _pjsua.buddy_set_user_data(self._id, 0)
+ _Trace((self, 'destroyed'))
+
+ def __str__(self):
+ return self._obj_name
+
+ def info(self):
+ """
+ Get buddy info as BuddyInfo.
+ """
+ lck = self._lib().auto_lock()
+ return BuddyInfo(_pjsua.buddy_get_info(self._id))
+
+ def set_callback(self, cb):
+ """Install callback to receive notifications from this object.
+
+ Keyword argument:
+ cb -- BuddyCallback instance.
+ """
+ if cb:
+ self._cb = cb
+ else:
+ self._cb = BuddyCallback(self)
+ self._cb._set_buddy(self)
+
+ def subscribe(self):
+ """
+ Subscribe to buddy's presence status notification.
+ """
+ lck = self._lib().auto_lock()
+ err = _pjsua.buddy_subscribe_pres(self._id, True)
+ self._lib()._err_check("subscribe()", self, err)
+
+ def unsubscribe(self):
+ """
+ Unsubscribe from buddy's presence status notification.
+ """
+ lck = self._lib().auto_lock()
+ err = _pjsua.buddy_subscribe_pres(self._id, False)
+ self._lib()._err_check("unsubscribe()", self, err)
+
+ def delete(self):
+ """
+ Remove this buddy from the buddy list.
+ """
+ lck = self._lib().auto_lock()
+ if self._id != -1:
+ _pjsua.buddy_set_user_data(self._id, 0)
+ err = _pjsua.buddy_del(self._id)
+ self._lib()._err_check("delete()", self, err)
+
+ def send_pager(self, text, im_id=0, content_type="text/plain", \
+ hdr_list=None):
+ """Send instant message to remote buddy.
+
+ Keyword arguments:
+ text -- Instant message to be sent
+ im_id -- Optional instant message ID to identify this
+ instant message when delivery status callback
+ is called.
+ content_type -- MIME type identifying the instant message
+ hdr_list -- Optional list of headers to be sent with the
+ request.
+
+ """
+ lck = self._lib().auto_lock()
+ err = _pjsua.im_send(self._acc()._id, self.info().uri, \
+ content_type, text, \
+ Lib._create_msg_data(hdr_list), \
+ im_id)
+ self._lib()._err_check("send_pager()", self, err)
+
+ def send_typing_ind(self, is_typing=True, hdr_list=None):
+ """Send typing indication to remote buddy.
+
+ Keyword argument:
+ is_typing -- boolean to indicate wheter user is typing.
+ hdr_list -- Optional list of headers to be sent with the
+ request.
+
+ """
+ lck = self._lib().auto_lock()
+ err = _pjsua.im_typing(self._acc()._id, self.info().uri, \
+ is_typing, Lib._create_msg_data(hdr_list))
+ self._lib()._err_check("send_typing_ind()", self, err)
+
+
+
+# Sound device info
+class SoundDeviceInfo:
+ """This described the sound device info.
+
+ Member documentation:
+ name -- device name.
+ input_channels -- number of capture channels supported.
+ output_channels -- number of playback channels supported.
+ default_clock_rate -- default sampling rate.
+ """
+ name = ""
+ input_channels = 0
+ output_channels = 0
+ default_clock_rate = 0
+
+ def __init__(self, sdi):
+ self.name = sdi.name
+ self.input_channels = sdi.input_count
+ self.output_channels = sdi.output_count
+ self.default_clock_rate = sdi.default_samples_per_sec
+
+
+# Codec info
+class CodecInfo:
+ """This describes codec info.
+
+ Member documentation:
+ name -- codec name
+ priority -- codec priority (0-255)
+ clock_rate -- clock rate
+ channel_count -- number of channels
+ avg_bps -- average bandwidth in bits per second
+ frm_ptime -- base frame length in milliseconds
+ ptime -- RTP frame length in milliseconds.
+ pt -- payload type.
+ vad_enabled -- specify if Voice Activity Detection is currently
+ enabled.
+ plc_enabled -- specify if Packet Lost Concealment is currently
+ enabled.
+ """
+ name = ""
+ priority = 0
+ clock_rate = 0
+ channel_count = 0
+ avg_bps = 0
+ frm_ptime = 0
+ ptime = 0
+ pt = 0
+ vad_enabled = False
+ plc_enabled = False
+
+ def __init__(self, codec_info, codec_param):
+ self.name = codec_info.codec_id
+ self.priority = codec_info.priority
+ self.clock_rate = codec_param.info.clock_rate
+ self.channel_count = codec_param.info.channel_cnt
+ self.avg_bps = codec_param.info.avg_bps
+ self.frm_ptime = codec_param.info.frm_ptime
+ self.ptime = codec_param.info.frm_ptime * \
+ codec_param.setting.frm_per_pkt
+ self.ptime = codec_param.info.pt
+ self.vad_enabled = codec_param.setting.vad
+ self.plc_enabled = codec_param.setting.plc
+
+ def _cvt_to_pjsua(self):
+ ci = _pjsua.Codec_Info()
+ ci.codec_id = self.name
+ ci.priority = self.priority
+ return ci
+
+
+# Codec parameter
+class CodecParameter:
+ """This specifies various parameters that can be configured for codec.
+
+ Member documentation:
+
+ ptime -- specify the outgoing RTP packet length in milliseconds.
+ vad_enabled -- specify if VAD should be enabled.
+ plc_enabled -- specify if PLC should be enabled.
+ """
+ ptime = 0
+ vad_enabled = False
+ plc_enabled = False
+ _codec_param = None
+
+ def __init__(self, codec_param):
+ self.ptime = codec_param.info.frm_ptime * \
+ codec_param.setting.frm_per_pkt
+ self.vad_enabled = codec_param.setting.vad
+ self.plc_enabled = codec_param.setting.plc
+ self._codec_param = codec_param
+
+ def _cvt_to_pjsua(self):
+ self._codec_param.setting.frm_per_pkt = self.ptime / \
+ self._codec_param.info.frm_ptime
+ self._codec_param.setting.vad = self.vad_enabled
+ self._codec_param.setting.plc = self.plc_enabled
+ return self._codec_param
+
+
+# Library mutex
+class _LibMutex:
+ def __init__(self, lck):
+ self._lck = lck
+ self._lck.acquire()
+ #_Trace(('lock acquired',))
+
+ def __del__(self):
+ try:
+ self._lck.release()
+ #_Trace(('lock released',))
+ except:
+ #_Trace(('lock release error',))
+ pass
+
+
+# PJSUA Library
+_lib = None
+enable_trace = False
+
+class Lib:
+ """Library instance.
+
+ """
+ _quit = False
+ _has_thread = False
+ _lock = None
+
+ def __init__(self):
+ global _lib
+ if _lib:
+ raise Error("__init()__", None, -1,
+ "Library instance already exist")
+
+ self._lock = threading.RLock()
+ err = _pjsua.create()
+ self._err_check("_pjsua.create()", None, err)
+ _lib = self
+
+ def __del__(self):
+ _pjsua.destroy()
+ del self._lock
+ _Trace(('Lib destroyed',))
+
+ def __str__(self):
+ return "Lib"
+
+ @staticmethod
+ def instance():
+ """Return singleton instance of Lib.
+ """
+ return _lib
+
+ def init(self, ua_cfg=None, log_cfg=None, media_cfg=None):
+ """
+ Initialize pjsua with the specified configurations.
+
+ Keyword arguments:
+ ua_cfg -- optional UAConfig instance
+ log_cfg -- optional LogConfig instance
+ media_cfg -- optional MediaConfig instance
+
+ """
+ if not ua_cfg: ua_cfg = UAConfig()
+ if not log_cfg: log_cfg = LogConfig()
+ if not media_cfg: media_cfg = MediaConfig()
+
+ py_ua_cfg = ua_cfg._cvt_to_pjsua()
+ py_ua_cfg.cb.on_call_state = _cb_on_call_state
+ py_ua_cfg.cb.on_incoming_call = _cb_on_incoming_call
+ py_ua_cfg.cb.on_call_media_state = _cb_on_call_media_state
+ py_ua_cfg.cb.on_dtmf_digit = _cb_on_dtmf_digit
+ py_ua_cfg.cb.on_call_transfer_request = _cb_on_call_transfer_request
+ py_ua_cfg.cb.on_call_transfer_status = _cb_on_call_transfer_status
+ py_ua_cfg.cb.on_call_replace_request = _cb_on_call_replace_request
+ py_ua_cfg.cb.on_call_replaced = _cb_on_call_replaced
+ py_ua_cfg.cb.on_reg_state = _cb_on_reg_state
+ py_ua_cfg.cb.on_incoming_subscribe = _cb_on_incoming_subscribe
+ py_ua_cfg.cb.on_buddy_state = _cb_on_buddy_state
+ py_ua_cfg.cb.on_pager = _cb_on_pager
+ py_ua_cfg.cb.on_pager_status = _cb_on_pager_status
+ py_ua_cfg.cb.on_typing = _cb_on_typing
+ py_ua_cfg.cb.on_mwi_info = _cb_on_mwi_info;
+
+ err = _pjsua.init(py_ua_cfg, log_cfg._cvt_to_pjsua(),
+ media_cfg._cvt_to_pjsua())
+ self._err_check("init()", self, err)
+
+ def destroy(self):
+ """Destroy the library, and pjsua."""
+ global _lib
+ if self._has_thread:
+ self._quit = 1
+ loop = 0
+ while self._quit != 2 and loop < 400:
+ self.handle_events(5)
+ loop = loop + 1
+ time.sleep(0.050)
+ _pjsua.destroy()
+ _lib = None
+
+ def start(self, with_thread=True):
+ """Start the library.
+
+ Keyword argument:
+ with_thread -- specify whether the module should create worker
+ thread.
+
+ """
+ lck = self.auto_lock()
+ err = _pjsua.start()
+ self._err_check("start()", self, err)
+ self._has_thread = with_thread
+ if self._has_thread:
+ thread.start_new(_worker_thread_main, (0,))
+
+ def handle_events(self, timeout=50):
+ """Poll the events from underlying pjsua library.
+
+ Application must poll the stack periodically if worker thread
+ is disable when starting the library.
+
+ Keyword argument:
+ timeout -- in milliseconds.
+
+ """
+ lck = self.auto_lock()
+ return _pjsua.handle_events(timeout)
+
+ def thread_register(self, name):
+ """Register external threads (threads that are not created by PJSIP,
+ such as threads that are created by Python API) to PJSIP.
+
+ The call must be made from the new thread before calling any pjlib
+ functions.
+
+ Keyword arguments:
+ name -- Non descriptive name for the thread
+ """
+ dummy = 1
+ err = _pjsua.thread_register(name, dummy)
+ self._err_check("thread_register()", self, err)
+
+ def verify_sip_url(self, sip_url):
+ """Verify that the specified string is a valid URI.
+
+ Keyword argument:
+ sip_url -- the URL string.
+
+ Return:
+ 0 is the the URI is valid, otherwise the appropriate error
+ code is returned.
+
+ """
+ lck = self.auto_lock()
+ return _pjsua.verify_sip_url(sip_url)
+
+ def create_transport(self, type, cfg=None):
+ """Create SIP transport instance of the specified type.
+
+ Keyword arguments:
+ type -- transport type from TransportType constant.
+ cfg -- TransportConfig instance
+
+ Return:
+ Transport object
+
+ """
+ lck = self.auto_lock()
+ if not cfg: cfg=TransportConfig()
+ err, tp_id = _pjsua.transport_create(type, cfg._cvt_to_pjsua())
+ self._err_check("create_transport()", self, err)
+ return Transport(self, tp_id)
+
+ def create_account(self, acc_config, set_default=True, cb=None):
+ """
+ Create a new local pjsua account using the specified configuration.
+
+ Keyword arguments:
+ acc_config -- AccountConfig
+ set_default -- boolean to specify whether to use this as the
+ default account.
+ cb -- AccountCallback instance.
+
+ Return:
+ Account instance
+
+ """
+ lck = self.auto_lock()
+ err, acc_id = _pjsua.acc_add(acc_config._cvt_to_pjsua(), set_default)
+ self._err_check("create_account()", self, err)
+ return Account(self, acc_id, cb)
+
+ def create_account_for_transport(self, transport, set_default=True,
+ cb=None):
+ """Create a new local pjsua transport for the specified transport.
+
+ Keyword arguments:
+ transport -- the Transport instance.
+ set_default -- boolean to specify whether to use this as the
+ default account.
+ cb -- AccountCallback instance.
+
+ Return:
+ Account instance
+
+ """
+ lck = self.auto_lock()
+ err, acc_id = _pjsua.acc_add_local(transport._id, set_default)
+ self._err_check("create_account_for_transport()", self, err)
+ return Account(self, acc_id, cb)
+
+ def hangup_all(self):
+ """Hangup all calls.
+
+ """
+ lck = self.auto_lock()
+ _pjsua.call_hangup_all()
+
+ # Sound device API
+
+ def enum_snd_dev(self):
+ """Enumerate sound devices in the system.
+
+ Return:
+ list of SoundDeviceInfo. The index of the element specifies
+ the device ID for the device.
+ """
+ lck = self.auto_lock()
+ sdi_list = _pjsua.enum_snd_devs()
+ info = []
+ for sdi in sdi_list:
+ info.append(SoundDeviceInfo(sdi))
+ return info
+
+ def get_snd_dev(self):
+ """Get the device IDs of current sound devices used by pjsua.
+
+ Return:
+ (capture_dev_id, playback_dev_id) tuple
+ """
+ lck = self.auto_lock()
+ return _pjsua.get_snd_dev()
+
+ def set_snd_dev(self, capture_dev, playback_dev):
+ """Change the current sound devices.
+
+ Keyword arguments:
+ capture_dev -- the device ID of capture device to be used
+ playback_dev -- the device ID of playback device to be used.
+
+ """
+ lck = self.auto_lock()
+ err = _pjsua.set_snd_dev(capture_dev, playback_dev)
+ self._err_check("set_current_sound_devices()", self, err)
+
+ def set_null_snd_dev(self):
+ """Disable the sound devices. This is useful if the system
+ does not have sound device installed.
+
+ """
+ lck = self.auto_lock()
+ err = _pjsua.set_null_snd_dev()
+ self._err_check("set_null_snd_dev()", self, err)
+
+
+ # Conference bridge
+
+ def conf_get_max_ports(self):
+ """Get the conference bridge capacity.
+
+ Return:
+ conference bridge capacity.
+
+ """
+ lck = self.auto_lock()
+ return _pjsua.conf_get_max_ports()
+
+ def conf_connect(self, src_slot, dst_slot):
+ """Establish unidirectional media flow from souce to sink.
+
+ One source may transmit to multiple destinations/sink. And if
+ multiple sources are transmitting to the same sink, the media
+ will be mixed together. Source and sink may refer to the same ID,
+ effectively looping the media.
+
+ If bidirectional media flow is desired, application needs to call
+ this function twice, with the second one having the arguments
+ reversed.
+
+ Keyword arguments:
+ src_slot -- integer to identify the conference slot number of
+ the source/transmitter.
+ dst_slot -- integer to identify the conference slot number of
+ the destination/receiver.
+
+ """
+ lck = self.auto_lock()
+ err = _pjsua.conf_connect(src_slot, dst_slot)
+ self._err_check("conf_connect()", self, err)
+
+ def conf_disconnect(self, src_slot, dst_slot):
+ """Disconnect media flow from the source to destination port.
+
+ Keyword arguments:
+ src_slot -- integer to identify the conference slot number of
+ the source/transmitter.
+ dst_slot -- integer to identify the conference slot number of
+ the destination/receiver.
+
+ """
+ lck = self.auto_lock()
+ err = _pjsua.conf_disconnect(src_slot, dst_slot)
+ self._err_check("conf_disconnect()", self, err)
+
+ def conf_set_tx_level(self, slot, level):
+ """Adjust the signal level to be transmitted from the bridge to
+ the specified port by making it louder or quieter.
+
+ Keyword arguments:
+ slot -- integer to identify the conference slot number.
+ level -- Signal level adjustment. Value 1.0 means no level
+ adjustment, while value 0 means to mute the port.
+ """
+ lck = self.auto_lock()
+ err = _pjsua.conf_set_tx_level(slot, level)
+ self._err_check("conf_set_tx_level()", self, err)
+
+ def conf_set_rx_level(self, slot, level):
+ """Adjust the signal level to be received from the specified port
+ (to the bridge) by making it louder or quieter.
+
+ Keyword arguments:
+ slot -- integer to identify the conference slot number.
+ level -- Signal level adjustment. Value 1.0 means no level
+ adjustment, while value 0 means to mute the port.
+ """
+ lck = self.auto_lock()
+ err = _pjsua.conf_set_rx_level(slot, level)
+ self._err_check("conf_set_rx_level()", self, err)
+
+ def conf_get_signal_level(self, slot):
+ """Get last signal level transmitted to or received from the
+ specified port. The signal levels are float values from 0.0 to 1.0,
+ with 0.0 indicates no signal, and 1.0 indicates the loudest signal
+ level.
+
+ Keyword arguments:
+ slot -- integer to identify the conference slot number.
+
+ Return value:
+ (tx_level, rx_level) tuple.
+ """
+ lck = self.auto_lock()
+ err, tx_level, rx_level = _pjsua.conf_get_signal_level(slot)
+ self._err_check("conf_get_signal_level()", self, err)
+ return (tx_level, rx_level)
+
+
+
+ # Codecs API
+
+ def enum_codecs(self):
+ """Return list of codecs supported by pjsua.
+
+ Return:
+ list of CodecInfo
+
+ """
+ lck = self.auto_lock()
+ ci_list = _pjsua.enum_codecs()
+ codec_info = []
+ for ci in ci_list:
+ cp = _pjsua.codec_get_param(ci.codec_id)
+ if cp:
+ codec_info.append(CodecInfo(ci, cp))
+ return codec_info
+
+ def set_codec_priority(self, name, priority):
+ """Change the codec priority.
+
+ Keyword arguments:
+ name -- Codec name
+ priority -- Codec priority, which range is 0-255.
+
+ """
+ lck = self.auto_lock()
+ err = _pjsua.codec_set_priority(name, priority)
+ self._err_check("set_codec_priority()", self, err)
+
+ def get_codec_parameter(self, name):
+ """Get codec parameter for the specified codec.
+
+ Keyword arguments:
+ name -- codec name.
+
+ """
+ lck = self.auto_lock()
+ cp = _pjsua.codec_get_param(name)
+ if not cp:
+ self._err_check("get_codec_parameter()", self, -1,
+ "Invalid codec name")
+ return CodecParameter(cp)
+
+ def set_codec_parameter(self, name, param):
+ """Modify codec parameter for the specified codec.
+
+ Keyword arguments:
+ name -- codec name
+ param -- codec parameter.
+
+ """
+ lck = self.auto_lock()
+ err = _pjsua.codec_set_param(name, param._cvt_to_pjsua())
+ self._err_check("set_codec_parameter()", self, err)
+
+ # WAV playback and recording
+
+ def create_player(self, filename, loop=False):
+ """Create WAV file player.
+
+ Keyword arguments
+ filename -- WAV file name
+ loop -- boolean to specify whether playback should
+ automatically restart upon EOF
+ Return:
+ WAV player ID
+
+ """
+ lck = self.auto_lock()
+ opt = 0
+ if not loop:
+ opt = opt + 1
+ err, player_id = _pjsua.player_create(filename, opt)
+ self._err_check("create_player()", self, err)
+ return player_id
+
+ def player_get_slot(self, player_id):
+ """Get the conference port ID for the specified player.
+
+ Keyword arguments:
+ player_id -- the WAV player ID
+
+ Return:
+ Conference slot number for the player
+
+ """
+ lck = self.auto_lock()
+ slot = _pjsua.player_get_conf_port(player_id)
+ if slot < 0:
+ self._err_check("player_get_slot()", self, -1,
+ "Invalid player id")
+ return slot
+
+ def player_set_pos(self, player_id, pos):
+ """Set WAV playback position.
+
+ Keyword arguments:
+ player_id -- WAV player ID
+ pos -- playback position, in samples
+
+ """
+ lck = self.auto_lock()
+ err = _pjsua.player_set_pos(player_id, pos)
+ self._err_check("player_set_pos()", self, err)
+
+ def player_destroy(self, player_id):
+ """Destroy the WAV player.
+
+ Keyword arguments:
+ player_id -- the WAV player ID.
+
+ """
+ lck = self.auto_lock()
+ err = _pjsua.player_destroy(player_id)
+ self._err_check("player_destroy()", self, err)
+
+ def create_playlist(self, filelist, label="playlist", loop=True):
+ """Create WAV playlist.
+
+ Keyword arguments:
+ filelist -- List of WAV file names.
+ label -- Optional name to be assigned to the playlist
+ object (useful for logging)
+ loop -- boolean to specify whether playback should
+ automatically restart upon EOF
+
+ Return:
+ playlist_id
+ """
+ lck = self.auto_lock()
+ opt = 0
+ if not loop:
+ opt = opt + 1
+ err, playlist_id = _pjsua.playlist_create(label, filelist, opt)
+ self._err_check("create_playlist()", self, err)
+ return playlist_id
+
+ def playlist_get_slot(self, playlist_id):
+ """Get the conference port ID for the specified playlist.
+
+ Keyword arguments:
+ playlist_id -- the WAV playlist ID
+
+ Return:
+ Conference slot number for the playlist
+
+ """
+ lck = self.auto_lock()
+ slot = _pjsua.player_get_conf_port(playlist_id)
+ if slot < 0:
+ self._err_check("playlist_get_slot()", self, -1,
+ "Invalid playlist id")
+ return slot
+
+ def playlist_destroy(self, playlist_id):
+ """Destroy the WAV playlist.
+
+ Keyword arguments:
+ playlist_id -- the WAV playlist ID.
+
+ """
+ lck = self.auto_lock()
+ err = _pjsua.player_destroy(playlist_id)
+ self._err_check("playlist_destroy()", self, err)
+
+ def create_recorder(self, filename):
+ """Create WAV file recorder.
+
+ Keyword arguments
+ filename -- WAV file name
+
+ Return:
+ WAV recorder ID
+
+ """
+ lck = self.auto_lock()
+ err, rec_id = _pjsua.recorder_create(filename, 0, None, -1, 0)
+ self._err_check("create_recorder()", self, err)
+ return rec_id
+
+ def recorder_get_slot(self, rec_id):
+ """Get the conference port ID for the specified recorder.
+
+ Keyword arguments:
+ rec_id -- the WAV recorder ID
+
+ Return:
+ Conference slot number for the recorder
+
+ """
+ lck = self.auto_lock()
+ slot = _pjsua.recorder_get_conf_port(rec_id)
+ if slot < 1:
+ self._err_check("recorder_get_slot()", self, -1,
+ "Invalid recorder id")
+ return slot
+
+ def recorder_destroy(self, rec_id):
+ """Destroy the WAV recorder.
+
+ Keyword arguments:
+ rec_id -- the WAV recorder ID.
+
+ """
+ lck = self.auto_lock()
+ err = _pjsua.recorder_destroy(rec_id)
+ self._err_check("recorder_destroy()", self, err)
+
+
+ # Internal functions
+
+ @staticmethod
+ def strerror(err):
+ return _pjsua.strerror(err)
+
+ def _err_check(self, op_name, obj, err_code, err_msg=""):
+ if err_code != 0:
+ raise Error(op_name, obj, err_code, err_msg)
+
+ @staticmethod
+ def _create_msg_data(hdr_list):
+ if not hdr_list:
+ return None
+ msg_data = _pjsua.Msg_Data()
+ msg_data.hdr_list = hdr_list
+ return msg_data
+
+ def auto_lock(self):
+ return _LibMutex(self._lock)
+
+ # Internal dictionary manipulation for calls, accounts, and buddies
+
+ def _lookup_call(self, call_id):
+ return _pjsua.call_get_user_data(call_id)
+
+ def _lookup_account(self, acc_id):
+ return _pjsua.acc_get_user_data(acc_id)
+
+ def _lookup_buddy(self, buddy_id, uri=None):
+ if buddy_id != -1:
+ buddy = _pjsua.buddy_get_user_data(buddy_id)
+ elif uri:
+ buddy_id = _pjsua.buddy_find(uri)
+ if buddy_id != -1:
+ buddy = _pjsua.buddy_get_user_data(buddy_id)
+ else:
+ buddy = None
+ else:
+ buddy = None
+
+ return buddy
+
+ # Account allbacks
+
+ def _cb_on_reg_state(self, acc_id):
+ acc = self._lookup_account(acc_id)
+ if acc:
+ acc._cb.on_reg_state()
+
+ def _cb_on_incoming_subscribe(self, acc_id, buddy_id, from_uri,
+ contact_uri, pres_obj):
+ acc = self._lookup_account(acc_id)
+ if acc:
+ buddy = self._lookup_buddy(buddy_id)
+ return acc._cb.on_incoming_subscribe(buddy, from_uri, contact_uri,
+ pres_obj)
+ else:
+ return (404, None)
+
+ def _cb_on_incoming_call(self, acc_id, call_id, rdata):
+ acc = self._lookup_account(acc_id)
+ if acc:
+ acc._cb.on_incoming_call( Call(self, call_id) )
+ else:
+ _pjsua.call_hangup(call_id, 603, None, None)
+
+ # Call callbacks
+
+ def _cb_on_call_state(self, call_id):
+ call = self._lookup_call(call_id)
+ if call:
+ if call._id == -1:
+ call.attach_to_id(call_id)
+ done = (call.info().state == CallState.DISCONNECTED)
+ call._cb.on_state()
+ if done:
+ _pjsua.call_set_user_data(call_id, 0)
+ else:
+ pass
+
+ def _cb_on_call_media_state(self, call_id):
+ call = self._lookup_call(call_id)
+ if call:
+ call._cb.on_media_state()
+
+ def _cb_on_dtmf_digit(self, call_id, digits):
+ call = self._lookup_call(call_id)
+ if call:
+ call._cb.on_dtmf_digit(digits)
+
+ def _cb_on_call_transfer_request(self, call_id, dst, code):
+ call = self._lookup_call(call_id)
+ if call:
+ return call._cb.on_transfer_request(dst, code)
+ else:
+ return 603
+
+ def _cb_on_call_transfer_status(self, call_id, code, text, final, cont):
+ call = self._lookup_call(call_id)
+ if call:
+ return call._cb.on_transfer_status(code, text, final, cont)
+ else:
+ return cont
+
+ def _cb_on_call_replace_request(self, call_id, rdata, code, reason):
+ call = self._lookup_call(call_id)
+ if call:
+ return call._cb.on_replace_request(code, reason)
+ else:
+ return code, reason
+
+ def _cb_on_call_replaced(self, old_call_id, new_call_id):
+ old_call = self._lookup_call(old_call_id)
+ new_call = self._lookup_call(new_call_id)
+ if old_call and new_call:
+ old_call._cb.on_replaced(new_call)
+
+ def _cb_on_pager(self, call_id, from_uri, to_uri, contact, mime_type,
+ body, acc_id):
+ call = None
+ if call_id != -1:
+ call = self._lookup_call(call_id)
+ if call:
+ call._cb.on_pager(mime_type, body)
+ else:
+ acc = self._lookup_account(acc_id)
+ buddy = self._lookup_buddy(-1, from_uri)
+ if buddy:
+ buddy._cb.on_pager(mime_type, body)
+ else:
+ acc._cb.on_pager(from_uri, contact, mime_type, body)
+
+ def _cb_on_pager_status(self, call_id, to_uri, body, user_data,
+ code, reason, acc_id):
+ call = None
+ if call_id != -1:
+ call = self._lookup_call(call_id)
+ if call:
+ call._cb.on_pager_status(body, user_data, code, reason)
+ else:
+ acc = self._lookup_account(acc_id)
+ buddy = self._lookup_buddy(-1, to_uri)
+ if buddy:
+ buddy._cb.on_pager_status(body, user_data, code, reason)
+ else:
+ acc._cb.on_pager_status(to_uri, body, user_data, code, reason)
+
+ def _cb_on_typing(self, call_id, from_uri, to_uri, contact, is_typing,
+ acc_id):
+ call = None
+ if call_id != -1:
+ call = self._lookup_call(call_id)
+ if call:
+ call._cb.on_typing(is_typing)
+ else:
+ acc = self._lookup_account(acc_id)
+ buddy = self._lookup_buddy(-1, from_uri)
+ if buddy:
+ buddy._cb.on_typing(is_typing)
+ else:
+ acc._cb.on_typing(from_uri, contact, is_typing)
+
+ def _cb_on_mwi_info(self, acc_id, body):
+ acc = self._lookup_account(acc_id)
+ if acc:
+ return acc._cb.on_mwi_info(body)
+
+ def _cb_on_buddy_state(self, buddy_id):
+ buddy = self._lookup_buddy(buddy_id)
+ if buddy:
+ buddy._cb.on_state()
+
+#
+# Internal
+#
+
+def _cb_on_call_state(call_id, e):
+ _lib._cb_on_call_state(call_id)
+
+def _cb_on_incoming_call(acc_id, call_id, rdata):
+ _lib._cb_on_incoming_call(acc_id, call_id, rdata)
+
+def _cb_on_call_media_state(call_id):
+ _lib._cb_on_call_media_state(call_id)
+
+def _cb_on_dtmf_digit(call_id, digits):
+ _lib._cb_on_dtmf_digit(call_id, digits)
+
+def _cb_on_call_transfer_request(call_id, dst, code):
+ return _lib._cb_on_call_transfer_request(call_id, dst, code)
+
+def _cb_on_call_transfer_status(call_id, code, reason, final, cont):
+ return _lib._cb_on_call_transfer_status(call_id, code, reason,
+ final, cont)
+def _cb_on_call_replace_request(call_id, rdata, code, reason):
+ return _lib._cb_on_call_replace_request(call_id, rdata, code, reason)
+
+def _cb_on_call_replaced(old_call_id, new_call_id):
+ _lib._cb_on_call_replaced(old_call_id, new_call_id)
+
+def _cb_on_reg_state(acc_id):
+ _lib._cb_on_reg_state(acc_id)
+
+def _cb_on_incoming_subscribe(acc_id, buddy_id, from_uri, contact_uri, pres):
+ return _lib._cb_on_incoming_subscribe(acc_id, buddy_id, from_uri,
+ contact_uri, pres)
+
+def _cb_on_buddy_state(buddy_id):
+ _lib._cb_on_buddy_state(buddy_id)
+
+def _cb_on_pager(call_id, from_uri, to, contact, mime_type, body, acc_id):
+ _lib._cb_on_pager(call_id, from_uri, to, contact, mime_type, body, acc_id)
+
+def _cb_on_pager_status(call_id, to, body, user_data, status, reason, acc_id):
+ _lib._cb_on_pager_status(call_id, to, body, user_data,
+ status, reason, acc_id)
+
+def _cb_on_typing(call_id, from_uri, to, contact, is_typing, acc_id):
+ _lib._cb_on_typing(call_id, from_uri, to, contact, is_typing, acc_id)
+
+def _cb_on_mwi_info(acc_id, body):
+ _lib._cb_on_mwi_info(acc_id, body)
+
+# Worker thread
+def _worker_thread_main(arg):
+ global _lib
+ _Trace(('worker thread started..',))
+ thread_desc = 0;
+ err = _pjsua.thread_register("python worker", thread_desc)
+ _lib._err_check("thread_register()", _lib, err)
+ while _lib and _lib._quit == 0:
+ _lib.handle_events(1)
+ time.sleep(0.050)
+ if _lib:
+ _lib._quit = 2
+ _Trace(('worker thread exited..',))
+
+def _Trace(args):
+ global enable_trace
+ if enable_trace:
+ print "** ",
+ for arg in args:
+ print arg,
+ print " **"
+