Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / third_party / libjingle / source / talk / examples / call / callclient.cc
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_) {
893     if (!options.is_muc) {
894       call_->SetLocalRenderer(local_renderer_);
895     }
896   }
897   if (options.is_muc) {
898     const std::string& nick = mucs_[jid]->local_jid().resource();
899     hangout_pubsub_client_ =
900         new buzz::HangoutPubSubClient(xmpp_client_, jid, nick);
901     hangout_pubsub_client_->SignalPresenterStateChange.connect(
902         this, &CallClient::OnPresenterStateChange);
903     hangout_pubsub_client_->SignalAudioMuteStateChange.connect(
904         this, &CallClient::OnAudioMuteStateChange);
905     hangout_pubsub_client_->SignalRecordingStateChange.connect(
906         this, &CallClient::OnRecordingStateChange);
907     hangout_pubsub_client_->SignalRemoteMute.connect(
908         this, &CallClient::OnRemoteMuted);
909     hangout_pubsub_client_->SignalMediaBlock.connect(
910         this, &CallClient::OnMediaBlocked);
911     hangout_pubsub_client_->SignalRequestError.connect(
912         this, &CallClient::OnHangoutRequestError);
913     hangout_pubsub_client_->SignalPublishAudioMuteError.connect(
914         this, &CallClient::OnHangoutPublishAudioMuteError);
915     hangout_pubsub_client_->SignalPublishPresenterError.connect(
916         this, &CallClient::OnHangoutPublishPresenterError);
917     hangout_pubsub_client_->SignalPublishRecordingError.connect(
918         this, &CallClient::OnHangoutPublishRecordingError);
919     hangout_pubsub_client_->SignalRemoteMuteError.connect(
920         this, &CallClient::OnHangoutRemoteMuteError);
921     hangout_pubsub_client_->RequestAll();
922   }
923
924   return true;
925 }
926
927 bool CallClient::InitiateAdditionalSession(const std::string& name,
928                                            cricket::CallOptions options) {
929   // Can't add a session if there is no call yet.
930   if (!call_)
931     return false;
932
933   buzz::Jid jid;
934   if (!FindJid(name, &jid, &options))
935     return false;
936
937   std::vector<cricket::Session*>& call_sessions = sessions_[call_->id()];
938   call_sessions.push_back(
939       call_->InitiateSession(jid,
940                              buzz::Jid(call_sessions[0]->remote_name()),
941                              options));
942
943   return true;
944 }
945
946 void CallClient::TerminateAndRemoveSession(cricket::Call* call,
947                                            const std::string& id) {
948   std::vector<cricket::Session*>& call_sessions = sessions_[call->id()];
949   for (std::vector<cricket::Session*>::iterator iter = call_sessions.begin();
950        iter != call_sessions.end(); ++iter) {
951     if ((*iter)->id() == id) {
952       RenderAllStreams(call, *iter, false);
953       call_->TerminateSession(*iter);
954       call_sessions.erase(iter);
955       break;
956     }
957   }
958 }
959
960 void CallClient::PrintCalls() {
961   const std::map<uint32, cricket::Call*>& calls = media_client_->calls();
962   for (std::map<uint32, cricket::Call*>::const_iterator i = calls.begin();
963        i != calls.end(); ++i) {
964     console_->PrintLine("Call (id:%d), is %s",
965                         i->first,
966                         i->second == call_ ? "active" : "on hold");
967     std::vector<cricket::Session *>& sessions = sessions_[call_->id()];
968     for (std::vector<cricket::Session *>::const_iterator j = sessions.begin();
969          j != sessions.end(); ++j) {
970       console_->PrintLine("|--Session (id:%s), to %s", (*j)->id().c_str(),
971                           (*j)->remote_name().c_str());
972
973       std::vector<cricket::StreamParams>::const_iterator k;
974       const std::vector<cricket::StreamParams>* streams =
975           i->second->GetAudioRecvStreams(*j);
976       if (streams)
977         for (k = streams->begin(); k != streams->end(); ++k) {
978           console_->PrintLine("|----Audio Stream: %s", k->ToString().c_str());
979         }
980       streams = i->second->GetVideoRecvStreams(*j);
981       if (streams)
982         for (k = streams->begin(); k != streams->end(); ++k) {
983           console_->PrintLine("|----Video Stream: %s", k->ToString().c_str());
984         }
985       streams = i->second->GetDataRecvStreams(*j);
986       if (streams)
987         for (k = streams->begin(); k != streams->end(); ++k) {
988           console_->PrintLine("|----Data Stream: %s", k->ToString().c_str());
989         }
990     }
991   }
992 }
993
994 void CallClient::SwitchToCall(uint32 call_id) {
995   const std::map<uint32, cricket::Call*>& calls = media_client_->calls();
996   std::map<uint32, cricket::Call*>::const_iterator call_iter =
997       calls.find(call_id);
998   if (call_iter != calls.end()) {
999     media_client_->SetFocus(call_iter->second);
1000     call_ = call_iter->second;
1001   } else {
1002     console_->PrintLine("Unable to find call: %d", call_id);
1003   }
1004 }
1005
1006 void CallClient::OnPresenterStateChange(
1007     const std::string& nick, bool was_presenting, bool is_presenting) {
1008   if (!was_presenting && is_presenting) {
1009     console_->PrintLine("%s now presenting.", nick.c_str());
1010   } else if (was_presenting && !is_presenting) {
1011     console_->PrintLine("%s no longer presenting.", nick.c_str());
1012   } else if (was_presenting && is_presenting) {
1013     console_->PrintLine("%s still presenting.", nick.c_str());
1014   } else if (!was_presenting && !is_presenting) {
1015     console_->PrintLine("%s still not presenting.", nick.c_str());
1016   }
1017 }
1018
1019 void CallClient::OnAudioMuteStateChange(
1020     const std::string& nick, bool was_muted, bool is_muted) {
1021   if (!was_muted && is_muted) {
1022     console_->PrintLine("%s now muted.", nick.c_str());
1023   } else if (was_muted && !is_muted) {
1024     console_->PrintLine("%s no longer muted.", nick.c_str());
1025   }
1026 }
1027
1028 void CallClient::OnRecordingStateChange(
1029     const std::string& nick, bool was_recording, bool is_recording) {
1030   if (!was_recording && is_recording) {
1031     console_->PrintLine("%s now recording.", nick.c_str());
1032   } else if (was_recording && !is_recording) {
1033     console_->PrintLine("%s no longer recording.", nick.c_str());
1034   }
1035 }
1036
1037 void CallClient::OnRemoteMuted(const std::string& mutee_nick,
1038                                const std::string& muter_nick,
1039                                bool should_mute_locally) {
1040   if (should_mute_locally) {
1041     call_->Mute(true);
1042     console_->PrintLine("Remote muted by %s.", muter_nick.c_str());
1043   } else {
1044     console_->PrintLine("%s remote muted by %s.",
1045                         mutee_nick.c_str(), muter_nick.c_str());
1046   }
1047 }
1048
1049 void CallClient::OnMediaBlocked(const std::string& blockee_nick,
1050                                 const std::string& blocker_nick) {
1051   console_->PrintLine("%s blocked by %s.",
1052                       blockee_nick.c_str(), blocker_nick.c_str());
1053 }
1054
1055 void CallClient::OnHangoutRequestError(const std::string& node,
1056                                        const buzz::XmlElement* stanza) {
1057   console_->PrintLine("Failed request pub sub items for node %s.",
1058                       node.c_str());
1059 }
1060
1061 void CallClient::OnHangoutPublishAudioMuteError(
1062     const std::string& task_id, const buzz::XmlElement* stanza) {
1063   console_->PrintLine("Failed to publish audio mute state.");
1064 }
1065
1066 void CallClient::OnHangoutPublishPresenterError(
1067     const std::string& task_id, const buzz::XmlElement* stanza) {
1068   console_->PrintLine("Failed to publish presenting state.");
1069 }
1070
1071 void CallClient::OnHangoutPublishRecordingError(
1072     const std::string& task_id, const buzz::XmlElement* stanza) {
1073   console_->PrintLine("Failed to publish recording state.");
1074 }
1075
1076 void CallClient::OnHangoutRemoteMuteError(const std::string& task_id,
1077                                           const std::string& mutee_nick,
1078                                           const buzz::XmlElement* stanza) {
1079   console_->PrintLine("Failed to remote mute.");
1080 }
1081
1082 void CallClient::Accept(const cricket::CallOptions& options) {
1083   ASSERT(call_ && incoming_call_);
1084   ASSERT(sessions_[call_->id()].size() == 1);
1085   cricket::Session* session = GetFirstSession();
1086   call_->AcceptSession(session, options);
1087   media_client_->SetFocus(call_);
1088   if (call_->has_video() && render_) {
1089     call_->SetLocalRenderer(local_renderer_);
1090     RenderAllStreams(call_, session, true);
1091   }
1092   SetupAcceptedCall();
1093   incoming_call_ = false;
1094 }
1095
1096 void CallClient::SetupAcceptedCall() {
1097   if (call_->has_data()) {
1098     call_->SignalDataReceived.connect(this, &CallClient::OnDataReceived);
1099   }
1100 }
1101
1102 void CallClient::Reject() {
1103   ASSERT(call_ && incoming_call_);
1104   call_->RejectSession(call_->sessions()[0]);
1105   incoming_call_ = false;
1106 }
1107
1108 void CallClient::Quit() {
1109   rtc::Thread::Current()->Quit();
1110 }
1111
1112 void CallClient::SetNick(const std::string& muc_nick) {
1113   my_status_.set_nick(muc_nick);
1114
1115   // TODO: We might want to re-send presence, but right
1116   // now, it appears to be ignored by the MUC.
1117   //
1118   // presence_out_->Send(my_status_); for (MucMap::const_iterator itr
1119   // = mucs_.begin(); itr != mucs_.end(); ++itr) {
1120   // presence_out_->SendDirected(itr->second->local_jid(),
1121   // my_status_); }
1122
1123   console_->PrintLine("Nick set to '%s'.", muc_nick.c_str());
1124 }
1125
1126 void CallClient::LookupAndJoinMuc(const std::string& room_name) {
1127   // The room_name can't be empty for lookup task.
1128   if (room_name.empty()) {
1129     console_->PrintLine("Please provide a room name or room jid.");
1130     return;
1131   }
1132
1133   std::string room = room_name;
1134   std::string domain = xmpp_client_->jid().domain();
1135   if (room_name.find("@") != std::string::npos) {
1136     // Assume the room_name is a fully qualified room name.
1137     // We'll find the room name string and domain name string from it.
1138     room = room_name.substr(0, room_name.find("@"));
1139     domain = room_name.substr(room_name.find("@") + 1);
1140   }
1141
1142   buzz::MucRoomLookupTask* lookup_query_task =
1143       buzz::MucRoomLookupTask::CreateLookupTaskForRoomName(
1144           xmpp_client_, buzz::Jid(buzz::STR_GOOGLE_MUC_LOOKUP_JID), room,
1145           domain);
1146   lookup_query_task->SignalResult.connect(this,
1147       &CallClient::OnRoomLookupResponse);
1148   lookup_query_task->SignalError.connect(this,
1149       &CallClient::OnRoomLookupError);
1150   lookup_query_task->Start();
1151 }
1152
1153 void CallClient::JoinMuc(const std::string& room_jid_str) {
1154   if (room_jid_str.empty()) {
1155     buzz::Jid room_jid = GenerateRandomMucJid();
1156     console_->PrintLine("Generated a random room jid: %s",
1157                         room_jid.Str().c_str());
1158     JoinMuc(room_jid);
1159   } else {
1160     JoinMuc(buzz::Jid(room_jid_str));
1161   }
1162 }
1163
1164 void CallClient::JoinMuc(const buzz::Jid& room_jid) {
1165   if (!room_jid.IsValid()) {
1166     console_->PrintLine("Unable to make valid muc endpoint for %s",
1167                         room_jid.Str().c_str());
1168     return;
1169   }
1170
1171   std::string room_nick = room_jid.resource();
1172   if (room_nick.empty()) {
1173     room_nick = (xmpp_client_->jid().node()
1174                  + "_" + xmpp_client_->jid().resource());
1175   }
1176
1177   MucMap::iterator elem = mucs_.find(room_jid);
1178   if (elem != mucs_.end()) {
1179     console_->PrintLine("This MUC already exists.");
1180     return;
1181   }
1182
1183   buzz::Muc* muc = new buzz::Muc(room_jid.BareJid(), room_nick);
1184   mucs_[muc->jid()] = muc;
1185   presence_out_->SendDirected(muc->local_jid(), my_status_);
1186 }
1187
1188 void CallClient::OnRoomLookupResponse(buzz::MucRoomLookupTask* task,
1189                                       const buzz::MucRoomInfo& room) {
1190   // The server requires the room be "configured" before being used.
1191   // We only need to configure it if we create it, but rooms are
1192   // auto-created at lookup, so there's currently no way to know if we
1193   // created it.  So, we configure it every time, just in case.
1194   // Luckily, it appears to be safe to configure a room that's already
1195   // configured.  Our current flow is:
1196   // 1. Lookup/auto-create
1197   // 2. Configure
1198   // 3. Join
1199   // TODO: In the future, once the server supports it, we
1200   // should:
1201   // 1. Lookup
1202   // 2. Create and Configure if necessary
1203   // 3. Join
1204   std::vector<std::string> room_features;
1205   room_features.push_back(buzz::STR_MUC_ROOM_FEATURE_ENTERPRISE);
1206   buzz::MucRoomConfigTask* room_config_task = new buzz::MucRoomConfigTask(
1207       xmpp_client_, room.jid, room.full_name(), room_features);
1208   room_config_task->SignalResult.connect(this,
1209       &CallClient::OnRoomConfigResult);
1210   room_config_task->SignalError.connect(this,
1211       &CallClient::OnRoomConfigError);
1212   room_config_task->Start();
1213 }
1214
1215 void CallClient::OnRoomLookupError(buzz::IqTask* task,
1216                                    const buzz::XmlElement* stanza) {
1217   if (stanza == NULL) {
1218     console_->PrintLine("Room lookup failed.");
1219   } else {
1220     console_->PrintLine("Room lookup error: ", stanza->Str().c_str());
1221   }
1222 }
1223
1224 void CallClient::OnRoomConfigResult(buzz::MucRoomConfigTask* task) {
1225   JoinMuc(task->room_jid());
1226 }
1227
1228 void CallClient::OnRoomConfigError(buzz::IqTask* task,
1229                                    const buzz::XmlElement* stanza) {
1230   console_->PrintLine("Room config failed.");
1231   // We join the muc anyway, because if the room is already
1232   // configured, the configure will fail, but we still want to join.
1233   // Idealy, we'd know why the room config failed and only do this on
1234   // "already configured" errors.  But right now all we get back is
1235   // "not-allowed".
1236   buzz::MucRoomConfigTask* config_task =
1237       static_cast<buzz::MucRoomConfigTask*>(task);
1238   JoinMuc(config_task->room_jid());
1239 }
1240
1241 void CallClient::OnMucInviteReceived(const buzz::Jid& inviter,
1242     const buzz::Jid& room,
1243     const std::vector<buzz::AvailableMediaEntry>& avail) {
1244
1245   console_->PrintLine("Invited to join %s by %s.", room.Str().c_str(),
1246       inviter.Str().c_str());
1247   console_->PrintLine("Available media:");
1248   if (avail.size() > 0) {
1249     for (std::vector<buzz::AvailableMediaEntry>::const_iterator i =
1250             avail.begin();
1251         i != avail.end();
1252         ++i) {
1253       console_->PrintLine("  %s, %s",
1254                           buzz::AvailableMediaEntry::TypeAsString(i->type),
1255                           buzz::AvailableMediaEntry::StatusAsString(i->status));
1256     }
1257   } else {
1258     console_->PrintLine("  None");
1259   }
1260   // We automatically join the room.
1261   JoinMuc(room);
1262 }
1263
1264 void CallClient::OnMucJoined(const buzz::Jid& endpoint) {
1265   MucMap::iterator elem = mucs_.find(endpoint);
1266   ASSERT(elem != mucs_.end() &&
1267          elem->second->state() == buzz::Muc::MUC_JOINING);
1268
1269   buzz::Muc* muc = elem->second;
1270   muc->set_state(buzz::Muc::MUC_JOINED);
1271   console_->PrintLine("Joined \"%s\"", muc->jid().Str().c_str());
1272 }
1273
1274 void CallClient::OnMucStatusUpdate(const buzz::Jid& jid,
1275     const buzz::MucPresenceStatus& status) {
1276
1277   // Look up this muc.
1278   MucMap::iterator elem = mucs_.find(jid);
1279   ASSERT(elem != mucs_.end());
1280
1281   buzz::Muc* muc = elem->second;
1282
1283   if (status.jid().IsBare() || status.jid() == muc->local_jid()) {
1284     // We are only interested in status about other users.
1285     return;
1286   }
1287
1288   if (!status.available()) {
1289     // Remove them from the room.
1290     muc->members().erase(status.jid().resource());
1291   }
1292 }
1293
1294 bool CallClient::InMuc() {
1295   const buzz::Jid* muc_jid = FirstMucJid();
1296   if (!muc_jid) return false;
1297   return muc_jid->IsValid();
1298 }
1299
1300 const buzz::Jid* CallClient::FirstMucJid() {
1301   if (mucs_.empty()) return NULL;
1302   return &(mucs_.begin()->first);
1303 }
1304
1305 void CallClient::LeaveMuc(const std::string& room) {
1306   buzz::Jid room_jid;
1307   const buzz::Jid* muc_jid = FirstMucJid();
1308   if (room.length() > 0) {
1309     room_jid = buzz::Jid(room);
1310   } else if (mucs_.size() > 0) {
1311     // leave the first MUC if no JID specified
1312     if (muc_jid) {
1313       room_jid = *(muc_jid);
1314     }
1315   }
1316
1317   if (!room_jid.IsValid()) {
1318     console_->PrintLine("Invalid MUC JID.");
1319     return;
1320   }
1321
1322   MucMap::iterator elem = mucs_.find(room_jid);
1323   if (elem == mucs_.end()) {
1324     console_->PrintLine("No such MUC.");
1325     return;
1326   }
1327
1328   buzz::Muc* muc = elem->second;
1329   muc->set_state(buzz::Muc::MUC_LEAVING);
1330
1331   buzz::PresenceStatus status;
1332   status.set_jid(my_status_.jid());
1333   status.set_available(false);
1334   status.set_priority(0);
1335   presence_out_->SendDirected(muc->local_jid(), status);
1336 }
1337
1338 void CallClient::OnMucLeft(const buzz::Jid& endpoint, int error) {
1339   // We could be kicked from a room from any state.  We would hope this
1340   // happens While in the MUC_LEAVING state
1341   MucMap::iterator elem = mucs_.find(endpoint);
1342   if (elem == mucs_.end())
1343     return;
1344
1345   buzz::Muc* muc = elem->second;
1346   if (muc->state() == buzz::Muc::MUC_JOINING) {
1347     console_->PrintLine("Failed to join \"%s\", code=%d",
1348                         muc->jid().Str().c_str(), error);
1349   } else if (muc->state() == buzz::Muc::MUC_JOINED) {
1350     console_->PrintLine("Kicked from \"%s\"",
1351                         muc->jid().Str().c_str());
1352   }
1353
1354   delete muc;
1355   mucs_.erase(elem);
1356 }
1357
1358 void CallClient::InviteToMuc(const std::string& given_user,
1359                              const std::string& room) {
1360   std::string user = given_user;
1361
1362   // First find the room.
1363   const buzz::Muc* found_muc;
1364   if (room.length() == 0) {
1365     if (mucs_.size() == 0) {
1366       console_->PrintLine("Not in a room yet; can't invite.");
1367       return;
1368     }
1369     // Invite to the first muc
1370     found_muc = mucs_.begin()->second;
1371   } else {
1372     MucMap::iterator elem = mucs_.find(buzz::Jid(room));
1373     if (elem == mucs_.end()) {
1374       console_->PrintLine("Not in room %s.", room.c_str());
1375       return;
1376     }
1377     found_muc = elem->second;
1378   }
1379
1380   buzz::Jid invite_to = found_muc->jid();
1381
1382   // Now find the user. We invite all of their resources.
1383   bool found_user = false;
1384   buzz::Jid user_jid(user);
1385   for (RosterMap::iterator iter = roster_->begin();
1386        iter != roster_->end(); ++iter) {
1387     if (iter->second.jid.BareEquals(user_jid)) {
1388       buzz::Jid invitee = iter->second.jid;
1389       muc_invite_send_->Send(invite_to, invitee);
1390       found_user = true;
1391     }
1392   }
1393   if (!found_user) {
1394     buzz::Jid invitee = user_jid;
1395     muc_invite_send_->Send(invite_to, invitee);
1396   }
1397 }
1398
1399 void CallClient::GetDevices() {
1400   std::vector<std::string> names;
1401   media_client_->GetAudioInputDevices(&names);
1402   console_->PrintLine("Audio input devices:");
1403   PrintDevices(names);
1404   media_client_->GetAudioOutputDevices(&names);
1405   console_->PrintLine("Audio output devices:");
1406   PrintDevices(names);
1407   media_client_->GetVideoCaptureDevices(&names);
1408   console_->PrintLine("Video capture devices:");
1409   PrintDevices(names);
1410 }
1411
1412 void CallClient::PrintDevices(const std::vector<std::string>& names) {
1413   for (size_t i = 0; i < names.size(); ++i) {
1414     console_->PrintLine("%d: %s", static_cast<int>(i), names[i].c_str());
1415   }
1416 }
1417
1418 void CallClient::OnDevicesChange() {
1419   console_->PrintLine("Devices changed.");
1420   SetMediaCaps(media_client_->GetCapabilities(), &my_status_);
1421   SendStatus(my_status_);
1422 }
1423
1424 void CallClient::SetVolume(const std::string& level) {
1425   media_client_->SetOutputVolume(strtol(level.c_str(), NULL, 10));
1426 }
1427
1428 void CallClient::OnMediaStreamsUpdate(cricket::Call* call,
1429                                       cricket::Session* session,
1430                                       const cricket::MediaStreams& added,
1431                                       const cricket::MediaStreams& removed) {
1432   if (call && call->has_video()) {
1433     for (std::vector<cricket::StreamParams>::const_iterator
1434          it = removed.video().begin(); it != removed.video().end(); ++it) {
1435       RemoveStaticRenderedView(it->first_ssrc());
1436     }
1437
1438     if (render_) {
1439       RenderStreams(call, session, added.video(), true);
1440     }
1441     SendViewRequest(call, session);
1442   }
1443 }
1444
1445 void CallClient::RenderAllStreams(cricket::Call* call,
1446                                   cricket::Session* session,
1447                                   bool enable) {
1448   const std::vector<cricket::StreamParams>* video_streams =
1449       call->GetVideoRecvStreams(session);
1450   if (video_streams) {
1451     RenderStreams(call, session, *video_streams, enable);
1452   }
1453 }
1454
1455 void CallClient::RenderStreams(
1456     cricket::Call* call,
1457     cricket::Session* session,
1458     const std::vector<cricket::StreamParams>& video_streams,
1459     bool enable) {
1460   std::vector<cricket::StreamParams>::const_iterator stream;
1461   for (stream = video_streams.begin(); stream != video_streams.end();
1462        ++stream) {
1463     RenderStream(call, session, *stream, enable);
1464   }
1465 }
1466
1467 void CallClient::RenderStream(cricket::Call* call,
1468                               cricket::Session* session,
1469                               const cricket::StreamParams& stream,
1470                               bool enable) {
1471   if (!stream.has_ssrcs()) {
1472     // Nothing to see here; move along.
1473     return;
1474   }
1475
1476   uint32 ssrc = stream.first_ssrc();
1477   StaticRenderedViews::iterator iter =
1478       static_rendered_views_.find(std::make_pair(session, ssrc));
1479   if (enable) {
1480     if (iter == static_rendered_views_.end()) {
1481       // TODO(pthatcher): Make dimensions and positions more configurable.
1482       int offset = (50 * static_views_accumulated_count_) % 300;
1483       AddStaticRenderedView(session, ssrc, 640, 400, 30,
1484                             offset, offset);
1485       // Should have it now.
1486       iter = static_rendered_views_.find(std::make_pair(session, ssrc));
1487     }
1488     call->SetVideoRenderer(session, ssrc, iter->second.renderer);
1489   } else {
1490     if (iter != static_rendered_views_.end()) {
1491       call->SetVideoRenderer(session, ssrc, NULL);
1492       RemoveStaticRenderedView(ssrc);
1493     }
1494   }
1495 }
1496
1497 // TODO: Would these methods to add and remove views make
1498 // more sense in call.cc?  Would other clients use them?
1499 void CallClient::AddStaticRenderedView(
1500     cricket::Session* session,
1501     uint32 ssrc, int width, int height, int framerate,
1502     int x_offset, int y_offset) {
1503   StaticRenderedView rendered_view(
1504       cricket::StaticVideoView(
1505           cricket::StreamSelector(ssrc), width, height, framerate),
1506       cricket::VideoRendererFactory::CreateGuiVideoRenderer(
1507           x_offset, y_offset));
1508   rendered_view.renderer->SetSize(width, height, 0);
1509   static_rendered_views_.insert(std::make_pair(std::make_pair(session, ssrc),
1510                                                rendered_view));
1511   ++static_views_accumulated_count_;
1512   console_->PrintLine("Added renderer for ssrc %d", ssrc);
1513 }
1514
1515 bool CallClient::RemoveStaticRenderedView(uint32 ssrc) {
1516   for (StaticRenderedViews::iterator it = static_rendered_views_.begin();
1517        it != static_rendered_views_.end(); ++it) {
1518     if (it->second.view.selector.ssrc == ssrc) {
1519       delete it->second.renderer;
1520       static_rendered_views_.erase(it);
1521       console_->PrintLine("Removed renderer for ssrc %d", ssrc);
1522       return true;
1523     }
1524   }
1525   return false;
1526 }
1527
1528 void CallClient::RemoveCallsStaticRenderedViews(cricket::Call* call) {
1529   std::vector<cricket::Session*>& sessions = sessions_[call->id()];
1530   std::set<cricket::Session*> call_sessions(sessions.begin(), sessions.end());
1531   for (StaticRenderedViews::iterator it = static_rendered_views_.begin();
1532        it != static_rendered_views_.end(); ) {
1533     if (call_sessions.find(it->first.first) != call_sessions.end()) {
1534       delete it->second.renderer;
1535       static_rendered_views_.erase(it++);
1536     } else {
1537       ++it;
1538     }
1539   }
1540 }
1541
1542 void CallClient::SendViewRequest(cricket::Call* call,
1543                                  cricket::Session* session) {
1544   cricket::ViewRequest request;
1545   for (StaticRenderedViews::iterator it = static_rendered_views_.begin();
1546        it != static_rendered_views_.end(); ++it) {
1547     if (it->first.first == session) {
1548       request.static_video_views.push_back(it->second.view);
1549     }
1550   }
1551   call->SendViewRequest(session, request);
1552 }
1553
1554 buzz::Jid CallClient::GenerateRandomMucJid() {
1555   // Generate a GUID of the form XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX,
1556   // for an eventual JID of private-chat-<GUID>@groupchat.google.com.
1557   char guid[37], guid_room[256];
1558   for (size_t i = 0; i < ARRAY_SIZE(guid) - 1;) {
1559     if (i == 8 || i == 13 || i == 18 || i == 23) {
1560       guid[i++] = '-';
1561     } else {
1562       sprintf(guid + i, "%04x", rand());
1563       i += 4;
1564     }
1565   }
1566
1567   rtc::sprintfn(guid_room,
1568                       ARRAY_SIZE(guid_room),
1569                       "private-chat-%s@%s",
1570                       guid,
1571                       pmuc_domain_.c_str());
1572   return buzz::Jid(guid_room);
1573 }
1574
1575 bool CallClient::SelectFirstDesktopScreencastId(
1576     cricket::ScreencastId* screencastid) {
1577   if (!rtc::WindowPickerFactory::IsSupported()) {
1578     LOG(LS_WARNING) << "Window picker not suported on this OS.";
1579     return false;
1580   }
1581
1582   rtc::WindowPicker* picker =
1583       rtc::WindowPickerFactory::CreateWindowPicker();
1584   if (!picker) {
1585     LOG(LS_WARNING) << "Could not create a window picker.";
1586     return false;
1587   }
1588
1589   rtc::DesktopDescriptionList desktops;
1590   if (!picker->GetDesktopList(&desktops) || desktops.empty()) {
1591     LOG(LS_WARNING) << "Could not get a list of desktops.";
1592     return false;
1593   }
1594
1595   *screencastid = cricket::ScreencastId(desktops[0].id());
1596   return true;
1597 }
1598
1599 void CallClient::PrintStats() const {
1600   const cricket::VoiceMediaInfo& vmi = call_->last_voice_media_info();
1601
1602   for (std::vector<cricket::VoiceSenderInfo>::const_iterator it =
1603        vmi.senders.begin(); it != vmi.senders.end(); ++it) {
1604     console_->PrintLine("Sender: ssrc=%u codec='%s' bytes=%d packets=%d "
1605                         "rtt=%d jitter=%d",
1606                         it->ssrc(), it->codec_name.c_str(), it->bytes_sent,
1607                         it->packets_sent, it->rtt_ms, it->jitter_ms);
1608   }
1609
1610   for (std::vector<cricket::VoiceReceiverInfo>::const_iterator it =
1611        vmi.receivers.begin(); it != vmi.receivers.end(); ++it) {
1612     console_->PrintLine("Receiver: ssrc=%u bytes=%d packets=%d "
1613                         "jitter=%d loss=%.2f",
1614                         it->ssrc(), it->bytes_rcvd, it->packets_rcvd,
1615                         it->jitter_ms, it->fraction_lost);
1616   }
1617 }