1 /*
2 * libjingle
3 * Copyright 2004--2005, Google Inc.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright notice,
11 * this list of conditions and the following disclaimer in the documentation
12 * and/or other materials provided with the distribution.
13 * 3. The name of the author may not be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28 #include "talk/examples/call/callclient.h"
29
30 #include <string>
31
32 #include "talk/base/basicpacketsocketfactory.h"
33 #include "talk/base/helpers.h"
34 #include "talk/base/logging.h"
35 #include "talk/base/network.h"
36 #include "talk/base/socketaddress.h"
37 #include "talk/base/stringencode.h"
38 #include "talk/base/stringutils.h"
39 #include "talk/base/thread.h"
40 #include "talk/examples/call/console.h"
41 #include "talk/examples/call/presencepushtask.h"
42 #include "talk/examples/call/presenceouttask.h"
43 #include "talk/examples/call/mucinviterecvtask.h"
44 #include "talk/examples/call/mucinvitesendtask.h"
45 #include "talk/examples/call/friendinvitesendtask.h"
46 #include "talk/examples/call/muc.h"
47 #include "talk/examples/call/voicemailjidrequester.h"
48 #include "talk/p2p/base/sessionmanager.h"
49 #include "talk/p2p/client/basicportallocator.h"
50 #include "talk/p2p/client/sessionmanagertask.h"
51 #include "talk/session/phone/devicemanager.h"
52 #include "talk/session/phone/mediaengine.h"
53 #include "talk/session/phone/mediasessionclient.h"
54 #include "talk/xmpp/constants.h"
55
56
57 class NullRenderer : public cricket::VideoRenderer {
58 public:
NullRenderer(const char * s)59 explicit NullRenderer(const char* s) : s_(s) {}
60 private:
SetSize(int width,int height,int reserved)61 bool SetSize(int width, int height, int reserved) {
62 LOG(LS_INFO) << "Video size for " << s_ << ": " << width << "x" << height;
63 return true;
64 }
RenderFrame(const cricket::VideoFrame * frame)65 bool RenderFrame(const cricket::VideoFrame *frame) {
66 return true;
67 }
68 const char* s_;
69 };
70
71 namespace {
72
DescribeStatus(buzz::Status::Show show,const std::string & desc)73 const char* DescribeStatus(buzz::Status::Show show, const std::string& desc) {
74 switch (show) {
75 case buzz::Status::SHOW_XA: return desc.c_str();
76 case buzz::Status::SHOW_ONLINE: return "online";
77 case buzz::Status::SHOW_AWAY: return "away";
78 case buzz::Status::SHOW_DND: return "do not disturb";
79 case buzz::Status::SHOW_CHAT: return "ready to chat";
80 default: return "offline";
81 }
82 }
83
GetWord(const std::vector<std::string> & words,size_t index,const std::string & def)84 std::string GetWord(const std::vector<std::string>& words,
85 size_t index, const std::string& def) {
86 if (words.size() > index) {
87 return words[index];
88 } else {
89 return def;
90 }
91 }
92
GetInt(const std::vector<std::string> & words,size_t index,int def)93 int GetInt(const std::vector<std::string>& words, size_t index, int def) {
94 int val;
95 if (words.size() > index && talk_base::FromString(words[index], &val)) {
96 return val;
97 } else {
98 return def;
99 }
100 }
101
102
103 } // namespace
104
105 const char* CALL_COMMANDS =
106 "Available commands:\n"
107 "\n"
108 " hangup Ends the call.\n"
109 " mute Stops sending voice.\n"
110 " unmute Re-starts sending voice.\n"
111 " dtmf Sends a DTMF tone.\n"
112 " quit Quits the application.\n"
113 "";
114
115 const char* RECEIVE_COMMANDS =
116 "Available commands:\n"
117 "\n"
118 " accept [bw] Accepts the incoming call and switches to it.\n"
119 " reject Rejects the incoming call and stays with the current call.\n"
120 " quit Quits the application.\n"
121 "";
122
123 const char* CONSOLE_COMMANDS =
124 "Available commands:\n"
125 "\n"
126 " roster Prints the online friends from your roster.\n"
127 " friend user Request to add a user to your roster.\n"
128 " call [jid] [bw] Initiates a call to the user[/room] with the\n"
129 " given JID and with optional bandwidth.\n"
130 " vcall [jid] [bw] Initiates a video call to the user[/room] with\n"
131 " the given JID and with optional bandwidth.\n"
132 " voicemail [jid] Leave a voicemail for the user with the given JID.\n"
133 " join [room] Joins a multi-user-chat.\n"
134 " invite user [room] Invites a friend to a multi-user-chat.\n"
135 " leave [room] Leaves a multi-user-chat.\n"
136 " getdevs Prints the available media devices.\n"
137 " quit Quits the application.\n"
138 "";
139
ParseLine(const std::string & line)140 void CallClient::ParseLine(const std::string& line) {
141 std::vector<std::string> words;
142 int start = -1;
143 int state = 0;
144 for (int index = 0; index <= static_cast<int>(line.size()); ++index) {
145 if (state == 0) {
146 if (!isspace(line[index])) {
147 start = index;
148 state = 1;
149 }
150 } else {
151 ASSERT(state == 1);
152 ASSERT(start >= 0);
153 if (isspace(line[index])) {
154 std::string word(line, start, index - start);
155 words.push_back(word);
156 start = -1;
157 state = 0;
158 }
159 }
160 }
161
162 // Global commands
163 const std::string& command = GetWord(words, 0, "");
164 if (command == "quit") {
165 Quit();
166 } else if (call_ && incoming_call_) {
167 if (command == "accept") {
168 cricket::CallOptions options;
169 options.video_bandwidth = GetInt(words, 1, cricket::kAutoBandwidth);
170 Accept(options);
171 } else if (command == "reject") {
172 Reject();
173 } else {
174 console_->Print(RECEIVE_COMMANDS);
175 }
176 } else if (call_) {
177 if (command == "hangup") {
178 // TODO: do more shutdown here, move to Terminate()
179 call_->Terminate();
180 call_ = NULL;
181 session_ = NULL;
182 console_->SetPrompt(NULL);
183 } else if (command == "mute") {
184 call_->Mute(true);
185 } else if (command == "unmute") {
186 call_->Mute(false);
187 } else if ((command == "dtmf") && (words.size() == 2)) {
188 int ev = std::string("0123456789*#").find(words[1][0]);
189 call_->PressDTMF(ev);
190 } else {
191 console_->Print(CALL_COMMANDS);
192 }
193 } else {
194 if (command == "roster") {
195 PrintRoster();
196 } else if (command == "send") {
197 buzz::Jid jid(words[1]);
198 if (jid.IsValid()) {
199 last_sent_to_ = words[1];
200 SendChat(words[1], words[2]);
201 } else if (!last_sent_to_.empty()) {
202 SendChat(last_sent_to_, words[1]);
203 } else {
204 console_->Printf(
205 "Invalid JID. JIDs should be in the form user@domain\n");
206 }
207 } else if ((words.size() == 2) && (command == "friend")) {
208 InviteFriend(words[1]);
209 } else if (command == "call") {
210 std::string to = GetWord(words, 1, "");
211 MakeCallTo(to, cricket::CallOptions());
212 } else if (command == "vcall") {
213 std::string to = GetWord(words, 1, "");
214 int bandwidth = GetInt(words, 2, cricket::kAutoBandwidth);
215 cricket::CallOptions options;
216 options.is_video = true;
217 options.video_bandwidth = bandwidth;
218 MakeCallTo(to, options);
219 } else if (command == "join") {
220 JoinMuc(GetWord(words, 1, ""));
221 } else if ((words.size() >= 2) && (command == "invite")) {
222 InviteToMuc(words[1], GetWord(words, 2, ""));
223 } else if (command == "leave") {
224 LeaveMuc(GetWord(words, 1, ""));
225 } else if (command == "getdevs") {
226 GetDevices();
227 } else if ((words.size() == 2) && (command == "setvol")) {
228 SetVolume(words[1]);
229 } else if (command == "voicemail") {
230 CallVoicemail((words.size() >= 2) ? words[1] : "");
231 } else {
232 console_->Print(CONSOLE_COMMANDS);
233 }
234 }
235 }
236
CallClient(buzz::XmppClient * xmpp_client)237 CallClient::CallClient(buzz::XmppClient* xmpp_client)
238 : xmpp_client_(xmpp_client),
239 media_engine_(NULL),
240 media_client_(NULL),
241 call_(NULL),
242 incoming_call_(false),
243 auto_accept_(false),
244 pmuc_domain_("groupchat.google.com"),
245 local_renderer_(NULL),
246 remote_renderer_(NULL),
247 roster_(new RosterMap),
248 portallocator_flags_(0),
249 allow_local_ips_(false),
250 initial_protocol_(cricket::PROTOCOL_HYBRID),
251 secure_policy_(cricket::SEC_DISABLED) {
252 xmpp_client_->SignalStateChange.connect(this, &CallClient::OnStateChange);
253 }
254
~CallClient()255 CallClient::~CallClient() {
256 delete media_client_;
257 delete roster_;
258 }
259
strerror(buzz::XmppEngine::Error err)260 const std::string CallClient::strerror(buzz::XmppEngine::Error err) {
261 switch (err) {
262 case buzz::XmppEngine::ERROR_NONE:
263 return "";
264 case buzz::XmppEngine::ERROR_XML:
265 return "Malformed XML or encoding error";
266 case buzz::XmppEngine::ERROR_STREAM:
267 return "XMPP stream error";
268 case buzz::XmppEngine::ERROR_VERSION:
269 return "XMPP version error";
270 case buzz::XmppEngine::ERROR_UNAUTHORIZED:
271 return "User is not authorized (Check your username and password)";
272 case buzz::XmppEngine::ERROR_TLS:
273 return "TLS could not be negotiated";
274 case buzz::XmppEngine::ERROR_AUTH:
275 return "Authentication could not be negotiated";
276 case buzz::XmppEngine::ERROR_BIND:
277 return "Resource or session binding could not be negotiated";
278 case buzz::XmppEngine::ERROR_CONNECTION_CLOSED:
279 return "Connection closed by output handler.";
280 case buzz::XmppEngine::ERROR_DOCUMENT_CLOSED:
281 return "Closed by </stream:stream>";
282 case buzz::XmppEngine::ERROR_SOCKET:
283 return "Socket error";
284 default:
285 return "Unknown error";
286 }
287 }
288
OnCallDestroy(cricket::Call * call)289 void CallClient::OnCallDestroy(cricket::Call* call) {
290 if (call == call_) {
291 if (remote_renderer_) {
292 delete remote_renderer_;
293 remote_renderer_ = NULL;
294 }
295 if (local_renderer_) {
296 delete local_renderer_;
297 local_renderer_ = NULL;
298 }
299 console_->SetPrompt(NULL);
300 console_->Print("call destroyed");
301 call_ = NULL;
302 session_ = NULL;
303 }
304 }
305
OnStateChange(buzz::XmppEngine::State state)306 void CallClient::OnStateChange(buzz::XmppEngine::State state) {
307 switch (state) {
308 case buzz::XmppEngine::STATE_START:
309 console_->Print("connecting...");
310 break;
311
312 case buzz::XmppEngine::STATE_OPENING:
313 console_->Print("logging in...");
314 break;
315
316 case buzz::XmppEngine::STATE_OPEN:
317 console_->Print("logged in...");
318 InitPhone();
319 InitPresence();
320 break;
321
322 case buzz::XmppEngine::STATE_CLOSED:
323 buzz::XmppEngine::Error error = xmpp_client_->GetError(NULL);
324 console_->Print("logged out..." + strerror(error));
325 Quit();
326 }
327 }
328
InitPhone()329 void CallClient::InitPhone() {
330 std::string client_unique = xmpp_client_->jid().Str();
331 talk_base::InitRandom(client_unique.c_str(), client_unique.size());
332
333 worker_thread_ = new talk_base::Thread();
334 // The worker thread must be started here since initialization of
335 // the ChannelManager will generate messages that need to be
336 // dispatched by it.
337 worker_thread_->Start();
338
339 // TODO: It looks like we are leaking many
340 // objects. E.g. |network_manager_| and |socket_factory_| are never
341 // deleted.
342
343 network_manager_ = new talk_base::NetworkManager();
344 socket_factory_ = new talk_base::BasicPacketSocketFactory(worker_thread_);
345
346 // TODO: Decide if the relay address should be specified here.
347 talk_base::SocketAddress stun_addr("stun.l.google.com", 19302);
348 port_allocator_ = new cricket::BasicPortAllocator(
349 network_manager_, socket_factory_, stun_addr,
350 talk_base::SocketAddress(), talk_base::SocketAddress(),
351 talk_base::SocketAddress());
352
353 if (portallocator_flags_ != 0) {
354 port_allocator_->set_flags(portallocator_flags_);
355 }
356 session_manager_ = new cricket::SessionManager(
357 port_allocator_, worker_thread_);
358 session_manager_->SignalRequestSignaling.connect(
359 this, &CallClient::OnRequestSignaling);
360 session_manager_->SignalSessionCreate.connect(
361 this, &CallClient::OnSessionCreate);
362 session_manager_->OnSignalingReady();
363
364 session_manager_task_ =
365 new cricket::SessionManagerTask(xmpp_client_, session_manager_);
366 session_manager_task_->EnableOutgoingMessages();
367 session_manager_task_->Start();
368
369 if (!media_engine_) {
370 media_engine_ = cricket::MediaEngine::Create();
371 }
372
373 media_client_ = new cricket::MediaSessionClient(
374 xmpp_client_->jid(),
375 session_manager_,
376 media_engine_,
377 new cricket::DeviceManager());
378 media_client_->SignalCallCreate.connect(this, &CallClient::OnCallCreate);
379 media_client_->SignalDevicesChange.connect(this,
380 &CallClient::OnDevicesChange);
381 media_client_->set_secure(secure_policy_);
382 }
383
OnRequestSignaling()384 void CallClient::OnRequestSignaling() {
385 session_manager_->OnSignalingReady();
386 }
387
OnSessionCreate(cricket::Session * session,bool initiate)388 void CallClient::OnSessionCreate(cricket::Session* session, bool initiate) {
389 session->set_allow_local_ips(allow_local_ips_);
390 session->set_current_protocol(initial_protocol_);
391 }
392
OnCallCreate(cricket::Call * call)393 void CallClient::OnCallCreate(cricket::Call* call) {
394 call->SignalSessionState.connect(this, &CallClient::OnSessionState);
395 if (call->video()) {
396 local_renderer_ = new NullRenderer("local");
397 remote_renderer_ = new NullRenderer("remote");
398 }
399 }
400
OnSessionState(cricket::Call * call,cricket::BaseSession * session,cricket::BaseSession::State state)401 void CallClient::OnSessionState(cricket::Call* call,
402 cricket::BaseSession* session,
403 cricket::BaseSession::State state) {
404 if (state == cricket::Session::STATE_RECEIVEDINITIATE) {
405 buzz::Jid jid(session->remote_name());
406 console_->Printf("Incoming call from '%s'", jid.Str().c_str());
407 call_ = call;
408 session_ = session;
409 incoming_call_ = true;
410 cricket::CallOptions options;
411 if (auto_accept_) {
412 Accept(options);
413 }
414 } else if (state == cricket::Session::STATE_SENTINITIATE) {
415 console_->Print("calling...");
416 } else if (state == cricket::Session::STATE_RECEIVEDACCEPT) {
417 console_->Print("call answered");
418 } else if (state == cricket::Session::STATE_RECEIVEDREJECT) {
419 console_->Print("call not answered");
420 } else if (state == cricket::Session::STATE_INPROGRESS) {
421 console_->Print("call in progress");
422 } else if (state == cricket::Session::STATE_RECEIVEDTERMINATE) {
423 console_->Print("other side hung up");
424 }
425 }
426
InitPresence()427 void CallClient::InitPresence() {
428 presence_push_ = new buzz::PresencePushTask(xmpp_client_, this);
429 presence_push_->SignalStatusUpdate.connect(
430 this, &CallClient::OnStatusUpdate);
431 presence_push_->SignalMucJoined.connect(this, &CallClient::OnMucJoined);
432 presence_push_->SignalMucLeft.connect(this, &CallClient::OnMucLeft);
433 presence_push_->SignalMucStatusUpdate.connect(
434 this, &CallClient::OnMucStatusUpdate);
435 presence_push_->Start();
436
437 presence_out_ = new buzz::PresenceOutTask(xmpp_client_);
438 RefreshStatus();
439 presence_out_->Start();
440
441 muc_invite_recv_ = new buzz::MucInviteRecvTask(xmpp_client_);
442 muc_invite_recv_->SignalInviteReceived.connect(this,
443 &CallClient::OnMucInviteReceived);
444 muc_invite_recv_->Start();
445
446 muc_invite_send_ = new buzz::MucInviteSendTask(xmpp_client_);
447 muc_invite_send_->Start();
448
449 friend_invite_send_ = new buzz::FriendInviteSendTask(xmpp_client_);
450 friend_invite_send_->Start();
451 }
452
RefreshStatus()453 void CallClient::RefreshStatus() {
454 int media_caps = media_client_->GetCapabilities();
455 my_status_.set_jid(xmpp_client_->jid());
456 my_status_.set_available(true);
457 my_status_.set_show(buzz::Status::SHOW_ONLINE);
458 my_status_.set_priority(0);
459 my_status_.set_know_capabilities(true);
460 my_status_.set_pmuc_capability(true);
461 my_status_.set_phone_capability(
462 (media_caps & cricket::MediaEngine::AUDIO_RECV) != 0);
463 my_status_.set_video_capability(
464 (media_caps & cricket::MediaEngine::VIDEO_RECV) != 0);
465 my_status_.set_camera_capability(
466 (media_caps & cricket::MediaEngine::VIDEO_SEND) != 0);
467 my_status_.set_is_google_client(true);
468 my_status_.set_version("1.0.0.67");
469 presence_out_->Send(my_status_);
470 }
471
OnStatusUpdate(const buzz::Status & status)472 void CallClient::OnStatusUpdate(const buzz::Status& status) {
473 RosterItem item;
474 item.jid = status.jid();
475 item.show = status.show();
476 item.status = status.status();
477
478 std::string key = item.jid.Str();
479
480 if (status.available() && status.phone_capability()) {
481 console_->Printf("Adding to roster: %s", key.c_str());
482 (*roster_)[key] = item;
483 } else {
484 console_->Printf("Removing from roster: %s", key.c_str());
485 RosterMap::iterator iter = roster_->find(key);
486 if (iter != roster_->end())
487 roster_->erase(iter);
488 }
489 }
490
PrintRoster()491 void CallClient::PrintRoster() {
492 console_->SetPrompting(false);
493 console_->Printf("Roster contains %d callable", roster_->size());
494 RosterMap::iterator iter = roster_->begin();
495 while (iter != roster_->end()) {
496 console_->Printf("%s - %s",
497 iter->second.jid.BareJid().Str().c_str(),
498 DescribeStatus(iter->second.show, iter->second.status));
499 iter++;
500 }
501 console_->SetPrompting(true);
502 }
503
SendChat(const std::string & to,const std::string msg)504 void CallClient::SendChat(const std::string& to, const std::string msg) {
505 buzz::XmlElement* stanza = new buzz::XmlElement(buzz::QN_MESSAGE);
506 stanza->AddAttr(buzz::QN_TO, to);
507 stanza->AddAttr(buzz::QN_ID, talk_base::CreateRandomString(16));
508 stanza->AddAttr(buzz::QN_TYPE, "chat");
509 buzz::XmlElement* body = new buzz::XmlElement(buzz::QN_BODY);
510 body->SetBodyText(msg);
511 stanza->AddElement(body);
512
513 xmpp_client_->SendStanza(stanza);
514 delete stanza;
515 }
516
InviteFriend(const std::string & name)517 void CallClient::InviteFriend(const std::string& name) {
518 buzz::Jid jid(name);
519 if (!jid.IsValid() || jid.node() == "") {
520 console_->Printf("Invalid JID. JIDs should be in the form user@domain\n");
521 return;
522 }
523 // Note: for some reason the Buzz backend does not forward our presence
524 // subscription requests to the end user when that user is another call
525 // client as opposed to a Smurf user. Thus, in that scenario, you must
526 // run the friend command as the other user too to create the linkage
527 // (and you won't be notified to do so).
528 friend_invite_send_->Send(jid);
529 console_->Printf("Requesting to befriend %s.\n", name.c_str());
530 }
531
MakeCallTo(const std::string & name,const cricket::CallOptions & given_options)532 void CallClient::MakeCallTo(const std::string& name,
533 const cricket::CallOptions& given_options) {
534 // Copy so we can change .is_muc.
535 cricket::CallOptions options = given_options;
536
537 bool found = false;
538 options.is_muc = false;
539 buzz::Jid callto_jid(name);
540 buzz::Jid found_jid;
541 if (name.length() == 0 && mucs_.size() > 0) {
542 // if no name, and in a MUC, establish audio with the MUC
543 found_jid = mucs_.begin()->first;
544 found = true;
545 options.is_muc = true;
546 } else if (name[0] == '+') {
547 // if the first character is a +, assume it's a phone number
548 found_jid = callto_jid;
549 found = true;
550 } else if (callto_jid.resource() == "voicemail") {
551 // if the resource is /voicemail, allow that
552 found_jid = callto_jid;
553 found = true;
554 } else {
555 // otherwise, it's a friend
556 for (RosterMap::iterator iter = roster_->begin();
557 iter != roster_->end(); ++iter) {
558 if (iter->second.jid.BareEquals(callto_jid)) {
559 found = true;
560 found_jid = iter->second.jid;
561 break;
562 }
563 }
564
565 if (!found) {
566 if (mucs_.count(callto_jid) == 1 &&
567 mucs_[callto_jid]->state() == buzz::Muc::MUC_JOINED) {
568 found = true;
569 found_jid = callto_jid;
570 options.is_muc = true;
571 }
572 }
573 }
574
575 if (found) {
576 console_->Printf("Found %s '%s'", options.is_muc ? "room" : "online friend",
577 found_jid.Str().c_str());
578 PlaceCall(found_jid, options);
579 } else {
580 console_->Printf("Could not find online friend '%s'", name.c_str());
581 }
582 }
583
PlaceCall(const buzz::Jid & jid,const cricket::CallOptions & options)584 void CallClient::PlaceCall(const buzz::Jid& jid,
585 const cricket::CallOptions& options) {
586 media_client_->SignalCallDestroy.connect(
587 this, &CallClient::OnCallDestroy);
588 if (!call_) {
589 call_ = media_client_->CreateCall();
590 console_->SetPrompt(jid.Str().c_str());
591 session_ = call_->InitiateSession(jid, options);
592 if (options.is_muc) {
593 // If people in this room are already in a call, must add all their
594 // streams.
595 buzz::Muc::MemberMap& members = mucs_[jid]->members();
596 for (buzz::Muc::MemberMap::iterator elem = members.begin();
597 elem != members.end();
598 ++elem) {
599 AddStream(elem->second.audio_src_id(), elem->second.video_src_id());
600 }
601 }
602 }
603 media_client_->SetFocus(call_);
604 if (call_->video()) {
605 call_->SetLocalRenderer(local_renderer_);
606 // TODO: Call this once for every different remote SSRC
607 // once we get to testing multiway video.
608 call_->SetVideoRenderer(session_, 0, remote_renderer_);
609 }
610 }
611
CallVoicemail(const std::string & name)612 void CallClient::CallVoicemail(const std::string& name) {
613 buzz::Jid jid(name);
614 if (!jid.IsValid() || jid.node() == "") {
615 console_->Printf("Invalid JID. JIDs should be in the form user@domain\n");
616 return;
617 }
618 buzz::VoicemailJidRequester *request =
619 new buzz::VoicemailJidRequester(xmpp_client_, jid, my_status_.jid());
620 request->SignalGotVoicemailJid.connect(this,
621 &CallClient::OnFoundVoicemailJid);
622 request->SignalVoicemailJidError.connect(this,
623 &CallClient::OnVoicemailJidError);
624 request->Start();
625 }
626
OnFoundVoicemailJid(const buzz::Jid & to,const buzz::Jid & voicemail)627 void CallClient::OnFoundVoicemailJid(const buzz::Jid& to,
628 const buzz::Jid& voicemail) {
629 console_->Printf("Calling %s's voicemail.\n", to.Str().c_str());
630 PlaceCall(voicemail, cricket::CallOptions());
631 }
632
OnVoicemailJidError(const buzz::Jid & to)633 void CallClient::OnVoicemailJidError(const buzz::Jid& to) {
634 console_->Printf("Unable to voicemail %s.\n", to.Str().c_str());
635 }
636
AddStream(uint32 audio_src_id,uint32 video_src_id)637 void CallClient::AddStream(uint32 audio_src_id, uint32 video_src_id) {
638 if (audio_src_id || video_src_id) {
639 console_->Printf("Adding stream (%u, %u)\n", audio_src_id, video_src_id);
640 call_->AddStream(session_, audio_src_id, video_src_id);
641 }
642 }
643
RemoveStream(uint32 audio_src_id,uint32 video_src_id)644 void CallClient::RemoveStream(uint32 audio_src_id, uint32 video_src_id) {
645 if (audio_src_id || video_src_id) {
646 console_->Printf("Removing stream (%u, %u)\n", audio_src_id, video_src_id);
647 call_->RemoveStream(session_, audio_src_id, video_src_id);
648 }
649 }
650
Accept(const cricket::CallOptions & options)651 void CallClient::Accept(const cricket::CallOptions& options) {
652 ASSERT(call_ && incoming_call_);
653 ASSERT(call_->sessions().size() == 1);
654 call_->AcceptSession(call_->sessions()[0], options);
655 media_client_->SetFocus(call_);
656 if (call_->video()) {
657 call_->SetLocalRenderer(local_renderer_);
658 // The client never does an accept for multiway, so this must be 1:1,
659 // so there's no SSRC.
660 call_->SetVideoRenderer(session_, 0, remote_renderer_);
661 }
662 incoming_call_ = false;
663 }
664
Reject()665 void CallClient::Reject() {
666 ASSERT(call_ && incoming_call_);
667 call_->RejectSession(call_->sessions()[0]);
668 incoming_call_ = false;
669 }
670
Quit()671 void CallClient::Quit() {
672 talk_base::Thread::Current()->Quit();
673 }
674
JoinMuc(const std::string & room)675 void CallClient::JoinMuc(const std::string& room) {
676 buzz::Jid room_jid;
677 if (room.length() > 0) {
678 room_jid = buzz::Jid(room);
679 } else {
680 // generate a GUID of the form XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX,
681 // for an eventual JID of private-chat-<GUID>@groupchat.google.com
682 char guid[37], guid_room[256];
683 for (size_t i = 0; i < ARRAY_SIZE(guid) - 1;) {
684 if (i == 8 || i == 13 || i == 18 || i == 23) {
685 guid[i++] = '-';
686 } else {
687 sprintf(guid + i, "%04x", rand());
688 i += 4;
689 }
690 }
691
692 talk_base::sprintfn(guid_room, ARRAY_SIZE(guid_room),
693 "private-chat-%s@%s", guid, pmuc_domain_.c_str());
694 room_jid = buzz::Jid(guid_room);
695 }
696
697 if (!room_jid.IsValid()) {
698 console_->Printf("Unable to make valid muc endpoint for %s", room.c_str());
699 return;
700 }
701
702 MucMap::iterator elem = mucs_.find(room_jid);
703 if (elem != mucs_.end()) {
704 console_->Printf("This MUC already exists.");
705 return;
706 }
707
708 buzz::Muc* muc = new buzz::Muc(room_jid, xmpp_client_->jid().node());
709 mucs_[room_jid] = muc;
710 presence_out_->SendDirected(muc->local_jid(), my_status_);
711 }
712
OnMucInviteReceived(const buzz::Jid & inviter,const buzz::Jid & room,const std::vector<buzz::AvailableMediaEntry> & avail)713 void CallClient::OnMucInviteReceived(const buzz::Jid& inviter,
714 const buzz::Jid& room,
715 const std::vector<buzz::AvailableMediaEntry>& avail) {
716
717 console_->Printf("Invited to join %s by %s.\n", room.Str().c_str(),
718 inviter.Str().c_str());
719 console_->Printf("Available media:\n");
720 if (avail.size() > 0) {
721 for (std::vector<buzz::AvailableMediaEntry>::const_iterator i =
722 avail.begin();
723 i != avail.end();
724 ++i) {
725 console_->Printf(" %s, %s\n",
726 buzz::AvailableMediaEntry::TypeAsString(i->type),
727 buzz::AvailableMediaEntry::StatusAsString(i->status));
728 }
729 } else {
730 console_->Printf(" None\n");
731 }
732 // We automatically join the room.
733 JoinMuc(room.Str());
734 }
735
OnMucJoined(const buzz::Jid & endpoint)736 void CallClient::OnMucJoined(const buzz::Jid& endpoint) {
737 MucMap::iterator elem = mucs_.find(endpoint);
738 ASSERT(elem != mucs_.end() &&
739 elem->second->state() == buzz::Muc::MUC_JOINING);
740
741 buzz::Muc* muc = elem->second;
742 muc->set_state(buzz::Muc::MUC_JOINED);
743 console_->Printf("Joined \"%s\"", muc->jid().Str().c_str());
744 }
745
OnMucStatusUpdate(const buzz::Jid & jid,const buzz::MucStatus & status)746 void CallClient::OnMucStatusUpdate(const buzz::Jid& jid,
747 const buzz::MucStatus& status) {
748
749 // Look up this muc.
750 MucMap::iterator elem = mucs_.find(jid);
751 ASSERT(elem != mucs_.end() &&
752 elem->second->state() == buzz::Muc::MUC_JOINED);
753
754 buzz::Muc* muc = elem->second;
755
756 if (status.jid().IsBare() || status.jid() == muc->local_jid()) {
757 // We are only interested in status about other users.
758 return;
759 }
760
761 if (!status.available()) {
762 // User is leaving the room.
763 buzz::Muc::MemberMap::iterator elem =
764 muc->members().find(status.jid().resource());
765
766 ASSERT(elem != muc->members().end());
767
768 // If user had src-ids, they have the left the room without explicitly
769 // hanging-up; must tear down the stream if in a call to this room.
770 if (call_ && session_->remote_name() == muc->jid().Str()) {
771 RemoveStream(elem->second.audio_src_id(), elem->second.video_src_id());
772 }
773
774 // Remove them from the room.
775 muc->members().erase(elem);
776 } else {
777 // Either user has joined or something changed about them.
778 // Note: The [] operator here will create a new entry if it does not
779 // exist, which is what we want.
780 buzz::MucStatus& member_status(
781 muc->members()[status.jid().resource()]);
782 if (call_ && session_->remote_name() == muc->jid().Str()) {
783 // We are in a call to this muc. Must potentially update our streams.
784 // The following code will correctly update our streams regardless of
785 // whether the SSRCs have been removed, added, or changed and regardless
786 // of whether that has been done to both or just one. This relies on the
787 // fact that AddStream/RemoveStream do nothing for SSRC arguments that are
788 // zero.
789 uint32 remove_audio_src_id = 0;
790 uint32 remove_video_src_id = 0;
791 uint32 add_audio_src_id = 0;
792 uint32 add_video_src_id = 0;
793 if (member_status.audio_src_id() != status.audio_src_id()) {
794 remove_audio_src_id = member_status.audio_src_id();
795 add_audio_src_id = status.audio_src_id();
796 }
797 if (member_status.video_src_id() != status.video_src_id()) {
798 remove_video_src_id = member_status.video_src_id();
799 add_video_src_id = status.video_src_id();
800 }
801 // Remove the old SSRCs, if any.
802 RemoveStream(remove_audio_src_id, remove_video_src_id);
803 // Add the new SSRCs, if any.
804 AddStream(add_audio_src_id, add_video_src_id);
805 }
806 // Update the status. This will use the compiler-generated copy
807 // constructor, which is perfectly adequate for this class.
808 member_status = status;
809 }
810 }
811
LeaveMuc(const std::string & room)812 void CallClient::LeaveMuc(const std::string& room) {
813 buzz::Jid room_jid;
814 if (room.length() > 0) {
815 room_jid = buzz::Jid(room);
816 } else if (mucs_.size() > 0) {
817 // leave the first MUC if no JID specified
818 room_jid = mucs_.begin()->first;
819 }
820
821 if (!room_jid.IsValid()) {
822 console_->Printf("Invalid MUC JID.");
823 return;
824 }
825
826 MucMap::iterator elem = mucs_.find(room_jid);
827 if (elem == mucs_.end()) {
828 console_->Printf("No such MUC.");
829 return;
830 }
831
832 buzz::Muc* muc = elem->second;
833 muc->set_state(buzz::Muc::MUC_LEAVING);
834
835 buzz::Status status;
836 status.set_jid(my_status_.jid());
837 status.set_available(false);
838 status.set_priority(0);
839 presence_out_->SendDirected(muc->local_jid(), status);
840 }
841
OnMucLeft(const buzz::Jid & endpoint,int error)842 void CallClient::OnMucLeft(const buzz::Jid& endpoint, int error) {
843 // We could be kicked from a room from any state. We would hope this
844 // happens While in the MUC_LEAVING state
845 MucMap::iterator elem = mucs_.find(endpoint);
846 if (elem == mucs_.end())
847 return;
848
849 buzz::Muc* muc = elem->second;
850 if (muc->state() == buzz::Muc::MUC_JOINING) {
851 console_->Printf("Failed to join \"%s\", code=%d",
852 muc->jid().Str().c_str(), error);
853 } else if (muc->state() == buzz::Muc::MUC_JOINED) {
854 console_->Printf("Kicked from \"%s\"",
855 muc->jid().Str().c_str());
856 }
857
858 delete muc;
859 mucs_.erase(elem);
860 }
861
InviteToMuc(const std::string & user,const std::string & room)862 void CallClient::InviteToMuc(const std::string& user, const std::string& room) {
863 // First find the room.
864 const buzz::Muc* found_muc;
865 if (room.length() == 0) {
866 if (mucs_.size() == 0) {
867 console_->Printf("Not in a room yet; can't invite.\n");
868 return;
869 }
870 // Invite to the first muc
871 found_muc = mucs_.begin()->second;
872 } else {
873 MucMap::iterator elem = mucs_.find(buzz::Jid(room));
874 if (elem == mucs_.end()) {
875 console_->Printf("Not in room %s.\n", room.c_str());
876 return;
877 }
878 found_muc = elem->second;
879 }
880 // Now find the user. We invite all of their resources.
881 bool found_user = false;
882 buzz::Jid user_jid(user);
883 for (RosterMap::iterator iter = roster_->begin();
884 iter != roster_->end(); ++iter) {
885 if (iter->second.jid.BareEquals(user_jid)) {
886 muc_invite_send_->Send(iter->second.jid, *found_muc);
887 found_user = true;
888 }
889 }
890 if (!found_user) {
891 console_->Printf("No such friend as %s.\n", user.c_str());
892 return;
893 }
894 }
895
GetDevices()896 void CallClient::GetDevices() {
897 std::vector<std::string> names;
898 media_client_->GetAudioInputDevices(&names);
899 printf("Audio input devices:\n");
900 PrintDevices(names);
901 media_client_->GetAudioOutputDevices(&names);
902 printf("Audio output devices:\n");
903 PrintDevices(names);
904 media_client_->GetVideoCaptureDevices(&names);
905 printf("Video capture devices:\n");
906 PrintDevices(names);
907 }
908
PrintDevices(const std::vector<std::string> & names)909 void CallClient::PrintDevices(const std::vector<std::string>& names) {
910 for (size_t i = 0; i < names.size(); ++i) {
911 printf("%d: %s\n", static_cast<int>(i), names[i].c_str());
912 }
913 }
914
OnDevicesChange()915 void CallClient::OnDevicesChange() {
916 printf("Devices changed.\n");
917 RefreshStatus();
918 }
919
SetVolume(const std::string & level)920 void CallClient::SetVolume(const std::string& level) {
921 media_client_->SetOutputVolume(strtol(level.c_str(), NULL, 10));
922 }
923