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