├── .gitignore ├── COPYING ├── Makefile ├── README.md ├── _pjsua.c ├── _pjsua.def ├── _pjsua.h ├── helper.mak ├── pjsua.py ├── samples ├── call.py ├── call.py.rej ├── presence.py ├── registration.py └── simplecall.py ├── setup-vc.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | build/ 3 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | python setup.py build 3 | 4 | clean distclean realclean: 5 | python setup.py clean 6 | rm -rf ./build 7 | 8 | install: 9 | python setup.py $@ 10 | 11 | dep doc: 12 | 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python 3 bindings for pjsip 2 | 3 | According to the official website "PJSIP is a free and open source multimedia communication library written in C language implementing standard based protocols such as SIP, SDP, RTP, STUN, TURN, and ICE. It combines signaling protocol (SIP) with rich multimedia framework and NAT traversal functionality into high level API that is portable and suitable for almost any type of systems ranging from desktops, embedded systems, to mobile handsets." 4 | 5 | To download, you can access the [project page](https://www.pjsip.org/) and click on [Download](https://www.pjsip.org/download.htm). 6 | 7 | 8 | ## Installation Instructions 9 | 10 | If you have downloaded the project from the official website, you should have a file similar to * pjproject-x.x.tar.bz2 *, x.x being the PJSIP version. 11 | 12 | Run the following commands: 13 | ```bash 14 | $ sudo apt install build-essential python3-dev libasound2-dev 15 | $ tar -xf pjproject-x.x.tar.bz2 && cd pjproject-x.x/ 16 | $ export CFLAGS="$CFLAGS -fPIC" 17 | $ ./configure --enable-shared 18 | $ make dep 19 | $ make 20 | $ sudo make install 21 | ``` 22 | 23 | With this you already have PJSIP installed on your machine. To work with pysua in python 3, run the following commands: 24 | ```bash 25 | $ sudo apt install python3-dev 26 | $ cd pjproject-x.x/pjsip-apps/src/ 27 | $ git clone https://github.com/mgwilliams/python3-pjsip.git 28 | $ cd python3-pjsip 29 | $ python3 setup.py build 30 | $ sudo python3 setup.py install 31 | ``` 32 | 33 | You can test the operation of pjsua directly in python: 34 | ```bash 35 | $ python3 36 | Python 3.5.2 (default, Nov 23 2017, 16:37:01) 37 | [GCC 5.4.0 20160609] on linux 38 | Type "help", "copyright", "credits" or "license" for more information. 39 | >>> import pjsua 40 | >>> 41 | ``` 42 | 43 | ## Comments 44 | 45 | If when you import the package the compiler complains about the lack of some *.so* file, add the `/usr/local/lib` directory to ldconfig - [Explanation](https://unix.stackexchange.com/a/67784/120790). -------------------------------------------------------------------------------- /_pjsua.def: -------------------------------------------------------------------------------- 1 | EXPORTS 2 | init_pjsua 3 | -------------------------------------------------------------------------------- /helper.mak: -------------------------------------------------------------------------------- 1 | include ../../../build.mak 2 | 3 | lib_dir: 4 | @for token in `echo $(APP_LDFLAGS)`; do \ 5 | echo $$token | grep '\-L' | sed 's/-L//'; \ 6 | done 7 | 8 | inc_dir: 9 | @for token in `echo $(APP_CFLAGS)`; do \ 10 | echo $$token | grep '\-I' | sed 's/-I//'; \ 11 | done 12 | 13 | libs: 14 | @for token in `echo $(APP_LDLIBS)`; do \ 15 | echo $$token | grep '\-l' | sed 's/-l//'; \ 16 | done 17 | 18 | -------------------------------------------------------------------------------- /pjsua.py: -------------------------------------------------------------------------------- 1 | # $Id: pjsua.py 4810 2014-04-07 06:56:06Z ming $ 2 | # 3 | # Object oriented PJSUA wrapper. 4 | # 5 | # Copyright (C) 2016 Matthew Williams 6 | # Copyright (C) 2003-2008 Benny Prijono 7 | # 8 | # This program is free software; you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; either version 2 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program; if not, write to the Free Software 20 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 21 | # 22 | 23 | """Multimedia communication client library based on SIP protocol. 24 | 25 | This implements a fully featured multimedia communication client 26 | library based on PJSIP stack (http://www.pjsip.org) 27 | 28 | 29 | 1. FEATURES 30 | 31 | - Session Initiation Protocol (SIP) features: 32 | - Basic registration and call 33 | - Multiple accounts 34 | - Call hold, attended and unattended call transfer 35 | - Presence 36 | - Instant messaging 37 | - Multiple SIP accounts 38 | - Media features: 39 | - Audio 40 | - Conferencing 41 | - Narrowband and wideband 42 | - Codecs: PCMA, PCMU, GSM, iLBC, Speex, G.722, L16 43 | - RTP/RTCP 44 | - Secure RTP (SRTP) 45 | - WAV playback, recording, and playlist 46 | - NAT traversal features 47 | - Symmetric RTP 48 | - STUN 49 | - TURN 50 | - ICE 51 | 52 | 53 | 2. USING 54 | 55 | See http://www.pjsip.org/trac/wiki/Python_SIP_Tutorial for a more thorough 56 | tutorial. The paragraphs below explain basic tasks on using this module. 57 | 58 | 59 | """ 60 | import _pjsua 61 | import _thread 62 | import threading 63 | import weakref 64 | import time 65 | 66 | class Error(BaseException): 67 | """Error exception class. 68 | 69 | Member documentation: 70 | 71 | op_name -- name of the operation that generated this error. 72 | obj -- the object that generated this error. 73 | err_code -- the error code. 74 | 75 | """ 76 | op_name = "" 77 | obj = None 78 | err_code = -1 79 | _err_msg = "" 80 | 81 | def __init__(self, op_name, obj, err_code, err_msg=""): 82 | self.op_name = op_name 83 | self.obj = obj 84 | self.err_code = err_code 85 | self._err_msg = err_msg 86 | 87 | def err_msg(self): 88 | "Retrieve the description of the error." 89 | if self._err_msg != "": 90 | return self._err_msg 91 | self._err_msg = Lib.strerror(self.err_code) 92 | return self._err_msg 93 | 94 | def __str__(self): 95 | return "Object: " + str(self.obj) + ", operation=" + self.op_name + \ 96 | ", error=" + str(self.err_msg()) 97 | 98 | # 99 | # Constants 100 | # 101 | 102 | class TransportType: 103 | """SIP transport type constants. 104 | 105 | Member documentation: 106 | UNSPECIFIED -- transport type is unknown or unspecified 107 | UDP -- UDP transport 108 | TCP -- TCP transport 109 | TLS -- TLS transport 110 | IPV6 -- this is not a transport type but rather a flag 111 | to select the IPv6 version of a transport 112 | UDP_IPV6 -- IPv6 UDP transport 113 | TCP_IPV6 -- IPv6 TCP transport 114 | """ 115 | UNSPECIFIED = 0 116 | UDP = 1 117 | TCP = 2 118 | TLS = 3 119 | IPV6 = 128 120 | UDP_IPV6 = UDP + IPV6 121 | TCP_IPV6 = TCP + IPV6 122 | 123 | class TransportFlag: 124 | """Transport flags to indicate the characteristics of the transport. 125 | 126 | Member documentation: 127 | 128 | RELIABLE -- transport is reliable. 129 | SECURE -- transport is secure. 130 | DATAGRAM -- transport is datagram based. 131 | 132 | """ 133 | RELIABLE = 1 134 | SECURE = 2 135 | DATAGRAM = 4 136 | 137 | class CallRole: 138 | """Call role constants. 139 | 140 | Member documentation: 141 | 142 | CALLER -- role is caller 143 | CALLEE -- role is callee 144 | 145 | """ 146 | CALLER = 0 147 | CALLEE = 1 148 | 149 | class CallState: 150 | """Call state constants. 151 | 152 | Member documentation: 153 | 154 | NULL -- call is not initialized. 155 | CALLING -- initial INVITE is sent. 156 | INCOMING -- initial INVITE is received. 157 | EARLY -- provisional response has been sent or received. 158 | CONNECTING -- 200/OK response has been sent or received. 159 | CONFIRMED -- ACK has been sent or received. 160 | DISCONNECTED -- call is disconnected. 161 | """ 162 | NULL = 0 163 | CALLING = 1 164 | INCOMING = 2 165 | EARLY = 3 166 | CONNECTING = 4 167 | CONFIRMED = 5 168 | DISCONNECTED = 6 169 | 170 | 171 | class MediaState: 172 | """Call media state constants. 173 | 174 | Member documentation: 175 | 176 | NULL -- media is not available. 177 | ACTIVE -- media is active. 178 | LOCAL_HOLD -- media is put on-hold by local party. 179 | REMOTE_HOLD -- media is put on-hold by remote party. 180 | ERROR -- media error (e.g. ICE negotiation failure). 181 | """ 182 | NULL = 0 183 | ACTIVE = 1 184 | LOCAL_HOLD = 2 185 | REMOTE_HOLD = 3 186 | ERROR = 4 187 | 188 | 189 | class MediaDir: 190 | """Media direction constants. 191 | 192 | Member documentation: 193 | 194 | NULL -- media is not active 195 | ENCODING -- media is active in transmit/encoding direction only. 196 | DECODING -- media is active in receive/decoding direction only 197 | ENCODING_DECODING -- media is active in both directions. 198 | """ 199 | NULL = 0 200 | ENCODING = 1 201 | DECODING = 2 202 | ENCODING_DECODING = 3 203 | 204 | 205 | class PresenceActivity: 206 | """Presence activities constants. 207 | 208 | Member documentation: 209 | 210 | UNKNOWN -- the person activity is unknown 211 | AWAY -- the person is currently away 212 | BUSY -- the person is currently engaging in other activity 213 | """ 214 | UNKNOWN = 0 215 | AWAY = 1 216 | BUSY = 2 217 | 218 | 219 | class SubscriptionState: 220 | """Presence subscription state constants. 221 | 222 | """ 223 | NULL = 0 224 | SENT = 1 225 | ACCEPTED = 2 226 | PENDING = 3 227 | ACTIVE = 4 228 | TERMINATED = 5 229 | UNKNOWN = 6 230 | 231 | 232 | class TURNConnType: 233 | """These constants specifies the connection type to TURN server. 234 | 235 | Member documentation: 236 | UDP -- use UDP transport. 237 | TCP -- use TCP transport. 238 | TLS -- use TLS transport. 239 | """ 240 | UDP = 17 241 | TCP = 6 242 | TLS = 255 243 | 244 | 245 | class UAConfig: 246 | """User agent configuration to be specified in Lib.init(). 247 | 248 | Member documentation: 249 | 250 | max_calls -- maximum number of calls to be supported. 251 | nameserver -- list of nameserver hostnames or IP addresses. Nameserver 252 | must be configured if DNS SRV resolution is desired. 253 | stun_domain -- if nameserver is configured, this can be used to query 254 | the STUN server with DNS SRV. 255 | stun_host -- the hostname or IP address of the STUN server. This will 256 | also be used if DNS SRV resolution for stun_domain fails. 257 | user_agent -- Optionally specify the user agent name. 258 | """ 259 | max_calls = 4 260 | nameserver = [] 261 | stun_domain = "" 262 | stun_host = "" 263 | user_agent = "pjsip python" 264 | 265 | def _cvt_from_pjsua(self, cfg): 266 | self.max_calls = cfg.max_calls 267 | self.thread_cnt = cfg.thread_cnt 268 | self.nameserver = cfg.nameserver 269 | self.stun_domain = cfg.stun_domain 270 | self.stun_host = cfg.stun_host 271 | self.user_agent = cfg.user_agent 272 | 273 | def _cvt_to_pjsua(self): 274 | cfg = _pjsua.config_default() 275 | cfg.max_calls = self.max_calls 276 | cfg.thread_cnt = 0 277 | cfg.nameserver = self.nameserver 278 | cfg.stun_domain = self.stun_domain 279 | cfg.stun_host = self.stun_host 280 | cfg.user_agent = self.user_agent 281 | return cfg 282 | 283 | 284 | class LogConfig: 285 | """Logging configuration to be specified in Lib.init(). 286 | 287 | Member documentation: 288 | 289 | msg_logging -- specify if SIP messages should be logged. Set to 290 | True. 291 | level -- specify the input verbosity level. 292 | console_level -- specify the output verbosity level. 293 | decor -- specify log decoration. 294 | filename -- specify the log filename. 295 | callback -- specify callback to be called to write the logging 296 | messages. Sample function: 297 | 298 | def log_cb(level, str, len): 299 | print str, 300 | 301 | """ 302 | msg_logging = True 303 | level = 5 304 | console_level = 5 305 | decor = 0 306 | filename = "" 307 | callback = None 308 | 309 | def __init__(self, level=-1, filename="", callback=None, 310 | console_level=-1): 311 | self._cvt_from_pjsua(_pjsua.logging_config_default()) 312 | if level != -1: 313 | self.level = level 314 | if filename != "": 315 | self.filename = filename 316 | if callback != None: 317 | self.callback = callback 318 | if console_level != -1: 319 | self.console_level = console_level 320 | 321 | def _cvt_from_pjsua(self, cfg): 322 | self.msg_logging = cfg.msg_logging 323 | self.level = cfg.level 324 | self.console_level = cfg.console_level 325 | self.decor = cfg.decor 326 | self.filename = cfg.log_filename 327 | self.callback = cfg.cb 328 | 329 | def _cvt_to_pjsua(self): 330 | cfg = _pjsua.logging_config_default() 331 | cfg.msg_logging = self.msg_logging 332 | cfg.level = self.level 333 | cfg.console_level = self.console_level 334 | cfg.decor = self.decor 335 | cfg.log_filename = self.filename 336 | cfg.cb = self.callback 337 | return cfg 338 | 339 | 340 | class MediaConfig: 341 | """Media configuration to be specified in Lib.init(). 342 | 343 | Member documentation: 344 | 345 | clock_rate -- specify the core clock rate of the audio, 346 | most notably the conference bridge. 347 | snd_clock_rate -- optionally specify different clock rate for 348 | the sound device. 349 | snd_auto_close_time -- specify the duration in seconds when the 350 | sound device should be closed after inactivity 351 | period. 352 | channel_count -- specify the number of channels to open the sound 353 | device and the conference bridge. 354 | audio_frame_ptime -- specify the length of audio frames in millisecond. 355 | max_media_ports -- specify maximum number of audio ports to be 356 | supported by the conference bridge. 357 | quality -- specify the audio quality setting (1-10) 358 | ptime -- specify the audio packet length of transmitted 359 | RTP packet. 360 | no_vad -- disable Voice Activity Detector (VAD) or Silence 361 | Detector (SD) 362 | ilbc_mode -- specify iLBC codec mode (must be 30 for now) 363 | tx_drop_pct -- randomly drop transmitted RTP packets (for 364 | simulation). Number is in percent. 365 | rx_drop_pct -- randomly drop received RTP packets (for 366 | simulation). Number is in percent. 367 | ec_options -- Echo Canceller option (specify zero). 368 | ec_tail_len -- specify Echo Canceller tail length in milliseconds. 369 | Value zero will disable the echo canceller. 370 | jb_min -- specify the minimum jitter buffer size in 371 | milliseconds. Put -1 for default. 372 | jb_max -- specify the maximum jitter buffer size in 373 | milliseconds. Put -1 for default. 374 | enable_ice -- enable Interactive Connectivity Establishment (ICE) 375 | enable_turn -- enable TURN relay. TURN server settings must also 376 | be configured. 377 | turn_server -- specify the domain or hostname or IP address of 378 | the TURN server, in "host[:port]" format. 379 | turn_conn_type -- specify connection type to the TURN server, from 380 | the TURNConnType constant. 381 | turn_cred -- specify AuthCred for the TURN credential. 382 | """ 383 | clock_rate = 16000 384 | snd_clock_rate = 0 385 | snd_auto_close_time = 5 386 | channel_count = 1 387 | audio_frame_ptime = 20 388 | max_media_ports = 32 389 | quality = 6 390 | ptime = 0 391 | no_vad = False 392 | ilbc_mode = 30 393 | tx_drop_pct = 0 394 | rx_drop_pct = 0 395 | ec_options = 0 396 | ec_tail_len = 256 397 | jb_min = -1 398 | jb_max = -1 399 | enable_ice = True 400 | enable_turn = False 401 | turn_server = "" 402 | turn_conn_type = TURNConnType.UDP 403 | turn_cred = None 404 | 405 | def __init__(self): 406 | default = _pjsua.media_config_default() 407 | self._cvt_from_pjsua(default) 408 | 409 | def _cvt_from_pjsua(self, cfg): 410 | self.clock_rate = cfg.clock_rate 411 | self.snd_clock_rate = cfg.snd_clock_rate 412 | self.snd_auto_close_time = cfg.snd_auto_close_time 413 | self.channel_count = cfg.channel_count 414 | self.audio_frame_ptime = cfg.audio_frame_ptime 415 | self.max_media_ports = cfg.max_media_ports 416 | self.quality = cfg.quality 417 | self.ptime = cfg.ptime 418 | self.no_vad = cfg.no_vad 419 | self.ilbc_mode = cfg.ilbc_mode 420 | self.tx_drop_pct = cfg.tx_drop_pct 421 | self.rx_drop_pct = cfg.rx_drop_pct 422 | self.ec_options = cfg.ec_options 423 | self.ec_tail_len = cfg.ec_tail_len 424 | self.jb_min = cfg.jb_min 425 | self.jb_max = cfg.jb_max 426 | self.enable_ice = cfg.enable_ice 427 | self.enable_turn = cfg.enable_turn 428 | self.turn_server = cfg.turn_server 429 | self.turn_conn_type = cfg.turn_conn_type 430 | if cfg.turn_username: 431 | self.turn_cred = AuthCred(cfg.turn_realm, cfg.turn_username, 432 | cfg.turn_passwd, cfg.turn_passwd_type) 433 | else: 434 | self.turn_cred = None 435 | 436 | def _cvt_to_pjsua(self): 437 | cfg = _pjsua.media_config_default() 438 | cfg.clock_rate = self.clock_rate 439 | cfg.snd_clock_rate = self.snd_clock_rate 440 | cfg.snd_auto_close_time = self.snd_auto_close_time 441 | cfg.channel_count = self.channel_count 442 | cfg.audio_frame_ptime = self.audio_frame_ptime 443 | cfg.max_media_ports = self.max_media_ports 444 | cfg.quality = self.quality 445 | cfg.ptime = self.ptime 446 | cfg.no_vad = self.no_vad 447 | cfg.ilbc_mode = self.ilbc_mode 448 | cfg.tx_drop_pct = self.tx_drop_pct 449 | cfg.rx_drop_pct = self.rx_drop_pct 450 | cfg.ec_options = self.ec_options 451 | cfg.ec_tail_len = self.ec_tail_len 452 | cfg.jb_min = self.jb_min 453 | cfg.jb_max = self.jb_max 454 | cfg.enable_ice = self.enable_ice 455 | cfg.enable_turn = self.enable_turn 456 | cfg.turn_server = self.turn_server 457 | cfg.turn_conn_type = self.turn_conn_type 458 | if self.turn_cred: 459 | cfg.turn_realm = self.turn_cred.realm 460 | cfg.turn_username = self.turn_cred.username 461 | cfg.turn_passwd_type = self.turn_cred.passwd_type 462 | cfg.turn_passwd = self.turn_cred.passwd 463 | return cfg 464 | 465 | 466 | class TransportConfig: 467 | """SIP transport configuration class. 468 | 469 | Member configuration: 470 | 471 | port -- port number. 472 | bound_addr -- optionally specify the address to bind the socket to. 473 | Default is empty to bind to INADDR_ANY. 474 | public_addr -- optionally override the published address for this 475 | transport. If empty, the default behavior is to get 476 | the public address from STUN or from the selected 477 | local interface. Format is "host:port". 478 | qos_type -- High level traffic classification. 479 | Enumerator: 480 | 0: PJ_QOS_TYPE_BEST_EFFORT 481 | Best effort traffic (default value). Any QoS function calls with 482 | specifying this value are effectively no-op 483 | 1: PJ_QOS_TYPE_BACKGROUND 484 | Background traffic. 485 | 2: PJ_QOS_TYPE_VIDEO 486 | Video traffic. 487 | 3: PJ_QOS_TYPE_VOICE 488 | Voice traffic. 489 | 4: PJ_QOS_TYPE_CONTROL 490 | Control traffic. 491 | qos_params_flags -- Determines which values to set, bitmask of pj_qos_flag. 492 | PJ_QOS_PARAM_HAS_DSCP = 1 493 | PJ_QOS_PARAM_HAS_SO_PRIO = 2 494 | PJ_QOS_PARAM_HAS_WMM = 4 495 | qos_params_dscp_val -- The 6 bits DSCP value to set. 496 | qos_params_so_prio -- Socket SO_PRIORITY value. 497 | qos_params_wmm_prio -- Standard WMM priorities. 498 | Enumerator: 499 | 0: PJ_QOS_WMM_PRIO_BULK_EFFORT: Bulk effort priority 500 | 1: PJ_QOS_WMM_PRIO_BULK: Bulk priority. 501 | 2: PJ_QOS_WMM_PRIO_VIDEO: Video priority 502 | 3: PJ_QOS_WMM_PRIO_VOICE: Voice priority. 503 | """ 504 | port = 0 505 | bound_addr = "" 506 | public_addr = "" 507 | 508 | qos_type = 0 509 | qos_params_flags = 0 510 | qos_params_dscp_val = 0 511 | qos_params_so_prio = 0 512 | qos_params_wmm_prio = 0 513 | 514 | 515 | 516 | def __init__(self, port=0, 517 | bound_addr="", public_addr=""): 518 | self.port = port 519 | self.bound_addr = bound_addr 520 | self.public_addr = public_addr 521 | 522 | def _cvt_from_pjsua(self, cfg): 523 | self.port = cfg.port 524 | self.bound_addr = cfg.bound_addr 525 | self.public_addr = cfg.public_addr 526 | self.qos_type = cfg.qos_type 527 | self.qos_params_flags = cfg.qos_params_flags 528 | self.qos_params_dscp_val = cfg.qos_params_dscp_val 529 | self.qos_params_so_prio = cfg.qos_params_so_prio 530 | self.qos_params_wmm_prio = cfg.qos_params_wmm_prio 531 | 532 | def _cvt_to_pjsua(self): 533 | cfg = _pjsua.transport_config_default() 534 | cfg.port = self.port 535 | cfg.bound_addr = self.bound_addr 536 | cfg.public_addr = self.public_addr 537 | cfg.qos_type = self.qos_type 538 | cfg.qos_params_flags = self.qos_params_flags 539 | cfg.qos_params_dscp_val = self.qos_params_dscp_val 540 | cfg.qos_params_so_prio = self.qos_params_so_prio 541 | cfg.qos_params_wmm_prio = self.qos_params_wmm_prio 542 | 543 | return cfg 544 | 545 | 546 | class TransportInfo: 547 | """SIP transport info. 548 | 549 | Member documentation: 550 | 551 | type -- transport type, from TransportType constants. 552 | description -- longer description for this transport. 553 | is_reliable -- True if transport is reliable. 554 | is_secure -- True if transport is secure. 555 | is_datagram -- True if transport is datagram based. 556 | host -- the IP address of this transport. 557 | port -- the port number. 558 | ref_cnt -- number of objects referencing this transport. 559 | """ 560 | type = "" 561 | description = "" 562 | is_reliable = False 563 | is_secure = False 564 | is_datagram = False 565 | host = "" 566 | port = 0 567 | ref_cnt = 0 568 | 569 | def __init__(self, ti): 570 | self.type = ti.type_name 571 | self.description = ti.info 572 | self.is_reliable = (ti.flag & TransportFlag.RELIABLE) 573 | self.is_secure = (ti.flag & TransportFlag.SECURE) 574 | self.is_datagram = (ti.flag & TransportFlag.DATAGRAM) 575 | self.host = ti.addr 576 | self.port = ti.port 577 | self.ref_cnt = ti.usage_count 578 | 579 | 580 | class Transport: 581 | "SIP transport class." 582 | _id = -1 583 | _lib = None 584 | _obj_name = "" 585 | 586 | def __init__(self, lib, id): 587 | self._lib = weakref.proxy(lib) 588 | self._id = id 589 | self._obj_name = "{Transport " + self.info().description + "}" 590 | _Trace((self, 'created')) 591 | 592 | def __del__(self): 593 | _Trace((self, 'destroyed')) 594 | 595 | def __str__(self): 596 | return self._obj_name 597 | 598 | def info(self): 599 | """Get TransportInfo. 600 | """ 601 | lck = self._lib.auto_lock() 602 | ti = _pjsua.transport_get_info(self._id) 603 | if not ti: 604 | self._lib._err_check("info()", self, -1, "Invalid transport") 605 | return TransportInfo(ti) 606 | 607 | def enable(self): 608 | """Enable this transport.""" 609 | lck = self._lib.auto_lock() 610 | err = _pjsua.transport_set_enable(self._id, True) 611 | self._lib._err_check("enable()", self, err) 612 | 613 | def disable(self): 614 | """Disable this transport.""" 615 | lck = self._lib.auto_lock() 616 | err = _pjsua.transport_set_enable(self._id, 0) 617 | self._lib._err_check("disable()", self, err) 618 | 619 | def close(self, force=False): 620 | """Close and destroy this transport. 621 | 622 | Keyword argument: 623 | force -- force deletion of this transport (not recommended). 624 | """ 625 | lck = self._lib.auto_lock() 626 | err = _pjsua.transport_close(self._id, force) 627 | self._lib._err_check("close()", self, err) 628 | 629 | 630 | class SIPUri: 631 | """Helper class to parse the most important components of SIP URI. 632 | 633 | Member documentation: 634 | 635 | scheme -- URI scheme ("sip" or "sips") 636 | user -- user part of the URI (may be empty) 637 | host -- host name part 638 | port -- optional port number (zero if port is not specified). 639 | transport -- transport parameter, or empty if transport is not 640 | specified. 641 | 642 | """ 643 | scheme = "" 644 | user = "" 645 | host = "" 646 | port = 0 647 | transport = "" 648 | 649 | def __init__(self, uri=None): 650 | if uri: 651 | self.decode(uri) 652 | 653 | def decode(self, uri): 654 | """Parse SIP URL. 655 | 656 | Keyword argument: 657 | uri -- the URI string. 658 | 659 | """ 660 | self.scheme, self.user, self.host, self.port, self.transport = \ 661 | _pjsua.parse_simple_uri(uri) 662 | 663 | def encode(self): 664 | """Encode this object into SIP URI string. 665 | 666 | Return: 667 | URI string. 668 | 669 | """ 670 | output = self.scheme + ":" 671 | if self.user and len(self.user): 672 | output = output + self.user + "@" 673 | output = output + self.host 674 | if self.port: 675 | output = output + ":" + output(self.port) 676 | if self.transport: 677 | output = output + ";transport=" + self.transport 678 | return output 679 | 680 | 681 | class AuthCred: 682 | """Authentication credential for SIP or TURN account. 683 | 684 | Member documentation: 685 | 686 | scheme -- authentication scheme (default is "Digest") 687 | realm -- realm 688 | username -- username 689 | passwd_type -- password encoding (zero for plain-text) 690 | passwd -- the password 691 | """ 692 | scheme = "Digest" 693 | realm = "*" 694 | username = "" 695 | passwd_type = 0 696 | passwd = "" 697 | 698 | def __init__(self, realm, username, passwd, scheme="Digest", passwd_type=0): 699 | self.scheme = scheme 700 | self.realm = realm 701 | self.username = username 702 | self.passwd_type = passwd_type 703 | self.passwd = passwd 704 | 705 | 706 | class AccountConfig: 707 | """ This describes account configuration to create an account. 708 | 709 | Member documentation: 710 | 711 | priority -- account priority for matching incoming 712 | messages. 713 | id -- SIP URI of this account. This setting is 714 | mandatory. 715 | force_contact -- force to use this URI as Contact URI. Setting 716 | this value is generally not recommended. 717 | reg_uri -- specify the registrar URI. Mandatory if 718 | registration is required. 719 | reg_timeout -- specify the SIP registration refresh interval 720 | in seconds. 721 | require_100rel -- specify if reliable provisional response is 722 | to be enforced (with Require header). 723 | publish_enabled -- specify if PUBLISH should be used. When 724 | enabled, the PUBLISH will be sent to the 725 | registrar. 726 | pidf_tuple_id -- optionally specify the tuple ID in outgoing 727 | PIDF document. 728 | proxy -- list of proxy URI. 729 | auth_cred -- list of AuthCred containing credentials to 730 | authenticate against the registrars and 731 | the proxies. 732 | auth_initial_send -- specify if empty Authorization header should be 733 | sent. May be needed for IMS. 734 | auth_initial_algorithm -- when auth_initial_send is enabled, optionally 735 | specify the authentication algorithm to use. 736 | Valid values are "md5", "akav1-md5", or 737 | "akav2-md5". 738 | transport_id -- optionally specify the transport ID to be used 739 | by this account. Shouldn't be needed unless 740 | for specific requirements (e.g. in multi-homed 741 | scenario). 742 | allow_contact_rewrite -- specify whether the account should learn its 743 | Contact address from REGISTER response and 744 | update the registration accordingly. Default is 745 | True. 746 | ka_interval -- specify the interval to send NAT keep-alive 747 | packet. 748 | ka_data -- specify the NAT keep-alive packet contents. 749 | use_srtp -- specify the SRTP usage policy. Valid values 750 | are: 0=disable, 1=optional, 2=mandatory. 751 | Default is 0. 752 | srtp_secure_signaling -- specify the signaling security level required 753 | by SRTP. Valid values are: 0=no secure 754 | transport is required, 1=hop-by-hop secure 755 | transport such as TLS is required, 2=end-to- 756 | end secure transport is required (i.e. "sips"). 757 | rtp_transport_cfg -- the rtp-transport-configuration that is usede, when 758 | a rtp-connection is being established. 759 | """ 760 | priority = 0 761 | id = "" 762 | force_contact = "" 763 | reg_uri = "" 764 | reg_timeout = 0 765 | require_100rel = False 766 | publish_enabled = False 767 | pidf_tuple_id = "" 768 | proxy = [] 769 | auth_cred = [] 770 | auth_initial_send = False 771 | auth_initial_algorithm = "" 772 | transport_id = -1 773 | allow_contact_rewrite = True 774 | ka_interval = 15 775 | ka_data = "\r\n" 776 | use_srtp = 0 777 | srtp_secure_signaling = 1 778 | rtp_transport_cfg = None 779 | mwi_enabled = False 780 | 781 | def __init__(self, domain="", username="", password="", 782 | display="", registrar="", proxy=""): 783 | """ 784 | Construct account config. If domain argument is specified, 785 | a typical configuration will be built. 786 | 787 | Keyword arguments: 788 | domain -- domain name of the server. 789 | username -- user name. 790 | password -- plain-text password. 791 | display -- optional display name for the user name. 792 | registrar -- the registrar URI. If domain name is specified 793 | and this argument is empty, the registrar URI 794 | will be constructed from the domain name. 795 | proxy -- the proxy URI. If domain name is specified 796 | and this argument is empty, the proxy URI 797 | will be constructed from the domain name. 798 | 799 | """ 800 | default = _pjsua.acc_config_default() 801 | self._cvt_from_pjsua(default) 802 | if domain!="": 803 | self.build_config(domain, username, password, 804 | display, registrar, proxy) 805 | self.rtp_transport_cfg = TransportConfig() 806 | 807 | def build_config(self, domain, username, password, display="", 808 | registrar="", proxy="", rtp_transport_cfg = None): 809 | """ 810 | Construct account config. If domain argument is specified, 811 | a typical configuration will be built. 812 | 813 | Keyword arguments: 814 | domain -- domain name of the server. 815 | username -- user name. 816 | password -- plain-text password. 817 | display -- optional display name for the user name. 818 | registrar -- the registrar URI. If domain name is specified 819 | and this argument is empty, the registrar URI 820 | will be constructed from the domain name. 821 | proxy -- the proxy URI. If domain name is specified 822 | and this argument is empty, the proxy URI 823 | will be constructed from the domain name. 824 | 825 | """ 826 | if display != "": 827 | display = display + " " 828 | userpart = username 829 | if userpart != "": 830 | userpart = userpart + "@" 831 | self.id = display + "" 832 | self.reg_uri = registrar 833 | if self.reg_uri == "": 834 | self.reg_uri = "sip:" + domain 835 | if proxy == "": 836 | proxy = "sip:" + domain + ";lr" 837 | if proxy.find(";lr") == -1: 838 | proxy = proxy + ";lr" 839 | self.proxy.append(proxy) 840 | if username != "": 841 | self.auth_cred.append(AuthCred("*", username, password)) 842 | 843 | if (rtp_transport_cfg is not None): 844 | self.rtp_transport_cfg = rtp_transport_cfg 845 | else: 846 | self.rtp_transport_cfg = TransportConfig() 847 | 848 | def _cvt_from_pjsua(self, cfg): 849 | self.priority = cfg.priority 850 | self.id = cfg.id 851 | self.force_contact = cfg.force_contact 852 | self.reg_uri = cfg.reg_uri 853 | self.reg_timeout = cfg.reg_timeout 854 | self.require_100rel = cfg.require_100rel 855 | self.publish_enabled = cfg.publish_enabled 856 | self.pidf_tuple_id = cfg.pidf_tuple_id 857 | self.proxy = cfg.proxy 858 | for cred in cfg.cred_info: 859 | self.auth_cred.append(AuthCred(cred.realm, cred.username, 860 | cred.data, cred.scheme, 861 | cred.data_type)) 862 | self.auth_initial_send = cfg.auth_initial_send 863 | self.auth_initial_algorithm = cfg.auth_initial_algorithm 864 | self.transport_id = cfg.transport_id 865 | self.allow_contact_rewrite = cfg.allow_contact_rewrite 866 | self.ka_interval = cfg.ka_interval 867 | self.ka_data = cfg.ka_data 868 | self.use_srtp = cfg.use_srtp 869 | self.srtp_secure_signaling = cfg.srtp_secure_signaling 870 | self.mwi_enabled = cfg.mwi_enabled 871 | if (self.rtp_transport_cfg is not None): 872 | self.rtp_transport_cfg._cvt_from_pjsua(cfg.rtp_transport_cfg) 873 | 874 | def _cvt_to_pjsua(self): 875 | cfg = _pjsua.acc_config_default() 876 | cfg.priority = self.priority 877 | cfg.id = self.id 878 | cfg.force_contact = self.force_contact 879 | cfg.reg_uri = self.reg_uri 880 | cfg.reg_timeout = self.reg_timeout 881 | cfg.require_100rel = self.require_100rel 882 | cfg.publish_enabled = self.publish_enabled 883 | cfg.pidf_tuple_id = self.pidf_tuple_id 884 | cfg.proxy = self.proxy 885 | for cred in self.auth_cred: 886 | c = _pjsua.Pjsip_Cred_Info() 887 | c.realm = cred.realm 888 | c.scheme = cred.scheme 889 | c.username = cred.username 890 | c.data_type = cred.passwd_type 891 | c.data = cred.passwd 892 | cfg.cred_info.append(c) 893 | 894 | cfg.auth_initial_send = self.auth_initial_send 895 | cfg.auth_initial_algorithm = self.auth_initial_algorithm 896 | cfg.transport_id = self.transport_id 897 | cfg.allow_contact_rewrite = self.allow_contact_rewrite 898 | cfg.ka_interval = self.ka_interval 899 | cfg.ka_data = self.ka_data 900 | cfg.use_srtp = self.use_srtp 901 | cfg.srtp_secure_signaling = self.srtp_secure_signaling 902 | cfg.mwi_enabled = self.mwi_enabled 903 | 904 | if (self.rtp_transport_cfg is not None): 905 | cfg.rtp_transport_cfg = self.rtp_transport_cfg._cvt_to_pjsua() 906 | 907 | return cfg 908 | 909 | 910 | # Account information 911 | class AccountInfo: 912 | """This describes Account info. Application retrives account info 913 | with Account.info(). 914 | 915 | Member documentation: 916 | 917 | is_default -- True if this is the default account. 918 | uri -- the account URI. 919 | reg_active -- True if registration is active for this account. 920 | reg_expires -- contains the current registration expiration value, 921 | in seconds. 922 | reg_status -- the registration status. If the value is less than 923 | 700, it specifies SIP status code. Value greater than 924 | this specifies the error code. 925 | reg_reason -- contains the registration status text (e.g. the 926 | error message). 927 | online_status -- the account's presence online status, True if it's 928 | publishing itself as online. 929 | online_text -- the account's presence status text. 930 | 931 | """ 932 | is_default = False 933 | uri = "" 934 | reg_active = False 935 | reg_expires = -1 936 | reg_status = 0 937 | reg_reason = "" 938 | online_status = False 939 | online_text = "" 940 | 941 | def __init__(self, ai): 942 | self.is_default = ai.is_default 943 | self.uri = ai.acc_uri 944 | self.reg_active = ai.has_registration 945 | self.reg_expires = ai.expires 946 | self.reg_status = ai.status 947 | self.reg_reason = ai.status_text 948 | self.online_status = ai.online_status 949 | self.online_text = ai.online_status_text 950 | 951 | # Account callback 952 | class AccountCallback: 953 | """Class to receive notifications on account's events. 954 | 955 | Derive a class from this class and register it to the Account object 956 | using Account.set_callback() to start receiving events from the Account 957 | object. 958 | 959 | Member documentation: 960 | 961 | account -- the Account object. 962 | 963 | """ 964 | account = None 965 | 966 | def __init__(self, account=None): 967 | self._set_account(account) 968 | 969 | def __del__(self): 970 | pass 971 | 972 | def _set_account(self, account): 973 | if account: 974 | self.account = weakref.proxy(account) 975 | else: 976 | self.account = None 977 | 978 | def on_reg_state(self): 979 | """Notification that the registration status has changed. 980 | """ 981 | pass 982 | 983 | def on_incoming_call(self, call): 984 | """Notification about incoming call. 985 | 986 | Application should implement one of on_incoming_call() or 987 | on_incoming_call2(), otherwise, the default behavior is to 988 | reject the call with default status code. Note that if both are 989 | implemented, only on_incoming_call2() will be called. 990 | 991 | Keyword arguments: 992 | call -- the new incoming call 993 | """ 994 | call.hangup() 995 | 996 | def on_incoming_call2(self, call, rdata): 997 | """Notification about incoming call, with received SIP message info. 998 | 999 | Application should implement one of on_incoming_call() or 1000 | on_incoming_call2(), otherwise, the default behavior is to 1001 | reject the call with default status code. Note that if both are 1002 | implemented, only on_incoming_call2() will be called. 1003 | 1004 | Keyword arguments: 1005 | call -- the new incoming call 1006 | rdata -- the received message 1007 | """ 1008 | call.hangup() 1009 | 1010 | def on_incoming_subscribe(self, buddy, from_uri, contact_uri, pres_obj): 1011 | """Notification when incoming SUBSCRIBE request is received. 1012 | 1013 | Application may use this callback to authorize the incoming 1014 | subscribe request (e.g. ask user permission if the request 1015 | should be granted) 1016 | 1017 | Keyword arguments: 1018 | buddy -- The buddy object, if buddy is found. Otherwise 1019 | the value is None. 1020 | from_uri -- The URI string of the sender. 1021 | pres_obj -- Opaque presence subscription object, which is 1022 | needed by Account.pres_notify() 1023 | 1024 | Return: 1025 | Tuple (code, reason), where: 1026 | code: The status code. If code is >= 300, the 1027 | request is rejected. If code is 200, the 1028 | request is accepted and NOTIFY will be sent 1029 | automatically. If code is 202, application 1030 | must accept or reject the request later with 1031 | Account.press_notify(). 1032 | reason: Optional reason phrase, or None to use the 1033 | default reasoh phrase for the status code. 1034 | """ 1035 | return (200, None) 1036 | 1037 | def on_pager(self, from_uri, contact, mime_type, body): 1038 | """ 1039 | Notification that incoming instant message is received on 1040 | this account. 1041 | 1042 | Keyword arguments: 1043 | from_uri -- sender's URI 1044 | contact -- sender's Contact URI 1045 | mime_type -- MIME type of the instant message body 1046 | body -- the instant message body 1047 | 1048 | """ 1049 | pass 1050 | 1051 | def on_pager_status(self, to_uri, body, im_id, code, reason): 1052 | """ 1053 | Notification about the delivery status of previously sent 1054 | instant message. 1055 | 1056 | Keyword arguments: 1057 | to_uri -- the destination URI of the message 1058 | body -- the message body 1059 | im_id -- message ID 1060 | code -- SIP status code 1061 | reason -- SIP reason phrase 1062 | 1063 | """ 1064 | pass 1065 | 1066 | def on_typing(self, from_uri, contact, is_typing): 1067 | """ 1068 | Notification that remote is typing or stop typing. 1069 | 1070 | Keyword arguments: 1071 | buddy -- Buddy object for the sender, if found. Otherwise 1072 | this will be None 1073 | from_uri -- sender's URI of the indication 1074 | contact -- sender's contact URI 1075 | is_typing -- boolean to indicate whether remote is currently 1076 | typing an instant message. 1077 | 1078 | """ 1079 | pass 1080 | 1081 | def on_mwi_info(self, body): 1082 | """ 1083 | Notification about change in Message Summary / Message Waiting 1084 | Indication (RFC 3842) status. MWI subscription must be enabled 1085 | in the account config to receive this notification. 1086 | 1087 | Keyword arguments: 1088 | body -- String containing message body as received in the 1089 | NOTIFY request. 1090 | 1091 | """ 1092 | pass 1093 | 1094 | 1095 | 1096 | class Account: 1097 | """This describes SIP account class. 1098 | 1099 | PJSUA accounts provide identity (or identities) of the user who is 1100 | currently using the application. In SIP terms, the identity is used 1101 | as the From header in outgoing requests. 1102 | 1103 | Account may or may not have client registration associated with it. 1104 | An account is also associated with route set and some authentication 1105 | credentials, which are used when sending SIP request messages using 1106 | the account. An account also has presence's online status, which 1107 | will be reported to remote peer when they subscribe to the account's 1108 | presence, or which is published to a presence server if presence 1109 | publication is enabled for the account. 1110 | 1111 | Account is created with Lib.create_account(). At least one account 1112 | MUST be created. If no user association is required, application can 1113 | create a userless account by calling Lib.create_account_for_transport(). 1114 | A userless account identifies local endpoint instead of a particular 1115 | user, and it correspond with a particular transport instance. 1116 | 1117 | Also one account must be set as the default account, which is used as 1118 | the account to use when PJSUA fails to match a request with any other 1119 | accounts. 1120 | 1121 | """ 1122 | _id = -1 1123 | _lib = None 1124 | _cb = AccountCallback(None) 1125 | _obj_name = "" 1126 | 1127 | def __init__(self, lib, id, cb=None): 1128 | """Construct this class. This is normally called by Lib class and 1129 | not by application. 1130 | 1131 | Keyword arguments: 1132 | lib -- the Lib instance. 1133 | id -- the pjsua account ID. 1134 | cb -- AccountCallback instance to receive events from this Account. 1135 | If callback is not specified here, it must be set later 1136 | using set_callback(). 1137 | """ 1138 | self._id = id 1139 | self._lib = weakref.ref(lib) 1140 | self._obj_name = "{Account " + self.info().uri + "}" 1141 | self.set_callback(cb) 1142 | _pjsua.acc_set_user_data(self._id, self) 1143 | _Trace((self, 'created')) 1144 | 1145 | def __del__(self): 1146 | if self._id != -1: 1147 | _pjsua.acc_set_user_data(self._id, 0) 1148 | _Trace((self, 'destroyed')) 1149 | 1150 | def __str__(self): 1151 | return self._obj_name 1152 | 1153 | def info(self): 1154 | """Retrieve AccountInfo for this account. 1155 | """ 1156 | lck = self._lib().auto_lock() 1157 | ai = _pjsua.acc_get_info(self._id) 1158 | if ai==None: 1159 | self._lib()._err_check("info()", self, -1, "Invalid account") 1160 | return AccountInfo(ai) 1161 | 1162 | def is_valid(self): 1163 | """ 1164 | Check if this account is still valid. 1165 | 1166 | """ 1167 | lck = self._lib().auto_lock() 1168 | return _pjsua.acc_is_valid(self._id) 1169 | 1170 | def set_callback(self, cb): 1171 | """Register callback to receive notifications from this object. 1172 | 1173 | Keyword argument: 1174 | cb -- AccountCallback instance. 1175 | 1176 | """ 1177 | if cb: 1178 | self._cb = cb 1179 | else: 1180 | self._cb = AccountCallback(self) 1181 | self._cb._set_account(self) 1182 | 1183 | def set_default(self): 1184 | """ Set this account as default account to send outgoing requests 1185 | and as the account to receive incoming requests when more exact 1186 | matching criteria fails. 1187 | """ 1188 | lck = self._lib().auto_lock() 1189 | err = _pjsua.acc_set_default(self._id) 1190 | self._lib()._err_check("set_default()", self, err) 1191 | 1192 | def is_default(self): 1193 | """ Check if this account is the default account. 1194 | 1195 | """ 1196 | lck = self._lib().auto_lock() 1197 | def_id = _pjsua.acc_get_default() 1198 | return self.is_valid() and def_id==self._id 1199 | 1200 | def delete(self): 1201 | """ Delete this account. 1202 | 1203 | """ 1204 | lck = self._lib().auto_lock() 1205 | err = _pjsua.acc_set_user_data(self._id, 0) 1206 | self._lib()._err_check("delete()", self, err) 1207 | err = _pjsua.acc_del(self._id) 1208 | self._lib()._err_check("delete()", self, err) 1209 | self._id = -1 1210 | 1211 | def set_basic_status(self, is_online): 1212 | """ Set basic presence status of this account. 1213 | 1214 | Keyword argument: 1215 | is_online -- boolean to indicate basic presence availability. 1216 | 1217 | """ 1218 | lck = self._lib().auto_lock() 1219 | err = _pjsua.acc_set_online_status(self._id, is_online) 1220 | self._lib()._err_check("set_basic_status()", self, err) 1221 | 1222 | def set_presence_status(self, is_online, 1223 | activity=PresenceActivity.UNKNOWN, 1224 | pres_text="", rpid_id=""): 1225 | """ Set presence status of this account. 1226 | 1227 | Keyword arguments: 1228 | is_online -- boolean to indicate basic presence availability 1229 | activity -- value from PresenceActivity 1230 | pres_text -- optional string to convey additional information about 1231 | the activity (such as "On the phone") 1232 | rpid_id -- optional string to be placed as RPID ID. 1233 | 1234 | """ 1235 | lck = self._lib().auto_lock() 1236 | err = _pjsua.acc_set_online_status2(self._id, is_online, activity, 1237 | pres_text, rpid_id) 1238 | self._lib()._err_check("set_presence_status()", self, err) 1239 | 1240 | def set_registration(self, renew): 1241 | """Manually renew registration or unregister from the server. 1242 | 1243 | Keyword argument: 1244 | renew -- boolean to indicate whether registration is renewed. 1245 | Setting this value for False will trigger unregistration. 1246 | 1247 | """ 1248 | lck = self._lib().auto_lock() 1249 | err = _pjsua.acc_set_registration(self._id, renew) 1250 | self._lib()._err_check("set_registration()", self, err) 1251 | 1252 | def set_transport(self, transport): 1253 | """Set this account to only use the specified transport to send 1254 | outgoing requests. 1255 | 1256 | Keyword argument: 1257 | transport -- Transport object. 1258 | 1259 | """ 1260 | lck = self._lib().auto_lock() 1261 | err = _pjsua.acc_set_transport(self._id, transport._id) 1262 | self._lib()._err_check("set_transport()", self, err) 1263 | 1264 | def make_call(self, dst_uri, cb=None, hdr_list=None): 1265 | """Make outgoing call to the specified URI. 1266 | 1267 | Keyword arguments: 1268 | dst_uri -- Destination SIP URI. 1269 | cb -- CallCallback instance to be installed to the newly 1270 | created Call object. If this CallCallback is not 1271 | specified (i.e. None is given), it must be installed 1272 | later using call.set_callback(). 1273 | hdr_list -- Optional list of headers to be sent with outgoing 1274 | INVITE 1275 | 1276 | Return: 1277 | Call instance. 1278 | """ 1279 | lck = self._lib().auto_lock() 1280 | call = Call(self._lib(), -1, cb) 1281 | err, cid = _pjsua.call_make_call(self._id, dst_uri, 0, 1282 | call, Lib._create_msg_data(hdr_list)) 1283 | self._lib()._err_check("make_call()", self, err) 1284 | call.attach_to_id(cid) 1285 | return call 1286 | 1287 | def add_buddy(self, uri, cb=None): 1288 | """Add new buddy. 1289 | 1290 | Keyword argument: 1291 | uri -- SIP URI of the buddy 1292 | cb -- BuddyCallback instance to be installed to the newly 1293 | created Buddy object. If this callback is not specified 1294 | (i.e. None is given), it must be installed later using 1295 | buddy.set_callback(). 1296 | 1297 | Return: 1298 | Buddy object 1299 | """ 1300 | lck = self._lib().auto_lock() 1301 | buddy_cfg = _pjsua.buddy_config_default() 1302 | buddy_cfg.uri = uri 1303 | buddy_cfg.subscribe = False 1304 | err, buddy_id = _pjsua.buddy_add(buddy_cfg) 1305 | self._lib()._err_check("add_buddy()", self, err) 1306 | buddy = Buddy(self._lib(), buddy_id, self, cb) 1307 | return buddy 1308 | 1309 | def pres_notify(self, pres_obj, state, reason="", hdr_list=None): 1310 | """Send NOTIFY to inform account presence status or to terminate 1311 | server side presence subscription. 1312 | 1313 | Keyword arguments: 1314 | pres_obj -- The subscription object from on_incoming_subscribe() 1315 | callback 1316 | state -- Subscription state, from SubscriptionState 1317 | reason -- Optional reason phrase. 1318 | hdr_list -- Optional header list. 1319 | """ 1320 | lck = self._lib().auto_lock() 1321 | _pjsua.acc_pres_notify(self._id, pres_obj, state, reason, 1322 | Lib._create_msg_data(hdr_list)) 1323 | 1324 | def send_pager(self, uri, text, im_id=0, content_type="text/plain", \ 1325 | hdr_list=None): 1326 | """Send instant message to arbitrary URI. 1327 | 1328 | Keyword arguments: 1329 | text -- Instant message to be sent 1330 | uri -- URI to send the Instant Message to. 1331 | im_id -- Optional instant message ID to identify this 1332 | instant message when delivery status callback 1333 | is called. 1334 | content_type -- MIME type identifying the instant message 1335 | hdr_list -- Optional list of headers to be sent with the 1336 | request. 1337 | 1338 | """ 1339 | lck = self._lib().auto_lock() 1340 | err = _pjsua.im_send(self._id, uri, \ 1341 | content_type, text, \ 1342 | Lib._create_msg_data(hdr_list), \ 1343 | im_id) 1344 | self._lib()._err_check("send_pager()", self, err) 1345 | 1346 | class CallCallback: 1347 | """Class to receive event notification from Call objects. 1348 | 1349 | Use Call.set_callback() method to install instance of this callback 1350 | class to receive event notifications from the call object. 1351 | 1352 | Member documentation: 1353 | 1354 | call -- the Call object. 1355 | 1356 | """ 1357 | call = None 1358 | 1359 | def __init__(self, call=None): 1360 | self._set_call(call) 1361 | 1362 | def __del__(self): 1363 | pass 1364 | 1365 | def _set_call(self, call): 1366 | if call: 1367 | self.call = weakref.proxy(call) 1368 | else: 1369 | self.call = None 1370 | 1371 | def on_state(self): 1372 | """Notification that the call's state has changed. 1373 | 1374 | """ 1375 | pass 1376 | 1377 | def on_media_state(self): 1378 | """Notification that the call's media state has changed. 1379 | 1380 | """ 1381 | pass 1382 | 1383 | def on_dtmf_digit(self, digits): 1384 | """Notification on incoming DTMF digits. 1385 | 1386 | Keyword argument: 1387 | digits -- string containing the received digits. 1388 | 1389 | """ 1390 | pass 1391 | 1392 | def on_transfer_request(self, dst, code): 1393 | """Notification that call is being transferred by remote party. 1394 | 1395 | Application can decide to accept/reject transfer request by returning 1396 | code greater than or equal to 500. The default behavior is to accept 1397 | the transfer by returning 202. 1398 | 1399 | Keyword arguments: 1400 | dst -- string containing the destination URI 1401 | code -- the suggested status code to return to accept the request. 1402 | 1403 | Return: 1404 | the callback should return 202 to accept the request, or 300-699 to 1405 | reject the request. 1406 | 1407 | """ 1408 | return code 1409 | 1410 | def on_transfer_status(self, code, reason, final, cont): 1411 | """ 1412 | Notification about the status of previous call transfer request. 1413 | 1414 | Keyword arguments: 1415 | code -- SIP status code to indicate completion status. 1416 | text -- SIP status reason phrase. 1417 | final -- if True then this is a final status and no further 1418 | notifications will be sent for this call transfer 1419 | status. 1420 | cont -- suggested return value. 1421 | 1422 | Return: 1423 | If the callback returns false then no further notification will 1424 | be sent for the transfer request for this call. 1425 | 1426 | """ 1427 | return cont 1428 | 1429 | def on_replace_request(self, code, reason): 1430 | """Notification when incoming INVITE with Replaces header is received. 1431 | 1432 | Application may reject the request by returning value greather than 1433 | or equal to 500. The default behavior is to accept the request. 1434 | 1435 | Keyword arguments: 1436 | code -- default status code to return 1437 | reason -- default reason phrase to return 1438 | 1439 | Return: 1440 | The callback should return (code, reason) tuple. 1441 | 1442 | """ 1443 | return code, reason 1444 | 1445 | def on_replaced(self, new_call): 1446 | """ 1447 | Notification that this call will be replaced with new_call. 1448 | After this callback is called, this call will be disconnected. 1449 | 1450 | Keyword arguments: 1451 | new_call -- the new call that will replace this call. 1452 | """ 1453 | pass 1454 | 1455 | def on_pager(self, mime_type, body): 1456 | """ 1457 | Notification that incoming instant message is received on 1458 | this call. 1459 | 1460 | Keyword arguments: 1461 | mime_type -- MIME type of the instant message body. 1462 | body -- the instant message body. 1463 | 1464 | """ 1465 | pass 1466 | 1467 | def on_pager_status(self, body, im_id, code, reason): 1468 | """ 1469 | Notification about the delivery status of previously sent 1470 | instant message. 1471 | 1472 | Keyword arguments: 1473 | body -- message body 1474 | im_id -- message ID 1475 | code -- SIP status code 1476 | reason -- SIP reason phrase 1477 | 1478 | """ 1479 | pass 1480 | 1481 | def on_typing(self, is_typing): 1482 | """ 1483 | Notification that remote is typing or stop typing. 1484 | 1485 | Keyword arguments: 1486 | is_typing -- boolean to indicate whether remote is currently 1487 | typing an instant message. 1488 | 1489 | """ 1490 | pass 1491 | 1492 | 1493 | class CallInfo: 1494 | """This structure contains various information about Call. 1495 | 1496 | Application may retrieve this information with Call.info(). 1497 | 1498 | Member documentation: 1499 | 1500 | role -- CallRole 1501 | account -- Account object. 1502 | uri -- SIP URI of local account. 1503 | contact -- local Contact URI. 1504 | remote_uri -- remote SIP URI. 1505 | remote_contact -- remote Contact URI 1506 | sip_call_id -- call's Call-ID identification 1507 | state -- CallState 1508 | state_text -- state text. 1509 | last_code -- last SIP status code 1510 | last_reason -- text phrase for last_code 1511 | media_state -- MediaState 1512 | media_dir -- MediaDir 1513 | conf_slot -- conference slot number for this call. 1514 | call_time -- call's connected duration in seconds. 1515 | total_time -- total call duration in seconds. 1516 | """ 1517 | role = CallRole.CALLER 1518 | account = None 1519 | uri = "" 1520 | contact = "" 1521 | remote_uri = "" 1522 | remote_contact = "" 1523 | sip_call_id = "" 1524 | state = CallState.NULL 1525 | state_text = "" 1526 | last_code = 0 1527 | last_reason = "" 1528 | media_state = MediaState.NULL 1529 | media_dir = MediaDir.NULL 1530 | conf_slot = -1 1531 | call_time = 0 1532 | total_time = 0 1533 | 1534 | def __init__(self, lib=None, ci=None): 1535 | if lib and ci: 1536 | self._cvt_from_pjsua(lib, ci) 1537 | 1538 | def _cvt_from_pjsua(self, lib, ci): 1539 | self.role = ci.role 1540 | self.account = lib._lookup_account(ci.acc_id) 1541 | self.uri = ci.local_info 1542 | self.contact = ci.local_contact 1543 | self.remote_uri = ci.remote_info 1544 | self.remote_contact = ci.remote_contact 1545 | self.sip_call_id = ci.call_id 1546 | self.state = ci.state 1547 | self.state_text = ci.state_text 1548 | self.last_code = ci.last_status 1549 | self.last_reason = ci.last_status_text 1550 | self.media_state = ci.media_status 1551 | self.media_dir = ci.media_dir 1552 | self.conf_slot = ci.conf_slot 1553 | self.call_time = ci.connect_duration / 1000 1554 | self.total_time = ci.total_duration / 1000 1555 | 1556 | 1557 | class Call: 1558 | """This class represents SIP call. 1559 | 1560 | Application initiates outgoing call with Account.make_call(), and 1561 | incoming calls are reported in AccountCallback.on_incoming_call(). 1562 | """ 1563 | _id = -1 1564 | _cb = None 1565 | _lib = None 1566 | _obj_name = "" 1567 | 1568 | def __init__(self, lib, call_id, cb=None): 1569 | self._lib = weakref.ref(lib) 1570 | self.set_callback(cb) 1571 | self.attach_to_id(call_id) 1572 | _Trace((self, 'created')) 1573 | 1574 | def __del__(self): 1575 | if self._id != -1: 1576 | _pjsua.call_set_user_data(self._id, 0) 1577 | _Trace((self, 'destroyed')) 1578 | 1579 | def __str__(self): 1580 | return self._obj_name 1581 | 1582 | def attach_to_id(self, call_id): 1583 | lck = self._lib().auto_lock() 1584 | if self._id != -1: 1585 | _pjsua.call_set_user_data(self._id, 0) 1586 | self._id = call_id 1587 | if self._id != -1: 1588 | _pjsua.call_set_user_data(self._id, self) 1589 | self._obj_name = "{Call " + self.info().remote_uri + "}" 1590 | else: 1591 | self._obj_name = "{Call object}" 1592 | 1593 | def set_callback(self, cb): 1594 | """ 1595 | Set callback object to retrieve event notifications from this call. 1596 | 1597 | Keyword arguments: 1598 | cb -- CallCallback instance. 1599 | """ 1600 | if cb: 1601 | self._cb = cb 1602 | else: 1603 | self._cb = CallCallback(self) 1604 | self._cb._set_call(self) 1605 | 1606 | def info(self): 1607 | """ 1608 | Get the CallInfo. 1609 | """ 1610 | lck = self._lib().auto_lock() 1611 | ci = _pjsua.call_get_info(self._id) 1612 | if not ci: 1613 | self._lib()._err_check("info", self, -1, "Invalid call") 1614 | call_info = CallInfo(self._lib(), ci) 1615 | return call_info 1616 | 1617 | def is_valid(self): 1618 | """ 1619 | Check if this call is still valid. 1620 | """ 1621 | lck = self._lib().auto_lock() 1622 | return _pjsua.call_is_active(self._id) 1623 | 1624 | def dump_status(self, with_media=True, indent="", max_len=1024): 1625 | """ 1626 | Dump the call status. 1627 | """ 1628 | lck = self._lib().auto_lock() 1629 | return _pjsua.call_dump(self._id, with_media, max_len, indent) 1630 | 1631 | def answer(self, code=200, reason="", hdr_list=None): 1632 | """ 1633 | Send provisional or final response to incoming call. 1634 | 1635 | Keyword arguments: 1636 | code -- SIP status code. 1637 | reason -- Reason phrase. Put empty to send default reason 1638 | phrase for the status code. 1639 | hdr_list -- Optional list of headers to be sent with the 1640 | INVITE response. 1641 | 1642 | """ 1643 | lck = self._lib().auto_lock() 1644 | err = _pjsua.call_answer(self._id, code, reason, 1645 | Lib._create_msg_data(hdr_list)) 1646 | self._lib()._err_check("answer()", self, err) 1647 | 1648 | def hangup(self, code=603, reason="", hdr_list=None): 1649 | """ 1650 | Terminate the call. 1651 | 1652 | Keyword arguments: 1653 | code -- SIP status code. 1654 | reason -- Reason phrase. Put empty to send default reason 1655 | phrase for the status code. 1656 | hdr_list -- Optional list of headers to be sent with the 1657 | message. 1658 | 1659 | """ 1660 | lck = self._lib().auto_lock() 1661 | err = _pjsua.call_hangup(self._id, code, reason, 1662 | Lib._create_msg_data(hdr_list)) 1663 | self._lib()._err_check("hangup()", self, err) 1664 | 1665 | def hold(self, hdr_list=None): 1666 | """ 1667 | Put the call on hold. 1668 | 1669 | Keyword arguments: 1670 | hdr_list -- Optional list of headers to be sent with the 1671 | message. 1672 | """ 1673 | lck = self._lib().auto_lock() 1674 | err = _pjsua.call_set_hold(self._id, Lib._create_msg_data(hdr_list)) 1675 | self._lib()._err_check("hold()", self, err) 1676 | 1677 | def unhold(self, hdr_list=None): 1678 | """ 1679 | Release the call from hold. 1680 | 1681 | Keyword arguments: 1682 | hdr_list -- Optional list of headers to be sent with the 1683 | message. 1684 | 1685 | """ 1686 | lck = self._lib().auto_lock() 1687 | err = _pjsua.call_reinvite(self._id, True, 1688 | Lib._create_msg_data(hdr_list)) 1689 | self._lib()._err_check("unhold()", self, err) 1690 | 1691 | def reinvite(self, hdr_list=None): 1692 | """ 1693 | Send re-INVITE and optionally offer new codecs to use. 1694 | 1695 | Keyword arguments: 1696 | hdr_list -- Optional list of headers to be sent with the 1697 | message. 1698 | 1699 | """ 1700 | lck = self._lib().auto_lock() 1701 | err = _pjsua.call_reinvite(self._id, True, 1702 | Lib._create_msg_data(hdr_list)) 1703 | self._lib()._err_check("reinvite()", self, err) 1704 | 1705 | def update(self, hdr_list=None, options=0): 1706 | """ 1707 | Send UPDATE and optionally offer new codecs to use. 1708 | 1709 | Keyword arguments: 1710 | hdr_list -- Optional list of headers to be sent with the 1711 | message. 1712 | options -- Must be zero for now. 1713 | 1714 | """ 1715 | lck = self._lib().auto_lock() 1716 | err = _pjsua.call_update(self._id, options, 1717 | Lib._create_msg_data(hdr_list)) 1718 | self._lib()._err_check("update()", self, err) 1719 | 1720 | def transfer(self, dest_uri, hdr_list=None): 1721 | """ 1722 | Transfer the call to new destination. 1723 | 1724 | Keyword arguments: 1725 | dest_uri -- Specify the SIP URI to transfer the call to. 1726 | hdr_list -- Optional list of headers to be sent with the 1727 | message. 1728 | 1729 | """ 1730 | lck = self._lib().auto_lock() 1731 | err = _pjsua.call_xfer(self._id, dest_uri, 1732 | Lib._create_msg_data(hdr_list)) 1733 | self._lib()._err_check("transfer()", self, err) 1734 | 1735 | def transfer_to_call(self, call, hdr_list=None, options=0): 1736 | """ 1737 | Attended call transfer. 1738 | 1739 | Keyword arguments: 1740 | call -- The Call object to transfer call to. 1741 | hdr_list -- Optional list of headers to be sent with the 1742 | message. 1743 | options -- Must be zero for now. 1744 | 1745 | """ 1746 | lck = self._lib().auto_lock() 1747 | err = _pjsua.call_xfer_replaces(self._id, call._id, options, 1748 | Lib._create_msg_data(hdr_list)) 1749 | self._lib()._err_check("transfer_to_call()", self, err) 1750 | 1751 | def dial_dtmf(self, digits): 1752 | """ 1753 | Send DTMF digits with RTP event package. 1754 | 1755 | Keyword arguments: 1756 | digits -- DTMF digit string. 1757 | 1758 | """ 1759 | lck = self._lib().auto_lock() 1760 | err = _pjsua.call_dial_dtmf(self._id, digits) 1761 | self._lib()._err_check("dial_dtmf()", self, err) 1762 | 1763 | def send_request(self, method, hdr_list=None, content_type=None, 1764 | body=None): 1765 | """ 1766 | Send arbitrary request to remote call. 1767 | 1768 | This is useful for example to send INFO request. Note that this 1769 | function should not be used to send request that will change the 1770 | call state such as CANCEL or BYE. 1771 | 1772 | Keyword arguments: 1773 | method -- SIP method name. 1774 | hdr_list -- Optional header list to be sent with the request. 1775 | content_type -- Content type to describe the body, if the body 1776 | is present 1777 | body -- Optional SIP message body. 1778 | 1779 | """ 1780 | lck = self._lib().auto_lock() 1781 | if hdr_list or body: 1782 | msg_data = _pjsua.Msg_Data() 1783 | if hdr_list: 1784 | msg_data.hdr_list = hdr_list 1785 | if content_type: 1786 | msg_data.content_type = content_type 1787 | if body: 1788 | msg_data.msg_body = body 1789 | else: 1790 | msg_data = None 1791 | 1792 | err = _pjsua.call_send_request(self._id, method, msg_data) 1793 | self._lib()._err_check("send_request()", self, err) 1794 | 1795 | def send_pager(self, text, im_id=0, content_type="text/plain", 1796 | hdr_list=None): 1797 | """Send instant message inside a call. 1798 | 1799 | Keyword arguments: 1800 | text -- Instant message to be sent 1801 | im_id -- Optional instant message ID to identify this 1802 | instant message when delivery status callback 1803 | is called. 1804 | content_type -- MIME type identifying the instant message 1805 | hdr_list -- Optional list of headers to be sent with the 1806 | request. 1807 | 1808 | """ 1809 | lck = self._lib().auto_lock() 1810 | err = _pjsua.call_send_im(self._id, \ 1811 | content_type, text, \ 1812 | Lib._create_msg_data(hdr_list), \ 1813 | im_id) 1814 | self._lib()._err_check("send_pager()", self, err) 1815 | 1816 | 1817 | class BuddyInfo: 1818 | """This class contains information about Buddy. Application may 1819 | retrieve this information by calling Buddy.info(). 1820 | 1821 | Member documentation: 1822 | 1823 | uri -- the Buddy URI. 1824 | contact -- the Buddy Contact URI, if available. 1825 | online_status -- the presence online status. 1826 | online_text -- the presence online status text. 1827 | activity -- the PresenceActivity 1828 | subscribed -- specify whether buddy's presence status is currently 1829 | being subscribed. 1830 | sub_state -- SubscriptionState 1831 | sub_term_reason -- The termination reason string of the last presence 1832 | subscription to this buddy, if any. 1833 | """ 1834 | uri = "" 1835 | contact = "" 1836 | online_status = 0 1837 | online_text = "" 1838 | activity = PresenceActivity.UNKNOWN 1839 | subscribed = False 1840 | sub_state = SubscriptionState.NULL 1841 | sub_term_reason = "" 1842 | 1843 | def __init__(self, pjsua_bi=None): 1844 | if pjsua_bi: 1845 | self._cvt_from_pjsua(pjsua_bi) 1846 | 1847 | def _cvt_from_pjsua(self, inf): 1848 | self.uri = inf.uri 1849 | self.contact = inf.contact 1850 | self.online_status = inf.status 1851 | self.online_text = inf.status_text 1852 | self.activity = inf.activity 1853 | self.subscribed = inf.monitor_pres 1854 | self.sub_state = inf.sub_state 1855 | self.sub_term_reason = inf.sub_term_reason 1856 | 1857 | 1858 | class BuddyCallback: 1859 | """This class can be used to receive notifications about Buddy's 1860 | presence status change. Application needs to derive a class from 1861 | this class, and register the instance with Buddy.set_callback(). 1862 | 1863 | Member documentation: 1864 | 1865 | buddy -- the Buddy object. 1866 | """ 1867 | buddy = None 1868 | 1869 | def __init__(self, buddy=None): 1870 | self._set_buddy(buddy) 1871 | 1872 | def _set_buddy(self, buddy): 1873 | if buddy: 1874 | self.buddy = weakref.proxy(buddy) 1875 | else: 1876 | self.buddy = None 1877 | 1878 | def on_state(self): 1879 | """ 1880 | Notification that buddy's presence state has changed. Application 1881 | may then retrieve the new status with Buddy.info() function. 1882 | """ 1883 | pass 1884 | 1885 | def on_pager(self, mime_type, body): 1886 | """Notification that incoming instant message is received from 1887 | this buddy. 1888 | 1889 | Keyword arguments: 1890 | mime_type -- MIME type of the instant message body 1891 | body -- the instant message body 1892 | 1893 | """ 1894 | pass 1895 | 1896 | def on_pager_status(self, body, im_id, code, reason): 1897 | """Notification about the delivery status of previously sent 1898 | instant message. 1899 | 1900 | Keyword arguments: 1901 | body -- the message body 1902 | im_id -- message ID 1903 | code -- SIP status code 1904 | reason -- SIP reason phrase 1905 | 1906 | """ 1907 | pass 1908 | 1909 | def on_typing(self, is_typing): 1910 | """Notification that remote is typing or stop typing. 1911 | 1912 | Keyword arguments: 1913 | is_typing -- boolean to indicate whether remote is currently 1914 | typing an instant message. 1915 | 1916 | """ 1917 | pass 1918 | 1919 | 1920 | class Buddy: 1921 | """A Buddy represents person or remote agent. 1922 | 1923 | This class provides functions to subscribe to buddy's presence and 1924 | to send or receive instant messages from the buddy. 1925 | """ 1926 | _id = -1 1927 | _lib = None 1928 | _cb = None 1929 | _obj_name = "" 1930 | _acc = None 1931 | 1932 | def __init__(self, lib, id, account, cb): 1933 | self._id = id 1934 | self._lib = weakref.ref(lib) 1935 | self._acc = weakref.ref(account) 1936 | self._obj_name = "{Buddy " + self.info().uri + "}" 1937 | self.set_callback(cb) 1938 | _pjsua.buddy_set_user_data(self._id, self) 1939 | _Trace((self, 'created')) 1940 | 1941 | def __del__(self): 1942 | if self._id != -1: 1943 | _pjsua.buddy_set_user_data(self._id, 0) 1944 | _Trace((self, 'destroyed')) 1945 | 1946 | def __str__(self): 1947 | return self._obj_name 1948 | 1949 | def info(self): 1950 | """ 1951 | Get buddy info as BuddyInfo. 1952 | """ 1953 | lck = self._lib().auto_lock() 1954 | return BuddyInfo(_pjsua.buddy_get_info(self._id)) 1955 | 1956 | def set_callback(self, cb): 1957 | """Install callback to receive notifications from this object. 1958 | 1959 | Keyword argument: 1960 | cb -- BuddyCallback instance. 1961 | """ 1962 | if cb: 1963 | self._cb = cb 1964 | else: 1965 | self._cb = BuddyCallback(self) 1966 | self._cb._set_buddy(self) 1967 | 1968 | def subscribe(self): 1969 | """ 1970 | Subscribe to buddy's presence status notification. 1971 | """ 1972 | lck = self._lib().auto_lock() 1973 | err = _pjsua.buddy_subscribe_pres(self._id, True) 1974 | self._lib()._err_check("subscribe()", self, err) 1975 | 1976 | def unsubscribe(self): 1977 | """ 1978 | Unsubscribe from buddy's presence status notification. 1979 | """ 1980 | lck = self._lib().auto_lock() 1981 | err = _pjsua.buddy_subscribe_pres(self._id, False) 1982 | self._lib()._err_check("unsubscribe()", self, err) 1983 | 1984 | def delete(self): 1985 | """ 1986 | Remove this buddy from the buddy list. 1987 | """ 1988 | lck = self._lib().auto_lock() 1989 | if self._id != -1: 1990 | _pjsua.buddy_set_user_data(self._id, 0) 1991 | err = _pjsua.buddy_del(self._id) 1992 | self._lib()._err_check("delete()", self, err) 1993 | 1994 | def send_pager(self, text, im_id=0, content_type="text/plain", \ 1995 | hdr_list=None): 1996 | """Send instant message to remote buddy. 1997 | 1998 | Keyword arguments: 1999 | text -- Instant message to be sent 2000 | im_id -- Optional instant message ID to identify this 2001 | instant message when delivery status callback 2002 | is called. 2003 | content_type -- MIME type identifying the instant message 2004 | hdr_list -- Optional list of headers to be sent with the 2005 | request. 2006 | 2007 | """ 2008 | lck = self._lib().auto_lock() 2009 | err = _pjsua.im_send(self._acc()._id, self.info().uri, \ 2010 | content_type, text, \ 2011 | Lib._create_msg_data(hdr_list), \ 2012 | im_id) 2013 | self._lib()._err_check("send_pager()", self, err) 2014 | 2015 | def send_typing_ind(self, is_typing=True, hdr_list=None): 2016 | """Send typing indication to remote buddy. 2017 | 2018 | Keyword argument: 2019 | is_typing -- boolean to indicate wheter user is typing. 2020 | hdr_list -- Optional list of headers to be sent with the 2021 | request. 2022 | 2023 | """ 2024 | lck = self._lib().auto_lock() 2025 | err = _pjsua.im_typing(self._acc()._id, self.info().uri, \ 2026 | is_typing, Lib._create_msg_data(hdr_list)) 2027 | self._lib()._err_check("send_typing_ind()", self, err) 2028 | 2029 | 2030 | 2031 | # Sound device info 2032 | class SoundDeviceInfo: 2033 | """This described the sound device info. 2034 | 2035 | Member documentation: 2036 | name -- device name. 2037 | input_channels -- number of capture channels supported. 2038 | output_channels -- number of playback channels supported. 2039 | default_clock_rate -- default sampling rate. 2040 | """ 2041 | name = "" 2042 | input_channels = 0 2043 | output_channels = 0 2044 | default_clock_rate = 0 2045 | 2046 | def __init__(self, sdi): 2047 | self.name = sdi.name 2048 | self.input_channels = sdi.input_count 2049 | self.output_channels = sdi.output_count 2050 | self.default_clock_rate = sdi.default_samples_per_sec 2051 | 2052 | 2053 | # Codec info 2054 | class CodecInfo: 2055 | """This describes codec info. 2056 | 2057 | Member documentation: 2058 | name -- codec name 2059 | priority -- codec priority (0-255) 2060 | clock_rate -- clock rate 2061 | channel_count -- number of channels 2062 | avg_bps -- average bandwidth in bits per second 2063 | frm_ptime -- base frame length in milliseconds 2064 | ptime -- RTP frame length in milliseconds. 2065 | pt -- payload type. 2066 | vad_enabled -- specify if Voice Activity Detection is currently 2067 | enabled. 2068 | plc_enabled -- specify if Packet Lost Concealment is currently 2069 | enabled. 2070 | """ 2071 | name = "" 2072 | priority = 0 2073 | clock_rate = 0 2074 | channel_count = 0 2075 | avg_bps = 0 2076 | frm_ptime = 0 2077 | ptime = 0 2078 | pt = 0 2079 | vad_enabled = False 2080 | plc_enabled = False 2081 | 2082 | def __init__(self, codec_info, codec_param): 2083 | self.name = codec_info.codec_id 2084 | self.priority = codec_info.priority 2085 | self.clock_rate = codec_param.info.clock_rate 2086 | self.channel_count = codec_param.info.channel_cnt 2087 | self.avg_bps = codec_param.info.avg_bps 2088 | self.frm_ptime = codec_param.info.frm_ptime 2089 | self.ptime = codec_param.info.frm_ptime * \ 2090 | codec_param.setting.frm_per_pkt 2091 | self.ptime = codec_param.info.pt 2092 | self.vad_enabled = codec_param.setting.vad 2093 | self.plc_enabled = codec_param.setting.plc 2094 | 2095 | def _cvt_to_pjsua(self): 2096 | ci = _pjsua.Codec_Info() 2097 | ci.codec_id = self.name 2098 | ci.priority = self.priority 2099 | return ci 2100 | 2101 | 2102 | # Codec parameter 2103 | class CodecParameter: 2104 | """This specifies various parameters that can be configured for codec. 2105 | 2106 | Member documentation: 2107 | 2108 | ptime -- specify the outgoing RTP packet length in milliseconds. 2109 | vad_enabled -- specify if VAD should be enabled. 2110 | plc_enabled -- specify if PLC should be enabled. 2111 | """ 2112 | ptime = 0 2113 | vad_enabled = False 2114 | plc_enabled = False 2115 | _codec_param = None 2116 | 2117 | def __init__(self, codec_param): 2118 | self.ptime = codec_param.info.frm_ptime * \ 2119 | codec_param.setting.frm_per_pkt 2120 | self.vad_enabled = codec_param.setting.vad 2121 | self.plc_enabled = codec_param.setting.plc 2122 | self._codec_param = codec_param 2123 | 2124 | def _cvt_to_pjsua(self): 2125 | self._codec_param.setting.frm_per_pkt = self.ptime / \ 2126 | self._codec_param.info.frm_ptime 2127 | self._codec_param.setting.vad = self.vad_enabled 2128 | self._codec_param.setting.plc = self.plc_enabled 2129 | return self._codec_param 2130 | 2131 | 2132 | # Library mutex 2133 | class _LibMutex: 2134 | def __init__(self, lck): 2135 | self._lck = lck 2136 | self._lck.acquire() 2137 | #_Trace(('lock acquired',)) 2138 | 2139 | def __del__(self): 2140 | try: 2141 | self._lck.release() 2142 | #_Trace(('lock released',)) 2143 | except: 2144 | #_Trace(('lock release error',)) 2145 | pass 2146 | 2147 | 2148 | # PJSUA Library 2149 | _lib = None 2150 | enable_trace = False 2151 | 2152 | class Lib: 2153 | """Library instance. 2154 | 2155 | """ 2156 | _quit = False 2157 | _has_thread = False 2158 | _lock = None 2159 | 2160 | def __init__(self): 2161 | global _lib 2162 | if _lib: 2163 | raise Error("__init()__", None, -1, 2164 | "Library instance already exist") 2165 | 2166 | self._lock = threading.RLock() 2167 | err = _pjsua.create() 2168 | self._err_check("_pjsua.create()", None, err) 2169 | _lib = self 2170 | 2171 | def __del__(self): 2172 | _pjsua.destroy() 2173 | del self._lock 2174 | _Trace(('Lib destroyed',)) 2175 | 2176 | def __str__(self): 2177 | return "Lib" 2178 | 2179 | @staticmethod 2180 | def instance(): 2181 | """Return singleton instance of Lib. 2182 | """ 2183 | return _lib 2184 | 2185 | def init(self, ua_cfg=None, log_cfg=None, media_cfg=None): 2186 | """ 2187 | Initialize pjsua with the specified configurations. 2188 | 2189 | Keyword arguments: 2190 | ua_cfg -- optional UAConfig instance 2191 | log_cfg -- optional LogConfig instance 2192 | media_cfg -- optional MediaConfig instance 2193 | 2194 | """ 2195 | if not ua_cfg: ua_cfg = UAConfig() 2196 | if not log_cfg: log_cfg = LogConfig() 2197 | if not media_cfg: media_cfg = MediaConfig() 2198 | 2199 | py_ua_cfg = ua_cfg._cvt_to_pjsua() 2200 | py_ua_cfg.cb.on_call_state = _cb_on_call_state 2201 | py_ua_cfg.cb.on_incoming_call = _cb_on_incoming_call 2202 | py_ua_cfg.cb.on_call_media_state = _cb_on_call_media_state 2203 | py_ua_cfg.cb.on_dtmf_digit = _cb_on_dtmf_digit 2204 | py_ua_cfg.cb.on_call_transfer_request = _cb_on_call_transfer_request 2205 | py_ua_cfg.cb.on_call_transfer_status = _cb_on_call_transfer_status 2206 | py_ua_cfg.cb.on_call_replace_request = _cb_on_call_replace_request 2207 | py_ua_cfg.cb.on_call_replaced = _cb_on_call_replaced 2208 | py_ua_cfg.cb.on_reg_state = _cb_on_reg_state 2209 | py_ua_cfg.cb.on_incoming_subscribe = _cb_on_incoming_subscribe 2210 | py_ua_cfg.cb.on_buddy_state = _cb_on_buddy_state 2211 | py_ua_cfg.cb.on_pager = _cb_on_pager 2212 | py_ua_cfg.cb.on_pager_status = _cb_on_pager_status 2213 | py_ua_cfg.cb.on_typing = _cb_on_typing 2214 | py_ua_cfg.cb.on_mwi_info = _cb_on_mwi_info; 2215 | 2216 | err = _pjsua.init(py_ua_cfg, log_cfg._cvt_to_pjsua(), 2217 | media_cfg._cvt_to_pjsua()) 2218 | self._err_check("init()", self, err) 2219 | 2220 | def destroy(self): 2221 | """Destroy the library, and pjsua.""" 2222 | global _lib 2223 | if self._has_thread: 2224 | self._quit = 1 2225 | loop = 0 2226 | while self._quit != 2 and loop < 400: 2227 | self.handle_events(5) 2228 | loop = loop + 1 2229 | time.sleep(0.050) 2230 | _pjsua.destroy() 2231 | _lib = None 2232 | 2233 | def start(self, with_thread=True): 2234 | """Start the library. 2235 | 2236 | Keyword argument: 2237 | with_thread -- specify whether the module should create worker 2238 | thread. 2239 | 2240 | """ 2241 | lck = self.auto_lock() 2242 | err = _pjsua.start() 2243 | self._err_check("start()", self, err) 2244 | self._has_thread = with_thread 2245 | if self._has_thread: 2246 | _thread.start_new(_worker_thread_main, (0,)) 2247 | 2248 | def handle_events(self, timeout=50): 2249 | """Poll the events from underlying pjsua library. 2250 | 2251 | Application must poll the stack periodically if worker thread 2252 | is disable when starting the library. 2253 | 2254 | Keyword argument: 2255 | timeout -- in milliseconds. 2256 | 2257 | """ 2258 | lck = self.auto_lock() 2259 | return _pjsua.handle_events(timeout) 2260 | 2261 | def thread_register(self, name): 2262 | """Register external threads (threads that are not created by PJSIP, 2263 | such as threads that are created by Python API) to PJSIP. 2264 | 2265 | The call must be made from the new thread before calling any pjlib 2266 | functions. 2267 | 2268 | Keyword arguments: 2269 | name -- Non descriptive name for the thread 2270 | """ 2271 | dummy = 1 2272 | err = _pjsua.thread_register(name, dummy) 2273 | self._err_check("thread_register()", self, err) 2274 | 2275 | def verify_sip_url(self, sip_url): 2276 | """Verify that the specified string is a valid URI. 2277 | 2278 | Keyword argument: 2279 | sip_url -- the URL string. 2280 | 2281 | Return: 2282 | 0 is the the URI is valid, otherwise the appropriate error 2283 | code is returned. 2284 | 2285 | """ 2286 | lck = self.auto_lock() 2287 | return _pjsua.verify_sip_url(sip_url) 2288 | 2289 | def create_transport(self, type, cfg=None): 2290 | """Create SIP transport instance of the specified type. 2291 | 2292 | Keyword arguments: 2293 | type -- transport type from TransportType constant. 2294 | cfg -- TransportConfig instance 2295 | 2296 | Return: 2297 | Transport object 2298 | 2299 | """ 2300 | lck = self.auto_lock() 2301 | if not cfg: cfg=TransportConfig() 2302 | err, tp_id = _pjsua.transport_create(type, cfg._cvt_to_pjsua()) 2303 | self._err_check("create_transport()", self, err) 2304 | return Transport(self, tp_id) 2305 | 2306 | def create_account(self, acc_config, set_default=True, cb=None): 2307 | """ 2308 | Create a new local pjsua account using the specified configuration. 2309 | 2310 | Keyword arguments: 2311 | acc_config -- AccountConfig 2312 | set_default -- boolean to specify whether to use this as the 2313 | default account. 2314 | cb -- AccountCallback instance. 2315 | 2316 | Return: 2317 | Account instance 2318 | 2319 | """ 2320 | lck = self.auto_lock() 2321 | err, acc_id = _pjsua.acc_add(acc_config._cvt_to_pjsua(), set_default) 2322 | self._err_check("create_account()", self, err) 2323 | return Account(self, acc_id, cb) 2324 | 2325 | def create_account_for_transport(self, transport, set_default=True, 2326 | cb=None): 2327 | """Create a new local pjsua transport for the specified transport. 2328 | 2329 | Keyword arguments: 2330 | transport -- the Transport instance. 2331 | set_default -- boolean to specify whether to use this as the 2332 | default account. 2333 | cb -- AccountCallback instance. 2334 | 2335 | Return: 2336 | Account instance 2337 | 2338 | """ 2339 | lck = self.auto_lock() 2340 | err, acc_id = _pjsua.acc_add_local(transport._id, set_default) 2341 | self._err_check("create_account_for_transport()", self, err) 2342 | return Account(self, acc_id, cb) 2343 | 2344 | def modify_account(self, acc_id, acc_config): 2345 | """Modify configuration of a pjsua account. 2346 | 2347 | Keyword arguments: 2348 | acc_id -- ID of the account to be modified. 2349 | acc_config -- New account configuration. 2350 | 2351 | """ 2352 | lck = self.auto_lock() 2353 | err = _pjsua.acc_modify(acc_id, acc_config._cvt_to_pjsua()) 2354 | self._err_check("modify_account()", self, err) 2355 | 2356 | def hangup_all(self): 2357 | """Hangup all calls. 2358 | 2359 | """ 2360 | lck = self.auto_lock() 2361 | _pjsua.call_hangup_all() 2362 | 2363 | # Sound device API 2364 | 2365 | def enum_snd_dev(self): 2366 | """Enumerate sound devices in the system. 2367 | 2368 | Return: 2369 | list of SoundDeviceInfo. The index of the element specifies 2370 | the device ID for the device. 2371 | """ 2372 | lck = self.auto_lock() 2373 | sdi_list = _pjsua.enum_snd_devs() 2374 | info = [] 2375 | for sdi in sdi_list: 2376 | info.append(SoundDeviceInfo(sdi)) 2377 | return info 2378 | 2379 | def get_snd_dev(self): 2380 | """Get the device IDs of current sound devices used by pjsua. 2381 | 2382 | Return: 2383 | (capture_dev_id, playback_dev_id) tuple 2384 | """ 2385 | lck = self.auto_lock() 2386 | return _pjsua.get_snd_dev() 2387 | 2388 | def set_snd_dev(self, capture_dev, playback_dev): 2389 | """Change the current sound devices. 2390 | 2391 | Keyword arguments: 2392 | capture_dev -- the device ID of capture device to be used 2393 | playback_dev -- the device ID of playback device to be used. 2394 | 2395 | """ 2396 | lck = self.auto_lock() 2397 | err = _pjsua.set_snd_dev(capture_dev, playback_dev) 2398 | self._err_check("set_current_sound_devices()", self, err) 2399 | 2400 | def set_null_snd_dev(self): 2401 | """Disable the sound devices. This is useful if the system 2402 | does not have sound device installed. 2403 | 2404 | """ 2405 | lck = self.auto_lock() 2406 | err = _pjsua.set_null_snd_dev() 2407 | self._err_check("set_null_snd_dev()", self, err) 2408 | 2409 | 2410 | # Conference bridge 2411 | 2412 | def conf_get_max_ports(self): 2413 | """Get the conference bridge capacity. 2414 | 2415 | Return: 2416 | conference bridge capacity. 2417 | 2418 | """ 2419 | lck = self.auto_lock() 2420 | return _pjsua.conf_get_max_ports() 2421 | 2422 | def conf_connect(self, src_slot, dst_slot): 2423 | """Establish unidirectional media flow from souce to sink. 2424 | 2425 | One source may transmit to multiple destinations/sink. And if 2426 | multiple sources are transmitting to the same sink, the media 2427 | will be mixed together. Source and sink may refer to the same ID, 2428 | effectively looping the media. 2429 | 2430 | If bidirectional media flow is desired, application needs to call 2431 | this function twice, with the second one having the arguments 2432 | reversed. 2433 | 2434 | Keyword arguments: 2435 | src_slot -- integer to identify the conference slot number of 2436 | the source/transmitter. 2437 | dst_slot -- integer to identify the conference slot number of 2438 | the destination/receiver. 2439 | 2440 | """ 2441 | lck = self.auto_lock() 2442 | err = _pjsua.conf_connect(src_slot, dst_slot) 2443 | self._err_check("conf_connect()", self, err) 2444 | 2445 | def conf_disconnect(self, src_slot, dst_slot): 2446 | """Disconnect media flow from the source to destination port. 2447 | 2448 | Keyword arguments: 2449 | src_slot -- integer to identify the conference slot number of 2450 | the source/transmitter. 2451 | dst_slot -- integer to identify the conference slot number of 2452 | the destination/receiver. 2453 | 2454 | """ 2455 | lck = self.auto_lock() 2456 | err = _pjsua.conf_disconnect(src_slot, dst_slot) 2457 | self._err_check("conf_disconnect()", self, err) 2458 | 2459 | def conf_set_tx_level(self, slot, level): 2460 | """Adjust the signal level to be transmitted from the bridge to 2461 | the specified port by making it louder or quieter. 2462 | 2463 | Keyword arguments: 2464 | slot -- integer to identify the conference slot number. 2465 | level -- Signal level adjustment. Value 1.0 means no level 2466 | adjustment, while value 0 means to mute the port. 2467 | """ 2468 | lck = self.auto_lock() 2469 | err = _pjsua.conf_set_tx_level(slot, level) 2470 | self._err_check("conf_set_tx_level()", self, err) 2471 | 2472 | def conf_set_rx_level(self, slot, level): 2473 | """Adjust the signal level to be received from the specified port 2474 | (to the bridge) by making it louder or quieter. 2475 | 2476 | Keyword arguments: 2477 | slot -- integer to identify the conference slot number. 2478 | level -- Signal level adjustment. Value 1.0 means no level 2479 | adjustment, while value 0 means to mute the port. 2480 | """ 2481 | lck = self.auto_lock() 2482 | err = _pjsua.conf_set_rx_level(slot, level) 2483 | self._err_check("conf_set_rx_level()", self, err) 2484 | 2485 | def conf_get_signal_level(self, slot): 2486 | """Get last signal level transmitted to or received from the 2487 | specified port. The signal levels are float values from 0.0 to 1.0, 2488 | with 0.0 indicates no signal, and 1.0 indicates the loudest signal 2489 | level. 2490 | 2491 | Keyword arguments: 2492 | slot -- integer to identify the conference slot number. 2493 | 2494 | Return value: 2495 | (tx_level, rx_level) tuple. 2496 | """ 2497 | lck = self.auto_lock() 2498 | err, tx_level, rx_level = _pjsua.conf_get_signal_level(slot) 2499 | self._err_check("conf_get_signal_level()", self, err) 2500 | return (tx_level, rx_level) 2501 | 2502 | 2503 | 2504 | # Codecs API 2505 | 2506 | def enum_codecs(self): 2507 | """Return list of codecs supported by pjsua. 2508 | 2509 | Return: 2510 | list of CodecInfo 2511 | 2512 | """ 2513 | lck = self.auto_lock() 2514 | ci_list = _pjsua.enum_codecs() 2515 | codec_info = [] 2516 | for ci in ci_list: 2517 | cp = _pjsua.codec_get_param(ci.codec_id) 2518 | if cp: 2519 | codec_info.append(CodecInfo(ci, cp)) 2520 | return codec_info 2521 | 2522 | def set_codec_priority(self, name, priority): 2523 | """Change the codec priority. 2524 | 2525 | Keyword arguments: 2526 | name -- Codec name 2527 | priority -- Codec priority, which range is 0-255. 2528 | 2529 | """ 2530 | lck = self.auto_lock() 2531 | err = _pjsua.codec_set_priority(name, priority) 2532 | self._err_check("set_codec_priority()", self, err) 2533 | 2534 | def get_codec_parameter(self, name): 2535 | """Get codec parameter for the specified codec. 2536 | 2537 | Keyword arguments: 2538 | name -- codec name. 2539 | 2540 | """ 2541 | lck = self.auto_lock() 2542 | cp = _pjsua.codec_get_param(name) 2543 | if not cp: 2544 | self._err_check("get_codec_parameter()", self, -1, 2545 | "Invalid codec name") 2546 | return CodecParameter(cp) 2547 | 2548 | def set_codec_parameter(self, name, param): 2549 | """Modify codec parameter for the specified codec. 2550 | 2551 | Keyword arguments: 2552 | name -- codec name 2553 | param -- codec parameter. 2554 | 2555 | """ 2556 | lck = self.auto_lock() 2557 | err = _pjsua.codec_set_param(name, param._cvt_to_pjsua()) 2558 | self._err_check("set_codec_parameter()", self, err) 2559 | 2560 | # WAV playback and recording 2561 | 2562 | def create_player(self, filename, loop=False): 2563 | """Create WAV file player. 2564 | 2565 | Keyword arguments 2566 | filename -- WAV file name 2567 | loop -- boolean to specify whether playback should 2568 | automatically restart upon EOF 2569 | Return: 2570 | WAV player ID 2571 | 2572 | """ 2573 | lck = self.auto_lock() 2574 | opt = 0 2575 | if not loop: 2576 | opt = opt + 1 2577 | err, player_id = _pjsua.player_create(filename, opt) 2578 | self._err_check("create_player()", self, err) 2579 | return player_id 2580 | 2581 | def player_get_slot(self, player_id): 2582 | """Get the conference port ID for the specified player. 2583 | 2584 | Keyword arguments: 2585 | player_id -- the WAV player ID 2586 | 2587 | Return: 2588 | Conference slot number for the player 2589 | 2590 | """ 2591 | lck = self.auto_lock() 2592 | slot = _pjsua.player_get_conf_port(player_id) 2593 | if slot < 0: 2594 | self._err_check("player_get_slot()", self, -1, 2595 | "Invalid player id") 2596 | return slot 2597 | 2598 | def player_set_pos(self, player_id, pos): 2599 | """Set WAV playback position. 2600 | 2601 | Keyword arguments: 2602 | player_id -- WAV player ID 2603 | pos -- playback position, in samples 2604 | 2605 | """ 2606 | lck = self.auto_lock() 2607 | err = _pjsua.player_set_pos(player_id, pos) 2608 | self._err_check("player_set_pos()", self, err) 2609 | 2610 | def player_destroy(self, player_id): 2611 | """Destroy the WAV player. 2612 | 2613 | Keyword arguments: 2614 | player_id -- the WAV player ID. 2615 | 2616 | """ 2617 | lck = self.auto_lock() 2618 | err = _pjsua.player_destroy(player_id) 2619 | self._err_check("player_destroy()", self, err) 2620 | 2621 | def create_playlist(self, filelist, label="playlist", loop=True): 2622 | """Create WAV playlist. 2623 | 2624 | Keyword arguments: 2625 | filelist -- List of WAV file names. 2626 | label -- Optional name to be assigned to the playlist 2627 | object (useful for logging) 2628 | loop -- boolean to specify whether playback should 2629 | automatically restart upon EOF 2630 | 2631 | Return: 2632 | playlist_id 2633 | """ 2634 | lck = self.auto_lock() 2635 | opt = 0 2636 | if not loop: 2637 | opt = opt + 1 2638 | err, playlist_id = _pjsua.playlist_create(label, filelist, opt) 2639 | self._err_check("create_playlist()", self, err) 2640 | return playlist_id 2641 | 2642 | def playlist_get_slot(self, playlist_id): 2643 | """Get the conference port ID for the specified playlist. 2644 | 2645 | Keyword arguments: 2646 | playlist_id -- the WAV playlist ID 2647 | 2648 | Return: 2649 | Conference slot number for the playlist 2650 | 2651 | """ 2652 | lck = self.auto_lock() 2653 | slot = _pjsua.player_get_conf_port(playlist_id) 2654 | if slot < 0: 2655 | self._err_check("playlist_get_slot()", self, -1, 2656 | "Invalid playlist id") 2657 | return slot 2658 | 2659 | def playlist_destroy(self, playlist_id): 2660 | """Destroy the WAV playlist. 2661 | 2662 | Keyword arguments: 2663 | playlist_id -- the WAV playlist ID. 2664 | 2665 | """ 2666 | lck = self.auto_lock() 2667 | err = _pjsua.player_destroy(playlist_id) 2668 | self._err_check("playlist_destroy()", self, err) 2669 | 2670 | def create_recorder(self, filename): 2671 | """Create WAV file recorder. 2672 | 2673 | Keyword arguments 2674 | filename -- WAV file name 2675 | 2676 | Return: 2677 | WAV recorder ID 2678 | 2679 | """ 2680 | lck = self.auto_lock() 2681 | err, rec_id = _pjsua.recorder_create(filename, 0, None, -1, 0) 2682 | self._err_check("create_recorder()", self, err) 2683 | return rec_id 2684 | 2685 | def recorder_get_slot(self, rec_id): 2686 | """Get the conference port ID for the specified recorder. 2687 | 2688 | Keyword arguments: 2689 | rec_id -- the WAV recorder ID 2690 | 2691 | Return: 2692 | Conference slot number for the recorder 2693 | 2694 | """ 2695 | lck = self.auto_lock() 2696 | slot = _pjsua.recorder_get_conf_port(rec_id) 2697 | if slot < 1: 2698 | self._err_check("recorder_get_slot()", self, -1, 2699 | "Invalid recorder id") 2700 | return slot 2701 | 2702 | def recorder_destroy(self, rec_id): 2703 | """Destroy the WAV recorder. 2704 | 2705 | Keyword arguments: 2706 | rec_id -- the WAV recorder ID. 2707 | 2708 | """ 2709 | lck = self.auto_lock() 2710 | err = _pjsua.recorder_destroy(rec_id) 2711 | self._err_check("recorder_destroy()", self, err) 2712 | 2713 | 2714 | # Internal functions 2715 | 2716 | @staticmethod 2717 | def strerror(err): 2718 | return _pjsua.strerror(err) 2719 | 2720 | def _err_check(self, op_name, obj, err_code, err_msg=""): 2721 | if err_code != 0: 2722 | raise Error(op_name, obj, err_code, err_msg) 2723 | 2724 | @staticmethod 2725 | def _create_msg_data(hdr_list): 2726 | if not hdr_list: 2727 | return None 2728 | msg_data = _pjsua.Msg_Data() 2729 | msg_data.hdr_list = hdr_list 2730 | return msg_data 2731 | 2732 | def auto_lock(self): 2733 | return _LibMutex(self._lock) 2734 | 2735 | # Internal dictionary manipulation for calls, accounts, and buddies 2736 | 2737 | def _lookup_call(self, call_id): 2738 | return _pjsua.call_get_user_data(call_id) 2739 | 2740 | def _lookup_account(self, acc_id): 2741 | return _pjsua.acc_get_user_data(acc_id) 2742 | 2743 | def _lookup_buddy(self, buddy_id, uri=None): 2744 | if buddy_id != -1: 2745 | buddy = _pjsua.buddy_get_user_data(buddy_id) 2746 | elif uri: 2747 | buddy_id = _pjsua.buddy_find(uri) 2748 | if buddy_id != -1: 2749 | buddy = _pjsua.buddy_get_user_data(buddy_id) 2750 | else: 2751 | buddy = None 2752 | else: 2753 | buddy = None 2754 | 2755 | return buddy 2756 | 2757 | # Account allbacks 2758 | 2759 | def _cb_on_reg_state(self, acc_id): 2760 | acc = self._lookup_account(acc_id) 2761 | if acc: 2762 | acc._cb.on_reg_state() 2763 | 2764 | def _cb_on_incoming_subscribe(self, acc_id, buddy_id, from_uri, 2765 | contact_uri, pres_obj): 2766 | acc = self._lookup_account(acc_id) 2767 | if acc: 2768 | buddy = self._lookup_buddy(buddy_id) 2769 | return acc._cb.on_incoming_subscribe(buddy, from_uri, contact_uri, 2770 | pres_obj) 2771 | else: 2772 | return (404, None) 2773 | 2774 | def _cb_on_incoming_call(self, acc_id, call_id, rdata): 2775 | acc = self._lookup_account(acc_id) 2776 | if acc: 2777 | if 'on_incoming_call2' in acc._cb.__class__.__dict__: 2778 | acc._cb.on_incoming_call2( Call(self, call_id), rdata ) 2779 | else: 2780 | acc._cb.on_incoming_call( Call(self, call_id) ) 2781 | else: 2782 | _pjsua.call_hangup(call_id, 603, None, None) 2783 | 2784 | # Call callbacks 2785 | 2786 | def _cb_on_call_state(self, call_id): 2787 | call = self._lookup_call(call_id) 2788 | if call: 2789 | if call._id == -1: 2790 | call.attach_to_id(call_id) 2791 | done = (call.info().state == CallState.DISCONNECTED) 2792 | call._cb.on_state() 2793 | if done: 2794 | _pjsua.call_set_user_data(call_id, 0) 2795 | else: 2796 | pass 2797 | 2798 | def _cb_on_call_media_state(self, call_id): 2799 | call = self._lookup_call(call_id) 2800 | if call: 2801 | call._cb.on_media_state() 2802 | 2803 | def _cb_on_dtmf_digit(self, call_id, digits): 2804 | call = self._lookup_call(call_id) 2805 | if call: 2806 | call._cb.on_dtmf_digit(digits) 2807 | 2808 | def _cb_on_call_transfer_request(self, call_id, dst, code): 2809 | call = self._lookup_call(call_id) 2810 | if call: 2811 | return call._cb.on_transfer_request(dst, code) 2812 | else: 2813 | return 603 2814 | 2815 | def _cb_on_call_transfer_status(self, call_id, code, text, final, cont): 2816 | call = self._lookup_call(call_id) 2817 | if call: 2818 | return call._cb.on_transfer_status(code, text, final, cont) 2819 | else: 2820 | return cont 2821 | 2822 | def _cb_on_call_replace_request(self, call_id, rdata, code, reason): 2823 | call = self._lookup_call(call_id) 2824 | if call: 2825 | return call._cb.on_replace_request(code, reason) 2826 | else: 2827 | return code, reason 2828 | 2829 | def _cb_on_call_replaced(self, old_call_id, new_call_id): 2830 | old_call = self._lookup_call(old_call_id) 2831 | new_call = self._lookup_call(new_call_id) 2832 | if old_call and new_call: 2833 | old_call._cb.on_replaced(new_call) 2834 | 2835 | def _cb_on_pager(self, call_id, from_uri, to_uri, contact, mime_type, 2836 | body, acc_id): 2837 | call = None 2838 | if call_id != -1: 2839 | call = self._lookup_call(call_id) 2840 | if call: 2841 | call._cb.on_pager(mime_type, body) 2842 | else: 2843 | acc = self._lookup_account(acc_id) 2844 | buddy = self._lookup_buddy(-1, from_uri) 2845 | if buddy: 2846 | buddy._cb.on_pager(mime_type, body) 2847 | else: 2848 | acc._cb.on_pager(from_uri, contact, mime_type, body) 2849 | 2850 | def _cb_on_pager_status(self, call_id, to_uri, body, user_data, 2851 | code, reason, acc_id): 2852 | call = None 2853 | if call_id != -1: 2854 | call = self._lookup_call(call_id) 2855 | if call: 2856 | call._cb.on_pager_status(body, user_data, code, reason) 2857 | else: 2858 | acc = self._lookup_account(acc_id) 2859 | buddy = self._lookup_buddy(-1, to_uri) 2860 | if buddy: 2861 | buddy._cb.on_pager_status(body, user_data, code, reason) 2862 | else: 2863 | acc._cb.on_pager_status(to_uri, body, user_data, code, reason) 2864 | 2865 | def _cb_on_typing(self, call_id, from_uri, to_uri, contact, is_typing, 2866 | acc_id): 2867 | call = None 2868 | if call_id != -1: 2869 | call = self._lookup_call(call_id) 2870 | if call: 2871 | call._cb.on_typing(is_typing) 2872 | else: 2873 | acc = self._lookup_account(acc_id) 2874 | buddy = self._lookup_buddy(-1, from_uri) 2875 | if buddy: 2876 | buddy._cb.on_typing(is_typing) 2877 | else: 2878 | acc._cb.on_typing(from_uri, contact, is_typing) 2879 | 2880 | def _cb_on_mwi_info(self, acc_id, body): 2881 | acc = self._lookup_account(acc_id) 2882 | if acc: 2883 | return acc._cb.on_mwi_info(body) 2884 | 2885 | def _cb_on_buddy_state(self, buddy_id): 2886 | buddy = self._lookup_buddy(buddy_id) 2887 | if buddy: 2888 | buddy._cb.on_state() 2889 | 2890 | # 2891 | # Internal 2892 | # 2893 | 2894 | def _cb_on_call_state(call_id, e): 2895 | _lib._cb_on_call_state(call_id) 2896 | 2897 | def _cb_on_incoming_call(acc_id, call_id, rdata): 2898 | _lib._cb_on_incoming_call(acc_id, call_id, rdata) 2899 | 2900 | def _cb_on_call_media_state(call_id): 2901 | _lib._cb_on_call_media_state(call_id) 2902 | 2903 | def _cb_on_dtmf_digit(call_id, digits): 2904 | _lib._cb_on_dtmf_digit(call_id, digits) 2905 | 2906 | def _cb_on_call_transfer_request(call_id, dst, code): 2907 | return _lib._cb_on_call_transfer_request(call_id, dst, code) 2908 | 2909 | def _cb_on_call_transfer_status(call_id, code, reason, final, cont): 2910 | return _lib._cb_on_call_transfer_status(call_id, code, reason, 2911 | final, cont) 2912 | def _cb_on_call_replace_request(call_id, rdata, code, reason): 2913 | return _lib._cb_on_call_replace_request(call_id, rdata, code, reason) 2914 | 2915 | def _cb_on_call_replaced(old_call_id, new_call_id): 2916 | _lib._cb_on_call_replaced(old_call_id, new_call_id) 2917 | 2918 | def _cb_on_reg_state(acc_id): 2919 | _lib._cb_on_reg_state(acc_id) 2920 | 2921 | def _cb_on_incoming_subscribe(acc_id, buddy_id, from_uri, contact_uri, pres): 2922 | return _lib._cb_on_incoming_subscribe(acc_id, buddy_id, from_uri, 2923 | contact_uri, pres) 2924 | 2925 | def _cb_on_buddy_state(buddy_id): 2926 | _lib._cb_on_buddy_state(buddy_id) 2927 | 2928 | def _cb_on_pager(call_id, from_uri, to, contact, mime_type, body, acc_id): 2929 | _lib._cb_on_pager(call_id, from_uri, to, contact, mime_type, body, acc_id) 2930 | 2931 | def _cb_on_pager_status(call_id, to, body, user_data, status, reason, acc_id): 2932 | _lib._cb_on_pager_status(call_id, to, body, user_data, 2933 | status, reason, acc_id) 2934 | 2935 | def _cb_on_typing(call_id, from_uri, to, contact, is_typing, acc_id): 2936 | _lib._cb_on_typing(call_id, from_uri, to, contact, is_typing, acc_id) 2937 | 2938 | def _cb_on_mwi_info(acc_id, body): 2939 | _lib._cb_on_mwi_info(acc_id, body) 2940 | 2941 | # Worker thread 2942 | def _worker_thread_main(arg): 2943 | global _lib 2944 | _Trace(('worker thread started..',)) 2945 | thread_desc = 0; 2946 | err = _pjsua.thread_register("python worker", thread_desc) 2947 | _lib._err_check("thread_register()", _lib, err) 2948 | while _lib and _lib._quit == 0: 2949 | _lib.handle_events(1) 2950 | time.sleep(0.050) 2951 | if _lib: 2952 | _lib._quit = 2 2953 | _Trace(('worker thread exited..',)) 2954 | 2955 | def _Trace(args): 2956 | global enable_trace 2957 | if enable_trace: 2958 | print("** ", end=' ') 2959 | for arg in args: 2960 | print(arg, end=' ') 2961 | print(" **") 2962 | 2963 | -------------------------------------------------------------------------------- /samples/call.py: -------------------------------------------------------------------------------- 1 | # $Id: call.py 2171 2008-07-24 09:01:33Z bennylp $ 2 | # 3 | # SIP call sample. 4 | # 5 | # Copyright (C) 2003-2008 Benny Prijono 6 | # 7 | # This program is free software; you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation; either version 2 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program; if not, write to the Free Software 19 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | # 21 | import sys 22 | import pjsua as pj 23 | 24 | LOG_LEVEL=3 25 | current_call = None 26 | 27 | # Logging callback 28 | def log_cb(level, str, len): 29 | print(str, end=' ') 30 | 31 | 32 | # Callback to receive events from account 33 | class MyAccountCallback(pj.AccountCallback): 34 | 35 | def __init__(self, account=None): 36 | pj.AccountCallback.__init__(self, account) 37 | 38 | # Notification on incoming call 39 | def on_incoming_call(self, call): 40 | global current_call 41 | if current_call: 42 | call.answer(486, "Busy") 43 | return 44 | 45 | print("Incoming call from ", call.info().remote_uri) 46 | print("Press 'a' to answer") 47 | 48 | current_call = call 49 | 50 | call_cb = MyCallCallback(current_call) 51 | current_call.set_callback(call_cb) 52 | 53 | current_call.answer(180) 54 | 55 | 56 | # Callback to receive events from Call 57 | class MyCallCallback(pj.CallCallback): 58 | 59 | def __init__(self, call=None): 60 | pj.CallCallback.__init__(self, call) 61 | 62 | # Notification when call state has changed 63 | def on_state(self): 64 | global current_call 65 | print("Call with", self.call.info().remote_uri, end=' ') 66 | print("is", self.call.info().state_text, end=' ') 67 | print("last code =", self.call.info().last_code, end=' ') 68 | print("(" + self.call.info().last_reason + ")") 69 | 70 | if self.call.info().state == pj.CallState.DISCONNECTED: 71 | current_call = None 72 | print('Current call is', current_call) 73 | 74 | # Notification when call's media state has changed. 75 | def on_media_state(self): 76 | if self.call.info().media_state == pj.MediaState.ACTIVE: 77 | # Connect the call to sound device 78 | call_slot = self.call.info().conf_slot 79 | pj.Lib.instance().conf_connect(call_slot, 0) 80 | pj.Lib.instance().conf_connect(0, call_slot) 81 | print("Media is now active") 82 | else: 83 | print("Media is inactive") 84 | 85 | # Function to make call 86 | def make_call(uri): 87 | try: 88 | print("Making call to", uri) 89 | return acc.make_call(uri, cb=MyCallCallback()) 90 | except pj.Error as e: 91 | print("Exception: " + str(e)) 92 | return None 93 | 94 | 95 | # Create library instance 96 | lib = pj.Lib() 97 | 98 | try: 99 | # Init library with default config and some customized 100 | # logging config. 101 | lib.init(log_cfg = pj.LogConfig(level=LOG_LEVEL, callback=log_cb)) 102 | 103 | # Create UDP transport which listens to any available port 104 | transport = lib.create_transport(pj.TransportType.UDP, 105 | pj.TransportConfig(0)) 106 | print("\nListening on", transport.info().host, end=' ') 107 | print("port", transport.info().port, "\n") 108 | 109 | # Start the library 110 | lib.start() 111 | 112 | # Create local account 113 | acc = lib.create_account_for_transport(transport, cb=MyAccountCallback()) 114 | 115 | # If argument is specified then make call to the URI 116 | if len(sys.argv) > 1: 117 | lck = lib.auto_lock() 118 | current_call = make_call(sys.argv[1]) 119 | print('Current call is', current_call) 120 | del lck 121 | 122 | my_sip_uri = "sip:" + transport.info().host + \ 123 | ":" + str(transport.info().port) 124 | 125 | # Menu loop 126 | while True: 127 | print("My SIP URI is", my_sip_uri) 128 | print("Menu: m=make call, h=hangup call, a=answer call, q=quit") 129 | 130 | input = sys.stdin.readline().rstrip("\r\n") 131 | if input == "m": 132 | if current_call: 133 | print("Already have another call") 134 | continue 135 | print("Enter destination URI to call: ", end=' ') 136 | input = sys.stdin.readline().rstrip("\r\n") 137 | if input == "": 138 | continue 139 | lck = lib.auto_lock() 140 | current_call = make_call(input) 141 | del lck 142 | 143 | elif input == "h": 144 | if not current_call: 145 | print("There is no call") 146 | continue 147 | current_call.hangup() 148 | 149 | elif input == "a": 150 | if not current_call: 151 | print("There is no call") 152 | continue 153 | current_call.answer(200) 154 | 155 | elif input == "q": 156 | break 157 | 158 | # Shutdown the library 159 | transport = None 160 | acc.delete() 161 | acc = None 162 | lib.destroy() 163 | lib = None 164 | 165 | except pj.Error as e: 166 | print("Exception: " + str(e)) 167 | lib.destroy() 168 | lib = None 169 | 170 | -------------------------------------------------------------------------------- /samples/call.py.rej: -------------------------------------------------------------------------------- 1 | --- presence.py (original) 2 | +++ presence.py (refactored) 3 | @@ -26,7 +26,7 @@ 4 | pending_uri = None 5 | 6 | def log_cb(level, str, len): 7 | - print str, 8 | + print(str, end=' ') 9 | 10 | class MyAccountCallback(pj.AccountCallback): 11 | def __init__(self, account=None): 12 | @@ -37,8 +37,8 @@ 13 | # Allow buddy to subscribe to our presence 14 | if buddy: 15 | return (200, None) 16 | - print 'Incoming SUBSCRIBE request from', from_uri 17 | - print 'Press "A" to accept and add, "R" to reject the request' 18 | + print('Incoming SUBSCRIBE request from', from_uri) 19 | + print('Press "A" to accept and add, "R" to reject the request') 20 | pending_pres = pres 21 | pending_uri = from_uri 22 | return (202, None) 23 | @@ -49,24 +49,24 @@ 24 | pj.BuddyCallback.__init__(self, buddy) 25 | 26 | def on_state(self): 27 | - print "Buddy", self.buddy.info().uri, "is", 28 | - print self.buddy.info().online_text 29 | + print("Buddy", self.buddy.info().uri, "is", end=' ') 30 | + print(self.buddy.info().online_text) 31 | 32 | def on_pager(self, mime_type, body): 33 | - print "Instant message from", self.buddy.info().uri, 34 | - print "(", mime_type, "):" 35 | - print body 36 | + print("Instant message from", self.buddy.info().uri, end=' ') 37 | + print("(", mime_type, "):") 38 | + print(body) 39 | 40 | def on_pager_status(self, body, im_id, code, reason): 41 | if code >= 300: 42 | - print "Message delivery failed for message", 43 | - print body, "to", self.buddy.info().uri, ":", reason 44 | + print("Message delivery failed for message", end=' ') 45 | + print(body, "to", self.buddy.info().uri, ":", reason) 46 | 47 | def on_typing(self, is_typing): 48 | if is_typing: 49 | - print self.buddy.info().uri, "is typing" 50 | + print(self.buddy.info().uri, "is typing") 51 | else: 52 | - print self.buddy.info().uri, "stops typing" 53 | + print(self.buddy.info().uri, "stops typing") 54 | 55 | 56 | lib = pj.Lib() 57 | @@ -79,8 +79,8 @@ 58 | # Create UDP transport which listens to any available port 59 | transport = lib.create_transport(pj.TransportType.UDP, 60 | pj.TransportConfig(0)) 61 | - print "\nListening on", transport.info().host, 62 | - print "port", transport.info().port, "\n" 63 | + print("\nListening on", transport.info().host, end=' ') 64 | + print("port", transport.info().port, "\n") 65 | 66 | # Start the library 67 | lib.start() 68 | @@ -96,14 +96,14 @@ 69 | 70 | # Menu loop 71 | while True: 72 | - print "My SIP URI is", my_sip_uri 73 | - print "Menu: a=add buddy, d=delete buddy, t=toggle", \ 74 | - " online status, i=send IM, q=quit" 75 | + print("My SIP URI is", my_sip_uri) 76 | + print("Menu: a=add buddy, d=delete buddy, t=toggle", \ 77 | + " online status, i=send IM, q=quit") 78 | 79 | input = sys.stdin.readline().rstrip("\r\n") 80 | if input == "a": 81 | # Add buddy 82 | - print "Enter buddy URI: ", 83 | + print("Enter buddy URI: ", end=' ') 84 | input = sys.stdin.readline().rstrip("\r\n") 85 | if input == "": 86 | continue 87 | @@ -116,12 +116,12 @@ 88 | 89 | elif input == "i": 90 | if not buddy: 91 | - print "Add buddy first" 92 | + print("Add buddy first") 93 | continue 94 | 95 | buddy.send_typing_ind(True) 96 | 97 | - print "Type the message: ", 98 | + print("Type the message: ", end=' ') 99 | input = sys.stdin.readline().rstrip("\r\n") 100 | if input == "": 101 | buddy.send_typing_ind(False) 102 | @@ -134,7 +134,7 @@ 103 | buddy.delete() 104 | buddy = None 105 | else: 106 | - print 'No buddy was added' 107 | + print('No buddy was added') 108 | 109 | elif input == "A": 110 | if pending_pres: 111 | @@ -144,7 +144,7 @@ 112 | pending_pres = None 113 | pending_uri = None 114 | else: 115 | - print "No pending request" 116 | + print("No pending request") 117 | 118 | elif input == "R": 119 | if pending_pres: 120 | @@ -153,7 +153,7 @@ 121 | pending_pres = None 122 | pending_uri = None 123 | else: 124 | - print "No pending request" 125 | + print("No pending request") 126 | 127 | elif input == "q": 128 | break 129 | @@ -168,8 +168,8 @@ 130 | lib.destroy() 131 | lib = None 132 | 133 | -except pj.Error, e: 134 | - print "Exception: " + str(e) 135 | +except pj.Error as e: 136 | + print("Exception: " + str(e)) 137 | lib.destroy() 138 | lib = None 139 | 140 | --- registration.py (original) 141 | +++ registration.py (refactored) 142 | @@ -25,7 +25,7 @@ 143 | 144 | 145 | def log_cb(level, str, len): 146 | - print str, 147 | + print(str, end=' ') 148 | 149 | class MyAccountCallback(pj.AccountCallback): 150 | sem = None 151 | @@ -55,16 +55,16 @@ 152 | acc.set_callback(acc_cb) 153 | acc_cb.wait() 154 | 155 | - print "\n" 156 | - print "Registration complete, status=", acc.info().reg_status, \ 157 | - "(" + acc.info().reg_reason + ")" 158 | - print "\nPress ENTER to quit" 159 | + print("\n") 160 | + print("Registration complete, status=", acc.info().reg_status, \ 161 | + "(" + acc.info().reg_reason + ")") 162 | + print("\nPress ENTER to quit") 163 | sys.stdin.readline() 164 | 165 | lib.destroy() 166 | lib = None 167 | 168 | -except pj.Error, e: 169 | - print "Exception: " + str(e) 170 | +except pj.Error as e: 171 | + print("Exception: " + str(e)) 172 | lib.destroy() 173 | 174 | --- simplecall.py (original) 175 | +++ simplecall.py (refactored) 176 | @@ -24,7 +24,7 @@ 177 | 178 | # Logging callback 179 | def log_cb(level, str, len): 180 | - print str, 181 | + print(str, end=' ') 182 | 183 | # Callback to receive events from Call 184 | class MyCallCallback(pj.CallCallback): 185 | @@ -33,9 +33,9 @@ 186 | 187 | # Notification when call state has changed 188 | def on_state(self): 189 | - print "Call is ", self.call.info().state_text, 190 | - print "last code =", self.call.info().last_code, 191 | - print "(" + self.call.info().last_reason + ")" 192 | + print("Call is ", self.call.info().state_text, end=' ') 193 | + print("last code =", self.call.info().last_code, end=' ') 194 | + print("(" + self.call.info().last_reason + ")") 195 | 196 | # Notification when call's media state has changed. 197 | def on_media_state(self): 198 | @@ -45,12 +45,12 @@ 199 | call_slot = self.call.info().conf_slot 200 | lib.conf_connect(call_slot, 0) 201 | lib.conf_connect(0, call_slot) 202 | - print "Hello world, I can talk!" 203 | + print("Hello world, I can talk!") 204 | 205 | 206 | # Check command line argument 207 | if len(sys.argv) != 2: 208 | - print "Usage: simplecall.py " 209 | + print("Usage: simplecall.py ") 210 | sys.exit(1) 211 | 212 | try: 213 | @@ -73,15 +73,15 @@ 214 | call = acc.make_call(sys.argv[1], MyCallCallback()) 215 | 216 | # Wait for ENTER before quitting 217 | - print "Press to quit" 218 | + print("Press to quit") 219 | input = sys.stdin.readline().rstrip("\r\n") 220 | 221 | # We're done, shutdown the library 222 | lib.destroy() 223 | lib = None 224 | 225 | -except pj.Error, e: 226 | - print "Exception: " + str(e) 227 | +except pj.Error as e: 228 | + print("Exception: " + str(e)) 229 | lib.destroy() 230 | lib = None 231 | sys.exit(1) 232 | -------------------------------------------------------------------------------- /samples/presence.py: -------------------------------------------------------------------------------- 1 | # $Id: presence.py 2171 2008-07-24 09:01:33Z bennylp $ 2 | # 3 | # Presence and instant messaging 4 | # 5 | # Copyright (C) 2003-2008 Benny Prijono 6 | # 7 | # This program is free software; you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation; either version 2 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program; if not, write to the Free Software 19 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | # 21 | import sys 22 | import pjsua as pj 23 | 24 | LOG_LEVEL = 3 25 | pending_pres = None 26 | pending_uri = None 27 | 28 | def log_cb(level, str, len): 29 | print(str, end=' ') 30 | 31 | class MyAccountCallback(pj.AccountCallback): 32 | def __init__(self, account=None): 33 | pj.AccountCallback.__init__(self, account) 34 | 35 | def on_incoming_subscribe(self, buddy, from_uri, contact_uri, pres): 36 | global pending_pres, pending_uri 37 | # Allow buddy to subscribe to our presence 38 | if buddy: 39 | return (200, None) 40 | print('Incoming SUBSCRIBE request from', from_uri) 41 | print('Press "A" to accept and add, "R" to reject the request') 42 | pending_pres = pres 43 | pending_uri = from_uri 44 | return (202, None) 45 | 46 | 47 | class MyBuddyCallback(pj.BuddyCallback): 48 | def __init__(self, buddy=None): 49 | pj.BuddyCallback.__init__(self, buddy) 50 | 51 | def on_state(self): 52 | print("Buddy", self.buddy.info().uri, "is", end=' ') 53 | print(self.buddy.info().online_text) 54 | 55 | def on_pager(self, mime_type, body): 56 | print("Instant message from", self.buddy.info().uri, end=' ') 57 | print("(", mime_type, "):") 58 | print(body) 59 | 60 | def on_pager_status(self, body, im_id, code, reason): 61 | if code >= 300: 62 | print("Message delivery failed for message", end=' ') 63 | print(body, "to", self.buddy.info().uri, ":", reason) 64 | 65 | def on_typing(self, is_typing): 66 | if is_typing: 67 | print(self.buddy.info().uri, "is typing") 68 | else: 69 | print(self.buddy.info().uri, "stops typing") 70 | 71 | 72 | lib = pj.Lib() 73 | 74 | try: 75 | # Init library with default config and some customized 76 | # logging config. 77 | lib.init(log_cfg = pj.LogConfig(level=LOG_LEVEL, callback=log_cb)) 78 | 79 | # Create UDP transport which listens to any available port 80 | transport = lib.create_transport(pj.TransportType.UDP, 81 | pj.TransportConfig(0)) 82 | print("\nListening on", transport.info().host, end=' ') 83 | print("port", transport.info().port, "\n") 84 | 85 | # Start the library 86 | lib.start() 87 | 88 | # Create local account 89 | acc = lib.create_account_for_transport(transport, cb=MyAccountCallback()) 90 | acc.set_basic_status(True) 91 | 92 | my_sip_uri = "sip:" + transport.info().host + \ 93 | ":" + str(transport.info().port) 94 | 95 | buddy = None 96 | 97 | # Menu loop 98 | while True: 99 | print("My SIP URI is", my_sip_uri) 100 | print("Menu: a=add buddy, d=delete buddy, t=toggle", \ 101 | " online status, i=send IM, q=quit") 102 | 103 | input = sys.stdin.readline().rstrip("\r\n") 104 | if input == "a": 105 | # Add buddy 106 | print("Enter buddy URI: ", end=' ') 107 | input = sys.stdin.readline().rstrip("\r\n") 108 | if input == "": 109 | continue 110 | 111 | buddy = acc.add_buddy(input, cb=MyBuddyCallback()) 112 | buddy.subscribe() 113 | 114 | elif input == "t": 115 | acc.set_basic_status(not acc.info().online_status) 116 | 117 | elif input == "i": 118 | if not buddy: 119 | print("Add buddy first") 120 | continue 121 | 122 | buddy.send_typing_ind(True) 123 | 124 | print("Type the message: ", end=' ') 125 | input = sys.stdin.readline().rstrip("\r\n") 126 | if input == "": 127 | buddy.send_typing_ind(False) 128 | continue 129 | 130 | buddy.send_pager(input) 131 | 132 | elif input == "d": 133 | if buddy: 134 | buddy.delete() 135 | buddy = None 136 | else: 137 | print('No buddy was added') 138 | 139 | elif input == "A": 140 | if pending_pres: 141 | acc.pres_notify(pending_pres, pj.SubscriptionState.ACTIVE) 142 | buddy = acc.add_buddy(pending_uri, cb=MyBuddyCallback()) 143 | buddy.subscribe() 144 | pending_pres = None 145 | pending_uri = None 146 | else: 147 | print("No pending request") 148 | 149 | elif input == "R": 150 | if pending_pres: 151 | acc.pres_notify(pending_pres, pj.SubscriptionState.TERMINATED, 152 | "rejected") 153 | pending_pres = None 154 | pending_uri = None 155 | else: 156 | print("No pending request") 157 | 158 | elif input == "q": 159 | break 160 | 161 | # Shutdown the library 162 | acc.delete() 163 | acc = None 164 | if pending_pres: 165 | acc.pres_notify(pending_pres, pj.SubscriptionState.TERMINATED, 166 | "rejected") 167 | transport = None 168 | lib.destroy() 169 | lib = None 170 | 171 | except pj.Error as e: 172 | print("Exception: " + str(e)) 173 | lib.destroy() 174 | lib = None 175 | 176 | -------------------------------------------------------------------------------- /samples/registration.py: -------------------------------------------------------------------------------- 1 | # $Id: registration.py 2171 2008-07-24 09:01:33Z bennylp $ 2 | # 3 | # SIP account and registration sample. In this sample, the program 4 | # will block to wait until registration is complete 5 | # 6 | # Copyright (C) 2003-2008 Benny Prijono 7 | # 8 | # This program is free software; you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; either version 2 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program; if not, write to the Free Software 20 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 21 | # 22 | import sys 23 | import pjsua as pj 24 | import threading 25 | 26 | 27 | def log_cb(level, str, len): 28 | print(str, end=' ') 29 | 30 | class MyAccountCallback(pj.AccountCallback): 31 | sem = None 32 | 33 | def __init__(self, account): 34 | pj.AccountCallback.__init__(self, account) 35 | 36 | def wait(self): 37 | self.sem = threading.Semaphore(0) 38 | self.sem.acquire() 39 | 40 | def on_reg_state(self): 41 | if self.sem: 42 | if self.account.info().reg_status >= 200: 43 | self.sem.release() 44 | 45 | lib = pj.Lib() 46 | 47 | try: 48 | lib.init(log_cfg = pj.LogConfig(level=4, callback=log_cb)) 49 | lib.create_transport(pj.TransportType.UDP, pj.TransportConfig(5080)) 50 | lib.start() 51 | 52 | acc = lib.create_account(pj.AccountConfig("pjsip.org", "bennylp", "***")) 53 | 54 | acc_cb = MyAccountCallback(acc) 55 | acc.set_callback(acc_cb) 56 | acc_cb.wait() 57 | 58 | print("\n") 59 | print("Registration complete, status=", acc.info().reg_status, \ 60 | "(" + acc.info().reg_reason + ")") 61 | print("\nPress ENTER to quit") 62 | sys.stdin.readline() 63 | 64 | lib.destroy() 65 | lib = None 66 | 67 | except pj.Error as e: 68 | print("Exception: " + str(e)) 69 | lib.destroy() 70 | 71 | -------------------------------------------------------------------------------- /samples/simplecall.py: -------------------------------------------------------------------------------- 1 | # $Id: simplecall.py 2171 2008-07-24 09:01:33Z bennylp $ 2 | # 3 | # SIP account and registration sample. In this sample, the program 4 | # will block to wait until registration is complete 5 | # 6 | # Copyright (C) 2003-2008 Benny Prijono 7 | # 8 | # This program is free software; you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; either version 2 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program; if not, write to the Free Software 20 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 21 | # 22 | import sys 23 | import pjsua as pj 24 | 25 | # Logging callback 26 | def log_cb(level, str, len): 27 | print(str, end=' ') 28 | 29 | # Callback to receive events from Call 30 | class MyCallCallback(pj.CallCallback): 31 | def __init__(self, call=None): 32 | pj.CallCallback.__init__(self, call) 33 | 34 | # Notification when call state has changed 35 | def on_state(self): 36 | print("Call is ", self.call.info().state_text, end=' ') 37 | print("last code =", self.call.info().last_code, end=' ') 38 | print("(" + self.call.info().last_reason + ")") 39 | 40 | # Notification when call's media state has changed. 41 | def on_media_state(self): 42 | global lib 43 | if self.call.info().media_state == pj.MediaState.ACTIVE: 44 | # Connect the call to sound device 45 | call_slot = self.call.info().conf_slot 46 | lib.conf_connect(call_slot, 0) 47 | lib.conf_connect(0, call_slot) 48 | print("Hello world, I can talk!") 49 | 50 | 51 | # Check command line argument 52 | if len(sys.argv) != 2: 53 | print("Usage: simplecall.py ") 54 | sys.exit(1) 55 | 56 | try: 57 | # Create library instance 58 | lib = pj.Lib() 59 | 60 | # Init library with default config 61 | lib.init(log_cfg = pj.LogConfig(level=3, callback=log_cb)) 62 | 63 | # Create UDP transport which listens to any available port 64 | transport = lib.create_transport(pj.TransportType.UDP) 65 | 66 | # Start the library 67 | lib.start() 68 | 69 | # Create local/user-less account 70 | acc = lib.create_account_for_transport(transport) 71 | 72 | # Make call 73 | call = acc.make_call(sys.argv[1], MyCallCallback()) 74 | 75 | # Wait for ENTER before quitting 76 | print("Press to quit") 77 | input = sys.stdin.readline().rstrip("\r\n") 78 | 79 | # We're done, shutdown the library 80 | lib.destroy() 81 | lib = None 82 | 83 | except pj.Error as e: 84 | print("Exception: " + str(e)) 85 | lib.destroy() 86 | lib = None 87 | sys.exit(1) 88 | 89 | -------------------------------------------------------------------------------- /setup-vc.py: -------------------------------------------------------------------------------- 1 | # $Id: setup-vc.py 4122 2012-05-14 11:04:46Z bennylp $ 2 | # 3 | # pjsua Setup script for Visual Studio 4 | # 5 | # Copyright (C) 2003-2008 Benny Prijono 6 | # 7 | # This program is free software; you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation; either version 2 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program; if not, write to the Free Software 19 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | # 21 | from distutils.core import setup, Extension 22 | import os 23 | import sys 24 | 25 | # Find version 26 | pj_version="" 27 | pj_version_major="" 28 | pj_version_minor="" 29 | pj_version_rev="" 30 | pj_version_suffix="" 31 | f = open('../../../version.mak', 'r') 32 | for line in f: 33 | if line.find("export PJ_VERSION_MAJOR") != -1: 34 | tokens=line.split("=") 35 | if len(tokens)>1: 36 | pj_version_major= tokens[1].strip() 37 | elif line.find("export PJ_VERSION_MINOR") != -1: 38 | tokens=line.split("=") 39 | if len(tokens)>1: 40 | pj_version_minor= line.split("=")[1].strip() 41 | elif line.find("export PJ_VERSION_REV") != -1: 42 | tokens=line.split("=") 43 | if len(tokens)>1: 44 | pj_version_rev= line.split("=")[1].strip() 45 | elif line.find("export PJ_VERSION_SUFFIX") != -1: 46 | tokens=line.split("=") 47 | if len(tokens)>1: 48 | pj_version_suffix= line.split("=")[1].strip() 49 | 50 | f.close() 51 | if not pj_version_major: 52 | print('Unable to get PJ_VERSION_MAJOR') 53 | sys.exit(1) 54 | 55 | pj_version = pj_version_major + "." + pj_version_minor 56 | if pj_version_rev: 57 | pj_version += "." + pj_version_rev 58 | if pj_version_suffix: 59 | pj_version += "-" + pj_version_suffix 60 | 61 | #print 'PJ_VERSION = "'+ pj_version + '"' 62 | 63 | 64 | # Check that extension has been built 65 | if not os.access('../../lib/_pjsua.pyd', os.R_OK): 66 | print('Error: file "../../lib/_pjsua.pyd" does not exist!') 67 | print('') 68 | print('Please build the extension with Visual Studio first') 69 | print('For more info, see http://trac.pjsip.org/repos/wiki/Python_SIP_Tutorial') 70 | sys.exit(1) 71 | 72 | setup(name="pjsua", 73 | version=pj_version, 74 | description='SIP User Agent Library based on PJSIP', 75 | url='http://trac.pjsip.org/repos/wiki/Python_SIP_Tutorial', 76 | data_files=[('lib/site-packages', ['../../lib/_pjsua.pyd'])], 77 | py_modules=["pjsua"] 78 | ) 79 | 80 | 81 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # $Id: setup.py 4232 2012-08-20 06:01:41Z ming $ 2 | # 3 | # pjsua Setup script. 4 | # 5 | # Copyright (C) 2003-2008 Benny Prijono 6 | # 7 | # This program is free software; you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation; either version 2 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program; if not, write to the Free Software 19 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | # 21 | from distutils.core import setup, Extension 22 | import os 23 | import sys 24 | import platform 25 | 26 | # find pjsip version 27 | pj_version="" 28 | pj_version_major="" 29 | pj_version_minor="" 30 | pj_version_rev="" 31 | pj_version_suffix="" 32 | f = open('../../../version.mak', 'r') 33 | for line in f: 34 | if line.find("export PJ_VERSION_MAJOR") != -1: 35 | tokens=line.split("=") 36 | else: 37 | tokens=[] 38 | if len(tokens)>1: 39 | pj_version_major= tokens[1].strip() 40 | elif line.find("export PJ_VERSION_MINOR") != -1: 41 | tokens=line.split("=") 42 | if len(tokens)>1: 43 | pj_version_minor= line.split("=")[1].strip() 44 | elif line.find("export PJ_VERSION_REV") != -1: 45 | tokens=line.split("=") 46 | if len(tokens)>1: 47 | pj_version_rev= line.split("=")[1].strip() 48 | elif line.find("export PJ_VERSION_SUFFIX") != -1: 49 | tokens=line.split("=") 50 | if len(tokens)>1: 51 | pj_version_suffix= line.split("=")[1].strip() 52 | 53 | f.close() 54 | if not pj_version_major: 55 | print('Unable to get PJ_VERSION_MAJOR') 56 | sys.exit(1) 57 | 58 | pj_version = pj_version_major + "." + pj_version_minor 59 | if pj_version_rev: 60 | pj_version += "." + pj_version_rev 61 | if pj_version_suffix: 62 | pj_version += "-" + pj_version_suffix 63 | 64 | #print 'PJ_VERSION = "'+ pj_version + '"' 65 | 66 | 67 | # Fill in pj_inc_dirs 68 | pj_inc_dirs = [] 69 | f = os.popen("make -f helper.mak inc_dir") 70 | for line in f: 71 | pj_inc_dirs.append(line.rstrip("\r\n")) 72 | f.close() 73 | 74 | # Fill in pj_lib_dirs 75 | pj_lib_dirs = [] 76 | f = os.popen("make -f helper.mak lib_dir") 77 | for line in f: 78 | pj_lib_dirs.append(line.rstrip("\r\n")) 79 | f.close() 80 | 81 | # Fill in pj_libs 82 | pj_libs = [] 83 | f = os.popen("make -f helper.mak libs") 84 | for line in f: 85 | pj_libs.append(line.rstrip("\r\n")) 86 | f.close() 87 | 88 | # Mac OS X depedencies 89 | if platform.system() == 'Darwin': 90 | extra_link_args = ["-framework", "CoreFoundation", 91 | "-framework", "AudioToolbox"] 92 | version = platform.mac_ver()[0].split(".") 93 | # OS X Lion (10.7.x) or above support 94 | if version[0] == '10' and int(version[1]) >= 7: 95 | extra_link_args += ["-framework", "AudioUnit"] 96 | else: 97 | extra_link_args = [] 98 | 99 | 100 | setup(name="pjsua", 101 | version=pj_version, 102 | description='SIP User Agent Library based on PJSIP', 103 | url='http://trac.pjsip.org/repos/wiki/Python_SIP_Tutorial', 104 | ext_modules = [Extension("_pjsua", 105 | ["_pjsua.c"], 106 | define_macros=[('PJ_AUTOCONF', '1'),], 107 | include_dirs=pj_inc_dirs, 108 | library_dirs=pj_lib_dirs, 109 | libraries=pj_libs, 110 | extra_link_args=extra_link_args 111 | ) 112 | ], 113 | py_modules=["pjsua"] 114 | ) 115 | 116 | 117 | --------------------------------------------------------------------------------