1 /* 2 * libjingle 3 * Copyright 2004--2005, Google Inc. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright notice, 9 * this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright notice, 11 * this list of conditions and the following disclaimer in the documentation 12 * and/or other materials provided with the distribution. 13 * 3. The name of the author may not be used to endorse or promote products 14 * derived from this software without specific prior written permission. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED 17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 */ 27 28 #include "talk/examples/call/callclient.h" 29 30 #include <string> 31 32 #include "talk/base/helpers.h" 33 #include "talk/base/logging.h" 34 #include "talk/base/network.h" 35 #include "talk/base/socketaddress.h" 36 #include "talk/base/stringencode.h" 37 #include "talk/base/stringutils.h" 38 #include "talk/base/thread.h" 39 #include "talk/base/windowpickerfactory.h" 40 #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 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 && talk_base::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_ = talk_base::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 talk_base::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 talk_base::BasicNetworkManager(); 490 491 // TODO: Decide if the relay address should be specified here. 492 talk_base::SocketAddress stun_addr("stun.l.google.com", 19302); 493 port_allocator_ = new cricket::BasicPortAllocator( 494 network_manager_, stun_addr, talk_base::SocketAddress(), 495 talk_base::SocketAddress(), talk_base::SocketAddress()); 496 497 if (portallocator_flags_ != 0) { 498 port_allocator_->set_flags(portallocator_flags_); 499 } 500 session_manager_ = new cricket::SessionManager( 501 port_allocator_, worker_thread_); 502 session_manager_->set_secure(dtls_policy_); 503 session_manager_->set_identity(ssl_identity_.get()); 504 session_manager_->set_transport_protocol(transport_protocol_); 505 session_manager_->SignalRequestSignaling.connect( 506 this, &CallClient::OnRequestSignaling); 507 session_manager_->SignalSessionCreate.connect( 508 this, &CallClient::OnSessionCreate); 509 session_manager_->OnSignalingReady(); 510 511 session_manager_task_ = 512 new cricket::SessionManagerTask(xmpp_client_, session_manager_); 513 session_manager_task_->EnableOutgoingMessages(); 514 session_manager_task_->Start(); 515 516 if (!media_engine_) { 517 media_engine_ = cricket::MediaEngineFactory::Create(); 518 } 519 520 if (!data_engine_) { 521 if (data_channel_type_ == cricket::DCT_SCTP) { 522 #ifdef HAVE_SCTP 523 data_engine_ = new cricket::SctpDataEngine(); 524 #else 525 LOG(LS_WARNING) << "SCTP Data Engine not supported."; 526 data_channel_type_ = cricket::DCT_NONE; 527 data_engine_ = new cricket::RtpDataEngine(); 528 #endif 529 } else { 530 // Even if we have DCT_NONE, we still have a data engine, just 531 // to make sure it isn't NULL. 532 data_engine_ = new cricket::RtpDataEngine(); 533 } 534 } 535 536 media_client_ = new cricket::MediaSessionClient( 537 xmpp_client_->jid(), 538 session_manager_, 539 media_engine_, 540 data_engine_, 541 cricket::DeviceManagerFactory::Create()); 542 media_client_->SignalCallCreate.connect(this, &CallClient::OnCallCreate); 543 media_client_->SignalCallDestroy.connect(this, &CallClient::OnCallDestroy); 544 media_client_->SignalDevicesChange.connect(this, 545 &CallClient::OnDevicesChange); 546 media_client_->set_secure(sdes_policy_); 547 media_client_->set_multisession_enabled(multisession_enabled_); 548 } 549 550 void CallClient::OnRequestSignaling() { 551 session_manager_->OnSignalingReady(); 552 } 553 554 void CallClient::OnSessionCreate(cricket::Session* session, bool initiate) { 555 session->set_current_protocol(signaling_protocol_); 556 } 557 558 void CallClient::OnCallCreate(cricket::Call* call) { 559 call->SignalSessionState.connect(this, &CallClient::OnSessionState); 560 call->SignalMediaStreamsUpdate.connect( 561 this, &CallClient::OnMediaStreamsUpdate); 562 } 563 564 void CallClient::OnSessionState(cricket::Call* call, 565 cricket::Session* session, 566 cricket::Session::State state) { 567 if (state == cricket::Session::STATE_RECEIVEDINITIATE) { 568 buzz::Jid jid(session->remote_name()); 569 if (call_ == call && multisession_enabled_) { 570 // We've received an initiate for an existing call. This is actually a 571 // new session for that call. 572 console_->PrintLine("Incoming session from '%s'", jid.Str().c_str()); 573 AddSession(session); 574 575 cricket::CallOptions options; 576 options.has_video = call_->has_video(); 577 options.data_channel_type = data_channel_type_; 578 call_->AcceptSession(session, options); 579 580 if (call_->has_video() && render_) { 581 RenderAllStreams(call, session, true); 582 } 583 } else { 584 console_->PrintLine("Incoming call from '%s'", jid.Str().c_str()); 585 call_ = call; 586 AddSession(session); 587 incoming_call_ = true; 588 if (call->has_video() && render_) { 589 local_renderer_ = 590 cricket::VideoRendererFactory::CreateGuiVideoRenderer(160, 100); 591 } 592 if (auto_accept_) { 593 cricket::CallOptions options; 594 options.has_video = true; 595 options.data_channel_type = data_channel_type_; 596 Accept(options); 597 } 598 } 599 } else if (state == cricket::Session::STATE_SENTINITIATE) { 600 if (call->has_video() && render_) { 601 local_renderer_ = 602 cricket::VideoRendererFactory::CreateGuiVideoRenderer(160, 100); 603 } 604 console_->PrintLine("calling..."); 605 } else if (state == cricket::Session::STATE_RECEIVEDACCEPT) { 606 console_->PrintLine("call answered"); 607 SetupAcceptedCall(); 608 } else if (state == cricket::Session::STATE_RECEIVEDREJECT) { 609 console_->PrintLine("call not answered"); 610 } else if (state == cricket::Session::STATE_INPROGRESS) { 611 console_->PrintLine("call in progress"); 612 call->SignalSpeakerMonitor.connect(this, &CallClient::OnSpeakerChanged); 613 call->StartSpeakerMonitor(session); 614 } else if (state == cricket::Session::STATE_RECEIVEDTERMINATE) { 615 console_->PrintLine("other side terminated"); 616 TerminateAndRemoveSession(call, session->id()); 617 } 618 } 619 620 void CallClient::OnSpeakerChanged(cricket::Call* call, 621 cricket::Session* session, 622 const cricket::StreamParams& speaker) { 623 if (!speaker.has_ssrcs()) { 624 console_->PrintLine("Session %s has no current speaker.", 625 session->id().c_str()); 626 } else if (speaker.id.empty()) { 627 console_->PrintLine("Session %s speaker change to unknown (%u).", 628 session->id().c_str(), speaker.first_ssrc()); 629 } else { 630 console_->PrintLine("Session %s speaker changed to %s (%u).", 631 session->id().c_str(), speaker.id.c_str(), 632 speaker.first_ssrc()); 633 } 634 } 635 636 void SetMediaCaps(int media_caps, buzz::PresenceStatus* status) { 637 status->set_voice_capability((media_caps & cricket::AUDIO_RECV) != 0); 638 status->set_video_capability((media_caps & cricket::VIDEO_RECV) != 0); 639 status->set_camera_capability((media_caps & cricket::VIDEO_SEND) != 0); 640 } 641 642 void SetCaps(int media_caps, buzz::PresenceStatus* status) { 643 status->set_know_capabilities(true); 644 status->set_pmuc_capability(true); 645 SetMediaCaps(media_caps, status); 646 } 647 648 void SetAvailable(const buzz::Jid& jid, buzz::PresenceStatus* status) { 649 status->set_jid(jid); 650 status->set_available(true); 651 status->set_show(buzz::PresenceStatus::SHOW_ONLINE); 652 } 653 654 void CallClient::InitPresence() { 655 presence_push_ = new buzz::PresencePushTask(xmpp_client_, this); 656 presence_push_->SignalStatusUpdate.connect( 657 this, &CallClient::OnStatusUpdate); 658 presence_push_->SignalMucJoined.connect(this, &CallClient::OnMucJoined); 659 presence_push_->SignalMucLeft.connect(this, &CallClient::OnMucLeft); 660 presence_push_->SignalMucStatusUpdate.connect( 661 this, &CallClient::OnMucStatusUpdate); 662 presence_push_->Start(); 663 664 presence_out_ = new buzz::PresenceOutTask(xmpp_client_); 665 SetAvailable(xmpp_client_->jid(), &my_status_); 666 SetCaps(media_client_->GetCapabilities(), &my_status_); 667 SendStatus(my_status_); 668 presence_out_->Start(); 669 670 muc_invite_recv_ = new buzz::MucInviteRecvTask(xmpp_client_); 671 muc_invite_recv_->SignalInviteReceived.connect(this, 672 &CallClient::OnMucInviteReceived); 673 muc_invite_recv_->Start(); 674 675 muc_invite_send_ = new buzz::MucInviteSendTask(xmpp_client_); 676 muc_invite_send_->Start(); 677 678 friend_invite_send_ = new buzz::FriendInviteSendTask(xmpp_client_); 679 friend_invite_send_->Start(); 680 681 StartXmppPing(); 682 } 683 684 void CallClient::StartXmppPing() { 685 buzz::PingTask* ping = new buzz::PingTask( 686 xmpp_client_, talk_base::Thread::Current(), 687 kPingPeriodMillis, kPingTimeoutMillis); 688 ping->SignalTimeout.connect(this, &CallClient::OnPingTimeout); 689 ping->Start(); 690 } 691 692 void CallClient::OnPingTimeout() { 693 LOG(LS_WARNING) << "XMPP Ping timeout. Will keep trying..."; 694 StartXmppPing(); 695 696 // Or should we do this instead? 697 // Quit(); 698 } 699 700 void CallClient::SendStatus(const buzz::PresenceStatus& status) { 701 presence_out_->Send(status); 702 } 703 704 void CallClient::OnStatusUpdate(const buzz::PresenceStatus& status) { 705 RosterItem item; 706 item.jid = status.jid(); 707 item.show = status.show(); 708 item.status = status.status(); 709 710 std::string key = item.jid.Str(); 711 712 if (status.available() && status.voice_capability()) { 713 if (show_roster_messages_) { 714 console_->PrintLine("Adding to roster: %s", key.c_str()); 715 } 716 (*roster_)[key] = item; 717 // TODO: Make some of these constants. 718 } else { 719 if (show_roster_messages_) { 720 console_->PrintLine("Removing from roster: %s", key.c_str()); 721 } 722 RosterMap::iterator iter = roster_->find(key); 723 if (iter != roster_->end()) 724 roster_->erase(iter); 725 } 726 } 727 728 void CallClient::PrintRoster() { 729 console_->PrintLine("Roster contains %d callable", roster_->size()); 730 RosterMap::iterator iter = roster_->begin(); 731 while (iter != roster_->end()) { 732 console_->PrintLine("%s - %s", 733 iter->second.jid.BareJid().Str().c_str(), 734 DescribeStatus(iter->second.show, iter->second.status)); 735 iter++; 736 } 737 } 738 739 void CallClient::SendChat(const std::string& to, const std::string msg) { 740 buzz::XmlElement* stanza = new buzz::XmlElement(buzz::QN_MESSAGE); 741 stanza->AddAttr(buzz::QN_TO, to); 742 stanza->AddAttr(buzz::QN_ID, talk_base::CreateRandomString(16)); 743 stanza->AddAttr(buzz::QN_TYPE, "chat"); 744 buzz::XmlElement* body = new buzz::XmlElement(buzz::QN_BODY); 745 body->SetBodyText(msg); 746 stanza->AddElement(body); 747 748 xmpp_client_->SendStanza(stanza); 749 delete stanza; 750 } 751 752 void CallClient::SendData(const std::string& streamid, 753 const std::string& text) { 754 // TODO(mylesj): Support sending data over sessions other than the first. 755 cricket::Session* session = GetFirstSession(); 756 if (!call_ || !session) { 757 console_->PrintLine("Must be in a call to send data."); 758 return; 759 } 760 if (!call_->has_data()) { 761 console_->PrintLine("This call doesn't have a data channel."); 762 return; 763 } 764 765 const cricket::DataContentDescription* data = 766 cricket::GetFirstDataContentDescription(session->local_description()); 767 if (!data) { 768 console_->PrintLine("This call doesn't have a data content."); 769 return; 770 } 771 772 cricket::StreamParams stream; 773 if (!cricket::GetStreamByIds( 774 data->streams(), "", streamid, &stream)) { 775 LOG(LS_WARNING) << "Could not send data: no such stream: " 776 << streamid << "."; 777 return; 778 } 779 780 cricket::SendDataParams params; 781 params.ssrc = stream.first_ssrc(); 782 talk_base::Buffer payload(text.data(), text.length()); 783 cricket::SendDataResult result; 784 bool sent = call_->SendData(session, params, payload, &result); 785 if (!sent) { 786 if (result == cricket::SDR_BLOCK) { 787 LOG(LS_WARNING) << "Could not send data because it would block."; 788 } else { 789 LOG(LS_WARNING) << "Could not send data for unknown reason."; 790 } 791 } 792 } 793 794 void CallClient::InviteFriend(const std::string& name) { 795 buzz::Jid jid(name); 796 if (!jid.IsValid() || jid.node() == "") { 797 console_->PrintLine("Invalid JID. JIDs should be in the form user@domain."); 798 return; 799 } 800 // Note: for some reason the Buzz backend does not forward our presence 801 // subscription requests to the end user when that user is another call 802 // client as opposed to a Smurf user. Thus, in that scenario, you must 803 // run the friend command as the other user too to create the linkage 804 // (and you won't be notified to do so). 805 friend_invite_send_->Send(jid); 806 console_->PrintLine("Requesting to befriend %s.", name.c_str()); 807 } 808 809 bool CallClient::FindJid(const std::string& name, buzz::Jid* found_jid, 810 cricket::CallOptions* options) { 811 bool found = false; 812 options->is_muc = false; 813 buzz::Jid callto_jid(name); 814 if (name.length() == 0 && mucs_.size() > 0) { 815 // if no name, and in a MUC, establish audio with the MUC 816 *found_jid = mucs_.begin()->first; 817 found = true; 818 options->is_muc = true; 819 } else if (name[0] == '+') { 820 // if the first character is a +, assume it's a phone number 821 *found_jid = callto_jid; 822 found = true; 823 } else { 824 // otherwise, it's a friend 825 for (RosterMap::iterator iter = roster_->begin(); 826 iter != roster_->end(); ++iter) { 827 if (iter->second.jid.BareEquals(callto_jid)) { 828 found = true; 829 *found_jid = iter->second.jid; 830 break; 831 } 832 } 833 834 if (!found) { 835 if (mucs_.count(callto_jid) == 1 && 836 mucs_[callto_jid]->state() == buzz::Muc::MUC_JOINED) { 837 found = true; 838 *found_jid = callto_jid; 839 options->is_muc = true; 840 } 841 } 842 } 843 844 if (found) { 845 console_->PrintLine("Found %s '%s'", 846 options->is_muc ? "room" : "online friend", 847 found_jid->Str().c_str()); 848 } else { 849 console_->PrintLine("Could not find online friend '%s'", name.c_str()); 850 } 851 852 return found; 853 } 854 855 void CallClient::OnDataReceived(cricket::Call*, 856 const cricket::ReceiveDataParams& params, 857 const talk_base::Buffer& payload) { 858 // TODO(mylesj): Support receiving data on sessions other than the first. 859 cricket::Session* session = GetFirstSession(); 860 if (!session) 861 return; 862 863 cricket::StreamParams stream; 864 const std::vector<cricket::StreamParams>* data_streams = 865 call_->GetDataRecvStreams(session); 866 std::string text(payload.data(), payload.length()); 867 if (data_streams && GetStreamBySsrc(*data_streams, params.ssrc, &stream)) { 868 console_->PrintLine( 869 "Received data from '%s' on stream '%s' (ssrc=%u): %s", 870 stream.groupid.c_str(), stream.id.c_str(), 871 params.ssrc, text.c_str()); 872 } else { 873 console_->PrintLine( 874 "Received data (ssrc=%u): %s", 875 params.ssrc, text.c_str()); 876 } 877 } 878 879 bool CallClient::PlaceCall(const std::string& name, 880 cricket::CallOptions options) { 881 buzz::Jid jid; 882 if (!FindJid(name, &jid, &options)) 883 return false; 884 885 if (!call_) { 886 call_ = media_client_->CreateCall(); 887 AddSession(call_->InitiateSession(jid, media_client_->jid(), options)); 888 } 889 media_client_->SetFocus(call_); 890 if (call_->has_video() && render_) { 891 if (!options.is_muc) { 892 call_->SetLocalRenderer(local_renderer_); 893 } 894 } 895 if (options.is_muc) { 896 const std::string& nick = mucs_[jid]->local_jid().resource(); 897 hangout_pubsub_client_ = 898 new buzz::HangoutPubSubClient(xmpp_client_, jid, nick); 899 hangout_pubsub_client_->SignalPresenterStateChange.connect( 900 this, &CallClient::OnPresenterStateChange); 901 hangout_pubsub_client_->SignalAudioMuteStateChange.connect( 902 this, &CallClient::OnAudioMuteStateChange); 903 hangout_pubsub_client_->SignalRecordingStateChange.connect( 904 this, &CallClient::OnRecordingStateChange); 905 hangout_pubsub_client_->SignalRemoteMute.connect( 906 this, &CallClient::OnRemoteMuted); 907 hangout_pubsub_client_->SignalMediaBlock.connect( 908 this, &CallClient::OnMediaBlocked); 909 hangout_pubsub_client_->SignalRequestError.connect( 910 this, &CallClient::OnHangoutRequestError); 911 hangout_pubsub_client_->SignalPublishAudioMuteError.connect( 912 this, &CallClient::OnHangoutPublishAudioMuteError); 913 hangout_pubsub_client_->SignalPublishPresenterError.connect( 914 this, &CallClient::OnHangoutPublishPresenterError); 915 hangout_pubsub_client_->SignalPublishRecordingError.connect( 916 this, &CallClient::OnHangoutPublishRecordingError); 917 hangout_pubsub_client_->SignalRemoteMuteError.connect( 918 this, &CallClient::OnHangoutRemoteMuteError); 919 hangout_pubsub_client_->RequestAll(); 920 } 921 922 return true; 923 } 924 925 bool CallClient::InitiateAdditionalSession(const std::string& name, 926 cricket::CallOptions options) { 927 // Can't add a session if there is no call yet. 928 if (!call_) 929 return false; 930 931 buzz::Jid jid; 932 if (!FindJid(name, &jid, &options)) 933 return false; 934 935 std::vector<cricket::Session*>& call_sessions = sessions_[call_->id()]; 936 call_sessions.push_back( 937 call_->InitiateSession(jid, 938 buzz::Jid(call_sessions[0]->remote_name()), 939 options)); 940 941 return true; 942 } 943 944 void CallClient::TerminateAndRemoveSession(cricket::Call* call, 945 const std::string& id) { 946 std::vector<cricket::Session*>& call_sessions = sessions_[call->id()]; 947 for (std::vector<cricket::Session*>::iterator iter = call_sessions.begin(); 948 iter != call_sessions.end(); ++iter) { 949 if ((*iter)->id() == id) { 950 RenderAllStreams(call, *iter, false); 951 call_->TerminateSession(*iter); 952 call_sessions.erase(iter); 953 break; 954 } 955 } 956 } 957 958 void CallClient::PrintCalls() { 959 const std::map<uint32, cricket::Call*>& calls = media_client_->calls(); 960 for (std::map<uint32, cricket::Call*>::const_iterator i = calls.begin(); 961 i != calls.end(); ++i) { 962 console_->PrintLine("Call (id:%d), is %s", 963 i->first, 964 i->second == call_ ? "active" : "on hold"); 965 std::vector<cricket::Session *>& sessions = sessions_[call_->id()]; 966 for (std::vector<cricket::Session *>::const_iterator j = sessions.begin(); 967 j != sessions.end(); ++j) { 968 console_->PrintLine("|--Session (id:%s), to %s", (*j)->id().c_str(), 969 (*j)->remote_name().c_str()); 970 971 std::vector<cricket::StreamParams>::const_iterator k; 972 const std::vector<cricket::StreamParams>* streams = 973 i->second->GetAudioRecvStreams(*j); 974 if (streams) 975 for (k = streams->begin(); k != streams->end(); ++k) { 976 console_->PrintLine("|----Audio Stream: %s", k->ToString().c_str()); 977 } 978 streams = i->second->GetVideoRecvStreams(*j); 979 if (streams) 980 for (k = streams->begin(); k != streams->end(); ++k) { 981 console_->PrintLine("|----Video Stream: %s", k->ToString().c_str()); 982 } 983 streams = i->second->GetDataRecvStreams(*j); 984 if (streams) 985 for (k = streams->begin(); k != streams->end(); ++k) { 986 console_->PrintLine("|----Data Stream: %s", k->ToString().c_str()); 987 } 988 } 989 } 990 } 991 992 void CallClient::SwitchToCall(uint32 call_id) { 993 const std::map<uint32, cricket::Call*>& calls = media_client_->calls(); 994 std::map<uint32, cricket::Call*>::const_iterator call_iter = 995 calls.find(call_id); 996 if (call_iter != calls.end()) { 997 media_client_->SetFocus(call_iter->second); 998 call_ = call_iter->second; 999 } else { 1000 console_->PrintLine("Unable to find call: %d", call_id); 1001 } 1002 } 1003 1004 void CallClient::OnPresenterStateChange( 1005 const std::string& nick, bool was_presenting, bool is_presenting) { 1006 if (!was_presenting && is_presenting) { 1007 console_->PrintLine("%s now presenting.", nick.c_str()); 1008 } else if (was_presenting && !is_presenting) { 1009 console_->PrintLine("%s no longer presenting.", nick.c_str()); 1010 } else if (was_presenting && is_presenting) { 1011 console_->PrintLine("%s still presenting.", nick.c_str()); 1012 } else if (!was_presenting && !is_presenting) { 1013 console_->PrintLine("%s still not presenting.", nick.c_str()); 1014 } 1015 } 1016 1017 void CallClient::OnAudioMuteStateChange( 1018 const std::string& nick, bool was_muted, bool is_muted) { 1019 if (!was_muted && is_muted) { 1020 console_->PrintLine("%s now muted.", nick.c_str()); 1021 } else if (was_muted && !is_muted) { 1022 console_->PrintLine("%s no longer muted.", nick.c_str()); 1023 } 1024 } 1025 1026 void CallClient::OnRecordingStateChange( 1027 const std::string& nick, bool was_recording, bool is_recording) { 1028 if (!was_recording && is_recording) { 1029 console_->PrintLine("%s now recording.", nick.c_str()); 1030 } else if (was_recording && !is_recording) { 1031 console_->PrintLine("%s no longer recording.", nick.c_str()); 1032 } 1033 } 1034 1035 void CallClient::OnRemoteMuted(const std::string& mutee_nick, 1036 const std::string& muter_nick, 1037 bool should_mute_locally) { 1038 if (should_mute_locally) { 1039 call_->Mute(true); 1040 console_->PrintLine("Remote muted by %s.", muter_nick.c_str()); 1041 } else { 1042 console_->PrintLine("%s remote muted by %s.", 1043 mutee_nick.c_str(), muter_nick.c_str()); 1044 } 1045 } 1046 1047 void CallClient::OnMediaBlocked(const std::string& blockee_nick, 1048 const std::string& blocker_nick) { 1049 console_->PrintLine("%s blocked by %s.", 1050 blockee_nick.c_str(), blocker_nick.c_str()); 1051 } 1052 1053 void CallClient::OnHangoutRequestError(const std::string& node, 1054 const buzz::XmlElement* stanza) { 1055 console_->PrintLine("Failed request pub sub items for node %s.", 1056 node.c_str()); 1057 } 1058 1059 void CallClient::OnHangoutPublishAudioMuteError( 1060 const std::string& task_id, const buzz::XmlElement* stanza) { 1061 console_->PrintLine("Failed to publish audio mute state."); 1062 } 1063 1064 void CallClient::OnHangoutPublishPresenterError( 1065 const std::string& task_id, const buzz::XmlElement* stanza) { 1066 console_->PrintLine("Failed to publish presenting state."); 1067 } 1068 1069 void CallClient::OnHangoutPublishRecordingError( 1070 const std::string& task_id, const buzz::XmlElement* stanza) { 1071 console_->PrintLine("Failed to publish recording state."); 1072 } 1073 1074 void CallClient::OnHangoutRemoteMuteError(const std::string& task_id, 1075 const std::string& mutee_nick, 1076 const buzz::XmlElement* stanza) { 1077 console_->PrintLine("Failed to remote mute."); 1078 } 1079 1080 void CallClient::Accept(const cricket::CallOptions& options) { 1081 ASSERT(call_ && incoming_call_); 1082 ASSERT(sessions_[call_->id()].size() == 1); 1083 cricket::Session* session = GetFirstSession(); 1084 call_->AcceptSession(session, options); 1085 media_client_->SetFocus(call_); 1086 if (call_->has_video() && render_) { 1087 call_->SetLocalRenderer(local_renderer_); 1088 RenderAllStreams(call_, session, true); 1089 } 1090 SetupAcceptedCall(); 1091 incoming_call_ = false; 1092 } 1093 1094 void CallClient::SetupAcceptedCall() { 1095 if (call_->has_data()) { 1096 call_->SignalDataReceived.connect(this, &CallClient::OnDataReceived); 1097 } 1098 } 1099 1100 void CallClient::Reject() { 1101 ASSERT(call_ && incoming_call_); 1102 call_->RejectSession(call_->sessions()[0]); 1103 incoming_call_ = false; 1104 } 1105 1106 void CallClient::Quit() { 1107 talk_base::Thread::Current()->Quit(); 1108 } 1109 1110 void CallClient::SetNick(const std::string& muc_nick) { 1111 my_status_.set_nick(muc_nick); 1112 1113 // TODO: We might want to re-send presence, but right 1114 // now, it appears to be ignored by the MUC. 1115 // 1116 // presence_out_->Send(my_status_); for (MucMap::const_iterator itr 1117 // = mucs_.begin(); itr != mucs_.end(); ++itr) { 1118 // presence_out_->SendDirected(itr->second->local_jid(), 1119 // my_status_); } 1120 1121 console_->PrintLine("Nick set to '%s'.", muc_nick.c_str()); 1122 } 1123 1124 void CallClient::LookupAndJoinMuc(const std::string& room_name) { 1125 // The room_name can't be empty for lookup task. 1126 if (room_name.empty()) { 1127 console_->PrintLine("Please provide a room name or room jid."); 1128 return; 1129 } 1130 1131 std::string room = room_name; 1132 std::string domain = xmpp_client_->jid().domain(); 1133 if (room_name.find("@") != std::string::npos) { 1134 // Assume the room_name is a fully qualified room name. 1135 // We'll find the room name string and domain name string from it. 1136 room = room_name.substr(0, room_name.find("@")); 1137 domain = room_name.substr(room_name.find("@") + 1); 1138 } 1139 1140 buzz::MucRoomLookupTask* lookup_query_task = 1141 buzz::MucRoomLookupTask::CreateLookupTaskForRoomName( 1142 xmpp_client_, buzz::Jid(buzz::STR_GOOGLE_MUC_LOOKUP_JID), room, 1143 domain); 1144 lookup_query_task->SignalResult.connect(this, 1145 &CallClient::OnRoomLookupResponse); 1146 lookup_query_task->SignalError.connect(this, 1147 &CallClient::OnRoomLookupError); 1148 lookup_query_task->Start(); 1149 } 1150 1151 void CallClient::JoinMuc(const std::string& room_jid_str) { 1152 if (room_jid_str.empty()) { 1153 buzz::Jid room_jid = GenerateRandomMucJid(); 1154 console_->PrintLine("Generated a random room jid: %s", 1155 room_jid.Str().c_str()); 1156 JoinMuc(room_jid); 1157 } else { 1158 JoinMuc(buzz::Jid(room_jid_str)); 1159 } 1160 } 1161 1162 void CallClient::JoinMuc(const buzz::Jid& room_jid) { 1163 if (!room_jid.IsValid()) { 1164 console_->PrintLine("Unable to make valid muc endpoint for %s", 1165 room_jid.Str().c_str()); 1166 return; 1167 } 1168 1169 std::string room_nick = room_jid.resource(); 1170 if (room_nick.empty()) { 1171 room_nick = (xmpp_client_->jid().node() 1172 + "_" + xmpp_client_->jid().resource()); 1173 } 1174 1175 MucMap::iterator elem = mucs_.find(room_jid); 1176 if (elem != mucs_.end()) { 1177 console_->PrintLine("This MUC already exists."); 1178 return; 1179 } 1180 1181 buzz::Muc* muc = new buzz::Muc(room_jid.BareJid(), room_nick); 1182 mucs_[muc->jid()] = muc; 1183 presence_out_->SendDirected(muc->local_jid(), my_status_); 1184 } 1185 1186 void CallClient::OnRoomLookupResponse(buzz::MucRoomLookupTask* task, 1187 const buzz::MucRoomInfo& room) { 1188 // The server requires the room be "configured" before being used. 1189 // We only need to configure it if we create it, but rooms are 1190 // auto-created at lookup, so there's currently no way to know if we 1191 // created it. So, we configure it every time, just in case. 1192 // Luckily, it appears to be safe to configure a room that's already 1193 // configured. Our current flow is: 1194 // 1. Lookup/auto-create 1195 // 2. Configure 1196 // 3. Join 1197 // TODO: In the future, once the server supports it, we 1198 // should: 1199 // 1. Lookup 1200 // 2. Create and Configure if necessary 1201 // 3. Join 1202 std::vector<std::string> room_features; 1203 room_features.push_back(buzz::STR_MUC_ROOM_FEATURE_ENTERPRISE); 1204 buzz::MucRoomConfigTask* room_config_task = new buzz::MucRoomConfigTask( 1205 xmpp_client_, room.jid, room.full_name(), room_features); 1206 room_config_task->SignalResult.connect(this, 1207 &CallClient::OnRoomConfigResult); 1208 room_config_task->SignalError.connect(this, 1209 &CallClient::OnRoomConfigError); 1210 room_config_task->Start(); 1211 } 1212 1213 void CallClient::OnRoomLookupError(buzz::IqTask* task, 1214 const buzz::XmlElement* stanza) { 1215 if (stanza == NULL) { 1216 console_->PrintLine("Room lookup failed."); 1217 } else { 1218 console_->PrintLine("Room lookup error: ", stanza->Str().c_str()); 1219 } 1220 } 1221 1222 void CallClient::OnRoomConfigResult(buzz::MucRoomConfigTask* task) { 1223 JoinMuc(task->room_jid()); 1224 } 1225 1226 void CallClient::OnRoomConfigError(buzz::IqTask* task, 1227 const buzz::XmlElement* stanza) { 1228 console_->PrintLine("Room config failed."); 1229 // We join the muc anyway, because if the room is already 1230 // configured, the configure will fail, but we still want to join. 1231 // Idealy, we'd know why the room config failed and only do this on 1232 // "already configured" errors. But right now all we get back is 1233 // "not-allowed". 1234 buzz::MucRoomConfigTask* config_task = 1235 static_cast<buzz::MucRoomConfigTask*>(task); 1236 JoinMuc(config_task->room_jid()); 1237 } 1238 1239 void CallClient::OnMucInviteReceived(const buzz::Jid& inviter, 1240 const buzz::Jid& room, 1241 const std::vector<buzz::AvailableMediaEntry>& avail) { 1242 1243 console_->PrintLine("Invited to join %s by %s.", room.Str().c_str(), 1244 inviter.Str().c_str()); 1245 console_->PrintLine("Available media:"); 1246 if (avail.size() > 0) { 1247 for (std::vector<buzz::AvailableMediaEntry>::const_iterator i = 1248 avail.begin(); 1249 i != avail.end(); 1250 ++i) { 1251 console_->PrintLine(" %s, %s", 1252 buzz::AvailableMediaEntry::TypeAsString(i->type), 1253 buzz::AvailableMediaEntry::StatusAsString(i->status)); 1254 } 1255 } else { 1256 console_->PrintLine(" None"); 1257 } 1258 // We automatically join the room. 1259 JoinMuc(room); 1260 } 1261 1262 void CallClient::OnMucJoined(const buzz::Jid& endpoint) { 1263 MucMap::iterator elem = mucs_.find(endpoint); 1264 ASSERT(elem != mucs_.end() && 1265 elem->second->state() == buzz::Muc::MUC_JOINING); 1266 1267 buzz::Muc* muc = elem->second; 1268 muc->set_state(buzz::Muc::MUC_JOINED); 1269 console_->PrintLine("Joined \"%s\"", muc->jid().Str().c_str()); 1270 } 1271 1272 void CallClient::OnMucStatusUpdate(const buzz::Jid& jid, 1273 const buzz::MucPresenceStatus& status) { 1274 1275 // Look up this muc. 1276 MucMap::iterator elem = mucs_.find(jid); 1277 ASSERT(elem != mucs_.end()); 1278 1279 buzz::Muc* muc = elem->second; 1280 1281 if (status.jid().IsBare() || status.jid() == muc->local_jid()) { 1282 // We are only interested in status about other users. 1283 return; 1284 } 1285 1286 if (!status.available()) { 1287 // Remove them from the room. 1288 muc->members().erase(status.jid().resource()); 1289 } 1290 } 1291 1292 bool CallClient::InMuc() { 1293 const buzz::Jid* muc_jid = FirstMucJid(); 1294 if (!muc_jid) return false; 1295 return muc_jid->IsValid(); 1296 } 1297 1298 const buzz::Jid* CallClient::FirstMucJid() { 1299 if (mucs_.empty()) return NULL; 1300 return &(mucs_.begin()->first); 1301 } 1302 1303 void CallClient::LeaveMuc(const std::string& room) { 1304 buzz::Jid room_jid; 1305 const buzz::Jid* muc_jid = FirstMucJid(); 1306 if (room.length() > 0) { 1307 room_jid = buzz::Jid(room); 1308 } else if (mucs_.size() > 0) { 1309 // leave the first MUC if no JID specified 1310 if (muc_jid) { 1311 room_jid = *(muc_jid); 1312 } 1313 } 1314 1315 if (!room_jid.IsValid()) { 1316 console_->PrintLine("Invalid MUC JID."); 1317 return; 1318 } 1319 1320 MucMap::iterator elem = mucs_.find(room_jid); 1321 if (elem == mucs_.end()) { 1322 console_->PrintLine("No such MUC."); 1323 return; 1324 } 1325 1326 buzz::Muc* muc = elem->second; 1327 muc->set_state(buzz::Muc::MUC_LEAVING); 1328 1329 buzz::PresenceStatus status; 1330 status.set_jid(my_status_.jid()); 1331 status.set_available(false); 1332 status.set_priority(0); 1333 presence_out_->SendDirected(muc->local_jid(), status); 1334 } 1335 1336 void CallClient::OnMucLeft(const buzz::Jid& endpoint, int error) { 1337 // We could be kicked from a room from any state. We would hope this 1338 // happens While in the MUC_LEAVING state 1339 MucMap::iterator elem = mucs_.find(endpoint); 1340 if (elem == mucs_.end()) 1341 return; 1342 1343 buzz::Muc* muc = elem->second; 1344 if (muc->state() == buzz::Muc::MUC_JOINING) { 1345 console_->PrintLine("Failed to join \"%s\", code=%d", 1346 muc->jid().Str().c_str(), error); 1347 } else if (muc->state() == buzz::Muc::MUC_JOINED) { 1348 console_->PrintLine("Kicked from \"%s\"", 1349 muc->jid().Str().c_str()); 1350 } 1351 1352 delete muc; 1353 mucs_.erase(elem); 1354 } 1355 1356 void CallClient::InviteToMuc(const std::string& given_user, 1357 const std::string& room) { 1358 std::string user = given_user; 1359 1360 // First find the room. 1361 const buzz::Muc* found_muc; 1362 if (room.length() == 0) { 1363 if (mucs_.size() == 0) { 1364 console_->PrintLine("Not in a room yet; can't invite."); 1365 return; 1366 } 1367 // Invite to the first muc 1368 found_muc = mucs_.begin()->second; 1369 } else { 1370 MucMap::iterator elem = mucs_.find(buzz::Jid(room)); 1371 if (elem == mucs_.end()) { 1372 console_->PrintLine("Not in room %s.", room.c_str()); 1373 return; 1374 } 1375 found_muc = elem->second; 1376 } 1377 1378 buzz::Jid invite_to = found_muc->jid(); 1379 1380 // Now find the user. We invite all of their resources. 1381 bool found_user = false; 1382 buzz::Jid user_jid(user); 1383 for (RosterMap::iterator iter = roster_->begin(); 1384 iter != roster_->end(); ++iter) { 1385 if (iter->second.jid.BareEquals(user_jid)) { 1386 buzz::Jid invitee = iter->second.jid; 1387 muc_invite_send_->Send(invite_to, invitee); 1388 found_user = true; 1389 } 1390 } 1391 if (!found_user) { 1392 buzz::Jid invitee = user_jid; 1393 muc_invite_send_->Send(invite_to, invitee); 1394 } 1395 } 1396 1397 void CallClient::GetDevices() { 1398 std::vector<std::string> names; 1399 media_client_->GetAudioInputDevices(&names); 1400 console_->PrintLine("Audio input devices:"); 1401 PrintDevices(names); 1402 media_client_->GetAudioOutputDevices(&names); 1403 console_->PrintLine("Audio output devices:"); 1404 PrintDevices(names); 1405 media_client_->GetVideoCaptureDevices(&names); 1406 console_->PrintLine("Video capture devices:"); 1407 PrintDevices(names); 1408 } 1409 1410 void CallClient::PrintDevices(const std::vector<std::string>& names) { 1411 for (size_t i = 0; i < names.size(); ++i) { 1412 console_->PrintLine("%d: %s", static_cast<int>(i), names[i].c_str()); 1413 } 1414 } 1415 1416 void CallClient::OnDevicesChange() { 1417 console_->PrintLine("Devices changed."); 1418 SetMediaCaps(media_client_->GetCapabilities(), &my_status_); 1419 SendStatus(my_status_); 1420 } 1421 1422 void CallClient::SetVolume(const std::string& level) { 1423 media_client_->SetOutputVolume(strtol(level.c_str(), NULL, 10)); 1424 } 1425 1426 void CallClient::OnMediaStreamsUpdate(cricket::Call* call, 1427 cricket::Session* session, 1428 const cricket::MediaStreams& added, 1429 const cricket::MediaStreams& removed) { 1430 if (call && call->has_video()) { 1431 for (std::vector<cricket::StreamParams>::const_iterator 1432 it = removed.video().begin(); it != removed.video().end(); ++it) { 1433 RemoveStaticRenderedView(it->first_ssrc()); 1434 } 1435 1436 if (render_) { 1437 RenderStreams(call, session, added.video(), true); 1438 } 1439 SendViewRequest(call, session); 1440 } 1441 } 1442 1443 void CallClient::RenderAllStreams(cricket::Call* call, 1444 cricket::Session* session, 1445 bool enable) { 1446 const std::vector<cricket::StreamParams>* video_streams = 1447 call->GetVideoRecvStreams(session); 1448 if (video_streams) { 1449 RenderStreams(call, session, *video_streams, enable); 1450 } 1451 } 1452 1453 void CallClient::RenderStreams( 1454 cricket::Call* call, 1455 cricket::Session* session, 1456 const std::vector<cricket::StreamParams>& video_streams, 1457 bool enable) { 1458 std::vector<cricket::StreamParams>::const_iterator stream; 1459 for (stream = video_streams.begin(); stream != video_streams.end(); 1460 ++stream) { 1461 RenderStream(call, session, *stream, enable); 1462 } 1463 } 1464 1465 void CallClient::RenderStream(cricket::Call* call, 1466 cricket::Session* session, 1467 const cricket::StreamParams& stream, 1468 bool enable) { 1469 if (!stream.has_ssrcs()) { 1470 // Nothing to see here; move along. 1471 return; 1472 } 1473 1474 uint32 ssrc = stream.first_ssrc(); 1475 StaticRenderedViews::iterator iter = 1476 static_rendered_views_.find(std::make_pair(session, ssrc)); 1477 if (enable) { 1478 if (iter == static_rendered_views_.end()) { 1479 // TODO(pthatcher): Make dimensions and positions more configurable. 1480 int offset = (50 * static_views_accumulated_count_) % 300; 1481 AddStaticRenderedView(session, ssrc, 640, 400, 30, 1482 offset, offset); 1483 // Should have it now. 1484 iter = static_rendered_views_.find(std::make_pair(session, ssrc)); 1485 } 1486 call->SetVideoRenderer(session, ssrc, iter->second.renderer); 1487 } else { 1488 if (iter != static_rendered_views_.end()) { 1489 call->SetVideoRenderer(session, ssrc, NULL); 1490 RemoveStaticRenderedView(ssrc); 1491 } 1492 } 1493 } 1494 1495 // TODO: Would these methods to add and remove views make 1496 // more sense in call.cc? Would other clients use them? 1497 void CallClient::AddStaticRenderedView( 1498 cricket::Session* session, 1499 uint32 ssrc, int width, int height, int framerate, 1500 int x_offset, int y_offset) { 1501 StaticRenderedView rendered_view( 1502 cricket::StaticVideoView( 1503 cricket::StreamSelector(ssrc), width, height, framerate), 1504 cricket::VideoRendererFactory::CreateGuiVideoRenderer( 1505 x_offset, y_offset)); 1506 rendered_view.renderer->SetSize(width, height, 0); 1507 static_rendered_views_.insert(std::make_pair(std::make_pair(session, ssrc), 1508 rendered_view)); 1509 ++static_views_accumulated_count_; 1510 console_->PrintLine("Added renderer for ssrc %d", ssrc); 1511 } 1512 1513 bool CallClient::RemoveStaticRenderedView(uint32 ssrc) { 1514 for (StaticRenderedViews::iterator it = static_rendered_views_.begin(); 1515 it != static_rendered_views_.end(); ++it) { 1516 if (it->second.view.selector.ssrc == ssrc) { 1517 delete it->second.renderer; 1518 static_rendered_views_.erase(it); 1519 console_->PrintLine("Removed renderer for ssrc %d", ssrc); 1520 return true; 1521 } 1522 } 1523 return false; 1524 } 1525 1526 void CallClient::RemoveCallsStaticRenderedViews(cricket::Call* call) { 1527 std::vector<cricket::Session*>& sessions = sessions_[call->id()]; 1528 std::set<cricket::Session*> call_sessions(sessions.begin(), sessions.end()); 1529 for (StaticRenderedViews::iterator it = static_rendered_views_.begin(); 1530 it != static_rendered_views_.end(); ) { 1531 if (call_sessions.find(it->first.first) != call_sessions.end()) { 1532 delete it->second.renderer; 1533 static_rendered_views_.erase(it++); 1534 } else { 1535 ++it; 1536 } 1537 } 1538 } 1539 1540 void CallClient::SendViewRequest(cricket::Call* call, 1541 cricket::Session* session) { 1542 cricket::ViewRequest request; 1543 for (StaticRenderedViews::iterator it = static_rendered_views_.begin(); 1544 it != static_rendered_views_.end(); ++it) { 1545 if (it->first.first == session) { 1546 request.static_video_views.push_back(it->second.view); 1547 } 1548 } 1549 call->SendViewRequest(session, request); 1550 } 1551 1552 buzz::Jid CallClient::GenerateRandomMucJid() { 1553 // Generate a GUID of the form XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX, 1554 // for an eventual JID of private-chat-<GUID>@groupchat.google.com. 1555 char guid[37], guid_room[256]; 1556 for (size_t i = 0; i < ARRAY_SIZE(guid) - 1;) { 1557 if (i == 8 || i == 13 || i == 18 || i == 23) { 1558 guid[i++] = '-'; 1559 } else { 1560 sprintf(guid + i, "%04x", rand()); 1561 i += 4; 1562 } 1563 } 1564 1565 talk_base::sprintfn(guid_room, 1566 ARRAY_SIZE(guid_room), 1567 "private-chat-%s@%s", 1568 guid, 1569 pmuc_domain_.c_str()); 1570 return buzz::Jid(guid_room); 1571 } 1572 1573 bool CallClient::SelectFirstDesktopScreencastId( 1574 cricket::ScreencastId* screencastid) { 1575 if (!talk_base::WindowPickerFactory::IsSupported()) { 1576 LOG(LS_WARNING) << "Window picker not suported on this OS."; 1577 return false; 1578 } 1579 1580 talk_base::WindowPicker* picker = 1581 talk_base::WindowPickerFactory::CreateWindowPicker(); 1582 if (!picker) { 1583 LOG(LS_WARNING) << "Could not create a window picker."; 1584 return false; 1585 } 1586 1587 talk_base::DesktopDescriptionList desktops; 1588 if (!picker->GetDesktopList(&desktops) || desktops.empty()) { 1589 LOG(LS_WARNING) << "Could not get a list of desktops."; 1590 return false; 1591 } 1592 1593 *screencastid = cricket::ScreencastId(desktops[0].id()); 1594 return true; 1595 } 1596 1597 void CallClient::PrintStats() const { 1598 const cricket::VoiceMediaInfo& vmi = call_->last_voice_media_info(); 1599 1600 for (std::vector<cricket::VoiceSenderInfo>::const_iterator it = 1601 vmi.senders.begin(); it != vmi.senders.end(); ++it) { 1602 console_->PrintLine("Sender: ssrc=%u codec='%s' bytes=%d packets=%d " 1603 "rtt=%d jitter=%d", 1604 it->ssrc(), it->codec_name.c_str(), it->bytes_sent, 1605 it->packets_sent, it->rtt_ms, it->jitter_ms); 1606 } 1607 1608 for (std::vector<cricket::VoiceReceiverInfo>::const_iterator it = 1609 vmi.receivers.begin(); it != vmi.receivers.end(); ++it) { 1610 console_->PrintLine("Receiver: ssrc=%u bytes=%d packets=%d " 1611 "jitter=%d loss=%.2f", 1612 it->ssrc(), it->bytes_rcvd, it->packets_rcvd, 1613 it->jitter_ms, it->fraction_lost); 1614 } 1615 } 1616