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/basicpacketsocketfactory.h" 33 #include "talk/base/helpers.h" 34 #include "talk/base/logging.h" 35 #include "talk/base/network.h" 36 #include "talk/base/socketaddress.h" 37 #include "talk/base/stringencode.h" 38 #include "talk/base/stringutils.h" 39 #include "talk/base/thread.h" 40 #include "talk/examples/call/console.h" 41 #include "talk/examples/call/presencepushtask.h" 42 #include "talk/examples/call/presenceouttask.h" 43 #include "talk/examples/call/mucinviterecvtask.h" 44 #include "talk/examples/call/mucinvitesendtask.h" 45 #include "talk/examples/call/friendinvitesendtask.h" 46 #include "talk/examples/call/muc.h" 47 #include "talk/examples/call/voicemailjidrequester.h" 48 #include "talk/p2p/base/sessionmanager.h" 49 #include "talk/p2p/client/basicportallocator.h" 50 #include "talk/p2p/client/sessionmanagertask.h" 51 #include "talk/session/phone/devicemanager.h" 52 #include "talk/session/phone/mediaengine.h" 53 #include "talk/session/phone/mediasessionclient.h" 54 #include "talk/xmpp/constants.h" 55 56 57 class NullRenderer : public cricket::VideoRenderer { 58 public: 59 explicit NullRenderer(const char* s) : s_(s) {} 60 private: 61 bool SetSize(int width, int height, int reserved) { 62 LOG(LS_INFO) << "Video size for " << s_ << ": " << width << "x" << height; 63 return true; 64 } 65 bool RenderFrame(const cricket::VideoFrame *frame) { 66 return true; 67 } 68 const char* s_; 69 }; 70 71 namespace { 72 73 const char* DescribeStatus(buzz::Status::Show show, const std::string& desc) { 74 switch (show) { 75 case buzz::Status::SHOW_XA: return desc.c_str(); 76 case buzz::Status::SHOW_ONLINE: return "online"; 77 case buzz::Status::SHOW_AWAY: return "away"; 78 case buzz::Status::SHOW_DND: return "do not disturb"; 79 case buzz::Status::SHOW_CHAT: return "ready to chat"; 80 default: return "offline"; 81 } 82 } 83 84 std::string GetWord(const std::vector<std::string>& words, 85 size_t index, const std::string& def) { 86 if (words.size() > index) { 87 return words[index]; 88 } else { 89 return def; 90 } 91 } 92 93 int GetInt(const std::vector<std::string>& words, size_t index, int def) { 94 int val; 95 if (words.size() > index && talk_base::FromString(words[index], &val)) { 96 return val; 97 } else { 98 return def; 99 } 100 } 101 102 103 } // namespace 104 105 const char* CALL_COMMANDS = 106 "Available commands:\n" 107 "\n" 108 " hangup Ends the call.\n" 109 " mute Stops sending voice.\n" 110 " unmute Re-starts sending voice.\n" 111 " dtmf Sends a DTMF tone.\n" 112 " quit Quits the application.\n" 113 ""; 114 115 const char* RECEIVE_COMMANDS = 116 "Available commands:\n" 117 "\n" 118 " accept [bw] Accepts the incoming call and switches to it.\n" 119 " reject Rejects the incoming call and stays with the current call.\n" 120 " quit Quits the application.\n" 121 ""; 122 123 const char* CONSOLE_COMMANDS = 124 "Available commands:\n" 125 "\n" 126 " roster Prints the online friends from your roster.\n" 127 " friend user Request to add a user to your roster.\n" 128 " call [jid] [bw] Initiates a call to the user[/room] with the\n" 129 " given JID and with optional bandwidth.\n" 130 " vcall [jid] [bw] Initiates a video call to the user[/room] with\n" 131 " the given JID and with optional bandwidth.\n" 132 " voicemail [jid] Leave a voicemail for the user with the given JID.\n" 133 " join [room] Joins a multi-user-chat.\n" 134 " invite user [room] Invites a friend to a multi-user-chat.\n" 135 " leave [room] Leaves a multi-user-chat.\n" 136 " getdevs Prints the available media devices.\n" 137 " quit Quits the application.\n" 138 ""; 139 140 void CallClient::ParseLine(const std::string& line) { 141 std::vector<std::string> words; 142 int start = -1; 143 int state = 0; 144 for (int index = 0; index <= static_cast<int>(line.size()); ++index) { 145 if (state == 0) { 146 if (!isspace(line[index])) { 147 start = index; 148 state = 1; 149 } 150 } else { 151 ASSERT(state == 1); 152 ASSERT(start >= 0); 153 if (isspace(line[index])) { 154 std::string word(line, start, index - start); 155 words.push_back(word); 156 start = -1; 157 state = 0; 158 } 159 } 160 } 161 162 // Global commands 163 const std::string& command = GetWord(words, 0, ""); 164 if (command == "quit") { 165 Quit(); 166 } else if (call_ && incoming_call_) { 167 if (command == "accept") { 168 cricket::CallOptions options; 169 options.video_bandwidth = GetInt(words, 1, cricket::kAutoBandwidth); 170 Accept(options); 171 } else if (command == "reject") { 172 Reject(); 173 } else { 174 console_->Print(RECEIVE_COMMANDS); 175 } 176 } else if (call_) { 177 if (command == "hangup") { 178 // TODO: do more shutdown here, move to Terminate() 179 call_->Terminate(); 180 call_ = NULL; 181 session_ = NULL; 182 console_->SetPrompt(NULL); 183 } else if (command == "mute") { 184 call_->Mute(true); 185 } else if (command == "unmute") { 186 call_->Mute(false); 187 } else if ((command == "dtmf") && (words.size() == 2)) { 188 int ev = std::string("0123456789*#").find(words[1][0]); 189 call_->PressDTMF(ev); 190 } else { 191 console_->Print(CALL_COMMANDS); 192 } 193 } else { 194 if (command == "roster") { 195 PrintRoster(); 196 } else if (command == "send") { 197 buzz::Jid jid(words[1]); 198 if (jid.IsValid()) { 199 last_sent_to_ = words[1]; 200 SendChat(words[1], words[2]); 201 } else if (!last_sent_to_.empty()) { 202 SendChat(last_sent_to_, words[1]); 203 } else { 204 console_->Printf( 205 "Invalid JID. JIDs should be in the form user@domain\n"); 206 } 207 } else if ((words.size() == 2) && (command == "friend")) { 208 InviteFriend(words[1]); 209 } else if (command == "call") { 210 std::string to = GetWord(words, 1, ""); 211 MakeCallTo(to, cricket::CallOptions()); 212 } else if (command == "vcall") { 213 std::string to = GetWord(words, 1, ""); 214 int bandwidth = GetInt(words, 2, cricket::kAutoBandwidth); 215 cricket::CallOptions options; 216 options.is_video = true; 217 options.video_bandwidth = bandwidth; 218 MakeCallTo(to, options); 219 } else if (command == "join") { 220 JoinMuc(GetWord(words, 1, "")); 221 } else if ((words.size() >= 2) && (command == "invite")) { 222 InviteToMuc(words[1], GetWord(words, 2, "")); 223 } else if (command == "leave") { 224 LeaveMuc(GetWord(words, 1, "")); 225 } else if (command == "getdevs") { 226 GetDevices(); 227 } else if ((words.size() == 2) && (command == "setvol")) { 228 SetVolume(words[1]); 229 } else if (command == "voicemail") { 230 CallVoicemail((words.size() >= 2) ? words[1] : ""); 231 } else { 232 console_->Print(CONSOLE_COMMANDS); 233 } 234 } 235 } 236 237 CallClient::CallClient(buzz::XmppClient* xmpp_client) 238 : xmpp_client_(xmpp_client), 239 media_engine_(NULL), 240 media_client_(NULL), 241 call_(NULL), 242 incoming_call_(false), 243 auto_accept_(false), 244 pmuc_domain_("groupchat.google.com"), 245 local_renderer_(NULL), 246 remote_renderer_(NULL), 247 roster_(new RosterMap), 248 portallocator_flags_(0), 249 allow_local_ips_(false), 250 initial_protocol_(cricket::PROTOCOL_HYBRID), 251 secure_policy_(cricket::SEC_DISABLED) { 252 xmpp_client_->SignalStateChange.connect(this, &CallClient::OnStateChange); 253 } 254 255 CallClient::~CallClient() { 256 delete media_client_; 257 delete roster_; 258 } 259 260 const std::string CallClient::strerror(buzz::XmppEngine::Error err) { 261 switch (err) { 262 case buzz::XmppEngine::ERROR_NONE: 263 return ""; 264 case buzz::XmppEngine::ERROR_XML: 265 return "Malformed XML or encoding error"; 266 case buzz::XmppEngine::ERROR_STREAM: 267 return "XMPP stream error"; 268 case buzz::XmppEngine::ERROR_VERSION: 269 return "XMPP version error"; 270 case buzz::XmppEngine::ERROR_UNAUTHORIZED: 271 return "User is not authorized (Check your username and password)"; 272 case buzz::XmppEngine::ERROR_TLS: 273 return "TLS could not be negotiated"; 274 case buzz::XmppEngine::ERROR_AUTH: 275 return "Authentication could not be negotiated"; 276 case buzz::XmppEngine::ERROR_BIND: 277 return "Resource or session binding could not be negotiated"; 278 case buzz::XmppEngine::ERROR_CONNECTION_CLOSED: 279 return "Connection closed by output handler."; 280 case buzz::XmppEngine::ERROR_DOCUMENT_CLOSED: 281 return "Closed by </stream:stream>"; 282 case buzz::XmppEngine::ERROR_SOCKET: 283 return "Socket error"; 284 default: 285 return "Unknown error"; 286 } 287 } 288 289 void CallClient::OnCallDestroy(cricket::Call* call) { 290 if (call == call_) { 291 if (remote_renderer_) { 292 delete remote_renderer_; 293 remote_renderer_ = NULL; 294 } 295 if (local_renderer_) { 296 delete local_renderer_; 297 local_renderer_ = NULL; 298 } 299 console_->SetPrompt(NULL); 300 console_->Print("call destroyed"); 301 call_ = NULL; 302 session_ = NULL; 303 } 304 } 305 306 void CallClient::OnStateChange(buzz::XmppEngine::State state) { 307 switch (state) { 308 case buzz::XmppEngine::STATE_START: 309 console_->Print("connecting..."); 310 break; 311 312 case buzz::XmppEngine::STATE_OPENING: 313 console_->Print("logging in..."); 314 break; 315 316 case buzz::XmppEngine::STATE_OPEN: 317 console_->Print("logged in..."); 318 InitPhone(); 319 InitPresence(); 320 break; 321 322 case buzz::XmppEngine::STATE_CLOSED: 323 buzz::XmppEngine::Error error = xmpp_client_->GetError(NULL); 324 console_->Print("logged out..." + strerror(error)); 325 Quit(); 326 } 327 } 328 329 void CallClient::InitPhone() { 330 std::string client_unique = xmpp_client_->jid().Str(); 331 talk_base::InitRandom(client_unique.c_str(), client_unique.size()); 332 333 worker_thread_ = new talk_base::Thread(); 334 // The worker thread must be started here since initialization of 335 // the ChannelManager will generate messages that need to be 336 // dispatched by it. 337 worker_thread_->Start(); 338 339 // TODO: It looks like we are leaking many 340 // objects. E.g. |network_manager_| and |socket_factory_| are never 341 // deleted. 342 343 network_manager_ = new talk_base::NetworkManager(); 344 socket_factory_ = new talk_base::BasicPacketSocketFactory(worker_thread_); 345 346 // TODO: Decide if the relay address should be specified here. 347 talk_base::SocketAddress stun_addr("stun.l.google.com", 19302); 348 port_allocator_ = new cricket::BasicPortAllocator( 349 network_manager_, socket_factory_, stun_addr, 350 talk_base::SocketAddress(), talk_base::SocketAddress(), 351 talk_base::SocketAddress()); 352 353 if (portallocator_flags_ != 0) { 354 port_allocator_->set_flags(portallocator_flags_); 355 } 356 session_manager_ = new cricket::SessionManager( 357 port_allocator_, worker_thread_); 358 session_manager_->SignalRequestSignaling.connect( 359 this, &CallClient::OnRequestSignaling); 360 session_manager_->SignalSessionCreate.connect( 361 this, &CallClient::OnSessionCreate); 362 session_manager_->OnSignalingReady(); 363 364 session_manager_task_ = 365 new cricket::SessionManagerTask(xmpp_client_, session_manager_); 366 session_manager_task_->EnableOutgoingMessages(); 367 session_manager_task_->Start(); 368 369 if (!media_engine_) { 370 media_engine_ = cricket::MediaEngine::Create(); 371 } 372 373 media_client_ = new cricket::MediaSessionClient( 374 xmpp_client_->jid(), 375 session_manager_, 376 media_engine_, 377 new cricket::DeviceManager()); 378 media_client_->SignalCallCreate.connect(this, &CallClient::OnCallCreate); 379 media_client_->SignalDevicesChange.connect(this, 380 &CallClient::OnDevicesChange); 381 media_client_->set_secure(secure_policy_); 382 } 383 384 void CallClient::OnRequestSignaling() { 385 session_manager_->OnSignalingReady(); 386 } 387 388 void CallClient::OnSessionCreate(cricket::Session* session, bool initiate) { 389 session->set_allow_local_ips(allow_local_ips_); 390 session->set_current_protocol(initial_protocol_); 391 } 392 393 void CallClient::OnCallCreate(cricket::Call* call) { 394 call->SignalSessionState.connect(this, &CallClient::OnSessionState); 395 if (call->video()) { 396 local_renderer_ = new NullRenderer("local"); 397 remote_renderer_ = new NullRenderer("remote"); 398 } 399 } 400 401 void CallClient::OnSessionState(cricket::Call* call, 402 cricket::BaseSession* session, 403 cricket::BaseSession::State state) { 404 if (state == cricket::Session::STATE_RECEIVEDINITIATE) { 405 buzz::Jid jid(session->remote_name()); 406 console_->Printf("Incoming call from '%s'", jid.Str().c_str()); 407 call_ = call; 408 session_ = session; 409 incoming_call_ = true; 410 cricket::CallOptions options; 411 if (auto_accept_) { 412 Accept(options); 413 } 414 } else if (state == cricket::Session::STATE_SENTINITIATE) { 415 console_->Print("calling..."); 416 } else if (state == cricket::Session::STATE_RECEIVEDACCEPT) { 417 console_->Print("call answered"); 418 } else if (state == cricket::Session::STATE_RECEIVEDREJECT) { 419 console_->Print("call not answered"); 420 } else if (state == cricket::Session::STATE_INPROGRESS) { 421 console_->Print("call in progress"); 422 } else if (state == cricket::Session::STATE_RECEIVEDTERMINATE) { 423 console_->Print("other side hung up"); 424 } 425 } 426 427 void CallClient::InitPresence() { 428 presence_push_ = new buzz::PresencePushTask(xmpp_client_, this); 429 presence_push_->SignalStatusUpdate.connect( 430 this, &CallClient::OnStatusUpdate); 431 presence_push_->SignalMucJoined.connect(this, &CallClient::OnMucJoined); 432 presence_push_->SignalMucLeft.connect(this, &CallClient::OnMucLeft); 433 presence_push_->SignalMucStatusUpdate.connect( 434 this, &CallClient::OnMucStatusUpdate); 435 presence_push_->Start(); 436 437 presence_out_ = new buzz::PresenceOutTask(xmpp_client_); 438 RefreshStatus(); 439 presence_out_->Start(); 440 441 muc_invite_recv_ = new buzz::MucInviteRecvTask(xmpp_client_); 442 muc_invite_recv_->SignalInviteReceived.connect(this, 443 &CallClient::OnMucInviteReceived); 444 muc_invite_recv_->Start(); 445 446 muc_invite_send_ = new buzz::MucInviteSendTask(xmpp_client_); 447 muc_invite_send_->Start(); 448 449 friend_invite_send_ = new buzz::FriendInviteSendTask(xmpp_client_); 450 friend_invite_send_->Start(); 451 } 452 453 void CallClient::RefreshStatus() { 454 int media_caps = media_client_->GetCapabilities(); 455 my_status_.set_jid(xmpp_client_->jid()); 456 my_status_.set_available(true); 457 my_status_.set_show(buzz::Status::SHOW_ONLINE); 458 my_status_.set_priority(0); 459 my_status_.set_know_capabilities(true); 460 my_status_.set_pmuc_capability(true); 461 my_status_.set_phone_capability( 462 (media_caps & cricket::MediaEngine::AUDIO_RECV) != 0); 463 my_status_.set_video_capability( 464 (media_caps & cricket::MediaEngine::VIDEO_RECV) != 0); 465 my_status_.set_camera_capability( 466 (media_caps & cricket::MediaEngine::VIDEO_SEND) != 0); 467 my_status_.set_is_google_client(true); 468 my_status_.set_version("1.0.0.67"); 469 presence_out_->Send(my_status_); 470 } 471 472 void CallClient::OnStatusUpdate(const buzz::Status& status) { 473 RosterItem item; 474 item.jid = status.jid(); 475 item.show = status.show(); 476 item.status = status.status(); 477 478 std::string key = item.jid.Str(); 479 480 if (status.available() && status.phone_capability()) { 481 console_->Printf("Adding to roster: %s", key.c_str()); 482 (*roster_)[key] = item; 483 } else { 484 console_->Printf("Removing from roster: %s", key.c_str()); 485 RosterMap::iterator iter = roster_->find(key); 486 if (iter != roster_->end()) 487 roster_->erase(iter); 488 } 489 } 490 491 void CallClient::PrintRoster() { 492 console_->SetPrompting(false); 493 console_->Printf("Roster contains %d callable", roster_->size()); 494 RosterMap::iterator iter = roster_->begin(); 495 while (iter != roster_->end()) { 496 console_->Printf("%s - %s", 497 iter->second.jid.BareJid().Str().c_str(), 498 DescribeStatus(iter->second.show, iter->second.status)); 499 iter++; 500 } 501 console_->SetPrompting(true); 502 } 503 504 void CallClient::SendChat(const std::string& to, const std::string msg) { 505 buzz::XmlElement* stanza = new buzz::XmlElement(buzz::QN_MESSAGE); 506 stanza->AddAttr(buzz::QN_TO, to); 507 stanza->AddAttr(buzz::QN_ID, talk_base::CreateRandomString(16)); 508 stanza->AddAttr(buzz::QN_TYPE, "chat"); 509 buzz::XmlElement* body = new buzz::XmlElement(buzz::QN_BODY); 510 body->SetBodyText(msg); 511 stanza->AddElement(body); 512 513 xmpp_client_->SendStanza(stanza); 514 delete stanza; 515 } 516 517 void CallClient::InviteFriend(const std::string& name) { 518 buzz::Jid jid(name); 519 if (!jid.IsValid() || jid.node() == "") { 520 console_->Printf("Invalid JID. JIDs should be in the form user@domain\n"); 521 return; 522 } 523 // Note: for some reason the Buzz backend does not forward our presence 524 // subscription requests to the end user when that user is another call 525 // client as opposed to a Smurf user. Thus, in that scenario, you must 526 // run the friend command as the other user too to create the linkage 527 // (and you won't be notified to do so). 528 friend_invite_send_->Send(jid); 529 console_->Printf("Requesting to befriend %s.\n", name.c_str()); 530 } 531 532 void CallClient::MakeCallTo(const std::string& name, 533 const cricket::CallOptions& given_options) { 534 // Copy so we can change .is_muc. 535 cricket::CallOptions options = given_options; 536 537 bool found = false; 538 options.is_muc = false; 539 buzz::Jid callto_jid(name); 540 buzz::Jid found_jid; 541 if (name.length() == 0 && mucs_.size() > 0) { 542 // if no name, and in a MUC, establish audio with the MUC 543 found_jid = mucs_.begin()->first; 544 found = true; 545 options.is_muc = true; 546 } else if (name[0] == '+') { 547 // if the first character is a +, assume it's a phone number 548 found_jid = callto_jid; 549 found = true; 550 } else if (callto_jid.resource() == "voicemail") { 551 // if the resource is /voicemail, allow that 552 found_jid = callto_jid; 553 found = true; 554 } else { 555 // otherwise, it's a friend 556 for (RosterMap::iterator iter = roster_->begin(); 557 iter != roster_->end(); ++iter) { 558 if (iter->second.jid.BareEquals(callto_jid)) { 559 found = true; 560 found_jid = iter->second.jid; 561 break; 562 } 563 } 564 565 if (!found) { 566 if (mucs_.count(callto_jid) == 1 && 567 mucs_[callto_jid]->state() == buzz::Muc::MUC_JOINED) { 568 found = true; 569 found_jid = callto_jid; 570 options.is_muc = true; 571 } 572 } 573 } 574 575 if (found) { 576 console_->Printf("Found %s '%s'", options.is_muc ? "room" : "online friend", 577 found_jid.Str().c_str()); 578 PlaceCall(found_jid, options); 579 } else { 580 console_->Printf("Could not find online friend '%s'", name.c_str()); 581 } 582 } 583 584 void CallClient::PlaceCall(const buzz::Jid& jid, 585 const cricket::CallOptions& options) { 586 media_client_->SignalCallDestroy.connect( 587 this, &CallClient::OnCallDestroy); 588 if (!call_) { 589 call_ = media_client_->CreateCall(); 590 console_->SetPrompt(jid.Str().c_str()); 591 session_ = call_->InitiateSession(jid, options); 592 if (options.is_muc) { 593 // If people in this room are already in a call, must add all their 594 // streams. 595 buzz::Muc::MemberMap& members = mucs_[jid]->members(); 596 for (buzz::Muc::MemberMap::iterator elem = members.begin(); 597 elem != members.end(); 598 ++elem) { 599 AddStream(elem->second.audio_src_id(), elem->second.video_src_id()); 600 } 601 } 602 } 603 media_client_->SetFocus(call_); 604 if (call_->video()) { 605 call_->SetLocalRenderer(local_renderer_); 606 // TODO: Call this once for every different remote SSRC 607 // once we get to testing multiway video. 608 call_->SetVideoRenderer(session_, 0, remote_renderer_); 609 } 610 } 611 612 void CallClient::CallVoicemail(const std::string& name) { 613 buzz::Jid jid(name); 614 if (!jid.IsValid() || jid.node() == "") { 615 console_->Printf("Invalid JID. JIDs should be in the form user@domain\n"); 616 return; 617 } 618 buzz::VoicemailJidRequester *request = 619 new buzz::VoicemailJidRequester(xmpp_client_, jid, my_status_.jid()); 620 request->SignalGotVoicemailJid.connect(this, 621 &CallClient::OnFoundVoicemailJid); 622 request->SignalVoicemailJidError.connect(this, 623 &CallClient::OnVoicemailJidError); 624 request->Start(); 625 } 626 627 void CallClient::OnFoundVoicemailJid(const buzz::Jid& to, 628 const buzz::Jid& voicemail) { 629 console_->Printf("Calling %s's voicemail.\n", to.Str().c_str()); 630 PlaceCall(voicemail, cricket::CallOptions()); 631 } 632 633 void CallClient::OnVoicemailJidError(const buzz::Jid& to) { 634 console_->Printf("Unable to voicemail %s.\n", to.Str().c_str()); 635 } 636 637 void CallClient::AddStream(uint32 audio_src_id, uint32 video_src_id) { 638 if (audio_src_id || video_src_id) { 639 console_->Printf("Adding stream (%u, %u)\n", audio_src_id, video_src_id); 640 call_->AddStream(session_, audio_src_id, video_src_id); 641 } 642 } 643 644 void CallClient::RemoveStream(uint32 audio_src_id, uint32 video_src_id) { 645 if (audio_src_id || video_src_id) { 646 console_->Printf("Removing stream (%u, %u)\n", audio_src_id, video_src_id); 647 call_->RemoveStream(session_, audio_src_id, video_src_id); 648 } 649 } 650 651 void CallClient::Accept(const cricket::CallOptions& options) { 652 ASSERT(call_ && incoming_call_); 653 ASSERT(call_->sessions().size() == 1); 654 call_->AcceptSession(call_->sessions()[0], options); 655 media_client_->SetFocus(call_); 656 if (call_->video()) { 657 call_->SetLocalRenderer(local_renderer_); 658 // The client never does an accept for multiway, so this must be 1:1, 659 // so there's no SSRC. 660 call_->SetVideoRenderer(session_, 0, remote_renderer_); 661 } 662 incoming_call_ = false; 663 } 664 665 void CallClient::Reject() { 666 ASSERT(call_ && incoming_call_); 667 call_->RejectSession(call_->sessions()[0]); 668 incoming_call_ = false; 669 } 670 671 void CallClient::Quit() { 672 talk_base::Thread::Current()->Quit(); 673 } 674 675 void CallClient::JoinMuc(const std::string& room) { 676 buzz::Jid room_jid; 677 if (room.length() > 0) { 678 room_jid = buzz::Jid(room); 679 } else { 680 // generate a GUID of the form XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX, 681 // for an eventual JID of private-chat-<GUID>@groupchat.google.com 682 char guid[37], guid_room[256]; 683 for (size_t i = 0; i < ARRAY_SIZE(guid) - 1;) { 684 if (i == 8 || i == 13 || i == 18 || i == 23) { 685 guid[i++] = '-'; 686 } else { 687 sprintf(guid + i, "%04x", rand()); 688 i += 4; 689 } 690 } 691 692 talk_base::sprintfn(guid_room, ARRAY_SIZE(guid_room), 693 "private-chat-%s@%s", guid, pmuc_domain_.c_str()); 694 room_jid = buzz::Jid(guid_room); 695 } 696 697 if (!room_jid.IsValid()) { 698 console_->Printf("Unable to make valid muc endpoint for %s", room.c_str()); 699 return; 700 } 701 702 MucMap::iterator elem = mucs_.find(room_jid); 703 if (elem != mucs_.end()) { 704 console_->Printf("This MUC already exists."); 705 return; 706 } 707 708 buzz::Muc* muc = new buzz::Muc(room_jid, xmpp_client_->jid().node()); 709 mucs_[room_jid] = muc; 710 presence_out_->SendDirected(muc->local_jid(), my_status_); 711 } 712 713 void CallClient::OnMucInviteReceived(const buzz::Jid& inviter, 714 const buzz::Jid& room, 715 const std::vector<buzz::AvailableMediaEntry>& avail) { 716 717 console_->Printf("Invited to join %s by %s.\n", room.Str().c_str(), 718 inviter.Str().c_str()); 719 console_->Printf("Available media:\n"); 720 if (avail.size() > 0) { 721 for (std::vector<buzz::AvailableMediaEntry>::const_iterator i = 722 avail.begin(); 723 i != avail.end(); 724 ++i) { 725 console_->Printf(" %s, %s\n", 726 buzz::AvailableMediaEntry::TypeAsString(i->type), 727 buzz::AvailableMediaEntry::StatusAsString(i->status)); 728 } 729 } else { 730 console_->Printf(" None\n"); 731 } 732 // We automatically join the room. 733 JoinMuc(room.Str()); 734 } 735 736 void CallClient::OnMucJoined(const buzz::Jid& endpoint) { 737 MucMap::iterator elem = mucs_.find(endpoint); 738 ASSERT(elem != mucs_.end() && 739 elem->second->state() == buzz::Muc::MUC_JOINING); 740 741 buzz::Muc* muc = elem->second; 742 muc->set_state(buzz::Muc::MUC_JOINED); 743 console_->Printf("Joined \"%s\"", muc->jid().Str().c_str()); 744 } 745 746 void CallClient::OnMucStatusUpdate(const buzz::Jid& jid, 747 const buzz::MucStatus& status) { 748 749 // Look up this muc. 750 MucMap::iterator elem = mucs_.find(jid); 751 ASSERT(elem != mucs_.end() && 752 elem->second->state() == buzz::Muc::MUC_JOINED); 753 754 buzz::Muc* muc = elem->second; 755 756 if (status.jid().IsBare() || status.jid() == muc->local_jid()) { 757 // We are only interested in status about other users. 758 return; 759 } 760 761 if (!status.available()) { 762 // User is leaving the room. 763 buzz::Muc::MemberMap::iterator elem = 764 muc->members().find(status.jid().resource()); 765 766 ASSERT(elem != muc->members().end()); 767 768 // If user had src-ids, they have the left the room without explicitly 769 // hanging-up; must tear down the stream if in a call to this room. 770 if (call_ && session_->remote_name() == muc->jid().Str()) { 771 RemoveStream(elem->second.audio_src_id(), elem->second.video_src_id()); 772 } 773 774 // Remove them from the room. 775 muc->members().erase(elem); 776 } else { 777 // Either user has joined or something changed about them. 778 // Note: The [] operator here will create a new entry if it does not 779 // exist, which is what we want. 780 buzz::MucStatus& member_status( 781 muc->members()[status.jid().resource()]); 782 if (call_ && session_->remote_name() == muc->jid().Str()) { 783 // We are in a call to this muc. Must potentially update our streams. 784 // The following code will correctly update our streams regardless of 785 // whether the SSRCs have been removed, added, or changed and regardless 786 // of whether that has been done to both or just one. This relies on the 787 // fact that AddStream/RemoveStream do nothing for SSRC arguments that are 788 // zero. 789 uint32 remove_audio_src_id = 0; 790 uint32 remove_video_src_id = 0; 791 uint32 add_audio_src_id = 0; 792 uint32 add_video_src_id = 0; 793 if (member_status.audio_src_id() != status.audio_src_id()) { 794 remove_audio_src_id = member_status.audio_src_id(); 795 add_audio_src_id = status.audio_src_id(); 796 } 797 if (member_status.video_src_id() != status.video_src_id()) { 798 remove_video_src_id = member_status.video_src_id(); 799 add_video_src_id = status.video_src_id(); 800 } 801 // Remove the old SSRCs, if any. 802 RemoveStream(remove_audio_src_id, remove_video_src_id); 803 // Add the new SSRCs, if any. 804 AddStream(add_audio_src_id, add_video_src_id); 805 } 806 // Update the status. This will use the compiler-generated copy 807 // constructor, which is perfectly adequate for this class. 808 member_status = status; 809 } 810 } 811 812 void CallClient::LeaveMuc(const std::string& room) { 813 buzz::Jid room_jid; 814 if (room.length() > 0) { 815 room_jid = buzz::Jid(room); 816 } else if (mucs_.size() > 0) { 817 // leave the first MUC if no JID specified 818 room_jid = mucs_.begin()->first; 819 } 820 821 if (!room_jid.IsValid()) { 822 console_->Printf("Invalid MUC JID."); 823 return; 824 } 825 826 MucMap::iterator elem = mucs_.find(room_jid); 827 if (elem == mucs_.end()) { 828 console_->Printf("No such MUC."); 829 return; 830 } 831 832 buzz::Muc* muc = elem->second; 833 muc->set_state(buzz::Muc::MUC_LEAVING); 834 835 buzz::Status status; 836 status.set_jid(my_status_.jid()); 837 status.set_available(false); 838 status.set_priority(0); 839 presence_out_->SendDirected(muc->local_jid(), status); 840 } 841 842 void CallClient::OnMucLeft(const buzz::Jid& endpoint, int error) { 843 // We could be kicked from a room from any state. We would hope this 844 // happens While in the MUC_LEAVING state 845 MucMap::iterator elem = mucs_.find(endpoint); 846 if (elem == mucs_.end()) 847 return; 848 849 buzz::Muc* muc = elem->second; 850 if (muc->state() == buzz::Muc::MUC_JOINING) { 851 console_->Printf("Failed to join \"%s\", code=%d", 852 muc->jid().Str().c_str(), error); 853 } else if (muc->state() == buzz::Muc::MUC_JOINED) { 854 console_->Printf("Kicked from \"%s\"", 855 muc->jid().Str().c_str()); 856 } 857 858 delete muc; 859 mucs_.erase(elem); 860 } 861 862 void CallClient::InviteToMuc(const std::string& user, const std::string& room) { 863 // First find the room. 864 const buzz::Muc* found_muc; 865 if (room.length() == 0) { 866 if (mucs_.size() == 0) { 867 console_->Printf("Not in a room yet; can't invite.\n"); 868 return; 869 } 870 // Invite to the first muc 871 found_muc = mucs_.begin()->second; 872 } else { 873 MucMap::iterator elem = mucs_.find(buzz::Jid(room)); 874 if (elem == mucs_.end()) { 875 console_->Printf("Not in room %s.\n", room.c_str()); 876 return; 877 } 878 found_muc = elem->second; 879 } 880 // Now find the user. We invite all of their resources. 881 bool found_user = false; 882 buzz::Jid user_jid(user); 883 for (RosterMap::iterator iter = roster_->begin(); 884 iter != roster_->end(); ++iter) { 885 if (iter->second.jid.BareEquals(user_jid)) { 886 muc_invite_send_->Send(iter->second.jid, *found_muc); 887 found_user = true; 888 } 889 } 890 if (!found_user) { 891 console_->Printf("No such friend as %s.\n", user.c_str()); 892 return; 893 } 894 } 895 896 void CallClient::GetDevices() { 897 std::vector<std::string> names; 898 media_client_->GetAudioInputDevices(&names); 899 printf("Audio input devices:\n"); 900 PrintDevices(names); 901 media_client_->GetAudioOutputDevices(&names); 902 printf("Audio output devices:\n"); 903 PrintDevices(names); 904 media_client_->GetVideoCaptureDevices(&names); 905 printf("Video capture devices:\n"); 906 PrintDevices(names); 907 } 908 909 void CallClient::PrintDevices(const std::vector<std::string>& names) { 910 for (size_t i = 0; i < names.size(); ++i) { 911 printf("%d: %s\n", static_cast<int>(i), names[i].c_str()); 912 } 913 } 914 915 void CallClient::OnDevicesChange() { 916 printf("Devices changed.\n"); 917 RefreshStatus(); 918 } 919 920 void CallClient::SetVolume(const std::string& level) { 921 media_client_->SetOutputVolume(strtol(level.c_str(), NULL, 10)); 922 } 923