• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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