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
68namespace {
69
70// Must be period >= timeout.
71const uint32 kPingPeriodMillis = 10000;
72const uint32 kPingTimeoutMillis = 10000;
73
74const 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
86std::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
95int 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
106const 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.
125const 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
139const 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
147const 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
168void 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
374CallClient::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
406CallClient::~CallClient() {
407  delete media_client_;
408  delete roster_;
409  delete worker_thread_;
410}
411
412const 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
441void 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
455void 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
480void 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
552void CallClient::OnRequestSignaling() {
553  session_manager_->OnSignalingReady();
554}
555
556void CallClient::OnSessionCreate(cricket::Session* session, bool initiate) {
557  session->set_current_protocol(signaling_protocol_);
558}
559
560void CallClient::OnCallCreate(cricket::Call* call) {
561  call->SignalSessionState.connect(this, &CallClient::OnSessionState);
562  call->SignalMediaStreamsUpdate.connect(
563      this, &CallClient::OnMediaStreamsUpdate);
564}
565
566void 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
622void 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
638void 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
644void 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
650void 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
656void 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
686void 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
694void 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
702void CallClient::SendStatus(const buzz::PresenceStatus& status) {
703  presence_out_->Send(status);
704}
705
706void 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
730void 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
741void 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
754void 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
796void 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
811bool 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
857void 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
881bool 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
925bool 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
944void 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
958void 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
992void 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
1004void 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
1017void 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
1026void 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
1035void 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
1047void 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
1053void 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
1059void CallClient::OnHangoutPublishAudioMuteError(
1060    const std::string& task_id, const buzz::XmlElement* stanza) {
1061  console_->PrintLine("Failed to publish audio mute state.");
1062}
1063
1064void CallClient::OnHangoutPublishPresenterError(
1065    const std::string& task_id, const buzz::XmlElement* stanza) {
1066  console_->PrintLine("Failed to publish presenting state.");
1067}
1068
1069void CallClient::OnHangoutPublishRecordingError(
1070    const std::string& task_id, const buzz::XmlElement* stanza) {
1071  console_->PrintLine("Failed to publish recording state.");
1072}
1073
1074void 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
1080void 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
1094void CallClient::SetupAcceptedCall() {
1095  if (call_->has_data()) {
1096    call_->SignalDataReceived.connect(this, &CallClient::OnDataReceived);
1097  }
1098}
1099
1100void CallClient::Reject() {
1101  ASSERT(call_ && incoming_call_);
1102  call_->RejectSession(call_->sessions()[0]);
1103  incoming_call_ = false;
1104}
1105
1106void CallClient::Quit() {
1107  rtc::Thread::Current()->Quit();
1108}
1109
1110void 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
1124void 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
1151void 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
1162void 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
1186void 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
1213void 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
1222void CallClient::OnRoomConfigResult(buzz::MucRoomConfigTask* task) {
1223  JoinMuc(task->room_jid());
1224}
1225
1226void 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
1239void 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
1262void 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
1272void 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
1293bool CallClient::InMuc() {
1294  const buzz::Jid* muc_jid = FirstMucJid();
1295  if (!muc_jid) return false;
1296  return muc_jid->IsValid();
1297}
1298
1299const buzz::Jid* CallClient::FirstMucJid() {
1300  if (mucs_.empty()) return NULL;
1301  return &(mucs_.begin()->first);
1302}
1303
1304void 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
1337void 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
1357void 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
1398void 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
1411void 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
1417void CallClient::OnDevicesChange() {
1418  console_->PrintLine("Devices changed.");
1419  SetMediaCaps(media_client_->GetCapabilities(), &my_status_);
1420  SendStatus(my_status_);
1421}
1422
1423void CallClient::SetVolume(const std::string& level) {
1424  media_client_->SetOutputVolume(strtol(level.c_str(), NULL, 10));
1425}
1426
1427void 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
1444void 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
1454void 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
1466void 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?
1498void 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
1514bool 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
1527void 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
1541void 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
1553buzz::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
1574bool 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
1598void 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