blob: 7d1cd806287dfc2d775f288156705b7b034429bc [file] [log] [blame]
henrike@webrtc.org28e20752013-07-10 00:45:36 +00001/*
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
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +000032#include "webrtc/base/helpers.h"
33#include "webrtc/base/logging.h"
34#include "webrtc/base/network.h"
35#include "webrtc/base/socketaddress.h"
36#include "webrtc/base/stringencode.h"
37#include "webrtc/base/stringutils.h"
38#include "webrtc/base/thread.h"
39#include "webrtc/base/windowpickerfactory.h"
henrike@webrtc.org28e20752013-07-10 00:45:36 +000040#include "talk/examples/call/console.h"
41#include "talk/examples/call/friendinvitesendtask.h"
42#include "talk/examples/call/muc.h"
43#include "talk/examples/call/mucinviterecvtask.h"
44#include "talk/examples/call/mucinvitesendtask.h"
45#include "talk/examples/call/presencepushtask.h"
46#include "talk/media/base/mediacommon.h"
47#include "talk/media/base/mediaengine.h"
48#include "talk/media/base/rtpdataengine.h"
49#include "talk/media/base/screencastid.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/presenceouttask.h"
66#include "talk/xmpp/pingtask.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;
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +000097 if (words.size() > index && rtc::FromString(words[index], &val)) {
henrike@webrtc.org28e20752013-07-10 00:45:36 +000098 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";
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000254 screencast_ssrc_ = rtc::CreateRandomId();
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000255 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),
wu@webrtc.org97077a32013-10-25 21:18:33 +0000399 ssl_identity_(),
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000400 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() {
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000481 worker_thread_ = new rtc::Thread();
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000482 // 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.
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000489 network_manager_ = new rtc::BasicNetworkManager();
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000490
491 // TODO: Decide if the relay address should be specified here.
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000492 rtc::SocketAddress stun_addr("stun.l.google.com", 19302);
buildbot@webrtc.org51c55082014-07-28 22:26:15 +0000493 cricket::ServerAddresses stun_servers;
494 stun_servers.insert(stun_addr);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000495 port_allocator_ = new cricket::BasicPortAllocator(
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000496 network_manager_, stun_servers, rtc::SocketAddress(),
497 rtc::SocketAddress(), rtc::SocketAddress());
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000498
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(
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000688 xmpp_client_, rtc::Thread::Current(),
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000689 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);
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000744 stanza->AddAttr(buzz::QN_ID, rtc::CreateRandomString(16));
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000745 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();
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000784 rtc::Buffer payload(text.data(), text.length());
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000785 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,
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000859 const rtc::Buffer& payload) {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000860 // 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_) {
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
927bool 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
946void 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
960void 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
994void 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
1006void 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
1019void 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
1028void 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
1037void 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
1049void 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
1055void 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
1061void CallClient::OnHangoutPublishAudioMuteError(
1062 const std::string& task_id, const buzz::XmlElement* stanza) {
1063 console_->PrintLine("Failed to publish audio mute state.");
1064}
1065
1066void CallClient::OnHangoutPublishPresenterError(
1067 const std::string& task_id, const buzz::XmlElement* stanza) {
1068 console_->PrintLine("Failed to publish presenting state.");
1069}
1070
1071void CallClient::OnHangoutPublishRecordingError(
1072 const std::string& task_id, const buzz::XmlElement* stanza) {
1073 console_->PrintLine("Failed to publish recording state.");
1074}
1075
1076void 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
1082void 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
1096void CallClient::SetupAcceptedCall() {
1097 if (call_->has_data()) {
1098 call_->SignalDataReceived.connect(this, &CallClient::OnDataReceived);
1099 }
1100}
1101
1102void CallClient::Reject() {
1103 ASSERT(call_ && incoming_call_);
1104 call_->RejectSession(call_->sessions()[0]);
1105 incoming_call_ = false;
1106}
1107
1108void CallClient::Quit() {
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +00001109 rtc::Thread::Current()->Quit();
henrike@webrtc.org28e20752013-07-10 00:45:36 +00001110}
1111
1112void 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
1126void 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
1153void 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
1164void 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
1188void 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
1215void 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
1224void CallClient::OnRoomConfigResult(buzz::MucRoomConfigTask* task) {
1225 JoinMuc(task->room_jid());
1226}
1227
1228void 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
1241void 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
1264void 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
1274void 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
1294bool CallClient::InMuc() {
1295 const buzz::Jid* muc_jid = FirstMucJid();
1296 if (!muc_jid) return false;
1297 return muc_jid->IsValid();
1298}
1299
1300const buzz::Jid* CallClient::FirstMucJid() {
1301 if (mucs_.empty()) return NULL;
1302 return &(mucs_.begin()->first);
1303}
1304
1305void 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
1338void 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
1358void 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
1399void 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
1412void 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
1418void CallClient::OnDevicesChange() {
1419 console_->PrintLine("Devices changed.");
1420 SetMediaCaps(media_client_->GetCapabilities(), &my_status_);
1421 SendStatus(my_status_);
1422}
1423
1424void CallClient::SetVolume(const std::string& level) {
1425 media_client_->SetOutputVolume(strtol(level.c_str(), NULL, 10));
1426}
1427
1428void 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
1445void 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
1455void 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
1467void 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?
1499void 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
1515bool 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
1528void 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
1542void 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
1554buzz::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
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +00001567 rtc::sprintfn(guid_room,
henrike@webrtc.org28e20752013-07-10 00:45:36 +00001568 ARRAY_SIZE(guid_room),
1569 "private-chat-%s@%s",
1570 guid,
1571 pmuc_domain_.c_str());
1572 return buzz::Jid(guid_room);
1573}
1574
1575bool CallClient::SelectFirstDesktopScreencastId(
1576 cricket::ScreencastId* screencastid) {
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +00001577 if (!rtc::WindowPickerFactory::IsSupported()) {
henrike@webrtc.org28e20752013-07-10 00:45:36 +00001578 LOG(LS_WARNING) << "Window picker not suported on this OS.";
1579 return false;
1580 }
1581
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +00001582 rtc::WindowPicker* picker =
1583 rtc::WindowPickerFactory::CreateWindowPicker();
henrike@webrtc.org28e20752013-07-10 00:45:36 +00001584 if (!picker) {
1585 LOG(LS_WARNING) << "Could not create a window picker.";
1586 return false;
1587 }
1588
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +00001589 rtc::DesktopDescriptionList desktops;
henrike@webrtc.org28e20752013-07-10 00:45:36 +00001590 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
1599void 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",
sergeyu@chromium.org5bc25c42013-12-05 00:24:06 +00001606 it->ssrc(), it->codec_name.c_str(), it->bytes_sent,
henrike@webrtc.org28e20752013-07-10 00:45:36 +00001607 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",
sergeyu@chromium.org5bc25c42013-12-05 00:24:06 +00001614 it->ssrc(), it->bytes_rcvd, it->packets_rcvd,
henrike@webrtc.org28e20752013-07-10 00:45:36 +00001615 it->jitter_ms, it->fraction_lost);
1616 }
1617}