Home | History | Annotate | Download | only in call
      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:
     59   explicit NullRenderer(const char* s) : s_(s) {}
     60  private:
     61   bool SetSize(int width, int height, int reserved) {
     62     LOG(LS_INFO) << "Video size for " << s_ << ": " << width << "x" << height;
     63     return true;
     64   }
     65   bool RenderFrame(const cricket::VideoFrame *frame) {
     66     return true;
     67   }
     68   const char* s_;
     69 };
     70 
     71 namespace {
     72 
     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 
     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 
     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 
    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 
    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 
    255 CallClient::~CallClient() {
    256   delete media_client_;
    257   delete roster_;
    258 }
    259 
    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 
    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 
    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 
    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 
    384 void CallClient::OnRequestSignaling() {
    385   session_manager_->OnSignalingReady();
    386 }
    387 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    633 void CallClient::OnVoicemailJidError(const buzz::Jid& to) {
    634   console_->Printf("Unable to voicemail %s.\n", to.Str().c_str());
    635 }
    636 
    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 
    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 
    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 
    665 void CallClient::Reject() {
    666   ASSERT(call_ && incoming_call_);
    667   call_->RejectSession(call_->sessions()[0]);
    668   incoming_call_ = false;
    669 }
    670 
    671 void CallClient::Quit() {
    672   talk_base::Thread::Current()->Quit();
    673 }
    674 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    915 void CallClient::OnDevicesChange() {
    916   printf("Devices changed.\n");
    917   RefreshStatus();
    918 }
    919 
    920 void CallClient::SetVolume(const std::string& level) {
    921   media_client_->SetOutputVolume(strtol(level.c_str(), NULL, 10));
    922 }
    923