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/examples/call/console.h"
     33 #include "talk/examples/call/friendinvitesendtask.h"
     34 #include "talk/examples/call/muc.h"
     35 #include "talk/examples/call/mucinviterecvtask.h"
     36 #include "talk/examples/call/mucinvitesendtask.h"
     37 #include "talk/examples/call/presencepushtask.h"
     38 #include "talk/media/base/mediacommon.h"
     39 #include "talk/media/base/mediaengine.h"
     40 #include "talk/media/base/rtpdataengine.h"
     41 #include "talk/media/base/screencastid.h"
     42 #include "webrtc/base/helpers.h"
     43 #include "webrtc/base/logging.h"
     44 #include "webrtc/base/network.h"
     45 #include "webrtc/base/socketaddress.h"
     46 #include "webrtc/base/stringencode.h"
     47 #include "webrtc/base/stringutils.h"
     48 #include "webrtc/base/thread.h"
     49 #include "webrtc/base/windowpickerfactory.h"
     50 #ifdef HAVE_SCTP
     51 #include "talk/media/sctp/sctpdataengine.h"
     52 #endif
     53 #include "talk/media/base/videorenderer.h"
     54 #include "talk/media/devices/devicemanager.h"
     55 #include "talk/media/devices/videorendererfactory.h"
     56 #include "talk/p2p/base/sessionmanager.h"
     57 #include "talk/p2p/client/basicportallocator.h"
     58 #include "talk/p2p/client/sessionmanagertask.h"
     59 #include "talk/session/media/mediamessages.h"
     60 #include "talk/session/media/mediasessionclient.h"
     61 #include "talk/xmpp/constants.h"
     62 #include "talk/xmpp/hangoutpubsubclient.h"
     63 #include "talk/xmpp/mucroomconfigtask.h"
     64 #include "talk/xmpp/mucroomlookuptask.h"
     65 #include "talk/xmpp/pingtask.h"
     66 #include "talk/xmpp/presenceouttask.h"
     67 
     68 namespace {
     69 
     70 // Must be period >= timeout.
     71 const uint32 kPingPeriodMillis = 10000;
     72 const uint32 kPingTimeoutMillis = 10000;
     73 
     74 const char* DescribeStatus(buzz::PresenceStatus::Show show,
     75                            const std::string& desc) {
     76   switch (show) {
     77   case buzz::PresenceStatus::SHOW_XA:      return desc.c_str();
     78   case buzz::PresenceStatus::SHOW_ONLINE:  return "online";
     79   case buzz::PresenceStatus::SHOW_AWAY:    return "away";
     80   case buzz::PresenceStatus::SHOW_DND:     return "do not disturb";
     81   case buzz::PresenceStatus::SHOW_CHAT:    return "ready to chat";
     82   default:                                 return "offline";
     83   }
     84 }
     85 
     86 std::string GetWord(const std::vector<std::string>& words,
     87                     size_t index, const std::string& def) {
     88   if (words.size() > index) {
     89     return words[index];
     90   } else {
     91     return def;
     92   }
     93 }
     94 
     95 int GetInt(const std::vector<std::string>& words, size_t index, int def) {
     96   int val;
     97   if (words.size() > index && rtc::FromString(words[index], &val)) {
     98     return val;
     99   } else {
    100     return def;
    101   }
    102 }
    103 
    104 }  // namespace
    105 
    106 const char* CALL_COMMANDS =
    107 "Available commands:\n"
    108 "\n"
    109 "  hangup            Ends the call.\n"
    110 "  hold              Puts the current call on hold\n"
    111 "  calls             Lists the current calls and their sessions\n"
    112 "  switch [call_id]  Switch to the specified call\n"
    113 "  addsession [jid]  Add a new session to the current call.\n"
    114 "  rmsession [sid]   Remove specified session.\n"
    115 "  mute              Stops sending voice.\n"
    116 "  unmute            Re-starts sending voice.\n"
    117 "  vmute             Stops sending video.\n"
    118 "  vunmute           Re-starts sending video.\n"
    119 "  dtmf              Sends a DTMF tone.\n"
    120 "  stats             Print voice stats for the current call.\n"
    121 "  quit              Quits the application.\n"
    122 "";
    123 
    124 // TODO: Make present and record really work.
    125 const char* HANGOUT_COMMANDS =
    126 "Available MUC commands:\n"
    127 "\n"
    128 "  present    Starts presenting (just signalling; not actually presenting.)\n"
    129 "  unpresent  Stops presenting (just signalling; not actually presenting.)\n"
    130 "  record     Starts recording (just signalling; not actually recording.)\n"
    131 "  unrecord   Stops recording (just signalling; not actually recording.)\n"
    132 "  rmute [nick] Remote mute another participant.\n"
    133 "  block [nick] Block another participant.\n"
    134 "  screencast [fps] Starts screencast. \n"
    135 "  unscreencast Stops screencast. \n"
    136 "  quit       Quits the application.\n"
    137 "";
    138 
    139 const char* RECEIVE_COMMANDS =
    140 "Available commands:\n"
    141 "\n"
    142 "  accept [bw] Accepts the incoming call and switches to it.\n"
    143 "  reject  Rejects the incoming call and stays with the current call.\n"
    144 "  quit    Quits the application.\n"
    145 "";
    146 
    147 const char* CONSOLE_COMMANDS =
    148 "Available commands:\n"
    149 "\n"
    150 "  roster              Prints the online friends from your roster.\n"
    151 "  friend user         Request to add a user to your roster.\n"
    152 "  call [jid] [bw]     Initiates a call to the user[/room] with the\n"
    153 "                      given JID and with optional bandwidth.\n"
    154 "  vcall [jid] [bw]    Initiates a video call to the user[/room] with\n"
    155 "                      the given JID and with optional bandwidth.\n"
    156 "  calls               Lists the current calls\n"
    157 "  switch [call_id]    Switch to the specified call\n"
    158 "  join [room_jid]     Joins a multi-user-chat with room JID.\n"
    159 "  ljoin [room_name]   Joins a MUC by looking up JID from room name.\n"
    160 "  invite user [room]  Invites a friend to a multi-user-chat.\n"
    161 "  leave [room]        Leaves a multi-user-chat.\n"
    162 "  nick [nick]         Sets the nick.\n"
    163 "  priority [int]      Sets the priority.\n"
    164 "  getdevs             Prints the available media devices.\n"
    165 "  quit                Quits the application.\n"
    166 "";
    167 
    168 void CallClient::ParseLine(const std::string& line) {
    169   std::vector<std::string> words;
    170   int start = -1;
    171   int state = 0;
    172   for (int index = 0; index <= static_cast<int>(line.size()); ++index) {
    173     if (state == 0) {
    174       if (!isspace(line[index])) {
    175         start = index;
    176         state = 1;
    177       }
    178     } else {
    179       ASSERT(state == 1);
    180       ASSERT(start >= 0);
    181       if (isspace(line[index])) {
    182         std::string word(line, start, index - start);
    183         words.push_back(word);
    184         start = -1;
    185         state = 0;
    186       }
    187     }
    188   }
    189 
    190   // Global commands
    191   const std::string& command = GetWord(words, 0, "");
    192   if (command == "quit") {
    193     Quit();
    194   } else if (call_ && incoming_call_) {
    195     if (command == "accept") {
    196       cricket::CallOptions options;
    197       options.video_bandwidth = GetInt(words, 1, cricket::kAutoBandwidth);
    198       options.has_video = true;
    199       options.data_channel_type = data_channel_type_;
    200       Accept(options);
    201     } else if (command == "reject") {
    202       Reject();
    203     } else {
    204       console_->PrintLine(RECEIVE_COMMANDS);
    205     }
    206   } else if (call_) {
    207     if (command == "hangup") {
    208       call_->Terminate();
    209     } else if (command == "hold") {
    210       media_client_->SetFocus(NULL);
    211       call_ = NULL;
    212     } else if (command == "addsession") {
    213       std::string to = GetWord(words, 1, "");
    214       cricket::CallOptions options;
    215       options.has_video = call_->has_video();
    216       options.video_bandwidth = cricket::kAutoBandwidth;
    217       options.data_channel_type = data_channel_type_;
    218       options.AddStream(cricket::MEDIA_TYPE_VIDEO, "", "");
    219       if (!InitiateAdditionalSession(to, options)) {
    220         console_->PrintLine("Failed to initiate additional session.");
    221       }
    222     } else if (command == "rmsession") {
    223       std::string id = GetWord(words, 1, "");
    224       TerminateAndRemoveSession(call_, id);
    225     } else if (command == "calls") {
    226       PrintCalls();
    227     } else if ((words.size() == 2) && (command == "switch")) {
    228       SwitchToCall(GetInt(words, 1, -1));
    229     } else if (command == "mute") {
    230       call_->Mute(true);
    231       if (InMuc()) {
    232         hangout_pubsub_client_->PublishAudioMuteState(true);
    233       }
    234     } else if (command == "unmute") {
    235       call_->Mute(false);
    236       if (InMuc()) {
    237         hangout_pubsub_client_->PublishAudioMuteState(false);
    238       }
    239     } else if (command == "vmute") {
    240       call_->MuteVideo(true);
    241       if (InMuc()) {
    242         hangout_pubsub_client_->PublishVideoMuteState(true);
    243       }
    244     } else if (command == "vunmute") {
    245       call_->MuteVideo(false);
    246       if (InMuc()) {
    247         hangout_pubsub_client_->PublishVideoMuteState(false);
    248       }
    249     } else if (command == "screencast") {
    250       if (screencast_ssrc_ != 0) {
    251         console_->PrintLine("Can't screencast twice.  Unscreencast first.");
    252       } else {
    253         std::string streamid = "screencast";
    254         screencast_ssrc_ = rtc::CreateRandomId();
    255         int fps = GetInt(words, 1, 5);  // Default to 5 fps.
    256 
    257         cricket::ScreencastId screencastid;
    258         cricket::Session* session = GetFirstSession();
    259         if (session && SelectFirstDesktopScreencastId(&screencastid)) {
    260           call_->StartScreencast(
    261               session, streamid, screencast_ssrc_, screencastid, fps);
    262         }
    263       }
    264     } else if (command == "unscreencast") {
    265       // TODO: Use a random ssrc
    266       std::string streamid = "screencast";
    267 
    268       cricket::Session* session = GetFirstSession();
    269       if (session) {
    270         call_->StopScreencast(session, streamid, screencast_ssrc_);
    271         screencast_ssrc_ = 0;
    272       }
    273     } else if (command == "present") {
    274       if (InMuc()) {
    275         hangout_pubsub_client_->PublishPresenterState(true);
    276       }
    277     } else if (command == "unpresent") {
    278       if (InMuc()) {
    279         hangout_pubsub_client_->PublishPresenterState(false);
    280       }
    281     } else if (command == "record") {
    282       if (InMuc()) {
    283         hangout_pubsub_client_->PublishRecordingState(true);
    284       }
    285     } else if (command == "unrecord") {
    286       if (InMuc()) {
    287         hangout_pubsub_client_->PublishRecordingState(false);
    288       }
    289     } else if ((command == "rmute") && (words.size() == 2)) {
    290       if (InMuc()) {
    291         const std::string& nick = words[1];
    292         hangout_pubsub_client_->RemoteMute(nick);
    293       }
    294     } else if ((command == "block") && (words.size() == 2)) {
    295       if (InMuc()) {
    296         const std::string& nick = words[1];
    297         hangout_pubsub_client_->BlockMedia(nick);
    298       }
    299     } else if (command == "senddata") {
    300       // "" is the default streamid.
    301       SendData("", words[1]);
    302     } else if ((command == "dtmf") && (words.size() == 2)) {
    303       int ev = std::string("0123456789*#").find(words[1][0]);
    304       call_->PressDTMF(ev);
    305     } else if (command == "stats") {
    306       PrintStats();
    307     } else {
    308       console_->PrintLine(CALL_COMMANDS);
    309       if (InMuc()) {
    310         console_->PrintLine(HANGOUT_COMMANDS);
    311       }
    312     }
    313   } else {
    314     if (command == "roster") {
    315       PrintRoster();
    316     } else if (command == "send") {
    317       buzz::Jid jid(words[1]);
    318       if (jid.IsValid()) {
    319         last_sent_to_ = words[1];
    320         SendChat(words[1], words[2]);
    321       } else if (!last_sent_to_.empty()) {
    322         SendChat(last_sent_to_, words[1]);
    323       } else {
    324         console_->PrintLine(
    325             "Invalid JID. JIDs should be in the form user@domain");
    326       }
    327     } else if ((words.size() == 2) && (command == "friend")) {
    328       InviteFriend(words[1]);
    329     } else if (command == "call") {
    330       std::string to = GetWord(words, 1, "");
    331       cricket::CallOptions options;
    332       options.data_channel_type = data_channel_type_;
    333       if (!PlaceCall(to, options)) {
    334         console_->PrintLine("Failed to initiate call.");
    335       }
    336     } else if (command == "vcall") {
    337       std::string to = GetWord(words, 1, "");
    338       int bandwidth = GetInt(words, 2, cricket::kAutoBandwidth);
    339       cricket::CallOptions options;
    340       options.has_video = true;
    341       options.video_bandwidth = bandwidth;
    342       options.data_channel_type = data_channel_type_;
    343       if (!PlaceCall(to, options)) {
    344         console_->PrintLine("Failed to initiate call.");
    345       }
    346     } else if (command == "calls") {
    347       PrintCalls();
    348     } else if ((words.size() == 2) && (command == "switch")) {
    349       SwitchToCall(GetInt(words, 1, -1));
    350     } else if (command == "join") {
    351       JoinMuc(GetWord(words, 1, ""));
    352     } else if (command == "ljoin") {
    353       LookupAndJoinMuc(GetWord(words, 1, ""));
    354     } else if ((words.size() >= 2) && (command == "invite")) {
    355       InviteToMuc(words[1], GetWord(words, 2, ""));
    356     } else if (command == "leave") {
    357       LeaveMuc(GetWord(words, 1, ""));
    358     } else if (command == "nick") {
    359       SetNick(GetWord(words, 1, ""));
    360     } else if (command == "priority") {
    361       int priority = GetInt(words, 1, 0);
    362       SetPriority(priority);
    363       SendStatus();
    364     } else if (command == "getdevs") {
    365       GetDevices();
    366     } else if ((words.size() == 2) && (command == "setvol")) {
    367       SetVolume(words[1]);
    368     } else {
    369       console_->PrintLine(CONSOLE_COMMANDS);
    370     }
    371   }
    372 }
    373 
    374 CallClient::CallClient(buzz::XmppClient* xmpp_client,
    375                        const std::string& caps_node, const std::string& version)
    376     : xmpp_client_(xmpp_client),
    377       worker_thread_(NULL),
    378       media_engine_(NULL),
    379       data_engine_(NULL),
    380       media_client_(NULL),
    381       call_(NULL),
    382       hangout_pubsub_client_(NULL),
    383       incoming_call_(false),
    384       auto_accept_(false),
    385       pmuc_domain_("groupchat.google.com"),
    386       render_(true),
    387       data_channel_type_(cricket::DCT_NONE),
    388       multisession_enabled_(false),
    389       local_renderer_(NULL),
    390       static_views_accumulated_count_(0),
    391       screencast_ssrc_(0),
    392       roster_(new RosterMap),
    393       portallocator_flags_(0),
    394       allow_local_ips_(false),
    395       signaling_protocol_(cricket::PROTOCOL_HYBRID),
    396       transport_protocol_(cricket::ICEPROTO_HYBRID),
    397       sdes_policy_(cricket::SEC_DISABLED),
    398       dtls_policy_(cricket::SEC_DISABLED),
    399       ssl_identity_(),
    400       show_roster_messages_(false) {
    401   xmpp_client_->SignalStateChange.connect(this, &CallClient::OnStateChange);
    402   my_status_.set_caps_node(caps_node);
    403   my_status_.set_version(version);
    404 }
    405 
    406 CallClient::~CallClient() {
    407   delete media_client_;
    408   delete roster_;
    409   delete worker_thread_;
    410 }
    411 
    412 const std::string CallClient::strerror(buzz::XmppEngine::Error err) {
    413   switch (err) {
    414     case buzz::XmppEngine::ERROR_NONE:
    415       return "";
    416     case buzz::XmppEngine::ERROR_XML:
    417       return "Malformed XML or encoding error";
    418     case buzz::XmppEngine::ERROR_STREAM:
    419       return "XMPP stream error";
    420     case buzz::XmppEngine::ERROR_VERSION:
    421       return "XMPP version error";
    422     case buzz::XmppEngine::ERROR_UNAUTHORIZED:
    423       return "User is not authorized (Check your username and password)";
    424     case buzz::XmppEngine::ERROR_TLS:
    425       return "TLS could not be negotiated";
    426     case buzz::XmppEngine::ERROR_AUTH:
    427       return "Authentication could not be negotiated";
    428     case buzz::XmppEngine::ERROR_BIND:
    429       return "Resource or session binding could not be negotiated";
    430     case buzz::XmppEngine::ERROR_CONNECTION_CLOSED:
    431       return "Connection closed by output handler.";
    432     case buzz::XmppEngine::ERROR_DOCUMENT_CLOSED:
    433       return "Closed by </stream:stream>";
    434     case buzz::XmppEngine::ERROR_SOCKET:
    435       return "Socket error";
    436     default:
    437       return "Unknown error";
    438   }
    439 }
    440 
    441 void CallClient::OnCallDestroy(cricket::Call* call) {
    442   RemoveCallsStaticRenderedViews(call);
    443   if (call == call_) {
    444     if (local_renderer_) {
    445       delete local_renderer_;
    446       local_renderer_ = NULL;
    447     }
    448     console_->PrintLine("call destroyed");
    449     call_ = NULL;
    450     delete hangout_pubsub_client_;
    451     hangout_pubsub_client_ = NULL;
    452   }
    453 }
    454 
    455 void CallClient::OnStateChange(buzz::XmppEngine::State state) {
    456   switch (state) {
    457     case buzz::XmppEngine::STATE_START:
    458       console_->PrintLine("connecting...");
    459       break;
    460     case buzz::XmppEngine::STATE_OPENING:
    461       console_->PrintLine("logging in...");
    462       break;
    463     case buzz::XmppEngine::STATE_OPEN:
    464       console_->PrintLine("logged in...");
    465       InitMedia();
    466       InitPresence();
    467       break;
    468     case buzz::XmppEngine::STATE_CLOSED:
    469       {
    470         buzz::XmppEngine::Error error = xmpp_client_->GetError(NULL);
    471         console_->PrintLine("logged out... %s", strerror(error).c_str());
    472         Quit();
    473       }
    474       break;
    475     default:
    476       break;
    477   }
    478 }
    479 
    480 void CallClient::InitMedia() {
    481   worker_thread_ = new rtc::Thread();
    482   // The worker thread must be started here since initialization of
    483   // the ChannelManager will generate messages that need to be
    484   // dispatched by it.
    485   worker_thread_->Start();
    486 
    487   // TODO: It looks like we are leaking many objects. E.g.
    488   // |network_manager_| is never deleted.
    489   network_manager_ = new rtc::BasicNetworkManager();
    490 
    491   // TODO: Decide if the relay address should be specified here.
    492   rtc::SocketAddress stun_addr("stun.l.google.com", 19302);
    493   cricket::ServerAddresses stun_servers;
    494   stun_servers.insert(stun_addr);
    495   port_allocator_ =  new cricket::BasicPortAllocator(
    496       network_manager_, stun_servers, rtc::SocketAddress(),
    497       rtc::SocketAddress(), rtc::SocketAddress());
    498 
    499   if (portallocator_flags_ != 0) {
    500     port_allocator_->set_flags(portallocator_flags_);
    501   }
    502   session_manager_ = new cricket::SessionManager(
    503       port_allocator_, worker_thread_);
    504   session_manager_->set_secure(dtls_policy_);
    505   session_manager_->set_identity(ssl_identity_.get());
    506   session_manager_->set_transport_protocol(transport_protocol_);
    507   session_manager_->SignalRequestSignaling.connect(
    508       this, &CallClient::OnRequestSignaling);
    509   session_manager_->SignalSessionCreate.connect(
    510       this, &CallClient::OnSessionCreate);
    511   session_manager_->OnSignalingReady();
    512 
    513   session_manager_task_ =
    514       new cricket::SessionManagerTask(xmpp_client_, session_manager_);
    515   session_manager_task_->EnableOutgoingMessages();
    516   session_manager_task_->Start();
    517 
    518   if (!media_engine_) {
    519     media_engine_ = cricket::MediaEngineFactory::Create();
    520   }
    521 
    522   if (!data_engine_) {
    523     if (data_channel_type_ == cricket::DCT_SCTP) {
    524 #ifdef HAVE_SCTP
    525       data_engine_ = new cricket::SctpDataEngine();
    526 #else
    527       LOG(LS_WARNING) << "SCTP Data Engine not supported.";
    528       data_channel_type_ = cricket::DCT_NONE;
    529       data_engine_ = new cricket::RtpDataEngine();
    530 #endif
    531     } else {
    532       // Even if we have DCT_NONE, we still have a data engine, just
    533       // to make sure it isn't NULL.
    534       data_engine_ = new cricket::RtpDataEngine();
    535     }
    536   }
    537 
    538   media_client_ = new cricket::MediaSessionClient(
    539       xmpp_client_->jid(),
    540       session_manager_,
    541       media_engine_,
    542       data_engine_,
    543       cricket::DeviceManagerFactory::Create());
    544   media_client_->SignalCallCreate.connect(this, &CallClient::OnCallCreate);
    545   media_client_->SignalCallDestroy.connect(this, &CallClient::OnCallDestroy);
    546   media_client_->SignalDevicesChange.connect(this,
    547                                              &CallClient::OnDevicesChange);
    548   media_client_->set_secure(sdes_policy_);
    549   media_client_->set_multisession_enabled(multisession_enabled_);
    550 }
    551 
    552 void CallClient::OnRequestSignaling() {
    553   session_manager_->OnSignalingReady();
    554 }
    555 
    556 void CallClient::OnSessionCreate(cricket::Session* session, bool initiate) {
    557   session->set_current_protocol(signaling_protocol_);
    558 }
    559 
    560 void CallClient::OnCallCreate(cricket::Call* call) {
    561   call->SignalSessionState.connect(this, &CallClient::OnSessionState);
    562   call->SignalMediaStreamsUpdate.connect(
    563       this, &CallClient::OnMediaStreamsUpdate);
    564 }
    565 
    566 void CallClient::OnSessionState(cricket::Call* call,
    567                                 cricket::Session* session,
    568                                 cricket::Session::State state) {
    569   if (state == cricket::Session::STATE_RECEIVEDINITIATE) {
    570     buzz::Jid jid(session->remote_name());
    571     if (call_ == call && multisession_enabled_) {
    572       // We've received an initiate for an existing call. This is actually a
    573       // new session for that call.
    574       console_->PrintLine("Incoming session from '%s'", jid.Str().c_str());
    575       AddSession(session);
    576 
    577       cricket::CallOptions options;
    578       options.has_video = call_->has_video();
    579       options.data_channel_type = data_channel_type_;
    580       call_->AcceptSession(session, options);
    581 
    582       if (call_->has_video() && render_) {
    583         RenderAllStreams(call, session, true);
    584       }
    585     } else {
    586       console_->PrintLine("Incoming call from '%s'", jid.Str().c_str());
    587       call_ = call;
    588       AddSession(session);
    589       incoming_call_ = true;
    590       if (call->has_video() && render_) {
    591         local_renderer_ =
    592             cricket::VideoRendererFactory::CreateGuiVideoRenderer(160, 100);
    593       }
    594       if (auto_accept_) {
    595         cricket::CallOptions options;
    596         options.has_video = true;
    597         options.data_channel_type = data_channel_type_;
    598         Accept(options);
    599       }
    600     }
    601   } else if (state == cricket::Session::STATE_SENTINITIATE) {
    602     if (call->has_video() && render_) {
    603       local_renderer_ =
    604           cricket::VideoRendererFactory::CreateGuiVideoRenderer(160, 100);
    605     }
    606     console_->PrintLine("calling...");
    607   } else if (state == cricket::Session::STATE_RECEIVEDACCEPT) {
    608     console_->PrintLine("call answered");
    609     SetupAcceptedCall();
    610   } else if (state == cricket::Session::STATE_RECEIVEDREJECT) {
    611     console_->PrintLine("call not answered");
    612   } else if (state == cricket::Session::STATE_INPROGRESS) {
    613     console_->PrintLine("call in progress");
    614     call->SignalSpeakerMonitor.connect(this, &CallClient::OnSpeakerChanged);
    615     call->StartSpeakerMonitor(session);
    616   } else if (state == cricket::Session::STATE_RECEIVEDTERMINATE) {
    617     console_->PrintLine("other side terminated");
    618     TerminateAndRemoveSession(call, session->id());
    619   }
    620 }
    621 
    622 void CallClient::OnSpeakerChanged(cricket::Call* call,
    623                                   cricket::Session* session,
    624                                   const cricket::StreamParams& speaker) {
    625   if (!speaker.has_ssrcs()) {
    626     console_->PrintLine("Session %s has no current speaker.",
    627                         session->id().c_str());
    628   } else if (speaker.id.empty()) {
    629     console_->PrintLine("Session %s speaker change to unknown (%u).",
    630                         session->id().c_str(), speaker.first_ssrc());
    631   } else {
    632     console_->PrintLine("Session %s speaker changed to %s (%u).",
    633                         session->id().c_str(), speaker.id.c_str(),
    634                         speaker.first_ssrc());
    635   }
    636 }
    637 
    638 void SetMediaCaps(int media_caps, buzz::PresenceStatus* status) {
    639   status->set_voice_capability((media_caps & cricket::AUDIO_RECV) != 0);
    640   status->set_video_capability((media_caps & cricket::VIDEO_RECV) != 0);
    641   status->set_camera_capability((media_caps & cricket::VIDEO_SEND) != 0);
    642 }
    643 
    644 void SetCaps(int media_caps, buzz::PresenceStatus* status) {
    645   status->set_know_capabilities(true);
    646   status->set_pmuc_capability(true);
    647   SetMediaCaps(media_caps, status);
    648 }
    649 
    650 void SetAvailable(const buzz::Jid& jid, buzz::PresenceStatus* status) {
    651   status->set_jid(jid);
    652   status->set_available(true);
    653   status->set_show(buzz::PresenceStatus::SHOW_ONLINE);
    654 }
    655 
    656 void CallClient::InitPresence() {
    657   presence_push_ = new buzz::PresencePushTask(xmpp_client_, this);
    658   presence_push_->SignalStatusUpdate.connect(
    659     this, &CallClient::OnStatusUpdate);
    660   presence_push_->SignalMucJoined.connect(this, &CallClient::OnMucJoined);
    661   presence_push_->SignalMucLeft.connect(this, &CallClient::OnMucLeft);
    662   presence_push_->SignalMucStatusUpdate.connect(
    663     this, &CallClient::OnMucStatusUpdate);
    664   presence_push_->Start();
    665 
    666   presence_out_ = new buzz::PresenceOutTask(xmpp_client_);
    667   SetAvailable(xmpp_client_->jid(), &my_status_);
    668   SetCaps(media_client_->GetCapabilities(), &my_status_);
    669   SendStatus(my_status_);
    670   presence_out_->Start();
    671 
    672   muc_invite_recv_ = new buzz::MucInviteRecvTask(xmpp_client_);
    673   muc_invite_recv_->SignalInviteReceived.connect(this,
    674       &CallClient::OnMucInviteReceived);
    675   muc_invite_recv_->Start();
    676 
    677   muc_invite_send_ = new buzz::MucInviteSendTask(xmpp_client_);
    678   muc_invite_send_->Start();
    679 
    680   friend_invite_send_ = new buzz::FriendInviteSendTask(xmpp_client_);
    681   friend_invite_send_->Start();
    682 
    683   StartXmppPing();
    684 }
    685 
    686 void CallClient::StartXmppPing() {
    687   buzz::PingTask* ping = new buzz::PingTask(
    688       xmpp_client_, rtc::Thread::Current(),
    689       kPingPeriodMillis, kPingTimeoutMillis);
    690   ping->SignalTimeout.connect(this, &CallClient::OnPingTimeout);
    691   ping->Start();
    692 }
    693 
    694 void CallClient::OnPingTimeout() {
    695   LOG(LS_WARNING) << "XMPP Ping timeout. Will keep trying...";
    696   StartXmppPing();
    697 
    698   // Or should we do this instead?
    699   // Quit();
    700 }
    701 
    702 void CallClient::SendStatus(const buzz::PresenceStatus& status) {
    703   presence_out_->Send(status);
    704 }
    705 
    706 void CallClient::OnStatusUpdate(const buzz::PresenceStatus& status) {
    707   RosterItem item;
    708   item.jid = status.jid();
    709   item.show = status.show();
    710   item.status = status.status();
    711 
    712   std::string key = item.jid.Str();
    713 
    714   if (status.available() && status.voice_capability()) {
    715     if (show_roster_messages_) {
    716       console_->PrintLine("Adding to roster: %s", key.c_str());
    717     }
    718     (*roster_)[key] = item;
    719     // TODO: Make some of these constants.
    720   } else {
    721     if (show_roster_messages_) {
    722       console_->PrintLine("Removing from roster: %s", key.c_str());
    723     }
    724     RosterMap::iterator iter = roster_->find(key);
    725     if (iter != roster_->end())
    726       roster_->erase(iter);
    727   }
    728 }
    729 
    730 void CallClient::PrintRoster() {
    731   console_->PrintLine("Roster contains %d callable", roster_->size());
    732   RosterMap::iterator iter = roster_->begin();
    733   while (iter != roster_->end()) {
    734     console_->PrintLine("%s - %s",
    735                         iter->second.jid.BareJid().Str().c_str(),
    736                         DescribeStatus(iter->second.show, iter->second.status));
    737     iter++;
    738   }
    739 }
    740 
    741 void CallClient::SendChat(const std::string& to, const std::string msg) {
    742   buzz::XmlElement* stanza = new buzz::XmlElement(buzz::QN_MESSAGE);
    743   stanza->AddAttr(buzz::QN_TO, to);
    744   stanza->AddAttr(buzz::QN_ID, rtc::CreateRandomString(16));
    745   stanza->AddAttr(buzz::QN_TYPE, "chat");
    746   buzz::XmlElement* body = new buzz::XmlElement(buzz::QN_BODY);
    747   body->SetBodyText(msg);
    748   stanza->AddElement(body);
    749 
    750   xmpp_client_->SendStanza(stanza);
    751   delete stanza;
    752 }
    753 
    754 void CallClient::SendData(const std::string& streamid,
    755                           const std::string& text) {
    756   // TODO(mylesj): Support sending data over sessions other than the first.
    757   cricket::Session* session = GetFirstSession();
    758   if (!call_ || !session) {
    759     console_->PrintLine("Must be in a call to send data.");
    760     return;
    761   }
    762   if (!call_->has_data()) {
    763     console_->PrintLine("This call doesn't have a data channel.");
    764     return;
    765   }
    766 
    767   const cricket::DataContentDescription* data =
    768       cricket::GetFirstDataContentDescription(session->local_description());
    769   if (!data) {
    770     console_->PrintLine("This call doesn't have a data content.");
    771     return;
    772   }
    773 
    774   cricket::StreamParams stream;
    775   if (!cricket::GetStreamByIds(
    776           data->streams(), "", streamid, &stream)) {
    777     LOG(LS_WARNING) << "Could not send data: no such stream: "
    778                     << streamid << ".";
    779     return;
    780   }
    781 
    782   cricket::SendDataParams params;
    783   params.ssrc = stream.first_ssrc();
    784   rtc::Buffer payload(text.data(), text.length());
    785   cricket::SendDataResult result;
    786   bool sent = call_->SendData(session, params, payload, &result);
    787   if (!sent) {
    788     if (result == cricket::SDR_BLOCK) {
    789       LOG(LS_WARNING) << "Could not send data because it would block.";
    790     } else {
    791       LOG(LS_WARNING) << "Could not send data for unknown reason.";
    792     }
    793   }
    794 }
    795 
    796 void CallClient::InviteFriend(const std::string& name) {
    797   buzz::Jid jid(name);
    798   if (!jid.IsValid() || jid.node() == "") {
    799     console_->PrintLine("Invalid JID. JIDs should be in the form user@domain.");
    800     return;
    801   }
    802   // Note: for some reason the Buzz backend does not forward our presence
    803   // subscription requests to the end user when that user is another call
    804   // client as opposed to a Smurf user. Thus, in that scenario, you must
    805   // run the friend command as the other user too to create the linkage
    806   // (and you won't be notified to do so).
    807   friend_invite_send_->Send(jid);
    808   console_->PrintLine("Requesting to befriend %s.", name.c_str());
    809 }
    810 
    811 bool CallClient::FindJid(const std::string& name, buzz::Jid* found_jid,
    812                          cricket::CallOptions* options) {
    813   bool found = false;
    814   options->is_muc = false;
    815   buzz::Jid callto_jid(name);
    816   if (name.length() == 0 && mucs_.size() > 0) {
    817     // if no name, and in a MUC, establish audio with the MUC
    818     *found_jid = mucs_.begin()->first;
    819     found = true;
    820     options->is_muc = true;
    821   } else if (name[0] == '+') {
    822     // if the first character is a +, assume it's a phone number
    823     *found_jid = callto_jid;
    824     found = true;
    825   } else {
    826     // otherwise, it's a friend
    827     for (RosterMap::iterator iter = roster_->begin();
    828          iter != roster_->end(); ++iter) {
    829       if (iter->second.jid.BareEquals(callto_jid)) {
    830         found = true;
    831         *found_jid = iter->second.jid;
    832         break;
    833       }
    834     }
    835 
    836     if (!found) {
    837       if (mucs_.count(callto_jid) == 1 &&
    838           mucs_[callto_jid]->state() == buzz::Muc::MUC_JOINED) {
    839         found = true;
    840         *found_jid = callto_jid;
    841         options->is_muc = true;
    842       }
    843     }
    844   }
    845 
    846   if (found) {
    847     console_->PrintLine("Found %s '%s'",
    848                         options->is_muc ? "room" : "online friend",
    849                         found_jid->Str().c_str());
    850   } else {
    851     console_->PrintLine("Could not find online friend '%s'", name.c_str());
    852   }
    853 
    854   return found;
    855 }
    856 
    857 void CallClient::OnDataReceived(cricket::Call*,
    858                                 const cricket::ReceiveDataParams& params,
    859                                 const rtc::Buffer& payload) {
    860   // TODO(mylesj): Support receiving data on sessions other than the first.
    861   cricket::Session* session = GetFirstSession();
    862   if (!session)
    863     return;
    864 
    865   cricket::StreamParams stream;
    866   const std::vector<cricket::StreamParams>* data_streams =
    867       call_->GetDataRecvStreams(session);
    868   std::string text(payload.data(), payload.length());
    869   if (data_streams && GetStreamBySsrc(*data_streams, params.ssrc, &stream)) {
    870     console_->PrintLine(
    871         "Received data from '%s' on stream '%s' (ssrc=%u): %s",
    872         stream.groupid.c_str(), stream.id.c_str(),
    873         params.ssrc, text.c_str());
    874   } else {
    875     console_->PrintLine(
    876         "Received data (ssrc=%u): %s",
    877         params.ssrc, text.c_str());
    878   }
    879 }
    880 
    881 bool CallClient::PlaceCall(const std::string& name,
    882                            cricket::CallOptions options) {
    883   buzz::Jid jid;
    884   if (!FindJid(name, &jid, &options))
    885     return false;
    886 
    887   if (!call_) {
    888     call_ = media_client_->CreateCall();
    889     AddSession(call_->InitiateSession(jid, media_client_->jid(), options));
    890   }
    891   media_client_->SetFocus(call_);
    892   if (call_->has_video() && render_ && !options.is_muc) {
    893     // TODO(pthatcher): Hookup local_render_ to the local capturer.
    894   }
    895   if (options.is_muc) {
    896     const std::string& nick = mucs_[jid]->local_jid().resource();
    897     hangout_pubsub_client_ =
    898         new buzz::HangoutPubSubClient(xmpp_client_, jid, nick);
    899     hangout_pubsub_client_->SignalPresenterStateChange.connect(
    900         this, &CallClient::OnPresenterStateChange);
    901     hangout_pubsub_client_->SignalAudioMuteStateChange.connect(
    902         this, &CallClient::OnAudioMuteStateChange);
    903     hangout_pubsub_client_->SignalRecordingStateChange.connect(
    904         this, &CallClient::OnRecordingStateChange);
    905     hangout_pubsub_client_->SignalRemoteMute.connect(
    906         this, &CallClient::OnRemoteMuted);
    907     hangout_pubsub_client_->SignalMediaBlock.connect(
    908         this, &CallClient::OnMediaBlocked);
    909     hangout_pubsub_client_->SignalRequestError.connect(
    910         this, &CallClient::OnHangoutRequestError);
    911     hangout_pubsub_client_->SignalPublishAudioMuteError.connect(
    912         this, &CallClient::OnHangoutPublishAudioMuteError);
    913     hangout_pubsub_client_->SignalPublishPresenterError.connect(
    914         this, &CallClient::OnHangoutPublishPresenterError);
    915     hangout_pubsub_client_->SignalPublishRecordingError.connect(
    916         this, &CallClient::OnHangoutPublishRecordingError);
    917     hangout_pubsub_client_->SignalRemoteMuteError.connect(
    918         this, &CallClient::OnHangoutRemoteMuteError);
    919     hangout_pubsub_client_->RequestAll();
    920   }
    921 
    922   return true;
    923 }
    924 
    925 bool CallClient::InitiateAdditionalSession(const std::string& name,
    926                                            cricket::CallOptions options) {
    927   // Can't add a session if there is no call yet.
    928   if (!call_)
    929     return false;
    930 
    931   buzz::Jid jid;
    932   if (!FindJid(name, &jid, &options))
    933     return false;
    934 
    935   std::vector<cricket::Session*>& call_sessions = sessions_[call_->id()];
    936   call_sessions.push_back(
    937       call_->InitiateSession(jid,
    938                              buzz::Jid(call_sessions[0]->remote_name()),
    939                              options));
    940 
    941   return true;
    942 }
    943 
    944 void CallClient::TerminateAndRemoveSession(cricket::Call* call,
    945                                            const std::string& id) {
    946   std::vector<cricket::Session*>& call_sessions = sessions_[call->id()];
    947   for (std::vector<cricket::Session*>::iterator iter = call_sessions.begin();
    948        iter != call_sessions.end(); ++iter) {
    949     if ((*iter)->id() == id) {
    950       RenderAllStreams(call, *iter, false);
    951       call_->TerminateSession(*iter);
    952       call_sessions.erase(iter);
    953       break;
    954     }
    955   }
    956 }
    957 
    958 void CallClient::PrintCalls() {
    959   const std::map<uint32, cricket::Call*>& calls = media_client_->calls();
    960   for (std::map<uint32, cricket::Call*>::const_iterator i = calls.begin();
    961        i != calls.end(); ++i) {
    962     console_->PrintLine("Call (id:%d), is %s",
    963                         i->first,
    964                         i->second == call_ ? "active" : "on hold");
    965     std::vector<cricket::Session *>& sessions = sessions_[call_->id()];
    966     for (std::vector<cricket::Session *>::const_iterator j = sessions.begin();
    967          j != sessions.end(); ++j) {
    968       console_->PrintLine("|--Session (id:%s), to %s", (*j)->id().c_str(),
    969                           (*j)->remote_name().c_str());
    970 
    971       std::vector<cricket::StreamParams>::const_iterator k;
    972       const std::vector<cricket::StreamParams>* streams =
    973           i->second->GetAudioRecvStreams(*j);
    974       if (streams)
    975         for (k = streams->begin(); k != streams->end(); ++k) {
    976           console_->PrintLine("|----Audio Stream: %s", k->ToString().c_str());
    977         }
    978       streams = i->second->GetVideoRecvStreams(*j);
    979       if (streams)
    980         for (k = streams->begin(); k != streams->end(); ++k) {
    981           console_->PrintLine("|----Video Stream: %s", k->ToString().c_str());
    982         }
    983       streams = i->second->GetDataRecvStreams(*j);
    984       if (streams)
    985         for (k = streams->begin(); k != streams->end(); ++k) {
    986           console_->PrintLine("|----Data Stream: %s", k->ToString().c_str());
    987         }
    988     }
    989   }
    990 }
    991 
    992 void CallClient::SwitchToCall(uint32 call_id) {
    993   const std::map<uint32, cricket::Call*>& calls = media_client_->calls();
    994   std::map<uint32, cricket::Call*>::const_iterator call_iter =
    995       calls.find(call_id);
    996   if (call_iter != calls.end()) {
    997     media_client_->SetFocus(call_iter->second);
    998     call_ = call_iter->second;
    999   } else {
   1000     console_->PrintLine("Unable to find call: %d", call_id);
   1001   }
   1002 }
   1003 
   1004 void CallClient::OnPresenterStateChange(
   1005     const std::string& nick, bool was_presenting, bool is_presenting) {
   1006   if (!was_presenting && is_presenting) {
   1007     console_->PrintLine("%s now presenting.", nick.c_str());
   1008   } else if (was_presenting && !is_presenting) {
   1009     console_->PrintLine("%s no longer presenting.", nick.c_str());
   1010   } else if (was_presenting && is_presenting) {
   1011     console_->PrintLine("%s still presenting.", nick.c_str());
   1012   } else if (!was_presenting && !is_presenting) {
   1013     console_->PrintLine("%s still not presenting.", nick.c_str());
   1014   }
   1015 }
   1016 
   1017 void CallClient::OnAudioMuteStateChange(
   1018     const std::string& nick, bool was_muted, bool is_muted) {
   1019   if (!was_muted && is_muted) {
   1020     console_->PrintLine("%s now muted.", nick.c_str());
   1021   } else if (was_muted && !is_muted) {
   1022     console_->PrintLine("%s no longer muted.", nick.c_str());
   1023   }
   1024 }
   1025 
   1026 void CallClient::OnRecordingStateChange(
   1027     const std::string& nick, bool was_recording, bool is_recording) {
   1028   if (!was_recording && is_recording) {
   1029     console_->PrintLine("%s now recording.", nick.c_str());
   1030   } else if (was_recording && !is_recording) {
   1031     console_->PrintLine("%s no longer recording.", nick.c_str());
   1032   }
   1033 }
   1034 
   1035 void CallClient::OnRemoteMuted(const std::string& mutee_nick,
   1036                                const std::string& muter_nick,
   1037                                bool should_mute_locally) {
   1038   if (should_mute_locally) {
   1039     call_->Mute(true);
   1040     console_->PrintLine("Remote muted by %s.", muter_nick.c_str());
   1041   } else {
   1042     console_->PrintLine("%s remote muted by %s.",
   1043                         mutee_nick.c_str(), muter_nick.c_str());
   1044   }
   1045 }
   1046 
   1047 void CallClient::OnMediaBlocked(const std::string& blockee_nick,
   1048                                 const std::string& blocker_nick) {
   1049   console_->PrintLine("%s blocked by %s.",
   1050                       blockee_nick.c_str(), blocker_nick.c_str());
   1051 }
   1052 
   1053 void CallClient::OnHangoutRequestError(const std::string& node,
   1054                                        const buzz::XmlElement* stanza) {
   1055   console_->PrintLine("Failed request pub sub items for node %s.",
   1056                       node.c_str());
   1057 }
   1058 
   1059 void CallClient::OnHangoutPublishAudioMuteError(
   1060     const std::string& task_id, const buzz::XmlElement* stanza) {
   1061   console_->PrintLine("Failed to publish audio mute state.");
   1062 }
   1063 
   1064 void CallClient::OnHangoutPublishPresenterError(
   1065     const std::string& task_id, const buzz::XmlElement* stanza) {
   1066   console_->PrintLine("Failed to publish presenting state.");
   1067 }
   1068 
   1069 void CallClient::OnHangoutPublishRecordingError(
   1070     const std::string& task_id, const buzz::XmlElement* stanza) {
   1071   console_->PrintLine("Failed to publish recording state.");
   1072 }
   1073 
   1074 void CallClient::OnHangoutRemoteMuteError(const std::string& task_id,
   1075                                           const std::string& mutee_nick,
   1076                                           const buzz::XmlElement* stanza) {
   1077   console_->PrintLine("Failed to remote mute.");
   1078 }
   1079 
   1080 void CallClient::Accept(const cricket::CallOptions& options) {
   1081   ASSERT(call_ && incoming_call_);
   1082   ASSERT(sessions_[call_->id()].size() == 1);
   1083   cricket::Session* session = GetFirstSession();
   1084   call_->AcceptSession(session, options);
   1085   media_client_->SetFocus(call_);
   1086   if (call_->has_video() && render_) {
   1087     // TODO(pthatcher): Hookup local_render_ to the local capturer.
   1088     RenderAllStreams(call_, session, true);
   1089   }
   1090   SetupAcceptedCall();
   1091   incoming_call_ = false;
   1092 }
   1093 
   1094 void CallClient::SetupAcceptedCall() {
   1095   if (call_->has_data()) {
   1096     call_->SignalDataReceived.connect(this, &CallClient::OnDataReceived);
   1097   }
   1098 }
   1099 
   1100 void CallClient::Reject() {
   1101   ASSERT(call_ && incoming_call_);
   1102   call_->RejectSession(call_->sessions()[0]);
   1103   incoming_call_ = false;
   1104 }
   1105 
   1106 void CallClient::Quit() {
   1107   rtc::Thread::Current()->Quit();
   1108 }
   1109 
   1110 void CallClient::SetNick(const std::string& muc_nick) {
   1111   my_status_.set_nick(muc_nick);
   1112 
   1113   // TODO: We might want to re-send presence, but right
   1114   // now, it appears to be ignored by the MUC.
   1115   //
   1116   // presence_out_->Send(my_status_); for (MucMap::const_iterator itr
   1117   // = mucs_.begin(); itr != mucs_.end(); ++itr) {
   1118   // presence_out_->SendDirected(itr->second->local_jid(),
   1119   // my_status_); }
   1120 
   1121   console_->PrintLine("Nick set to '%s'.", muc_nick.c_str());
   1122 }
   1123 
   1124 void CallClient::LookupAndJoinMuc(const std::string& room_name) {
   1125   // The room_name can't be empty for lookup task.
   1126   if (room_name.empty()) {
   1127     console_->PrintLine("Please provide a room name or room jid.");
   1128     return;
   1129   }
   1130 
   1131   std::string room = room_name;
   1132   std::string domain = xmpp_client_->jid().domain();
   1133   if (room_name.find("@") != std::string::npos) {
   1134     // Assume the room_name is a fully qualified room name.
   1135     // We'll find the room name string and domain name string from it.
   1136     room = room_name.substr(0, room_name.find("@"));
   1137     domain = room_name.substr(room_name.find("@") + 1);
   1138   }
   1139 
   1140   buzz::MucRoomLookupTask* lookup_query_task =
   1141       buzz::MucRoomLookupTask::CreateLookupTaskForRoomName(
   1142           xmpp_client_, buzz::Jid(buzz::STR_GOOGLE_MUC_LOOKUP_JID), room,
   1143           domain);
   1144   lookup_query_task->SignalResult.connect(this,
   1145       &CallClient::OnRoomLookupResponse);
   1146   lookup_query_task->SignalError.connect(this,
   1147       &CallClient::OnRoomLookupError);
   1148   lookup_query_task->Start();
   1149 }
   1150 
   1151 void CallClient::JoinMuc(const std::string& room_jid_str) {
   1152   if (room_jid_str.empty()) {
   1153     buzz::Jid room_jid = GenerateRandomMucJid();
   1154     console_->PrintLine("Generated a random room jid: %s",
   1155                         room_jid.Str().c_str());
   1156     JoinMuc(room_jid);
   1157   } else {
   1158     JoinMuc(buzz::Jid(room_jid_str));
   1159   }
   1160 }
   1161 
   1162 void CallClient::JoinMuc(const buzz::Jid& room_jid) {
   1163   if (!room_jid.IsValid()) {
   1164     console_->PrintLine("Unable to make valid muc endpoint for %s",
   1165                         room_jid.Str().c_str());
   1166     return;
   1167   }
   1168 
   1169   std::string room_nick = room_jid.resource();
   1170   if (room_nick.empty()) {
   1171     room_nick = (xmpp_client_->jid().node()
   1172                  + "_" + xmpp_client_->jid().resource());
   1173   }
   1174 
   1175   MucMap::iterator elem = mucs_.find(room_jid);
   1176   if (elem != mucs_.end()) {
   1177     console_->PrintLine("This MUC already exists.");
   1178     return;
   1179   }
   1180 
   1181   buzz::Muc* muc = new buzz::Muc(room_jid.BareJid(), room_nick);
   1182   mucs_[muc->jid()] = muc;
   1183   presence_out_->SendDirected(muc->local_jid(), my_status_);
   1184 }
   1185 
   1186 void CallClient::OnRoomLookupResponse(buzz::MucRoomLookupTask* task,
   1187                                       const buzz::MucRoomInfo& room) {
   1188   // The server requires the room be "configured" before being used.
   1189   // We only need to configure it if we create it, but rooms are
   1190   // auto-created at lookup, so there's currently no way to know if we
   1191   // created it.  So, we configure it every time, just in case.
   1192   // Luckily, it appears to be safe to configure a room that's already
   1193   // configured.  Our current flow is:
   1194   // 1. Lookup/auto-create
   1195   // 2. Configure
   1196   // 3. Join
   1197   // TODO: In the future, once the server supports it, we
   1198   // should:
   1199   // 1. Lookup
   1200   // 2. Create and Configure if necessary
   1201   // 3. Join
   1202   std::vector<std::string> room_features;
   1203   room_features.push_back(buzz::STR_MUC_ROOM_FEATURE_ENTERPRISE);
   1204   buzz::MucRoomConfigTask* room_config_task = new buzz::MucRoomConfigTask(
   1205       xmpp_client_, room.jid, room.full_name(), room_features);
   1206   room_config_task->SignalResult.connect(this,
   1207       &CallClient::OnRoomConfigResult);
   1208   room_config_task->SignalError.connect(this,
   1209       &CallClient::OnRoomConfigError);
   1210   room_config_task->Start();
   1211 }
   1212 
   1213 void CallClient::OnRoomLookupError(buzz::IqTask* task,
   1214                                    const buzz::XmlElement* stanza) {
   1215   if (stanza == NULL) {
   1216     console_->PrintLine("Room lookup failed.");
   1217   } else {
   1218     console_->PrintLine("Room lookup error: ", stanza->Str().c_str());
   1219   }
   1220 }
   1221 
   1222 void CallClient::OnRoomConfigResult(buzz::MucRoomConfigTask* task) {
   1223   JoinMuc(task->room_jid());
   1224 }
   1225 
   1226 void CallClient::OnRoomConfigError(buzz::IqTask* task,
   1227                                    const buzz::XmlElement* stanza) {
   1228   console_->PrintLine("Room config failed.");
   1229   // We join the muc anyway, because if the room is already
   1230   // configured, the configure will fail, but we still want to join.
   1231   // Idealy, we'd know why the room config failed and only do this on
   1232   // "already configured" errors.  But right now all we get back is
   1233   // "not-allowed".
   1234   buzz::MucRoomConfigTask* config_task =
   1235       static_cast<buzz::MucRoomConfigTask*>(task);
   1236   JoinMuc(config_task->room_jid());
   1237 }
   1238 
   1239 void CallClient::OnMucInviteReceived(const buzz::Jid& inviter,
   1240     const buzz::Jid& room,
   1241     const std::vector<buzz::AvailableMediaEntry>& avail) {
   1242 
   1243   console_->PrintLine("Invited to join %s by %s.", room.Str().c_str(),
   1244       inviter.Str().c_str());
   1245   console_->PrintLine("Available media:");
   1246   if (avail.size() > 0) {
   1247     for (std::vector<buzz::AvailableMediaEntry>::const_iterator i =
   1248             avail.begin();
   1249         i != avail.end();
   1250         ++i) {
   1251       console_->PrintLine("  %s, %s",
   1252                           buzz::AvailableMediaEntry::TypeAsString(i->type),
   1253                           buzz::AvailableMediaEntry::StatusAsString(i->status));
   1254     }
   1255   } else {
   1256     console_->PrintLine("  None");
   1257   }
   1258   // We automatically join the room.
   1259   JoinMuc(room);
   1260 }
   1261 
   1262 void CallClient::OnMucJoined(const buzz::Jid& endpoint) {
   1263   MucMap::iterator elem = mucs_.find(endpoint);
   1264   ASSERT(elem != mucs_.end() &&
   1265          elem->second->state() == buzz::Muc::MUC_JOINING);
   1266 
   1267   buzz::Muc* muc = elem->second;
   1268   muc->set_state(buzz::Muc::MUC_JOINED);
   1269   console_->PrintLine("Joined \"%s\"", muc->jid().Str().c_str());
   1270 }
   1271 
   1272 void CallClient::OnMucStatusUpdate(const buzz::Jid& jid,
   1273     const buzz::MucPresenceStatus& status) {
   1274 
   1275   // Look up this muc.
   1276   MucMap::iterator elem = mucs_.find(jid);
   1277   ASSERT(elem != mucs_.end());
   1278 
   1279   buzz::Muc* muc = elem->second;
   1280 
   1281   if (status.jid().IsBare() || status.jid() == muc->local_jid()) {
   1282     // We are only interested in status about other users.
   1283     return;
   1284   }
   1285 
   1286   if (status.available()) {
   1287     muc->members()[status.jid().resource()] = status;
   1288   } else {
   1289     muc->members().erase(status.jid().resource());
   1290   }
   1291 }
   1292 
   1293 bool CallClient::InMuc() {
   1294   const buzz::Jid* muc_jid = FirstMucJid();
   1295   if (!muc_jid) return false;
   1296   return muc_jid->IsValid();
   1297 }
   1298 
   1299 const buzz::Jid* CallClient::FirstMucJid() {
   1300   if (mucs_.empty()) return NULL;
   1301   return &(mucs_.begin()->first);
   1302 }
   1303 
   1304 void CallClient::LeaveMuc(const std::string& room) {
   1305   buzz::Jid room_jid;
   1306   const buzz::Jid* muc_jid = FirstMucJid();
   1307   if (room.length() > 0) {
   1308     room_jid = buzz::Jid(room);
   1309   } else if (mucs_.size() > 0) {
   1310     // leave the first MUC if no JID specified
   1311     if (muc_jid) {
   1312       room_jid = *(muc_jid);
   1313     }
   1314   }
   1315 
   1316   if (!room_jid.IsValid()) {
   1317     console_->PrintLine("Invalid MUC JID.");
   1318     return;
   1319   }
   1320 
   1321   MucMap::iterator elem = mucs_.find(room_jid);
   1322   if (elem == mucs_.end()) {
   1323     console_->PrintLine("No such MUC.");
   1324     return;
   1325   }
   1326 
   1327   buzz::Muc* muc = elem->second;
   1328   muc->set_state(buzz::Muc::MUC_LEAVING);
   1329 
   1330   buzz::PresenceStatus status;
   1331   status.set_jid(my_status_.jid());
   1332   status.set_available(false);
   1333   status.set_priority(0);
   1334   presence_out_->SendDirected(muc->local_jid(), status);
   1335 }
   1336 
   1337 void CallClient::OnMucLeft(const buzz::Jid& endpoint, int error) {
   1338   // We could be kicked from a room from any state.  We would hope this
   1339   // happens While in the MUC_LEAVING state
   1340   MucMap::iterator elem = mucs_.find(endpoint);
   1341   if (elem == mucs_.end())
   1342     return;
   1343 
   1344   buzz::Muc* muc = elem->second;
   1345   if (muc->state() == buzz::Muc::MUC_JOINING) {
   1346     console_->PrintLine("Failed to join \"%s\", code=%d",
   1347                         muc->jid().Str().c_str(), error);
   1348   } else if (muc->state() == buzz::Muc::MUC_JOINED) {
   1349     console_->PrintLine("Kicked from \"%s\"",
   1350                         muc->jid().Str().c_str());
   1351   }
   1352 
   1353   delete muc;
   1354   mucs_.erase(elem);
   1355 }
   1356 
   1357 void CallClient::InviteToMuc(const std::string& given_user,
   1358                              const std::string& room) {
   1359   std::string user = given_user;
   1360 
   1361   // First find the room.
   1362   const buzz::Muc* found_muc;
   1363   if (room.length() == 0) {
   1364     if (mucs_.size() == 0) {
   1365       console_->PrintLine("Not in a room yet; can't invite.");
   1366       return;
   1367     }
   1368     // Invite to the first muc
   1369     found_muc = mucs_.begin()->second;
   1370   } else {
   1371     MucMap::iterator elem = mucs_.find(buzz::Jid(room));
   1372     if (elem == mucs_.end()) {
   1373       console_->PrintLine("Not in room %s.", room.c_str());
   1374       return;
   1375     }
   1376     found_muc = elem->second;
   1377   }
   1378 
   1379   buzz::Jid invite_to = found_muc->jid();
   1380 
   1381   // Now find the user. We invite all of their resources.
   1382   bool found_user = false;
   1383   buzz::Jid user_jid(user);
   1384   for (RosterMap::iterator iter = roster_->begin();
   1385        iter != roster_->end(); ++iter) {
   1386     if (iter->second.jid.BareEquals(user_jid)) {
   1387       buzz::Jid invitee = iter->second.jid;
   1388       muc_invite_send_->Send(invite_to, invitee);
   1389       found_user = true;
   1390     }
   1391   }
   1392   if (!found_user) {
   1393     buzz::Jid invitee = user_jid;
   1394     muc_invite_send_->Send(invite_to, invitee);
   1395   }
   1396 }
   1397 
   1398 void CallClient::GetDevices() {
   1399   std::vector<std::string> names;
   1400   media_client_->GetAudioInputDevices(&names);
   1401   console_->PrintLine("Audio input devices:");
   1402   PrintDevices(names);
   1403   media_client_->GetAudioOutputDevices(&names);
   1404   console_->PrintLine("Audio output devices:");
   1405   PrintDevices(names);
   1406   media_client_->GetVideoCaptureDevices(&names);
   1407   console_->PrintLine("Video capture devices:");
   1408   PrintDevices(names);
   1409 }
   1410 
   1411 void CallClient::PrintDevices(const std::vector<std::string>& names) {
   1412   for (size_t i = 0; i < names.size(); ++i) {
   1413     console_->PrintLine("%d: %s", static_cast<int>(i), names[i].c_str());
   1414   }
   1415 }
   1416 
   1417 void CallClient::OnDevicesChange() {
   1418   console_->PrintLine("Devices changed.");
   1419   SetMediaCaps(media_client_->GetCapabilities(), &my_status_);
   1420   SendStatus(my_status_);
   1421 }
   1422 
   1423 void CallClient::SetVolume(const std::string& level) {
   1424   media_client_->SetOutputVolume(strtol(level.c_str(), NULL, 10));
   1425 }
   1426 
   1427 void CallClient::OnMediaStreamsUpdate(cricket::Call* call,
   1428                                       cricket::Session* session,
   1429                                       const cricket::MediaStreams& added,
   1430                                       const cricket::MediaStreams& removed) {
   1431   if (call && call->has_video()) {
   1432     for (std::vector<cricket::StreamParams>::const_iterator
   1433          it = removed.video().begin(); it != removed.video().end(); ++it) {
   1434       RemoveStaticRenderedView(it->first_ssrc());
   1435     }
   1436 
   1437     if (render_) {
   1438       RenderStreams(call, session, added.video(), true);
   1439     }
   1440     SendViewRequest(call, session);
   1441   }
   1442 }
   1443 
   1444 void CallClient::RenderAllStreams(cricket::Call* call,
   1445                                   cricket::Session* session,
   1446                                   bool enable) {
   1447   const std::vector<cricket::StreamParams>* video_streams =
   1448       call->GetVideoRecvStreams(session);
   1449   if (video_streams) {
   1450     RenderStreams(call, session, *video_streams, enable);
   1451   }
   1452 }
   1453 
   1454 void CallClient::RenderStreams(
   1455     cricket::Call* call,
   1456     cricket::Session* session,
   1457     const std::vector<cricket::StreamParams>& video_streams,
   1458     bool enable) {
   1459   std::vector<cricket::StreamParams>::const_iterator stream;
   1460   for (stream = video_streams.begin(); stream != video_streams.end();
   1461        ++stream) {
   1462     RenderStream(call, session, *stream, enable);
   1463   }
   1464 }
   1465 
   1466 void CallClient::RenderStream(cricket::Call* call,
   1467                               cricket::Session* session,
   1468                               const cricket::StreamParams& stream,
   1469                               bool enable) {
   1470   if (!stream.has_ssrcs()) {
   1471     // Nothing to see here; move along.
   1472     return;
   1473   }
   1474 
   1475   uint32 ssrc = stream.first_ssrc();
   1476   StaticRenderedViews::iterator iter =
   1477       static_rendered_views_.find(std::make_pair(session, ssrc));
   1478   if (enable) {
   1479     if (iter == static_rendered_views_.end()) {
   1480       // TODO(pthatcher): Make dimensions and positions more configurable.
   1481       int offset = (50 * static_views_accumulated_count_) % 300;
   1482       AddStaticRenderedView(session, ssrc, 640, 400, 30,
   1483                             offset, offset);
   1484       // Should have it now.
   1485       iter = static_rendered_views_.find(std::make_pair(session, ssrc));
   1486     }
   1487     call->SetVideoRenderer(session, ssrc, iter->second.renderer);
   1488   } else {
   1489     if (iter != static_rendered_views_.end()) {
   1490       call->SetVideoRenderer(session, ssrc, NULL);
   1491       RemoveStaticRenderedView(ssrc);
   1492     }
   1493   }
   1494 }
   1495 
   1496 // TODO: Would these methods to add and remove views make
   1497 // more sense in call.cc?  Would other clients use them?
   1498 void CallClient::AddStaticRenderedView(
   1499     cricket::Session* session,
   1500     uint32 ssrc, int width, int height, int framerate,
   1501     int x_offset, int y_offset) {
   1502   StaticRenderedView rendered_view(
   1503       cricket::StaticVideoView(
   1504           cricket::StreamSelector(ssrc), width, height, framerate),
   1505       cricket::VideoRendererFactory::CreateGuiVideoRenderer(
   1506           x_offset, y_offset));
   1507   rendered_view.renderer->SetSize(width, height, 0);
   1508   static_rendered_views_.insert(std::make_pair(std::make_pair(session, ssrc),
   1509                                                rendered_view));
   1510   ++static_views_accumulated_count_;
   1511   console_->PrintLine("Added renderer for ssrc %d", ssrc);
   1512 }
   1513 
   1514 bool CallClient::RemoveStaticRenderedView(uint32 ssrc) {
   1515   for (StaticRenderedViews::iterator it = static_rendered_views_.begin();
   1516        it != static_rendered_views_.end(); ++it) {
   1517     if (it->second.view.selector.ssrc == ssrc) {
   1518       delete it->second.renderer;
   1519       static_rendered_views_.erase(it);
   1520       console_->PrintLine("Removed renderer for ssrc %d", ssrc);
   1521       return true;
   1522     }
   1523   }
   1524   return false;
   1525 }
   1526 
   1527 void CallClient::RemoveCallsStaticRenderedViews(cricket::Call* call) {
   1528   std::vector<cricket::Session*>& sessions = sessions_[call->id()];
   1529   std::set<cricket::Session*> call_sessions(sessions.begin(), sessions.end());
   1530   for (StaticRenderedViews::iterator it = static_rendered_views_.begin();
   1531        it != static_rendered_views_.end(); ) {
   1532     if (call_sessions.find(it->first.first) != call_sessions.end()) {
   1533       delete it->second.renderer;
   1534       static_rendered_views_.erase(it++);
   1535     } else {
   1536       ++it;
   1537     }
   1538   }
   1539 }
   1540 
   1541 void CallClient::SendViewRequest(cricket::Call* call,
   1542                                  cricket::Session* session) {
   1543   cricket::ViewRequest request;
   1544   for (StaticRenderedViews::iterator it = static_rendered_views_.begin();
   1545        it != static_rendered_views_.end(); ++it) {
   1546     if (it->first.first == session) {
   1547       request.static_video_views.push_back(it->second.view);
   1548     }
   1549   }
   1550   call->SendViewRequest(session, request);
   1551 }
   1552 
   1553 buzz::Jid CallClient::GenerateRandomMucJid() {
   1554   // Generate a GUID of the form XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX,
   1555   // for an eventual JID of private-chat-<GUID>@groupchat.google.com.
   1556   char guid[37], guid_room[256];
   1557   for (size_t i = 0; i < ARRAY_SIZE(guid) - 1;) {
   1558     if (i == 8 || i == 13 || i == 18 || i == 23) {
   1559       guid[i++] = '-';
   1560     } else {
   1561       sprintf(guid + i, "%04x", rand());
   1562       i += 4;
   1563     }
   1564   }
   1565 
   1566   rtc::sprintfn(guid_room,
   1567                       ARRAY_SIZE(guid_room),
   1568                       "private-chat-%s@%s",
   1569                       guid,
   1570                       pmuc_domain_.c_str());
   1571   return buzz::Jid(guid_room);
   1572 }
   1573 
   1574 bool CallClient::SelectFirstDesktopScreencastId(
   1575     cricket::ScreencastId* screencastid) {
   1576   if (!rtc::WindowPickerFactory::IsSupported()) {
   1577     LOG(LS_WARNING) << "Window picker not suported on this OS.";
   1578     return false;
   1579   }
   1580 
   1581   rtc::WindowPicker* picker =
   1582       rtc::WindowPickerFactory::CreateWindowPicker();
   1583   if (!picker) {
   1584     LOG(LS_WARNING) << "Could not create a window picker.";
   1585     return false;
   1586   }
   1587 
   1588   rtc::DesktopDescriptionList desktops;
   1589   if (!picker->GetDesktopList(&desktops) || desktops.empty()) {
   1590     LOG(LS_WARNING) << "Could not get a list of desktops.";
   1591     return false;
   1592   }
   1593 
   1594   *screencastid = cricket::ScreencastId(desktops[0].id());
   1595   return true;
   1596 }
   1597 
   1598 void CallClient::PrintStats() const {
   1599   const cricket::VoiceMediaInfo& vmi = call_->last_voice_media_info();
   1600 
   1601   for (std::vector<cricket::VoiceSenderInfo>::const_iterator it =
   1602        vmi.senders.begin(); it != vmi.senders.end(); ++it) {
   1603     console_->PrintLine("Sender: ssrc=%u codec='%s' bytes=%d packets=%d "
   1604                         "rtt=%d jitter=%d",
   1605                         it->ssrc(), it->codec_name.c_str(), it->bytes_sent,
   1606                         it->packets_sent, it->rtt_ms, it->jitter_ms);
   1607   }
   1608 
   1609   for (std::vector<cricket::VoiceReceiverInfo>::const_iterator it =
   1610        vmi.receivers.begin(); it != vmi.receivers.end(); ++it) {
   1611     console_->PrintLine("Receiver: ssrc=%u bytes=%d packets=%d "
   1612                         "jitter=%d loss=%.2f",
   1613                         it->ssrc(), it->bytes_rcvd, it->packets_rcvd,
   1614                         it->jitter_ms, it->fraction_lost);
   1615   }
   1616 }
   1617