9. PJSUA2示例应用程序
9.1 示例应用程序
9.1.1 C++
pjsip-apps/src/samples/pjsua2_demo.cpp 是一个非常简单可用的C++示例应用程序。
1 /* $Id: pjsua2_demo.cpp 5467 2016-10-21 07:55:41Z nanang $ */ 2 /* 3 * Copyright (C) 2008-2013 Teluu Inc. (http://www.teluu.com) 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 2 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program; if not, write to the Free Software 17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 */ 19 #include <pjsua2.hpp> 20 #include <iostream> 21 #include <memory> 22 #include <pj/file_access.h> 23 24 #define THIS_FILE "pjsua2_demo.cpp" 25 26 using namespace pj; 27 28 class MyAccount; 29 30 class MyCall : public Call 31 { 32 private: 33 MyAccount *myAcc; 34 35 public: 36 MyCall(Account &acc, int call_id = PJSUA_INVALID_ID) 37 : Call(acc, call_id) 38 { 39 myAcc = (MyAccount *)&acc; 40 } 41 42 virtual void onCallState(OnCallStateParam &prm); 43 }; 44 45 class MyAccount : public Account 46 { 47 public: 48 std::vector<Call *> calls; 49 50 public: 51 MyAccount() 52 {} 53 54 ~MyAccount() 55 { 56 std::cout << "*** Account is being deleted: No of calls=" 57 << calls.size() << std::endl; 58 } 59 60 void removeCall(Call *call) 61 { 62 for (std::vector<Call *>::iterator it = calls.begin(); 63 it != calls.end(); ++it) 64 { 65 if (*it == call) { 66 calls.erase(it); 67 break; 68 } 69 } 70 } 71 72 virtual void onRegState(OnRegStateParam &prm) 73 { 74 AccountInfo ai = getInfo(); 75 std::cout << (ai.regIsActive? "*** Register: code=" : "*** Unregister: code=") 76 << prm.code << std::endl; 77 } 78 79 virtual void onIncomingCall(OnIncomingCallParam &iprm) 80 { 81 Call *call = new MyCall(*this, iprm.callId); 82 CallInfo ci = call->getInfo(); 83 CallOpParam prm; 84 85 std::cout << "*** Incoming Call: " << ci.remoteUri << " [" 86 << ci.stateText << "]" << std::endl; 87 88 calls.push_back(call); 89 prm.statusCode = (pjsip_status_code)200; 90 call->answer(prm); 91 } 92 }; 93 94 void MyCall::onCallState(OnCallStateParam &prm) 95 { 96 PJ_UNUSED_ARG(prm); 97 98 CallInfo ci = getInfo(); 99 std::cout << "*** Call: " << ci.remoteUri << " [" << ci.stateText 100 << "]" << std::endl; 101 102 if (ci.state == PJSIP_INV_STATE_DISCONNECTED) { 103 myAcc->removeCall(this); 104 /* Delete the call */ 105 delete this; 106 } 107 } 108 109 static void mainProg1(Endpoint &ep) throw(Error) 110 { 111 // Init library 112 EpConfig ep_cfg; 113 ep_cfg.logConfig.level = 4; 114 ep.libInit( ep_cfg ); 115 116 // Transport 117 TransportConfig tcfg; 118 tcfg.port = 5060; 119 ep.transportCreate(PJSIP_TRANSPORT_UDP, tcfg); 120 121 // Start library 122 ep.libStart(); 123 std::cout << "*** PJSUA2 STARTED ***" << std::endl; 124 125 // Add account 126 AccountConfig acc_cfg; 127 acc_cfg.idUri = "sip:test1@pjsip.org"; 128 acc_cfg.regConfig.registrarUri = "sip:sip.pjsip.org"; 129 acc_cfg.sipConfig.authCreds.push_back( AuthCredInfo("digest", "*", 130 "test1", 0, "test1") ); 131 std::auto_ptr<MyAccount> acc(new MyAccount); 132 acc->create(acc_cfg); 133 134 pj_thread_sleep(2000); 135 136 // Make outgoing call 137 Call *call = new MyCall(*acc); 138 acc->calls.push_back(call); 139 CallOpParam prm(true); 140 prm.opt.audioCount = 1; 141 prm.opt.videoCount = 0; 142 call->makeCall("sip:test1@pjsip.org", prm); 143 144 // Hangup all calls 145 pj_thread_sleep(8000); 146 ep.hangupAllCalls(); 147 pj_thread_sleep(4000); 148 149 // Destroy library 150 std::cout << "*** PJSUA2 SHUTTING DOWN ***" << std::endl; 151 } 152 153 static void mainProg2() throw(Error) 154 { 155 string json_str; 156 { 157 EpConfig epCfg; 158 JsonDocument jDoc; 159 160 epCfg.uaConfig.maxCalls = 61; 161 epCfg.uaConfig.userAgent = "Just JSON Test"; 162 epCfg.uaConfig.stunServer.push_back("stun1.pjsip.org"); 163 epCfg.uaConfig.stunServer.push_back("stun2.pjsip.org"); 164 epCfg.logConfig.filename = "THE.LOG"; 165 166 jDoc.writeObject(epCfg); 167 json_str = jDoc.saveString(); 168 std::cout << json_str << std::endl << std::endl; 169 } 170 171 { 172 EpConfig epCfg; 173 JsonDocument rDoc; 174 string output; 175 176 rDoc.loadString(json_str); 177 rDoc.readObject(epCfg); 178 179 JsonDocument wDoc; 180 181 wDoc.writeObject(epCfg); 182 json_str = wDoc.saveString(); 183 std::cout << json_str << std::endl << std::endl; 184 185 wDoc.saveFile("jsontest.js"); 186 } 187 188 { 189 EpConfig epCfg; 190 JsonDocument rDoc; 191 192 rDoc.loadFile("jsontest.js"); 193 rDoc.readObject(epCfg); 194 pj_file_delete("jsontest.js"); 195 } 196 } 197 198 199 static void mainProg3(Endpoint &ep) throw(Error) 200 { 201 const char *paths[] = { "../../../../tests/pjsua/wavs/input.16.wav", 202 "../../tests/pjsua/wavs/input.16.wav", 203 "input.16.wav"}; 204 unsigned i; 205 const char *filename = NULL; 206 207 // Init library 208 EpConfig ep_cfg; 209 ep.libInit( ep_cfg ); 210 211 for (i=0; i<PJ_ARRAY_SIZE(paths); ++i) { 212 if (pj_file_exists(paths[i])) { 213 filename = paths[i]; 214 break; 215 } 216 } 217 218 if (!filename) { 219 PJSUA2_RAISE_ERROR3(PJ_ENOTFOUND, "mainProg3()", 220 "Could not locate input.16.wav"); 221 } 222 223 // Start library 224 ep.libStart(); 225 std::cout << "*** PJSUA2 STARTED ***" << std::endl; 226 227 // Create player and recorder 228 { 229 AudioMediaPlayer amp; 230 amp.createPlayer(filename); 231 232 AudioMediaRecorder amr; 233 amr.createRecorder("recorder_test_output.wav"); 234 235 amp.startTransmit(ep.audDevManager().getPlaybackDevMedia()); 236 amp.startTransmit(amr); 237 238 pj_thread_sleep(5000); 239 } 240 } 241 242 243 static void mainProg() throw(Error) 244 { 245 string json_str; 246 247 { 248 JsonDocument jdoc; 249 AccountConfig accCfg; 250 251 accCfg.idUri = ""Just Test" <sip:test@pjsip.org>"; 252 accCfg.regConfig.registrarUri = "sip:sip.pjsip.org"; 253 SipHeader h; 254 h.hName = "X-Header"; 255 h.hValue = "User header"; 256 accCfg.regConfig.headers.push_back(h); 257 258 accCfg.sipConfig.proxies.push_back("<sip:sip.pjsip.org;transport=tcp>"); 259 accCfg.sipConfig.proxies.push_back("<sip:sip.pjsip.org;transport=tls>"); 260 261 accCfg.mediaConfig.transportConfig.tlsConfig.ciphers.push_back(1); 262 accCfg.mediaConfig.transportConfig.tlsConfig.ciphers.push_back(2); 263 accCfg.mediaConfig.transportConfig.tlsConfig.ciphers.push_back(3); 264 265 AuthCredInfo aci; 266 aci.scheme = "digest"; 267 aci.username = "test"; 268 aci.data = "passwd"; 269 aci.realm = "*"; 270 accCfg.sipConfig.authCreds.push_back(aci); 271 272 jdoc.writeObject(accCfg); 273 json_str = jdoc.saveString(); 274 std::cout << "Original:" << std::endl; 275 std::cout << json_str << std::endl << std::endl; 276 } 277 278 { 279 JsonDocument rdoc; 280 281 rdoc.loadString(json_str); 282 AccountConfig accCfg; 283 rdoc.readObject(accCfg); 284 285 JsonDocument wdoc; 286 wdoc.writeObject(accCfg); 287 json_str = wdoc.saveString(); 288 289 std::cout << "Parsed:" << std::endl; 290 std::cout << json_str << std::endl << std::endl; 291 } 292 } 293 294 295 static void mainProg4(Endpoint &ep) throw(Error) 296 { 297 // Init library 298 EpConfig ep_cfg; 299 ep.libInit( ep_cfg ); 300 301 // Create transport 302 TransportConfig tcfg; 303 tcfg.port = 5060; 304 ep.transportCreate(PJSIP_TRANSPORT_UDP, tcfg); 305 ep.transportCreate(PJSIP_TRANSPORT_TCP, tcfg); 306 307 // Add account 308 AccountConfig acc_cfg; 309 acc_cfg.idUri = "sip:localhost"; 310 std::auto_ptr<MyAccount> acc(new MyAccount); 311 acc->create(acc_cfg); 312 313 // Start library 314 ep.libStart(); 315 std::cout << "*** PJSUA2 STARTED ***" << std::endl; 316 317 // Just wait for ENTER key 318 std::cout << "Press ENTER to quit..." << std::endl; 319 std::cin.get(); 320 } 321 322 323 int main() 324 { 325 int ret = 0; 326 Endpoint ep; 327 328 try { 329 ep.libCreate(); 330 331 mainProg4(ep); 332 ret = PJ_SUCCESS; 333 } catch (Error & err) { 334 std::cout << "Exception: " << err.info() << std::endl; 335 ret = 1; 336 } 337 338 try { 339 ep.libDestroy(); 340 } catch(Error &err) { 341 std::cout << "Exception: " << err.info() << std::endl; 342 ret = 1; 343 } 344 345 if (ret == PJ_SUCCESS) { 346 std::cout << "Success" << std::endl; 347 } else { 348 std::cout << "Error Found" << std::endl; 349 } 350 351 return ret; 352 }
二进制文件位于 pjsip-apps/bin/samples 目录下
9.1.2 Python GUI
有一个相当完整的Python GUI示例程序,位于 pjsip-apps/src/pygui目录
1 # $Id: application.py 4798 2014-03-19 21:20:17Z bennylp $ 2 # 3 # pjsua Python GUI Demo 4 # 5 # Copyright (C)2013 Teluu Inc. (http://www.teluu.com) 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 if sys.version_info[0] >= 3: # Python 3 23 import tkinter as tk 24 from tkinter import ttk 25 from tkinter import messagebox as msgbox 26 else: 27 import Tkinter as tk 28 import tkMessageBox as msgbox 29 import ttk 30 31 import pjsua2 as pj 32 import log 33 import accountsetting 34 import account 35 import buddy 36 import endpoint 37 import settings 38 39 import os 40 import traceback 41 42 # You may try to enable pjsua worker thread by setting USE_THREADS below to True *and* 43 # recreate the swig module with adding -threads option to swig (uncomment USE_THREADS 44 # in swig/python/Makefile). In my experiment this would crash Python as reported in: 45 # http://lists.pjsip.org/pipermail/pjsip_lists.pjsip.org/2014-March/017223.html 46 USE_THREADS = False 47 48 class Application(ttk.Frame): 49 """ 50 The Application main frame. 51 """ 52 def __init__(self): 53 global USE_THREADS 54 ttk.Frame.__init__(self, name='application', width=300, height=500) 55 self.pack(expand='yes', fill='both') 56 self.master.title('pjsua2 Demo') 57 self.master.geometry('500x500+100+100') 58 59 # Logger 60 self.logger = log.Logger() 61 62 # Accounts 63 self.accList = [] 64 65 # GUI variables 66 self.showLogWindow = tk.IntVar(value=0) 67 self.quitting = False 68 69 # Construct GUI 70 self._createWidgets() 71 72 # Log window 73 self.logWindow = log.LogWindow(self) 74 self._onMenuShowHideLogWindow() 75 76 # Instantiate endpoint 77 self.ep = endpoint.Endpoint() 78 self.ep.libCreate() 79 80 # Default config 81 self.appConfig = settings.AppConfig() 82 if USE_THREADS: 83 self.appConfig.epConfig.uaConfig.threadCnt = 1 84 self.appConfig.epConfig.uaConfig.mainThreadOnly = False 85 else: 86 self.appConfig.epConfig.uaConfig.threadCnt = 0 87 self.appConfig.epConfig.uaConfig.mainThreadOnly = True 88 self.appConfig.epConfig.logConfig.writer = self.logger 89 self.appConfig.epConfig.logConfig.filename = "pygui.log" 90 self.appConfig.epConfig.logConfig.fileFlags = pj.PJ_O_APPEND 91 self.appConfig.epConfig.logConfig.level = 5 92 self.appConfig.epConfig.logConfig.consoleLevel = 5 93 94 def saveConfig(self, filename='pygui.js'): 95 # Save disabled accounts since they are not listed in self.accList 96 disabled_accs = [ac for ac in self.appConfig.accounts if not ac.enabled] 97 self.appConfig.accounts = [] 98 99 # Get account configs from active accounts 100 for acc in self.accList: 101 acfg = settings.AccConfig() 102 acfg.enabled = True 103 acfg.config = acc.cfg 104 for bud in acc.buddyList: 105 acfg.buddyConfigs.append(bud.cfg) 106 self.appConfig.accounts.append(acfg) 107 108 # Put back disabled accounts 109 self.appConfig.accounts.extend(disabled_accs) 110 # Save 111 self.appConfig.saveFile(filename) 112 113 def start(self, cfg_file='pygui.js'): 114 global USE_THREADS 115 # Load config 116 if cfg_file and os.path.exists(cfg_file): 117 self.appConfig.loadFile(cfg_file) 118 119 if USE_THREADS: 120 self.appConfig.epConfig.uaConfig.threadCnt = 1 121 self.appConfig.epConfig.uaConfig.mainThreadOnly = False 122 else: 123 self.appConfig.epConfig.uaConfig.threadCnt = 0 124 self.appConfig.epConfig.uaConfig.mainThreadOnly = True 125 self.appConfig.epConfig.uaConfig.threadCnt = 0 126 self.appConfig.epConfig.uaConfig.mainThreadOnly = True 127 self.appConfig.epConfig.logConfig.writer = self.logger 128 self.appConfig.epConfig.logConfig.level = 5 129 self.appConfig.epConfig.logConfig.consoleLevel = 5 130 131 # Initialize library 132 self.appConfig.epConfig.uaConfig.userAgent = "pygui-" + self.ep.libVersion().full; 133 self.ep.libInit(self.appConfig.epConfig) 134 self.master.title('pjsua2 Demo version ' + self.ep.libVersion().full) 135 136 # Create transports 137 if self.appConfig.udp.enabled: 138 self.ep.transportCreate(self.appConfig.udp.type, self.appConfig.udp.config) 139 if self.appConfig.tcp.enabled: 140 self.ep.transportCreate(self.appConfig.tcp.type, self.appConfig.tcp.config) 141 if self.appConfig.tls.enabled: 142 self.ep.transportCreate(self.appConfig.tls.type, self.appConfig.tls.config) 143 144 # Add accounts 145 for cfg in self.appConfig.accounts: 146 if cfg.enabled: 147 self._createAcc(cfg.config) 148 acc = self.accList[-1] 149 for buddy_cfg in cfg.buddyConfigs: 150 self._createBuddy(acc, buddy_cfg) 151 152 # Start library 153 self.ep.libStart() 154 155 # Start polling 156 if not USE_THREADS: 157 self._onTimer() 158 159 def updateAccount(self, acc): 160 if acc.deleting: 161 return # ignore 162 iid = str(acc.randId) 163 text = acc.cfg.idUri 164 status = acc.statusText() 165 166 values = (status,) 167 if self.tv.exists(iid): 168 self.tv.item(iid, text=text, values=values) 169 else: 170 self.tv.insert('', 'end', iid, open=True, text=text, values=values) 171 172 def updateBuddy(self, bud): 173 iid = 'buddy' + str(bud.randId) 174 text = bud.cfg.uri 175 status = bud.statusText() 176 177 values = (status,) 178 if self.tv.exists(iid): 179 self.tv.item(iid, text=text, values=values) 180 else: 181 self.tv.insert(str(bud.account.randId), 'end', iid, open=True, text=text, values=values) 182 183 def _createAcc(self, acc_cfg): 184 acc = account.Account(self) 185 acc.cfg = acc_cfg 186 self.accList.append(acc) 187 self.updateAccount(acc) 188 acc.create(acc.cfg) 189 acc.cfgChanged = False 190 self.updateAccount(acc) 191 192 def _createBuddy(self, acc, buddy_cfg): 193 bud = buddy.Buddy(self) 194 bud.cfg = buddy_cfg 195 bud.account = acc 196 bud.create(acc, bud.cfg) 197 self.updateBuddy(bud) 198 acc.buddyList.append(bud) 199 200 def _createWidgets(self): 201 self._createAppMenu() 202 203 # Main pane, a Treeview 204 self.tv = ttk.Treeview(self, columns=('Status'), show='tree') 205 self.tv.pack(side='top', fill='both', expand='yes', padx=5, pady=5) 206 207 self._createContextMenu() 208 209 # Handle close event 210 self.master.protocol("WM_DELETE_WINDOW", self._onClose) 211 212 def _createAppMenu(self): 213 # Main menu bar 214 top = self.winfo_toplevel() 215 self.menubar = tk.Menu() 216 top.configure(menu=self.menubar) 217 218 # File menu 219 file_menu = tk.Menu(self.menubar, tearoff=False) 220 self.menubar.add_cascade(label="File", menu=file_menu) 221 file_menu.add_command(label="Add account..", command=self._onMenuAddAccount) 222 file_menu.add_checkbutton(label="Show/hide log window", command=self._onMenuShowHideLogWindow, variable=self.showLogWindow) 223 file_menu.add_separator() 224 file_menu.add_command(label="Settings...", command=self._onMenuSettings) 225 file_menu.add_command(label="Save Settings", command=self._onMenuSaveSettings) 226 file_menu.add_separator() 227 file_menu.add_command(label="Quit", command=self._onMenuQuit) 228 229 # Window menu 230 self.window_menu = tk.Menu(self.menubar, tearoff=False) 231 self.menubar.add_cascade(label="Window", menu=self.window_menu) 232 233 # Help menu 234 help_menu = tk.Menu(self.menubar, tearoff=False) 235 self.menubar.add_cascade(label="Help", menu=help_menu) 236 help_menu.add_command(label="About", underline=2, command=self._onMenuAbout) 237 238 def _showChatWindow(self, chat_inst): 239 chat_inst.showWindow() 240 241 def updateWindowMenu(self): 242 # Chat windows 243 self.window_menu.delete(0, tk.END) 244 for acc in self.accList: 245 for c in acc.chatList: 246 cmd = lambda arg=c: self._showChatWindow(arg) 247 self.window_menu.add_command(label=c.title, command=cmd) 248 249 def _createContextMenu(self): 250 top = self.winfo_toplevel() 251 252 # Create Account context menu 253 self.accMenu = tk.Menu(top, tearoff=False) 254 # Labels, must match with _onAccContextMenu() 255 labels = ['Unregister', 'Reregister', 'Add buddy...', '-', 256 'Online', 'Invisible', 'Away', 'Busy', '-', 257 'Settings...', '-', 258 'Delete...'] 259 for label in labels: 260 if label=='-': 261 self.accMenu.add_separator() 262 else: 263 cmd = lambda arg=label: self._onAccContextMenu(arg) 264 self.accMenu.add_command(label=label, command=cmd) 265 266 # Create Buddy context menu 267 # Labels, must match with _onBuddyContextMenu() 268 self.buddyMenu = tk.Menu(top, tearoff=False) 269 labels = ['Audio call', 'Send instant message', '-', 270 'Subscribe', 'Unsubscribe', '-', 271 'Settings...', '-', 272 'Delete...'] 273 274 for label in labels: 275 if label=='-': 276 self.buddyMenu.add_separator() 277 else: 278 cmd = lambda arg=label: self._onBuddyContextMenu(arg) 279 self.buddyMenu.add_command(label=label, command=cmd) 280 281 if (top.tk.call('tk', 'windowingsystem')=='aqua'): 282 self.tv.bind('<2>', self._onTvRightClick) 283 self.tv.bind('<Control-1>', self._onTvRightClick) 284 else: 285 self.tv.bind('<3>', self._onTvRightClick) 286 self.tv.bind('<Double-Button-1>', self._onTvDoubleClick) 287 288 def _getSelectedAccount(self): 289 items = self.tv.selection() 290 if not items: 291 return None 292 try: 293 iid = int(items[0]) 294 except: 295 return None 296 accs = [acc for acc in self.accList if acc.randId==iid] 297 if not accs: 298 return None 299 return accs[0] 300 301 def _getSelectedBuddy(self): 302 items = self.tv.selection() 303 if not items: 304 return None 305 try: 306 iid = int(items[0][5:]) 307 iid_parent = int(self.tv.parent(items[0])) 308 except: 309 return None 310 311 accs = [acc for acc in self.accList if acc.randId==iid_parent] 312 if not accs: 313 return None 314 315 buds = [b for b in accs[0].buddyList if b.randId==iid] 316 if not buds: 317 return None 318 319 return buds[0] 320 321 def _onTvRightClick(self, event): 322 iid = self.tv.identify_row(event.y) 323 #iid = self.tv.identify('item', event.x, event.y) 324 if iid: 325 self.tv.selection_set( (iid,) ) 326 acc = self._getSelectedAccount() 327 if acc: 328 self.accMenu.post(event.x_root, event.y_root) 329 else: 330 # A buddy is selected 331 self.buddyMenu.post(event.x_root, event.y_root) 332 333 def _onTvDoubleClick(self, event): 334 iid = self.tv.identify_row(event.y) 335 if iid: 336 self.tv.selection_set( (iid,) ) 337 acc = self._getSelectedAccount() 338 if acc: 339 self.cfgChanged = False 340 dlg = accountsetting.Dialog(self.master, acc.cfg) 341 if dlg.doModal(): 342 self.updateAccount(acc) 343 acc.modify(acc.cfg) 344 else: 345 bud = self._getSelectedBuddy() 346 acc = bud.account 347 chat = acc.findChat(bud.cfg.uri) 348 if not chat: 349 chat = acc.newChat(bud.cfg.uri) 350 chat.showWindow() 351 352 def _onAccContextMenu(self, label): 353 acc = self._getSelectedAccount() 354 if not acc: 355 return 356 357 if label=='Unregister': 358 acc.setRegistration(False) 359 elif label=='Reregister': 360 acc.setRegistration(True) 361 elif label=='Online': 362 ps = pj.PresenceStatus() 363 ps.status = pj.PJSUA_BUDDY_STATUS_ONLINE 364 acc.setOnlineStatus(ps) 365 elif label=='Invisible': 366 ps = pj.PresenceStatus() 367 ps.status = pj.PJSUA_BUDDY_STATUS_OFFLINE 368 acc.setOnlineStatus(ps) 369 elif label=='Away': 370 ps = pj.PresenceStatus() 371 ps.status = pj.PJSUA_BUDDY_STATUS_ONLINE 372 ps.activity = pj.PJRPID_ACTIVITY_AWAY 373 ps.note = "Away" 374 acc.setOnlineStatus(ps) 375 elif label=='Busy': 376 ps = pj.PresenceStatus() 377 ps.status = pj.PJSUA_BUDDY_STATUS_ONLINE 378 ps.activity = pj.PJRPID_ACTIVITY_BUSY 379 ps.note = "Busy" 380 acc.setOnlineStatus(ps) 381 elif label=='Settings...': 382 self.cfgChanged = False 383 dlg = accountsetting.Dialog(self.master, acc.cfg) 384 if dlg.doModal(): 385 self.updateAccount(acc) 386 acc.modify(acc.cfg) 387 elif label=='Delete...': 388 msg = "Do you really want to delete account '%s'?" % acc.cfg.idUri 389 if msgbox.askquestion('Delete account?', msg, default=msgbox.NO) != u'yes': 390 return 391 iid = str(acc.randId) 392 self.accList.remove(acc) 393 acc.setRegistration(False) 394 acc.deleting = True 395 del acc 396 self.tv.delete( (iid,) ) 397 elif label=='Add buddy...': 398 cfg = pj.BuddyConfig() 399 dlg = buddy.SettingDialog(self.master, cfg) 400 if dlg.doModal(): 401 self._createBuddy(acc, cfg) 402 else: 403 assert not ("Unknown menu " + label) 404 405 def _onBuddyContextMenu(self, label): 406 bud = self._getSelectedBuddy() 407 if not bud: 408 return 409 acc = bud.account 410 411 if label=='Audio call': 412 chat = acc.findChat(bud.cfg.uri) 413 if not chat: chat = acc.newChat(bud.cfg.uri) 414 chat.showWindow() 415 chat.startCall() 416 elif label=='Send instant message': 417 chat = acc.findChat(bud.cfg.uri) 418 if not chat: chat = acc.newChat(bud.cfg.uri) 419 chat.showWindow(True) 420 elif label=='Subscribe': 421 bud.subscribePresence(True) 422 elif label=='Unsubscribe': 423 bud.subscribePresence(False) 424 elif label=='Settings...': 425 subs = bud.cfg.subscribe 426 uri = bud.cfg.uri 427 dlg = buddy.SettingDialog(self.master, bud.cfg) 428 if dlg.doModal(): 429 self.updateBuddy(bud) 430 # URI updated? 431 if uri != bud.cfg.uri: 432 cfg = bud.cfg 433 # del old 434 iid = 'buddy' + str(bud.randId) 435 acc.buddyList.remove(bud) 436 del bud 437 self.tv.delete( (iid,) ) 438 # add new 439 self._createBuddy(acc, cfg) 440 # presence subscribe setting updated 441 elif subs != bud.cfg.subscribe: 442 bud.subscribePresence(bud.cfg.subscribe) 443 elif label=='Delete...': 444 msg = "Do you really want to delete buddy '%s'?" % bud.cfg.uri 445 if msgbox.askquestion('Delete buddy?', msg, default=msgbox.NO) != u'yes': 446 return 447 iid = 'buddy' + str(bud.randId) 448 acc.buddyList.remove(bud) 449 del bud 450 self.tv.delete( (iid,) ) 451 else: 452 assert not ("Unknown menu " + label) 453 454 def _onTimer(self): 455 if not self.quitting: 456 self.ep.libHandleEvents(10) 457 if not self.quitting: 458 self.master.after(50, self._onTimer) 459 460 def _onClose(self): 461 self.saveConfig() 462 self.quitting = True 463 self.ep.libDestroy() 464 self.ep = None 465 self.update() 466 self.quit() 467 468 def _onMenuAddAccount(self): 469 cfg = pj.AccountConfig() 470 dlg = accountsetting.Dialog(self.master, cfg) 471 if dlg.doModal(): 472 self._createAcc(cfg) 473 474 def _onMenuShowHideLogWindow(self): 475 if self.showLogWindow.get(): 476 self.logWindow.deiconify() 477 else: 478 self.logWindow.withdraw() 479 480 def _onMenuSettings(self): 481 dlg = settings.Dialog(self, self.appConfig) 482 if dlg.doModal(): 483 msgbox.showinfo(self.master.title(), 'You need to restart for new settings to take effect') 484 485 def _onMenuSaveSettings(self): 486 self.saveConfig() 487 488 def _onMenuQuit(self): 489 self._onClose() 490 491 def _onMenuAbout(self): 492 msgbox.showinfo(self.master.title(), 'About') 493 494 495 class ExceptionCatcher: 496 """Custom Tk exception catcher, mainly to display more information 497 from pj.Error exception 498 """ 499 def __init__(self, func, subst, widget): 500 self.func = func 501 self.subst = subst 502 self.widget = widget 503 def __call__(self, *args): 504 try: 505 if self.subst: 506 args = apply(self.subst, args) 507 return apply(self.func, args) 508 except pj.Error, error: 509 print 'Exception:' 510 print ' ', error.info() 511 print 'Traceback:' 512 print traceback.print_stack() 513 log.writeLog2(1, 'Exception: ' + error.info() + ' ') 514 except Exception, error: 515 print 'Exception:' 516 print ' ', str(error) 517 print 'Traceback:' 518 print traceback.print_stack() 519 log.writeLog2(1, 'Exception: ' + str(error) + ' ') 520 521 def main(): 522 #tk.CallWrapper = ExceptionCatcher 523 app = Application() 524 app.start() 525 app.mainloop() 526 527 if __name__ == '__main__': 528 main()
需要Python 2.7及以上版本,以及Python SWIG模块。要使用应用程序,只需运行:
python application.py
9.1.3 安卓
请参考 https://trac.pjsip.org/repos/wiki/Getting-Started/Android#pjsua2 的示例应用程序。
9.1.4 Java
在目录 pjsip-apps/src/swig/java 下
有一个Hello World类型的应用程序。
需要Java SWIG模块。构建SWIG模块后,从该目录运行该应用程序。
make test
9.1.5 iOS
可拷贝 pjsip-apps/src/samples/pjsua2_demo.cpp 的代码(即本页第一个代码段中的代码)到
.mm
文件中,然后加入到 iOS XCode项目中。
注意:必须使用Obj-C ++(.mm
)文件,而不是默认的Obj-C(.m
)文件