示例应用程序
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() + '\n')
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) + '\n')
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)文件

