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