/indra/newview/llvoicevivox.cpp
C++ | 2376 lines | 1661 code | 397 blank | 318 comment | 208 complexity | bf3d28757257273bd32b19557d17e31c MD5 | raw file
Possible License(s): LGPL-2.1
1 /** 2 * @file LLVivoxVoiceClient.cpp 3 * @brief Implementation of LLVivoxVoiceClient class which is the interface to the voice client process. 4 * 5 * $LicenseInfo:firstyear=2001&license=viewerlgpl$ 6 * Second Life Viewer Source Code 7 * Copyright (C) 2010, Linden Research, Inc. 8 * 9 * This library is free software; you can redistribute it and/or 10 * modify it under the terms of the GNU Lesser General Public 11 * License as published by the Free Software Foundation; 12 * version 2.1 of the License only. 13 * 14 * This library is distributed in the hope that it will be useful, 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 * Lesser General Public License for more details. 18 * 19 * You should have received a copy of the GNU Lesser General Public 20 * License along with this library; if not, write to the Free Software 21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 22 * 23 * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA 24 * $/LicenseInfo$ 25 */ 26 27#include "llviewerprecompiledheaders.h" 28#include "llvoicevivox.h" 29 30#include <boost/tokenizer.hpp> 31 32#include "llsdutil.h" 33 34// Linden library includes 35#include "llavatarnamecache.h" 36#include "llvoavatarself.h" 37#include "llbufferstream.h" 38#include "llfile.h" 39#ifdef LL_STANDALONE 40# include "expat.h" 41#else 42# include "expat/expat.h" 43#endif 44#include "llcallbacklist.h" 45#include "llviewerregion.h" 46#include "llviewernetwork.h" // for gGridChoice 47#include "llbase64.h" 48#include "llviewercontrol.h" 49#include "llappviewer.h" // for gDisconnected, gDisableVoice 50 51// Viewer includes 52#include "llmutelist.h" // to check for muted avatars 53#include "llagent.h" 54#include "llcachename.h" 55#include "llimview.h" // for LLIMMgr 56#include "llparcel.h" 57#include "llviewerparcelmgr.h" 58#include "llfirstuse.h" 59#include "llspeakers.h" 60#include "lltrans.h" 61#include "llviewerwindow.h" 62#include "llviewercamera.h" 63 64#include "llviewernetwork.h" 65#include "llnotificationsutil.h" 66 67#include "stringize.h" 68 69// for base64 decoding 70#include "apr_base64.h" 71 72#define USE_SESSION_GROUPS 0 73 74const F32 VOLUME_SCALE_VIVOX = 0.01f; 75 76const F32 SPEAKING_TIMEOUT = 1.f; 77 78static const std::string VOICE_SERVER_TYPE = "Vivox"; 79 80// Don't retry connecting to the daemon more frequently than this: 81const F32 CONNECT_THROTTLE_SECONDS = 1.0f; 82 83// Don't send positional updates more frequently than this: 84const F32 UPDATE_THROTTLE_SECONDS = 0.1f; 85 86const F32 LOGIN_RETRY_SECONDS = 10.0f; 87const int MAX_LOGIN_RETRIES = 12; 88 89// Defines the maximum number of times(in a row) "stateJoiningSession" case for spatial channel is reached in stateMachine() 90// which is treated as normal. If this number is exceeded we suspect there is a problem with connection 91// to voice server (EXT-4313). When voice works correctly, there is from 1 to 15 times. 50 was chosen 92// to make sure we don't make mistake when slight connection problems happen- situation when connection to server is 93// blocked is VERY rare and it's better to sacrifice response time in this situation for the sake of stability. 94const int MAX_NORMAL_JOINING_SPATIAL_NUM = 50; 95 96// How often to check for expired voice fonts in seconds 97const F32 VOICE_FONT_EXPIRY_INTERVAL = 10.f; 98// Time of day at which Vivox expires voice font subscriptions. 99// Used to replace the time portion of received expiry timestamps. 100static const std::string VOICE_FONT_EXPIRY_TIME = "T05:00:00Z"; 101 102// Maximum length of capture buffer recordings in seconds. 103const F32 CAPTURE_BUFFER_MAX_TIME = 10.f; 104 105 106static int scale_mic_volume(float volume) 107{ 108 // incoming volume has the range [0.0 ... 2.0], with 1.0 as the default. 109 // Map it to Vivox levels as follows: 0.0 -> 30, 1.0 -> 50, 2.0 -> 70 110 return 30 + (int)(volume * 20.0f); 111} 112 113static int scale_speaker_volume(float volume) 114{ 115 // incoming volume has the range [0.0 ... 1.0], with 0.5 as the default. 116 // Map it to Vivox levels as follows: 0.0 -> 30, 0.5 -> 50, 1.0 -> 70 117 return 30 + (int)(volume * 40.0f); 118 119} 120 121class LLVivoxVoiceAccountProvisionResponder : 122 public LLHTTPClient::Responder 123{ 124public: 125 LLVivoxVoiceAccountProvisionResponder(int retries) 126 { 127 mRetries = retries; 128 } 129 130 virtual void error(U32 status, const std::string& reason) 131 { 132 if ( mRetries > 0 ) 133 { 134 LL_WARNS("Voice") << "ProvisionVoiceAccountRequest returned an error, retrying. status = " << status << ", reason = \"" << reason << "\"" << LL_ENDL; 135 LLVivoxVoiceClient::getInstance()->requestVoiceAccountProvision( 136 mRetries - 1); 137 } 138 else 139 { 140 LL_WARNS("Voice") << "ProvisionVoiceAccountRequest returned an error, too many retries (giving up). status = " << status << ", reason = \"" << reason << "\"" << LL_ENDL; 141 LLVivoxVoiceClient::getInstance()->giveUp(); 142 } 143 } 144 145 virtual void result(const LLSD& content) 146 { 147 148 std::string voice_sip_uri_hostname; 149 std::string voice_account_server_uri; 150 151 LL_DEBUGS("Voice") << "ProvisionVoiceAccountRequest response:" << ll_pretty_print_sd(content) << LL_ENDL; 152 153 if(content.has("voice_sip_uri_hostname")) 154 voice_sip_uri_hostname = content["voice_sip_uri_hostname"].asString(); 155 156 // this key is actually misnamed -- it will be an entire URI, not just a hostname. 157 if(content.has("voice_account_server_name")) 158 voice_account_server_uri = content["voice_account_server_name"].asString(); 159 160 LLVivoxVoiceClient::getInstance()->login( 161 content["username"].asString(), 162 content["password"].asString(), 163 voice_sip_uri_hostname, 164 voice_account_server_uri); 165 166 } 167 168private: 169 int mRetries; 170}; 171 172 173 174/////////////////////////////////////////////////////////////////////////////////////////////// 175 176class LLVivoxVoiceClientMuteListObserver : public LLMuteListObserver 177{ 178 /* virtual */ void onChange() { LLVivoxVoiceClient::getInstance()->muteListChanged();} 179}; 180 181class LLVivoxVoiceClientFriendsObserver : public LLFriendObserver 182{ 183public: 184 /* virtual */ void changed(U32 mask) { LLVivoxVoiceClient::getInstance()->updateFriends(mask);} 185}; 186 187static LLVivoxVoiceClientMuteListObserver mutelist_listener; 188static bool sMuteListListener_listening = false; 189 190static LLVivoxVoiceClientFriendsObserver *friendslist_listener = NULL; 191 192/////////////////////////////////////////////////////////////////////////////////////////////// 193 194class LLVivoxVoiceClientCapResponder : public LLHTTPClient::Responder 195{ 196public: 197 LLVivoxVoiceClientCapResponder(LLVivoxVoiceClient::state requesting_state) : mRequestingState(requesting_state) {}; 198 199 virtual void error(U32 status, const std::string& reason); // called with bad status codes 200 virtual void result(const LLSD& content); 201 202private: 203 LLVivoxVoiceClient::state mRequestingState; // state 204}; 205 206void LLVivoxVoiceClientCapResponder::error(U32 status, const std::string& reason) 207{ 208 LL_WARNS("Voice") << "LLVivoxVoiceClientCapResponder::error(" 209 << status << ": " << reason << ")" 210 << LL_ENDL; 211 LLVivoxVoiceClient::getInstance()->sessionTerminate(); 212} 213 214void LLVivoxVoiceClientCapResponder::result(const LLSD& content) 215{ 216 LLSD::map_const_iterator iter; 217 218 LL_DEBUGS("Voice") << "ParcelVoiceInfoRequest response:" << ll_pretty_print_sd(content) << LL_ENDL; 219 220 std::string uri; 221 std::string credentials; 222 223 if ( content.has("voice_credentials") ) 224 { 225 LLSD voice_credentials = content["voice_credentials"]; 226 if ( voice_credentials.has("channel_uri") ) 227 { 228 uri = voice_credentials["channel_uri"].asString(); 229 } 230 if ( voice_credentials.has("channel_credentials") ) 231 { 232 credentials = 233 voice_credentials["channel_credentials"].asString(); 234 } 235 } 236 237 // set the spatial channel. If no voice credentials or uri are 238 // available, then we simply drop out of voice spatially. 239 if(LLVivoxVoiceClient::getInstance()->parcelVoiceInfoReceived(mRequestingState)) 240 { 241 LLVivoxVoiceClient::getInstance()->setSpatialChannel(uri, credentials); 242 } 243} 244 245 246 247#if LL_WINDOWS 248static HANDLE sGatewayHandle = 0; 249 250static bool isGatewayRunning() 251{ 252 bool result = false; 253 if(sGatewayHandle != 0) 254 { 255 DWORD waitresult = WaitForSingleObject(sGatewayHandle, 0); 256 if(waitresult != WAIT_OBJECT_0) 257 { 258 result = true; 259 } 260 } 261 return result; 262} 263static void killGateway() 264{ 265 if(sGatewayHandle != 0) 266 { 267 TerminateProcess(sGatewayHandle,0); 268 } 269} 270 271#else // Mac and linux 272 273static pid_t sGatewayPID = 0; 274static bool isGatewayRunning() 275{ 276 bool result = false; 277 if(sGatewayPID != 0) 278 { 279 // A kill with signal number 0 has no effect, just does error checking. It should return an error if the process no longer exists. 280 if(kill(sGatewayPID, 0) == 0) 281 { 282 result = true; 283 } 284 } 285 return result; 286} 287 288static void killGateway() 289{ 290 if(sGatewayPID != 0) 291 { 292 kill(sGatewayPID, SIGTERM); 293 } 294} 295 296#endif 297 298/////////////////////////////////////////////////////////////////////////////////////////////// 299 300LLVivoxVoiceClient::LLVivoxVoiceClient() : 301 mState(stateDisabled), 302 mSessionTerminateRequested(false), 303 mRelogRequested(false), 304 mConnected(false), 305 mPump(NULL), 306 mSpatialJoiningNum(0), 307 308 mTuningMode(false), 309 mTuningEnergy(0.0f), 310 mTuningMicVolume(0), 311 mTuningMicVolumeDirty(true), 312 mTuningSpeakerVolume(0), 313 mTuningSpeakerVolumeDirty(true), 314 mTuningExitState(stateDisabled), 315 316 mAreaVoiceDisabled(false), 317 mAudioSession(NULL), 318 mAudioSessionChanged(false), 319 mNextAudioSession(NULL), 320 321 mCurrentParcelLocalID(0), 322 mNumberOfAliases(0), 323 mCommandCookie(0), 324 mLoginRetryCount(0), 325 326 mBuddyListMapPopulated(false), 327 mBlockRulesListReceived(false), 328 mAutoAcceptRulesListReceived(false), 329 330 mCaptureDeviceDirty(false), 331 mRenderDeviceDirty(false), 332 mSpatialCoordsDirty(false), 333 334 mMuteMic(false), 335 mMuteMicDirty(false), 336 mFriendsListDirty(true), 337 338 mEarLocation(0), 339 mSpeakerVolumeDirty(true), 340 mSpeakerMuteDirty(true), 341 mMicVolume(0), 342 mMicVolumeDirty(true), 343 344 mVoiceEnabled(false), 345 mWriteInProgress(false), 346 347 mLipSyncEnabled(false), 348 349 mVoiceFontsReceived(false), 350 mVoiceFontsNew(false), 351 mVoiceFontListDirty(false), 352 353 mCaptureBufferMode(false), 354 mCaptureBufferRecording(false), 355 mCaptureBufferRecorded(false), 356 mCaptureBufferPlaying(false), 357 mPlayRequestCount(0) 358{ 359 mSpeakerVolume = scale_speaker_volume(0); 360 361 mVoiceVersion.serverVersion = ""; 362 mVoiceVersion.serverType = VOICE_SERVER_TYPE; 363 364 // gMuteListp isn't set up at this point, so we defer this until later. 365// gMuteListp->addObserver(&mutelist_listener); 366 367 368#if LL_DARWIN || LL_LINUX || LL_SOLARIS 369 // HACK: THIS DOES NOT BELONG HERE 370 // When the vivox daemon dies, the next write attempt on our socket generates a SIGPIPE, which kills us. 371 // This should cause us to ignore SIGPIPE and handle the error through proper channels. 372 // This should really be set up elsewhere. Where should it go? 373 signal(SIGPIPE, SIG_IGN); 374 375 // Since we're now launching the gateway with fork/exec instead of system(), we need to deal with zombie processes. 376 // Ignoring SIGCHLD should prevent zombies from being created. Alternately, we could use wait(), but I'd rather not do that. 377 signal(SIGCHLD, SIG_IGN); 378#endif 379 380 // set up state machine 381 setState(stateDisabled); 382 383 gIdleCallbacks.addFunction(idle, this); 384} 385 386//--------------------------------------------------- 387 388LLVivoxVoiceClient::~LLVivoxVoiceClient() 389{ 390} 391 392//--------------------------------------------------- 393 394void LLVivoxVoiceClient::init(LLPumpIO *pump) 395{ 396 // constructor will set up LLVoiceClient::getInstance() 397 LLVivoxVoiceClient::getInstance()->mPump = pump; 398} 399 400void LLVivoxVoiceClient::terminate() 401{ 402 if(mConnected) 403 { 404 logout(); 405 connectorShutdown(); 406 closeSocket(); // Need to do this now -- bad things happen if the destructor does it later. 407 cleanUp(); 408 } 409 else 410 { 411 killGateway(); 412 } 413} 414 415//--------------------------------------------------- 416 417void LLVivoxVoiceClient::cleanUp() 418{ 419 deleteAllSessions(); 420 deleteAllBuddies(); 421 deleteAllVoiceFonts(); 422 deleteVoiceFontTemplates(); 423} 424 425//--------------------------------------------------- 426 427const LLVoiceVersionInfo& LLVivoxVoiceClient::getVersion() 428{ 429 return mVoiceVersion; 430} 431 432//--------------------------------------------------- 433 434void LLVivoxVoiceClient::updateSettings() 435{ 436 setVoiceEnabled(gSavedSettings.getBOOL("EnableVoiceChat")); 437 setEarLocation(gSavedSettings.getS32("VoiceEarLocation")); 438 439 std::string inputDevice = gSavedSettings.getString("VoiceInputAudioDevice"); 440 setCaptureDevice(inputDevice); 441 std::string outputDevice = gSavedSettings.getString("VoiceOutputAudioDevice"); 442 setRenderDevice(outputDevice); 443 F32 mic_level = gSavedSettings.getF32("AudioLevelMic"); 444 setMicGain(mic_level); 445 setLipSyncEnabled(gSavedSettings.getBOOL("LipSyncEnabled")); 446} 447 448///////////////////////////// 449// utility functions 450 451bool LLVivoxVoiceClient::writeString(const std::string &str) 452{ 453 bool result = false; 454 if(mConnected) 455 { 456 apr_status_t err; 457 apr_size_t size = (apr_size_t)str.size(); 458 apr_size_t written = size; 459 460 //MARK: Turn this on to log outgoing XML 461// LL_DEBUGS("Voice") << "sending: " << str << LL_ENDL; 462 463 // check return code - sockets will fail (broken, etc.) 464 err = apr_socket_send( 465 mSocket->getSocket(), 466 (const char*)str.data(), 467 &written); 468 469 if(err == 0) 470 { 471 // Success. 472 result = true; 473 } 474 // TODO: handle partial writes (written is number of bytes written) 475 // Need to set socket to non-blocking before this will work. 476// else if(APR_STATUS_IS_EAGAIN(err)) 477// { 478// // 479// } 480 else 481 { 482 // Assume any socket error means something bad. For now, just close the socket. 483 char buf[MAX_STRING]; 484 LL_WARNS("Voice") << "apr error " << err << " ("<< apr_strerror(err, buf, MAX_STRING) << ") sending data to vivox daemon." << LL_ENDL; 485 daemonDied(); 486 } 487 } 488 489 return result; 490} 491 492 493///////////////////////////// 494// session control messages 495void LLVivoxVoiceClient::connectorCreate() 496{ 497 std::ostringstream stream; 498 std::string logpath = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, ""); 499 std::string loglevel = "0"; 500 501 // Transition to stateConnectorStarted when the connector handle comes back. 502 setState(stateConnectorStarting); 503 504 std::string savedLogLevel = gSavedSettings.getString("VivoxDebugLevel"); 505 506 if(savedLogLevel != "-1") 507 { 508 LL_DEBUGS("Voice") << "creating connector with logging enabled" << LL_ENDL; 509 loglevel = "10"; 510 } 511 512 stream 513 << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.Create.1\">" 514 << "<ClientName>V2 SDK</ClientName>" 515 << "<AccountManagementServer>" << mVoiceAccountServerURI << "</AccountManagementServer>" 516 << "<Mode>Normal</Mode>" 517 << "<Logging>" 518 << "<Folder>" << logpath << "</Folder>" 519 << "<FileNamePrefix>Connector</FileNamePrefix>" 520 << "<FileNameSuffix>.log</FileNameSuffix>" 521 << "<LogLevel>" << loglevel << "</LogLevel>" 522 << "</Logging>" 523 << "<Application>SecondLifeViewer.1</Application>" 524 << "</Request>\n\n\n"; 525 526 writeString(stream.str()); 527} 528 529void LLVivoxVoiceClient::connectorShutdown() 530{ 531 setState(stateConnectorStopping); 532 533 if(!mConnectorHandle.empty()) 534 { 535 std::ostringstream stream; 536 stream 537 << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.InitiateShutdown.1\">" 538 << "<ConnectorHandle>" << mConnectorHandle << "</ConnectorHandle>" 539 << "</Request>" 540 << "\n\n\n"; 541 542 mConnectorHandle.clear(); 543 544 writeString(stream.str()); 545 } 546} 547 548void LLVivoxVoiceClient::userAuthorized(const std::string& user_id, const LLUUID &agentID) 549{ 550 551 mAccountDisplayName = user_id; 552 553 LL_INFOS("Voice") << "name \"" << mAccountDisplayName << "\" , ID " << agentID << LL_ENDL; 554 555 mAccountName = nameFromID(agentID); 556} 557 558void LLVivoxVoiceClient::requestVoiceAccountProvision(S32 retries) 559{ 560 LLViewerRegion *region = gAgent.getRegion(); 561 562 if ( region && mVoiceEnabled ) 563 { 564 std::string url = 565 region->getCapability("ProvisionVoiceAccountRequest"); 566 567 if ( url.empty() ) 568 { 569 // we've not received the capability yet, so return. 570 // the password will remain empty, so we'll remain in 571 // stateIdle 572 return; 573 } 574 575 LLHTTPClient::post( 576 url, 577 LLSD(), 578 new LLVivoxVoiceAccountProvisionResponder(retries)); 579 580 setState(stateConnectorStart); 581 } 582} 583 584void LLVivoxVoiceClient::login( 585 const std::string& account_name, 586 const std::string& password, 587 const std::string& voice_sip_uri_hostname, 588 const std::string& voice_account_server_uri) 589{ 590 mVoiceSIPURIHostName = voice_sip_uri_hostname; 591 mVoiceAccountServerURI = voice_account_server_uri; 592 593 if(!mAccountHandle.empty()) 594 { 595 // Already logged in. 596 LL_WARNS("Voice") << "Called while already logged in." << LL_ENDL; 597 598 // Don't process another login. 599 return; 600 } 601 else if ( account_name != mAccountName ) 602 { 603 //TODO: error? 604 LL_WARNS("Voice") << "Wrong account name! " << account_name 605 << " instead of " << mAccountName << LL_ENDL; 606 } 607 else 608 { 609 mAccountPassword = password; 610 } 611 612 std::string debugSIPURIHostName = gSavedSettings.getString("VivoxDebugSIPURIHostName"); 613 614 if( !debugSIPURIHostName.empty() ) 615 { 616 mVoiceSIPURIHostName = debugSIPURIHostName; 617 } 618 619 if( mVoiceSIPURIHostName.empty() ) 620 { 621 // we have an empty account server name 622 // so we fall back to hardcoded defaults 623 624 if(LLGridManager::getInstance()->isInProductionGrid()) 625 { 626 // Use the release account server 627 mVoiceSIPURIHostName = "bhr.vivox.com"; 628 } 629 else 630 { 631 // Use the development account server 632 mVoiceSIPURIHostName = "bhd.vivox.com"; 633 } 634 } 635 636 std::string debugAccountServerURI = gSavedSettings.getString("VivoxDebugVoiceAccountServerURI"); 637 638 if( !debugAccountServerURI.empty() ) 639 { 640 mVoiceAccountServerURI = debugAccountServerURI; 641 } 642 643 if( mVoiceAccountServerURI.empty() ) 644 { 645 // If the account server URI isn't specified, construct it from the SIP URI hostname 646 mVoiceAccountServerURI = "https://www." + mVoiceSIPURIHostName + "/api2/"; 647 } 648} 649 650void LLVivoxVoiceClient::idle(void* user_data) 651{ 652 LLVivoxVoiceClient* self = (LLVivoxVoiceClient*)user_data; 653 self->stateMachine(); 654} 655 656std::string LLVivoxVoiceClient::state2string(LLVivoxVoiceClient::state inState) 657{ 658 std::string result = "UNKNOWN"; 659 660 // Prevent copy-paste errors when updating this list... 661#define CASE(x) case x: result = #x; break 662 663 switch(inState) 664 { 665 CASE(stateDisableCleanup); 666 CASE(stateDisabled); 667 CASE(stateStart); 668 CASE(stateDaemonLaunched); 669 CASE(stateConnecting); 670 CASE(stateConnected); 671 CASE(stateIdle); 672 CASE(stateMicTuningStart); 673 CASE(stateMicTuningRunning); 674 CASE(stateMicTuningStop); 675 CASE(stateCaptureBufferPaused); 676 CASE(stateCaptureBufferRecStart); 677 CASE(stateCaptureBufferRecording); 678 CASE(stateCaptureBufferPlayStart); 679 CASE(stateCaptureBufferPlaying); 680 CASE(stateConnectorStart); 681 CASE(stateConnectorStarting); 682 CASE(stateConnectorStarted); 683 CASE(stateLoginRetry); 684 CASE(stateLoginRetryWait); 685 CASE(stateNeedsLogin); 686 CASE(stateLoggingIn); 687 CASE(stateLoggedIn); 688 CASE(stateVoiceFontsWait); 689 CASE(stateVoiceFontsReceived); 690 CASE(stateCreatingSessionGroup); 691 CASE(stateNoChannel); 692 CASE(stateRetrievingParcelVoiceInfo); 693 CASE(stateJoiningSession); 694 CASE(stateSessionJoined); 695 CASE(stateRunning); 696 CASE(stateLeavingSession); 697 CASE(stateSessionTerminated); 698 CASE(stateLoggingOut); 699 CASE(stateLoggedOut); 700 CASE(stateConnectorStopping); 701 CASE(stateConnectorStopped); 702 CASE(stateConnectorFailed); 703 CASE(stateConnectorFailedWaiting); 704 CASE(stateLoginFailed); 705 CASE(stateLoginFailedWaiting); 706 CASE(stateJoinSessionFailed); 707 CASE(stateJoinSessionFailedWaiting); 708 CASE(stateJail); 709 } 710 711#undef CASE 712 713 return result; 714} 715 716 717 718void LLVivoxVoiceClient::setState(state inState) 719{ 720 LL_DEBUGS("Voice") << "entering state " << state2string(inState) << LL_ENDL; 721 722 mState = inState; 723} 724 725void LLVivoxVoiceClient::stateMachine() 726{ 727 if(gDisconnected) 728 { 729 // The viewer has been disconnected from the sim. Disable voice. 730 setVoiceEnabled(false); 731 } 732 733 if(mVoiceEnabled) 734 { 735 updatePosition(); 736 } 737 else if(mTuningMode) 738 { 739 // Tuning mode is special -- it needs to launch SLVoice even if voice is disabled. 740 } 741 else 742 { 743 if((getState() != stateDisabled) && (getState() != stateDisableCleanup)) 744 { 745 // User turned off voice support. Send the cleanup messages, close the socket, and reset. 746 if(!mConnected) 747 { 748 // if voice was turned off after the daemon was launched but before we could connect to it, we may need to issue a kill. 749 LL_INFOS("Voice") << "Disabling voice before connection to daemon, terminating." << LL_ENDL; 750 killGateway(); 751 } 752 753 logout(); 754 connectorShutdown(); 755 756 setState(stateDisableCleanup); 757 } 758 } 759 760 761 switch(getState()) 762 { 763 //MARK: stateDisableCleanup 764 case stateDisableCleanup: 765 // Clean up and reset everything. 766 closeSocket(); 767 cleanUp(); 768 769 mAccountHandle.clear(); 770 mAccountPassword.clear(); 771 mVoiceAccountServerURI.clear(); 772 773 setState(stateDisabled); 774 break; 775 776 //MARK: stateDisabled 777 case stateDisabled: 778 if(mTuningMode || (mVoiceEnabled && !mAccountName.empty())) 779 { 780 setState(stateStart); 781 } 782 break; 783 784 //MARK: stateStart 785 case stateStart: 786 if(gSavedSettings.getBOOL("CmdLineDisableVoice")) 787 { 788 // Voice is locked out, we must not launch the vivox daemon. 789 setState(stateJail); 790 } 791 else if(!isGatewayRunning()) 792 { 793 if(true) 794 { 795 // Launch the voice daemon 796 797 // *FIX:Mani - Using the executable dir instead 798 // of mAppRODataDir, the working directory from which the app 799 // is launched. 800 //std::string exe_path = gDirUtilp->getAppRODataDir(); 801 std::string exe_path = gDirUtilp->getExecutableDir(); 802 exe_path += gDirUtilp->getDirDelimiter(); 803#if LL_WINDOWS 804 exe_path += "SLVoice.exe"; 805#elif LL_DARWIN 806 exe_path += "../Resources/SLVoice"; 807#else 808 exe_path += "SLVoice"; 809#endif 810 // See if the vivox executable exists 811 llstat s; 812 if(!LLFile::stat(exe_path, &s)) 813 { 814 // vivox executable exists. Build the command line and launch the daemon. 815 // SLIM SDK: these arguments are no longer necessary. 816// std::string args = " -p tcp -h -c"; 817 std::string args; 818 std::string cmd; 819 std::string loglevel = gSavedSettings.getString("VivoxDebugLevel"); 820 821 if(loglevel.empty()) 822 { 823 loglevel = "-1"; // turn logging off completely 824 } 825 826 args += " -ll "; 827 args += loglevel; 828 829 LL_DEBUGS("Voice") << "Args for SLVoice: " << args << LL_ENDL; 830 831#if LL_WINDOWS 832 PROCESS_INFORMATION pinfo; 833 STARTUPINFOA sinfo; 834 835 memset(&sinfo, 0, sizeof(sinfo)); 836 837 std::string exe_dir = gDirUtilp->getAppRODataDir(); 838 cmd = "SLVoice.exe"; 839 cmd += args; 840 841 // So retarded. Windows requires that the second parameter to CreateProcessA be writable (non-const) string... 842 char *args2 = new char[args.size() + 1]; 843 strcpy(args2, args.c_str()); 844 if(!CreateProcessA(exe_path.c_str(), args2, NULL, NULL, FALSE, 0, NULL, exe_dir.c_str(), &sinfo, &pinfo)) 845 { 846// DWORD dwErr = GetLastError(); 847 } 848 else 849 { 850 // foo = pinfo.dwProcessId; // get your pid here if you want to use it later on 851 // CloseHandle(pinfo.hProcess); // stops leaks - nothing else 852 sGatewayHandle = pinfo.hProcess; 853 CloseHandle(pinfo.hThread); // stops leaks - nothing else 854 } 855 856 delete[] args2; 857#else // LL_WINDOWS 858 // This should be the same for mac and linux 859 { 860 std::vector<std::string> arglist; 861 arglist.push_back(exe_path); 862 863 // Split the argument string into separate strings for each argument 864 typedef boost::tokenizer<boost::char_separator<char> > tokenizer; 865 boost::char_separator<char> sep(" "); 866 tokenizer tokens(args, sep); 867 tokenizer::iterator token_iter; 868 869 for(token_iter = tokens.begin(); token_iter != tokens.end(); ++token_iter) 870 { 871 arglist.push_back(*token_iter); 872 } 873 874 // create an argv vector for the child process 875 char **fakeargv = new char*[arglist.size() + 1]; 876 int i; 877 for(i=0; i < arglist.size(); i++) 878 fakeargv[i] = const_cast<char*>(arglist[i].c_str()); 879 880 fakeargv[i] = NULL; 881 882 fflush(NULL); // flush all buffers before the child inherits them 883 pid_t id = vfork(); 884 if(id == 0) 885 { 886 // child 887 execv(exe_path.c_str(), fakeargv); 888 889 // If we reach this point, the exec failed. 890 // Use _exit() instead of exit() per the vfork man page. 891 _exit(0); 892 } 893 894 // parent 895 delete[] fakeargv; 896 sGatewayPID = id; 897 } 898#endif // LL_WINDOWS 899 mDaemonHost = LLHost(gSavedSettings.getString("VivoxVoiceHost").c_str(), gSavedSettings.getU32("VivoxVoicePort")); 900 } 901 else 902 { 903 LL_INFOS("Voice") << exe_path << " not found." << LL_ENDL; 904 } 905 } 906 else 907 { 908 // SLIM SDK: port changed from 44124 to 44125. 909 // We can connect to a client gateway running on another host. This is useful for testing. 910 // To do this, launch the gateway on a nearby host like this: 911 // vivox-gw.exe -p tcp -i 0.0.0.0:44125 912 // and put that host's IP address here. 913 mDaemonHost = LLHost(gSavedSettings.getString("VivoxVoiceHost"), gSavedSettings.getU32("VivoxVoicePort")); 914 } 915 916 mUpdateTimer.start(); 917 mUpdateTimer.setTimerExpirySec(CONNECT_THROTTLE_SECONDS); 918 919 setState(stateDaemonLaunched); 920 921 // Dirty the states we'll need to sync with the daemon when it comes up. 922 mMuteMicDirty = true; 923 mMicVolumeDirty = true; 924 mSpeakerVolumeDirty = true; 925 mSpeakerMuteDirty = true; 926 // These only need to be set if they're not default (i.e. empty string). 927 mCaptureDeviceDirty = !mCaptureDevice.empty(); 928 mRenderDeviceDirty = !mRenderDevice.empty(); 929 930 mMainSessionGroupHandle.clear(); 931 } 932 break; 933 934 //MARK: stateDaemonLaunched 935 case stateDaemonLaunched: 936 if(mUpdateTimer.hasExpired()) 937 { 938 LL_DEBUGS("Voice") << "Connecting to vivox daemon:" << mDaemonHost << LL_ENDL; 939 940 mUpdateTimer.setTimerExpirySec(CONNECT_THROTTLE_SECONDS); 941 942 if(!mSocket) 943 { 944 mSocket = LLSocket::create(gAPRPoolp, LLSocket::STREAM_TCP); 945 } 946 947 mConnected = mSocket->blockingConnect(mDaemonHost); 948 if(mConnected) 949 { 950 setState(stateConnecting); 951 } 952 else 953 { 954 // If the connect failed, the socket may have been put into a bad state. Delete it. 955 closeSocket(); 956 } 957 } 958 break; 959 960 //MARK: stateConnecting 961 case stateConnecting: 962 // Can't do this until we have the pump available. 963 if(mPump) 964 { 965 // MBW -- Note to self: pumps and pipes examples in 966 // indra/test/io.cpp 967 // indra/test/llpipeutil.{cpp|h} 968 969 // Attach the pumps and pipes 970 971 LLPumpIO::chain_t readChain; 972 973 readChain.push_back(LLIOPipe::ptr_t(new LLIOSocketReader(mSocket))); 974 readChain.push_back(LLIOPipe::ptr_t(new LLVivoxProtocolParser())); 975 976 mPump->addChain(readChain, NEVER_CHAIN_EXPIRY_SECS); 977 978 setState(stateConnected); 979 } 980 981 break; 982 983 //MARK: stateConnected 984 case stateConnected: 985 // Initial devices query 986 getCaptureDevicesSendMessage(); 987 getRenderDevicesSendMessage(); 988 989 mLoginRetryCount = 0; 990 991 setState(stateIdle); 992 break; 993 994 //MARK: stateIdle 995 case stateIdle: 996 // This is the idle state where we're connected to the daemon but haven't set up a connector yet. 997 if(mTuningMode) 998 { 999 mTuningExitState = stateIdle; 1000 setState(stateMicTuningStart); 1001 } 1002 else if(!mVoiceEnabled) 1003 { 1004 // We never started up the connector. This will shut down the daemon. 1005 setState(stateConnectorStopped); 1006 } 1007 else if(!mAccountName.empty()) 1008 { 1009 if ( mAccountPassword.empty() ) 1010 { 1011 requestVoiceAccountProvision(); 1012 } 1013 } 1014 break; 1015 1016 //MARK: stateMicTuningStart 1017 case stateMicTuningStart: 1018 if(mUpdateTimer.hasExpired()) 1019 { 1020 if(mCaptureDeviceDirty || mRenderDeviceDirty) 1021 { 1022 // These can't be changed while in tuning mode. Set them before starting. 1023 std::ostringstream stream; 1024 1025 buildSetCaptureDevice(stream); 1026 buildSetRenderDevice(stream); 1027 1028 if(!stream.str().empty()) 1029 { 1030 writeString(stream.str()); 1031 } 1032 1033 // This will come around again in the same state and start the capture, after the timer expires. 1034 mUpdateTimer.start(); 1035 mUpdateTimer.setTimerExpirySec(UPDATE_THROTTLE_SECONDS); 1036 } 1037 else 1038 { 1039 // duration parameter is currently unused, per Mike S. 1040 tuningCaptureStartSendMessage(10000); 1041 1042 setState(stateMicTuningRunning); 1043 } 1044 } 1045 1046 break; 1047 1048 //MARK: stateMicTuningRunning 1049 case stateMicTuningRunning: 1050 if(!mTuningMode || mCaptureDeviceDirty || mRenderDeviceDirty) 1051 { 1052 // All of these conditions make us leave tuning mode. 1053 setState(stateMicTuningStop); 1054 } 1055 else 1056 { 1057 // process mic/speaker volume changes 1058 if(mTuningMicVolumeDirty || mTuningSpeakerVolumeDirty) 1059 { 1060 std::ostringstream stream; 1061 1062 if(mTuningMicVolumeDirty) 1063 { 1064 LL_INFOS("Voice") << "setting tuning mic level to " << mTuningMicVolume << LL_ENDL; 1065 stream 1066 << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetMicLevel.1\">" 1067 << "<Level>" << mTuningMicVolume << "</Level>" 1068 << "</Request>\n\n\n"; 1069 } 1070 1071 if(mTuningSpeakerVolumeDirty) 1072 { 1073 stream 1074 << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetSpeakerLevel.1\">" 1075 << "<Level>" << mTuningSpeakerVolume << "</Level>" 1076 << "</Request>\n\n\n"; 1077 } 1078 1079 mTuningMicVolumeDirty = false; 1080 mTuningSpeakerVolumeDirty = false; 1081 1082 if(!stream.str().empty()) 1083 { 1084 writeString(stream.str()); 1085 } 1086 } 1087 } 1088 break; 1089 1090 //MARK: stateMicTuningStop 1091 case stateMicTuningStop: 1092 { 1093 // transition out of mic tuning 1094 tuningCaptureStopSendMessage(); 1095 1096 setState(mTuningExitState); 1097 1098 // if we exited just to change devices, this will keep us from re-entering too fast. 1099 mUpdateTimer.start(); 1100 mUpdateTimer.setTimerExpirySec(UPDATE_THROTTLE_SECONDS); 1101 1102 } 1103 break; 1104 1105 //MARK: stateCaptureBufferPaused 1106 case stateCaptureBufferPaused: 1107 if (!mCaptureBufferMode) 1108 { 1109 // Leaving capture mode. 1110 1111 mCaptureBufferRecording = false; 1112 mCaptureBufferRecorded = false; 1113 mCaptureBufferPlaying = false; 1114 1115 // Return to stateNoChannel to trigger reconnection to a channel. 1116 setState(stateNoChannel); 1117 } 1118 else if (mCaptureBufferRecording) 1119 { 1120 setState(stateCaptureBufferRecStart); 1121 } 1122 else if (mCaptureBufferPlaying) 1123 { 1124 setState(stateCaptureBufferPlayStart); 1125 } 1126 break; 1127 1128 //MARK: stateCaptureBufferRecStart 1129 case stateCaptureBufferRecStart: 1130 captureBufferRecordStartSendMessage(); 1131 1132 // Flag that something is recorded to allow playback. 1133 mCaptureBufferRecorded = true; 1134 1135 // Start the timer, recording will be stopped when it expires. 1136 mCaptureTimer.start(); 1137 mCaptureTimer.setTimerExpirySec(CAPTURE_BUFFER_MAX_TIME); 1138 1139 // Update UI, should really use a separate callback. 1140 notifyVoiceFontObservers(); 1141 1142 setState(stateCaptureBufferRecording); 1143 break; 1144 1145 //MARK: stateCaptureBufferRecording 1146 case stateCaptureBufferRecording: 1147 if (!mCaptureBufferMode || !mCaptureBufferRecording || 1148 mCaptureBufferPlaying || mCaptureTimer.hasExpired()) 1149 { 1150 // Stop recording 1151 captureBufferRecordStopSendMessage(); 1152 mCaptureBufferRecording = false; 1153 1154 // Update UI, should really use a separate callback. 1155 notifyVoiceFontObservers(); 1156 1157 setState(stateCaptureBufferPaused); 1158 } 1159 break; 1160 1161 //MARK: stateCaptureBufferPlayStart 1162 case stateCaptureBufferPlayStart: 1163 captureBufferPlayStartSendMessage(mPreviewVoiceFont); 1164 1165 // Store the voice font being previewed, so that we know to restart if it changes. 1166 mPreviewVoiceFontLast = mPreviewVoiceFont; 1167 1168 // Update UI, should really use a separate callback. 1169 notifyVoiceFontObservers(); 1170 1171 setState(stateCaptureBufferPlaying); 1172 break; 1173 1174 //MARK: stateCaptureBufferPlaying 1175 case stateCaptureBufferPlaying: 1176 if (mCaptureBufferPlaying && mPreviewVoiceFont != mPreviewVoiceFontLast) 1177 { 1178 // If the preview voice font changes, restart playing with the new font. 1179 setState(stateCaptureBufferPlayStart); 1180 } 1181 else if (!mCaptureBufferMode || !mCaptureBufferPlaying || mCaptureBufferRecording) 1182 { 1183 // Stop playing. 1184 captureBufferPlayStopSendMessage(); 1185 mCaptureBufferPlaying = false; 1186 1187 // Update UI, should really use a separate callback. 1188 notifyVoiceFontObservers(); 1189 1190 setState(stateCaptureBufferPaused); 1191 } 1192 break; 1193 1194 //MARK: stateConnectorStart 1195 case stateConnectorStart: 1196 if(!mVoiceEnabled) 1197 { 1198 // We were never logged in. This will shut down the connector. 1199 setState(stateLoggedOut); 1200 } 1201 else if(!mVoiceAccountServerURI.empty()) 1202 { 1203 connectorCreate(); 1204 } 1205 break; 1206 1207 //MARK: stateConnectorStarting 1208 case stateConnectorStarting: // waiting for connector handle 1209 // connectorCreateResponse() will transition from here to stateConnectorStarted. 1210 break; 1211 1212 //MARK: stateConnectorStarted 1213 case stateConnectorStarted: // connector handle received 1214 if(!mVoiceEnabled) 1215 { 1216 // We were never logged in. This will shut down the connector. 1217 setState(stateLoggedOut); 1218 } 1219 else 1220 { 1221 // The connector is started. Send a login message. 1222 setState(stateNeedsLogin); 1223 } 1224 break; 1225 1226 //MARK: stateLoginRetry 1227 case stateLoginRetry: 1228 if(mLoginRetryCount == 0) 1229 { 1230 // First retry -- display a message to the user 1231 notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LOGIN_RETRY); 1232 } 1233 1234 mLoginRetryCount++; 1235 1236 if(mLoginRetryCount > MAX_LOGIN_RETRIES) 1237 { 1238 LL_WARNS("Voice") << "too many login retries, giving up." << LL_ENDL; 1239 setState(stateLoginFailed); 1240 LLSD args; 1241 std::stringstream errs; 1242 errs << mVoiceAccountServerURI << "\n:UDP: 3478, 3479, 5060, 5062, 12000-17000"; 1243 args["HOSTID"] = errs.str(); 1244 if (LLGridManager::getInstance()->isSystemGrid()) 1245 { 1246 LLNotificationsUtil::add("NoVoiceConnect", args); 1247 } 1248 else 1249 { 1250 LLNotificationsUtil::add("NoVoiceConnect-GIAB", args); 1251 } 1252 } 1253 else 1254 { 1255 LL_INFOS("Voice") << "will retry login in " << LOGIN_RETRY_SECONDS << " seconds." << LL_ENDL; 1256 mUpdateTimer.start(); 1257 mUpdateTimer.setTimerExpirySec(LOGIN_RETRY_SECONDS); 1258 setState(stateLoginRetryWait); 1259 } 1260 break; 1261 1262 //MARK: stateLoginRetryWait 1263 case stateLoginRetryWait: 1264 if(mUpdateTimer.hasExpired()) 1265 { 1266 setState(stateNeedsLogin); 1267 } 1268 break; 1269 1270 //MARK: stateNeedsLogin 1271 case stateNeedsLogin: 1272 if(!mAccountPassword.empty()) 1273 { 1274 setState(stateLoggingIn); 1275 loginSendMessage(); 1276 } 1277 break; 1278 1279 //MARK: stateLoggingIn 1280 case stateLoggingIn: // waiting for account handle 1281 // loginResponse() will transition from here to stateLoggedIn. 1282 break; 1283 1284 //MARK: stateLoggedIn 1285 case stateLoggedIn: // account handle received 1286 1287 notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LOGGED_IN); 1288 1289 if (LLVoiceClient::instance().getVoiceEffectEnabled()) 1290 { 1291 // Request the set of available voice fonts. 1292 setState(stateVoiceFontsWait); 1293 refreshVoiceEffectLists(true); 1294 } 1295 else 1296 { 1297 // If voice effects are disabled, pretend we've received them and carry on. 1298 setState(stateVoiceFontsReceived); 1299 } 1300 1301 // request the current set of block rules (we'll need them when updating the friends list) 1302 accountListBlockRulesSendMessage(); 1303 1304 // request the current set of auto-accept rules 1305 accountListAutoAcceptRulesSendMessage(); 1306 1307 // Set up the mute list observer if it hasn't been set up already. 1308 if((!sMuteListListener_listening)) 1309 { 1310 LLMuteList::getInstance()->addObserver(&mutelist_listener); 1311 sMuteListListener_listening = true; 1312 } 1313 1314 // Set up the friends list observer if it hasn't been set up already. 1315 if(friendslist_listener == NULL) 1316 { 1317 friendslist_listener = new LLVivoxVoiceClientFriendsObserver; 1318 LLAvatarTracker::instance().addObserver(friendslist_listener); 1319 } 1320 1321 // Set the initial state of mic mute, local speaker volume, etc. 1322 { 1323 std::ostringstream stream; 1324 1325 buildLocalAudioUpdates(stream); 1326 1327 if(!stream.str().empty()) 1328 { 1329 writeString(stream.str()); 1330 } 1331 } 1332 break; 1333 1334 //MARK: stateVoiceFontsWait 1335 case stateVoiceFontsWait: // Await voice font list 1336 // accountGetSessionFontsResponse() will transition from here to 1337 // stateVoiceFontsReceived, to ensure we have the voice font list 1338 // before attempting to create a session. 1339 break; 1340 1341 //MARK: stateVoiceFontsReceived 1342 case stateVoiceFontsReceived: // Voice font list received 1343 // Set up the timer to check for expiring voice fonts 1344 mVoiceFontExpiryTimer.start(); 1345 mVoiceFontExpiryTimer.setTimerExpirySec(VOICE_FONT_EXPIRY_INTERVAL); 1346 1347#if USE_SESSION_GROUPS 1348 // create the main session group 1349 setState(stateCreatingSessionGroup); 1350 sessionGroupCreateSendMessage(); 1351#else 1352 setState(stateNoChannel); 1353#endif 1354 break; 1355 1356 //MARK: stateCreatingSessionGroup 1357 case stateCreatingSessionGroup: 1358 if(mSessionTerminateRequested || !mVoiceEnabled) 1359 { 1360 // *TODO: Question: is this the right way out of this state 1361 setState(stateSessionTerminated); 1362 } 1363 else if(!mMainSessionGroupHandle.empty()) 1364 { 1365 // Start looped recording (needed for "panic button" anti-griefing tool) 1366 recordingLoopStart(); 1367 setState(stateNoChannel); 1368 } 1369 break; 1370 1371 //MARK: stateRetrievingParcelVoiceInfo 1372 case stateRetrievingParcelVoiceInfo: 1373 // wait until parcel voice info is received. 1374 if(mSessionTerminateRequested || !mVoiceEnabled) 1375 { 1376 // if a terminate request has been received, 1377 // bail and go to the stateSessionTerminated 1378 // state. If the cap request is still pending, 1379 // the responder will check to see if we've moved 1380 // to a new session and won't change any state. 1381 setState(stateSessionTerminated); 1382 } 1383 break; 1384 1385 1386 //MARK: stateNoChannel 1387 case stateNoChannel: 1388 LL_DEBUGS("Voice") << "State No Channel" << LL_ENDL; 1389 mSpatialJoiningNum = 0; 1390 // Do this here as well as inside sendPositionalUpdate(). 1391 // Otherwise, if you log in but don't join a proximal channel (such as when your login location has voice disabled), your friends list won't sync. 1392 sendFriendsListUpdates(); 1393 1394 if(mSessionTerminateRequested || !mVoiceEnabled) 1395 { 1396 // TODO: Question: Is this the right way out of this state? 1397 setState(stateSessionTerminated); 1398 } 1399 else if(mTuningMode) 1400 { 1401 mTuningExitState = stateNoChannel; 1402 setState(stateMicTuningStart); 1403 } 1404 else if(mCaptureBufferMode) 1405 { 1406 setState(stateCaptureBufferPaused); 1407 } 1408 else if(checkParcelChanged() || (mNextAudioSession == NULL)) 1409 { 1410 // the parcel is changed, or we have no pending audio sessions, 1411 // so try to request the parcel voice info 1412 // if we have the cap, we move to the appropriate state 1413 if(requestParcelVoiceInfo()) 1414 { 1415 setState(stateRetrievingParcelVoiceInfo); 1416 } 1417 } 1418 else if(sessionNeedsRelog(mNextAudioSession)) 1419 { 1420 requestRelog(); 1421 setState(stateSessionTerminated); 1422 } 1423 else if(mNextAudioSession) 1424 { 1425 sessionState *oldSession = mAudioSession; 1426 1427 mAudioSession = mNextAudioSession; 1428 mAudioSessionChanged = true; 1429 if(!mAudioSession->mReconnect) 1430 { 1431 mNextAudioSession = NULL; 1432 } 1433 1434 // The old session may now need to be deleted. 1435 reapSession(oldSession); 1436 1437 if(!mAudioSession->mHandle.empty()) 1438 { 1439 // Connect to a session by session handle 1440 1441 sessionMediaConnectSendMessage(mAudioSession); 1442 } 1443 else 1444 { 1445 // Connect to a session by URI 1446 sessionCreateSendMessage(mAudioSession, true, false); 1447 } 1448 1449 notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINING); 1450 setState(stateJoiningSession); 1451 } 1452 break; 1453 1454 //MARK: stateJoiningSession 1455 case stateJoiningSession: // waiting for session handle 1456 1457 // If this is true we have problem with connection to voice server (EXT-4313). 1458 // See descriptions of mSpatialJoiningNum and MAX_NORMAL_JOINING_SPATIAL_NUM. 1459 if(mSpatialJoiningNum == MAX_NORMAL_JOINING_SPATIAL_NUM) 1460 { 1461 // Notify observers to let them know there is problem with voice 1462 notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED); 1463 llwarns << "There seems to be problem with connection to voice server. Disabling voice chat abilities." << llendl; 1464 } 1465 1466 // Increase mSpatialJoiningNum only for spatial sessions- it's normal to reach this case for 1467 // example for p2p many times while waiting for response, so it can't be used to detect errors 1468 if(mAudioSession && mAudioSession->mIsSpatial) 1469 { 1470 1471 mSpatialJoiningNum++; 1472 } 1473 1474 // joinedAudioSession() will transition from here to stateSessionJoined. 1475 if(!mVoiceEnabled) 1476 { 1477 // User bailed out during connect -- jump straight to teardown. 1478 setState(stateSessionTerminated); 1479 } 1480 else if(mSessionTerminateRequested) 1481 { 1482 if(mAudioSession && !mAudioSession->mHandle.empty()) 1483 { 1484 // Only allow direct exits from this state in p2p calls (for cancelling an invite). 1485 // Terminating a half-connected session on other types of calls seems to break something in the vivox gateway. 1486 if(mAudioSession->mIsP2P) 1487 { 1488 sessionMediaDisconnectSendMessage(mAudioSession); 1489 setState(stateSessionTerminated); 1490 } 1491 } 1492 } 1493 break; 1494 1495 //MARK: stateSessionJoined 1496 case stateSessionJoined: // session handle received 1497 1498 1499 mSpatialJoiningNum = 0; 1500 // It appears that I need to wait for BOTH the SessionGroup.AddSession response and the SessionStateChangeEvent with state 4 1501 // before continuing from this state. They can happen in either order, and if I don't wait for both, things can get stuck. 1502 // For now, the SessionGroup.AddSession response handler sets mSessionHandle and the SessionStateChangeEvent handler transitions to stateSessionJoined. 1503 // This is a cheap way to make sure both have happened before proceeding. 1504 if(mAudioSession && mAudioSession->mVoiceEnabled) 1505 { 1506 // Dirty state that may need to be sync'ed with the daemon. 1507 mMuteMicDirty = true; 1508 mSpeakerVolumeDirty = true; 1509 mSpatialCoordsDirty = true; 1510 1511 setState(stateRunning); 1512 1513 // Start the throttle timer 1514 mUpdateTimer.start(); 1515 mUpdateTimer.setTimerExpirySec(UPDATE_THROTTLE_SECONDS); 1516 1517 // Events that need to happen when a session is joined could go here. 1518 // Maybe send initial spatial data? 1519 notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINED); 1520 1521 } 1522 else if(!mVoiceEnabled) 1523 { 1524 // User bailed out during connect -- jump straight to teardown. 1525 setState(stateSessionTerminated); 1526 } 1527 else if(mSessionTerminateRequested) 1528 { 1529 // Only allow direct exits from this state in p2p calls (for cancelling an invite). 1530 // Terminating a half-connected session on other types of calls seems to break something in the vivox gateway. 1531 if(mAudioSession && mAudioSession->mIsP2P) 1532 { 1533 sessionMediaDisconnectSendMessage(mAudioSession); 1534 setState(stateSessionTerminated); 1535 } 1536 } 1537 break; 1538 1539 //MARK: stateRunning 1540 case stateRunning: // steady state 1541 // Disabling voice or disconnect requested. 1542 if(!mVoiceEnabled || mSessionTerminateRequested) 1543 { 1544 leaveAudioSession(); 1545 } 1546 else 1547 { 1548 1549 if(!inSpatialChannel()) 1550 { 1551 // When in a non-spatial channel, never send positional updates. 1552 mSpatialCoordsDirty = false; 1553 } 1554 else 1555 { 1556 if(checkParcelChanged()) 1557 { 1558 // if the parcel has changed, attempted to request the 1559 // cap for the parcel voice info. If we can't request it 1560 // then we don't have the cap URL so we do nothing and will 1561 // recheck next time around 1562 if(requestParcelVoiceInfo()) 1563 { 1564 // we did get the cap, and we made the request, 1565 // so go wait for the response. 1566 setState(stateRetrievingParcelVoiceInfo); 1567 } 1568 } 1569 // Do the calculation that enforces the listener<->speaker tether (and also updates the real camera position) 1570 enforceTether(); 1571 1572 } 1573 1574 // Do notifications for expiring Voice Fonts. 1575 if (mVoiceFontExpiryTimer.hasExpired()) 1576 { 1577 expireVoiceFonts(); 1578 mVoiceFontExpiryTimer.setTimerExpirySec(VOICE_FONT_EXPIRY_INTERVAL); 1579 } 1580 1581 // Send an update only if the ptt or mute state has changed (which shouldn't be able to happen that often 1582 // -- the user can only click so fast) or every 10hz, whichever is sooner. 1583 // Sending for every volume update causes an excessive flood of messages whenever a volume slider is dragged. 1584 if((mAudioSession && mAudioSession->mMuteDirty) || mMuteMicDirty || mUpdateTimer.hasExpired()) 1585 { 1586 mUpdateTimer.setTimerExpirySec(UPDATE_THROTTLE_SECONDS); 1587 sendPositionalUpdate(); 1588 } 1589 } 1590 break; 1591 1592 //MARK: stateLeavingSession 1593 case stateLeavingSession: // waiting for terminate session response 1594 // The handler for the Session.Terminate response will transition from here to stateSessionTerminated. 1595 break; 1596 1597 //MARK: stateSessionTerminated 1598 case stateSessionTerminated: 1599 1600 // Must do this first, since it uses mAudioSession. 1601 notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL); 1602 1603 if(mAudioSession) 1604 { 1605 sessionState *oldSession = mAudioSession; 1606 1607 mAudioSession = NULL; 1608 // We just notified status observers about this change. Don't do it again. 1609 mAudioSessionChanged = false; 1610 1611 // The old session may now need to be deleted. 1612 reapSession(oldSession); 1613 } 1614 else 1615 { 1616 LL_WARNS("Voice") << "stateSessionTerminated with NULL mAudioSession" << LL_ENDL; 1617 } 1618 1619 // Always reset the terminate request flag when we get here. 1620 mSessionTerminateRequested = false; 1621 1622 if(mVoiceEnabled && !mRelogRequested) 1623 { 1624 // Just leaving a channel, go back to stateNoChannel (the "logged in but have no channel" state). 1625 setState(stateNoChannel); 1626 } 1627 else 1628 { 1629 // Shutting down voice, continue with disconnecting. 1630 logout(); 1631 1632 // The state machine will take it from here 1633 mRelogRequested = false; 1634 } 1635 1636 break; 1637 1638 //MARK: stateLoggingOut 1639 case stateLoggingOut: // waiting for logout response 1640 // The handler for the AccountLoginStateChangeEvent will transition from here to stateLoggedOut. 1641 break; 1642 1643 //MARK: stateLoggedOut 1644 case stateLoggedOut: // logout response received 1645 1646 // Once we're logged out, these things are invalid. 1647 mAccountHandle.clear(); 1648 cleanUp(); 1649 1650 if(mVoiceEnabled && !mRelogRequested) 1651 { 1652 // User was logged out, but wants to be logged in. Send a new login request. 1653 setState(stateNeedsLogin); 1654 } 1655 else 1656 { 1657 // shut down the connector 1658 connectorShutdown(); 1659 } 1660 break; 1661 1662 //MARK: stateConnectorStopping 1663 case stateConnectorStopping: // waiting for connector stop 1664 // The handler for the Connector.InitiateShutdown response will transition from here to stateConnectorStopped. 1665 break; 1666 1667 //MARK: stateConnectorStopped 1668 case stateConnectorStopped: // connector stop received 1669 setState(stateDisableCleanup); 1670 break; 1671 1672 //MARK: stateConnectorFailed 1673 case stateConnectorFailed: 1674 setState(stateConnectorFailedWaiting); 1675 break; 1676 //MARK: stateConnectorFailedWaiting 1677 case stateConnectorFailedWaiting: 1678 if(!mVoiceEnabled) 1679 { 1680 setState(stateDisableCleanup); 1681 } 1682 break; 1683 1684 //MARK: stateLoginFailed 1685 case stateLoginFailed: 1686 setState(stateLoginFailedWaiting); 1687 break; 1688 //MARK: stateLoginFailedWaiting 1689 case stateLoginFailedWaiting: 1690 if(!mVoiceEnabled) 1691 { 1692 setState(stateDisableCleanup); 1693 } 1694 break; 1695 1696 //MARK: stateJoinSessionFailed 1697 case stateJoinSessionFailed: 1698 // Transition to error state. Send out any notifications here. 1699 if(mAudioSession) 1700 { 1701 LL_WARNS("Voice") << "stateJoinSessionFailed: (" << mAudioSession->mErrorStatusCode << "): " << mAudioSession->mErrorStatusString << LL_ENDL; 1702 } 1703 else 1704 { 1705 LL_WARNS("Voice") << "stateJoinSessionFailed with no current session" << LL_ENDL; 1706 } 1707 1708 notifyStatusObservers(LLVoiceClientStatusObserver::ERROR_UNKNOWN); 1709 setState(stateJoinSessionFailedWaiting); 1710 break; 1711 1712 //MARK: stateJoinSessionFailedWaiting 1713 case stateJoinSessionFailedWaiting: 1714 // Joining a channel failed, either due to a failed channel name -> sip url lookup or an error from the join message. 1715 // Region crossings may leave this state and try the join again. 1716 if(mSessionTerminateRequested) 1717 { 1718 setState(stateSessionTerminated); 1719 } 1720 break; 1721 1722 //MARK: stateJail 1723 case stateJail: 1724 // We have given up. Do nothing. 1725 break; 1726 1727 } 1728 1729 if (mAudioSessionChanged) 1730 { 1731 mAudioSessionChanged = false; 1732 notifyParticipantObservers(); 1733 notifyVoiceFontObservers(); 1734 } 1735 else if (mAudioSession && mAudioSession->mParticipantsChanged) 1736 { 1737 mAudioSession->mParticipantsChanged = false; 1738 notifyParticipantObservers(); 1739 } 1740} 1741 1742void LLVivoxVoiceClient::closeSocket(void) 1743{ 1744 mSocket.reset(); 1745 mConnected = false; 1746 mConnectorHandle.clear(); 1747 mAccountHandle.clear(); 1748} 1749 1750void LLVivoxVoiceClient::loginSendMessage() 1751{ 1752 std::ostringstream stream; 1753 1754 bool autoPostCrashDumps = gSavedSettings.getBOOL("VivoxAutoPostCrashDumps"); 1755 1756 stream 1757 << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.Login.1\">" 1758 << "<ConnectorHandle>" << mConnectorHandle << "</ConnectorHandle>" 1759 << "<AccountName>" << mAccountName << "</AccountName>" 1760 << "<AccountPassword>" << mAccountPassword << "</AccountPassword>" 1761 << "<AudioSessionAnswerMode>VerifyAnswer</AudioSessionAnswerMode>" 1762 << "<EnableBuddiesAndPresence>true</EnableBuddiesAndPresence>" 1763 << "<BuddyManagementMode>Application</BuddyManagementMode>" 1764 << "<ParticipantPropertyFrequency>5</ParticipantPropertyFrequency>" 1765 << (autoPostCrashDumps?"<AutopostCrashDumps>true</AutopostCrashDumps>":"") 1766 << "</Request>\n\n\n"; 1767 1768 writeString(stream.str()); 1769} 1770 1771void LLVivoxVoiceClient::logout() 1772{ 1773 // Ensure that we'll re-request provisioning before logging in again 1774 mAccountPassword.clear(); 1775 mVoiceAccountServerURI.clear(); 1776 1777 setState(stateLoggingOut); 1778 logoutSendMessage(); 1779} 1780 1781void LLVivoxVoiceClient::logoutSendMessage() 1782{ 1783 if(!mAccountHandle.empty()) 1784 { 1785 std::ostringstream stream; 1786 stream 1787 << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.Logout.1\">" 1788 << "<AccountHandle>" << mAccountHandle << "</AccountHandle>" 1789 << "</Request>" 1790 << "\n\n\n"; 1791 1792 mAccountHandle.clear(); 1793 1794 writeString(stream.str()); 1795 } 1796} 1797 1798void LLVivoxVoiceClient::accountListBlockRulesSendMessage() 1799{ 1800 if(!mAccountHandle.empty()) 1801 { 1802 std::ostringstream stream; 1803 1804 LL_DEBUGS("Voice") << "requesting block rules" << LL_ENDL; 1805 1806 stream 1807 << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.ListBlockRules.1\">" 1808 << "<AccountHandle>" << mAccountHandle << "</AccountHandle>" 1809 << "</Request>" 1810 << "\n\n\n"; 1811 1812 writeString(stream.str()); 1813 } 1814} 1815 1816void LLVivoxVoiceClient::accountListAutoAcceptRulesSendMessage() 1817{ 1818 if(!mAccountHandle.empty()) 1819 { 1820 std::ostringstream stream; 1821 1822 LL_DEBUGS("Voice") << "requesting auto-accept rules" << LL_ENDL; 1823 1824 stream 1825 << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.ListAutoAcceptRules.1\">" 1826 << "<AccountHandle>" << mAccountHandle << "</AccountHandle>" 1827 << "</Request>" 1828 << "\n\n\n"; 1829 1830 writeString(stream.str()); 1831 } 1832} 1833 1834void LLVivoxVoiceClient::sessionGroupCreateSendMessage() 1835{ 1836 if(!mAccountHandle.empty()) 1837 { 1838 std::ostringstream stream; 1839 1840 LL_DEBUGS("Voice") << "creating session group" << LL_ENDL; 1841 1842 stream 1843 << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.Create.1\">" 1844 << "<AccountHandle>" << mAccountHandle << "</AccountHandle>" 1845 << "<Type>Normal</Type>" 1846 << "</Request>" 1847 << "\n\n\n"; 1848 1849 writeString(stream.str()); 1850 } 1851} 1852 1853void LLVivoxVoiceClient::sessionCreateSendMessage(sessionState *session, bool startAudio, bool startText) 1854{ 1855 LL_DEBUGS("Voice") << "Requesting create: " << session->mSIPURI << LL_ENDL; 1856 1857 S32 font_index = getVoiceFontIndex(session->mVoiceFontID); 1858 LL_DEBUGS("Voice") << "With voice font: " << session->mVoiceFontID << " (" << font_index << ")" << LL_ENDL; 1859 1860 session->mCreateInProgress = true; 1861 if(startAudio) 1862 { 1863 session->mMediaConnectInProgress = true; 1864 } 1865 1866 std::ostringstream stream; 1867 stream 1868 << "<Request requestId=\"" << session->mSIPURI << "\" action=\"Session.Create.1\">" 1869 << "<AccountHandle>" << mAccountHandle << "</AccountHandle>" 1870 << "<URI>" << session->mSIPURI << "</URI>"; 1871 1872 static const std::string allowed_chars = 1873 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" 1874 "0123456789" 1875 "-._~"; 1876 1877 if(!session->mHash.empty()) 1878 { 1879 stream 1880 << "<Password>" << LLURI::escape(session->mHash, allowed_chars) << "</Password>" 1881 << "<PasswordHashAlgorithm>SHA1UserName</PasswordHashAlgorithm>"; 1882 } 1883 1884 stream 1885 << "<ConnectAudio>" << (startAudio?"true":"false") << "</ConnectAudio>" 1886 << "<ConnectText>" << (startText?"true":"false") << "</ConnectText>" 1887 << "<VoiceFontID>" << font_index << "</VoiceFontID>" 1888 << "<Name>" << mChannelName << "</Name>" 1889 << "</Request>\n\n\n"; 1890 writeString(stream.str()); 1891} 1892 1893void LLVivoxVoiceClient::sessionGroupAddSessionSendMessage(sessionState *session, bool startAudio, bool startText) 1894{ 1895 LL_DEBUGS("Voice") << "Requesting create: " << session->mSIPURI << LL_ENDL; 1896 1897 S32 font_index = getVoiceFontIndex(session->mVoiceFontID); 1898 LL_DEBUGS("Voice") << "With voice font: " << session->mVoiceFontID << " (" << font_index << ")" << LL_ENDL; 1899 1900 session->mCreateInProgress = true; 1901 if(startAudio) 1902 { 1903 session->mMediaConnectInProgress = true; 1904 } 1905 1906 std::string password; 1907 if(!session->mHash.empty()) 1908 { 1909 static const std::string allowed_chars = 1910 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" 1911 "0123456789" 1912 "-._~" 1913 ; 1914 password = LLURI::escape(session->mHash, allowed_chars); 1915 } 1916 1917 std::ostringstream stream; 1918 stream 1919 << "<Request requestId=\"" << session->mSIPURI << "\" action=\"SessionGroup.AddSession.1\">" 1920 << "<SessionGroupHandle>" << session->mGroupHandle << "</SessionGroupHandle>" 1921 << "<URI>" << session->mSIPURI << "</URI>" 1922 << "<Name>" << mChannelName << "</Name>" 1923 << "<ConnectAudio>" << (startAudio?"true":"false") << "</ConnectAudio>" 1924 << "<ConnectText>" << (startText?"true":"false") << "</ConnectText>" 1925 << "<VoiceFontID>" << font_index << "</VoiceFontID>" 1926 << "<Password>" << password << "</Password>" 1927 << "<PasswordHashAlgorithm>SHA1UserName</PasswordHashAlgorithm>" 1928 << "</Request>\n\n\n" 1929 ; 1930 1931 writeString(stream.str()); 1932} 1933 1934void LLVivoxVoiceClient::sessionMediaConnectSendMessage(sessionState *session) 1935{ 1936 LL_DEBUGS("Voice") << "Connecting audio to session handle: " << session->mHandle << LL_ENDL; 1937 1938 S32 font_index = getVoiceFontIndex(session->mVoiceFontID); 1939 LL_DEBUGS("Voice") << "With voice font: " << session->mVoiceFontID << " (" << font_index << ")" << LL_ENDL; 1940 1941 session->mMediaConnectInProgress = true; 1942 1943 std::ostringstream stream; 1944 1945 stream 1946 << "<Request requestId=\"" << session->mHandle << "\" action=\"Session.MediaConnect.1\">" 1947 << "<SessionGroupHandle>" << session->mGroupHandle << "</SessionGroupHandle>" 1948 << "<SessionHandle>" << session->mHandle << "</SessionHandle>" 1949 << "<VoiceFontID>" << font_index << "</VoiceFontID>" 1950 << "<Media>Audio</Media>" 1951 << "</Request>\n\n\n"; 1952 1953 writeString(stream.str()); 1954} 1955 1956void LLVivoxVoiceClient::sessionTextConnectSendMessage(sessionState *session) 1957{ 1958 LL_DEBUGS("Voice") << "connecting text to session handle: " << session->mHandle << LL_ENDL; 1959 1960 std::ostringstream stream; 1961 1962 stream 1963 << "<Request requestId=\"" << session->mHandle << "\" action=\"Session.TextConnect.1\">" 1964 << "<SessionGroupHandle>" << session->mGroupHandle << "</SessionGroupHandle>" 1965 << "<SessionHandle>" << session->mHandle << "</SessionHandle>" 1966 << "</Request>\n\n\n"; 1967 1968 writeString(stream.str()); 1969} 1970 1971void LLVivoxVoiceClient::sessionTerminate() 1972{ 1973 mSessionTerminateRequested = true; 1974} 1975 1976void LLVivoxVoiceClient::requestRelog() 1977{ 1978 mSessionTerminateRequested = true; 1979 mRelogRequested = true; 1980} 1981 1982 1983void LLVivoxVoiceClient::leaveAudioSession() 1984{ 1985 if(mAudioSession) 1986 { 1987 LL_DEBUGS("Voice") << "leaving session: " << mAudioSession->mSIPURI << LL_ENDL; 1988 1989 switch(getState()) 1990 { 1991 case stateNoChannel: 1992 // In this case, we want to pretend the join failed so our state machine doesn't get stuck. 1993 // Skip the join failed transition state so we don't send out error notifications. 1994 setState(stateJoinSessionFailedWaiting); 1995 break; 1996 case stateJoiningSession: 1997 case stateSessionJoined: 1998 case stateRunning: 1999 if(!mAudioSession->mHandle.empty()) 2000 { 2001 2002#if RECORD_EVERYTHING 2003 // HACK: for testing only 2004 // Save looped recording 2005 std::string savepath("/tmp/vivoxrecording"); 2006 { 2007 time_t now = time(NULL); 2008 const size_t BUF_SIZE = 64; 2009 char time_str[BUF_SIZE]; /* Flawfinder: ignore */ 2010 2011 strftime(time_str, BUF_SIZE, "%Y-%m-%dT%H:%M:%SZ", gmtime(&now)); 2012 savepath += time_str; 2013 } 2014 recordingLoopSave(savepath); 2015#endif 2016 2017 sessionMediaDisconnectSendMessage(mAudioSession); 2018 setState(stateLeavingSession); 2019 } 2020 else 2021 { 2022 LL_WARNS("Voice") << "called with no session handle" << LL_ENDL; 2023 setState(stateSessionTerminated); 2024 } 2025 break; 2026 case stateJoinSessionFailed: 2027 case stateJoinSessionFailedWaiting: 2028 setState(stateSessionTerminated); 2029 break; 2030 2031 default: 2032 LL_WARNS("Voice") << "called from unknown state" << LL_ENDL; 2033 break; 2034 } 2035 } 2036 else 2037 { 2038 LL_WARNS("Voice") << "called with no active session" << LL_ENDL; 2039 setState(stateSessionTerminated); 2040 } 2041} 2042 2043void LLVivoxVoiceClient::sessionTerminateSendMessage(sessionState *session) 2044{ 2045 std::ostringstream stream; 2046 2047 LL_DEBUGS("Voice") << "Sending Session.Terminate with handle " << session->mHandle << LL_ENDL; 2048 stream 2049 << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.Terminate.1\">" 2050 << "<SessionHandle>" << session->mHandle << "</SessionHandle>" 2051 << "</Request>\n\n\n"; 2052 2053 writeString(stream.str()); 2054} 2055 2056void LLVivoxVoiceClient::sessionGroupTerminateSendMessage(sessionState *session) 2057{ 2058 std::ostringstream stream; 2059 2060 LL_DEBUGS("Voice") << "Sending SessionGroup.Terminate with handle " << session->mGroupHandle << LL_ENDL; 2061 stream 2062 << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.Terminate.1\">" 2063 << "<SessionGroupHandle>" << session->mGroupHandle << "</SessionGroupHandle>" 2064 << "</Request>\n\n\n"; 2065 2066 writeString(stream.str()); 2067} 2068 2069void LLVivoxVoiceClient::sessionMediaDisconnectSendMessage(sessionState *session) 2070{ 2071 std::ostringstream stream; 2072 2073 LL_DEBUGS("Voice") << "Sending Session.MediaDisconnect with handle " << session->mHandle << LL_ENDL; 2074 stream 2075 << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.MediaDisconnect.1\">" 2076 << "<SessionGroupHandle>" << session->mGroupHandle << "</SessionGroupHandle>" 2077 << "<SessionHandle>" << session->mHandle << "</SessionHandle>" 2078 << "<Media>Audio</Media>" 2079 << "</Request>\n\n\n"; 2080 2081 writeString(stream.str()); 2082 2083} 2084 2085void LLVivoxVoiceClient::sessionTextDisconnectSendMessage(sessionState *session) 2086{ 2087 std::ostringstream stream; 2088 2089 LL_DEBUGS("Voice") << "Sending Session.TextDisconnect with handle " << session->mHandle << LL_ENDL; 2090 stream 2091 << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.TextDisconnect.1\">" 2092 << "<SessionGroupHandle>" << session->mGroupHandle << "</SessionGroupHandle>" 2093 << "<SessionHandle>" << session->mHandle << "</SessionHandle>" 2094 << "</Request>\n\n\n"; 2095 2096 writeString(stream.str()); 2097} 2098 2099void LLVivoxVoiceClient::getCaptureDevicesSendMessage() 2100{ 2101 std::ostringstream stream; 2102 stream 2103 << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.GetCaptureDevices.1\">" 2104 << "</Request>\n\n\n"; 2105 2106 writeString(stream.str()); 2107} 2108 2109void LLVivoxVoiceClient::getRenderDevicesSendMessage() 2110{ 2111 std::ostringstream stream; 2112 stream 2113 << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.GetRenderDevices.1\">" 2114 << "</Request>\n\n\n"; 2115 2116 writeString(stream.str()); 2117} 2118 2119void LLVivoxVoiceClient::clearCaptureDevices() 2120{ 2121 LL_DEBUGS("Voice") << "called" << LL_ENDL; 2122 mCaptureDevices.clear(); 2123} 2124 2125void LLVivoxVoiceClient::addCaptureDevice(const std::string& name) 2126{ 2127 LL_DEBUGS("Voice") << name << LL_ENDL; 2128 2129 mCaptureDevices.push_back(name); 2130} 2131 2132LLVoiceDeviceList& LLVivoxVoiceClient::getCaptureDevices() 2133{ 2134 return mCaptureDevices; 2135} 2136 2137void LLVivoxVoiceClient::setCaptureDevice(const std::string& name) 2138{ 2139 if(name == "Default") 2140 { 2141 if(!mCaptureDevice.empty()) 2142 { 2143 mCaptureDevice.clear(); 2144 mCaptureDeviceDirty = true; 2145 } 2146 } 2147 else 2148 { 2149 if(mCaptureDevice != name) 2150 { 2151 mCaptureDevice = name; 2152 mCaptureDeviceDirty = true; 2153 } 2154 } 2155} 2156 2157void LLVivoxVoiceClient::clearRenderDevices() 2158{ 2159 LL_DEBUGS("Voice") << "called" << LL_ENDL; 2160 mRenderDevices.clear(); 2161} 2162 2163void LLVivoxVoiceClient::addRenderDevice(const std::string& name) 2164{ 2165 LL_DEBUGS("Voice") << name << LL_ENDL; 2166 mRenderDevices.push_back(name); 2167} 2168 2169LLVoiceDeviceList& LLVivoxVoiceClient::getRenderDevices() 2170{ 2171 return mRenderDevices; 2172} 2173 2174void LLVivoxVoiceClient::setRenderDevice(const std::string& name) 2175{ 2176 if(name == "Default") 2177 { 2178 if(!mRenderDevice.empty()) 2179 { 2180 mRenderDevice.clear(); 2181 mRenderDeviceDirty = true; 2182 } 2183 } 2184 else 2185 { 2186 if(mRenderDevice != name) 2187 { 2188 mRenderDevice = name; 2189 mRenderDeviceDirty = true; 2190 } 2191 } 2192 2193} 2194 2195void LLVivoxVoiceClient::tuningStart() 2196{ 2197 mTuningMode = true; 2198 LL_DEBUGS("Voice") << "Starting tuning" << LL_ENDL; 2199 if(getState() >= stateNoChannel) 2200 { 2201 LL_DEBUGS("Voice") << "no channel" << LL_ENDL; 2202 sessionTerminate(); 2203 } 2204} 2205 2206void LLVivoxVoiceClient::tuningStop() 2207{ 2208 mTuningMode = false; 2209} 2210 2211bool LLVivoxVoiceClient::inTuningMode() 2212{ 2213 bool result = false; 2214 switch(getState()) 2215 { 2216 case stateMicTuningRunning: 2217 result = true; 2218 break; 2219 default: 2220 break; 2221 } 2222 return result; 2223} 2224 2225void LLVivoxVoiceClient::tuningRenderStartSendMessage(const std::string& name, bool loop) 2226{ 2227 mTuningAudioFile = name; 2228 std::ostringstream stream; 2229 stream 2230 << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.RenderAudioStart.1\">" 2231 << "<SoundFilePath>" << mTuningAudioFile << "</SoundFilePath>" 2232 << "<Loop>" << (loop?"1":"0") << "</Loop>" 2233 << "</Request>\n\n\n"; 2234 2235 writeString(stream.str()); 2236} 2237 2238void LLVivoxVoiceClient::tuningRenderStopSendMessage() 2239{ 2240 std::ostringstream stream; 2241 stream 2242 << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.RenderAudioStop.1\">" 2243 << "<SoundFilePath>" << mTuningAudioFile << "</SoundFilePath>" 2244 << "</Request>\n\n\n"; 2245 2246 writeString(stream.str()); 2247} 2248 2249void LLVivoxVoiceClient::tuningCaptureStartSendMessage(int duration) 2250{ 2251 LL_DEBUGS("Voice") << "sending CaptureAudioStart" << LL_ENDL; 2252 2253 std::ostringstream stream; 2254 stream 2255 << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.CaptureAudioStart.1\">" 2256 << "<Duration>" << duration << "</Duration>" 2257 << "</Request>\n\n\n"; 2258 2259 writeString(stream.str()); 2260} 2261 2262void LLVivoxVoiceClient::tuningCaptureStopSendMessage() 2263{ 2264 LL_DEBUGS("Voice") << "sending CaptureAudioStop" << LL_ENDL; 2265 2266 std::ostringstream stream; 2267 stream 2268 << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.CaptureAudioStop.1\">" 2269 << "</Request>\n\n\n"; 2270 2271 writeString(stream.str()); 2272 2273 mTuningEnergy = 0.0f; 2274} 2275 2276void LLVivoxVoiceClient::tuningSetMicVolume(float volume) 2277{ 2278 int scaled_volume = scale_mic_volume(volume); 2279 2280 if(scaled_volume != mTuningMicVolume) 2281 { 2282 mTuningMicVolume = scaled_volume; 2283 mTuningMicVolumeDirty = true; 2284 } 2285} 2286 2287void LLVivoxVoiceClient::tuningSetSpeakerVolume(float volume) 2288{ 2289 int scaled_volume = scale_speaker_volume(volume); 2290 2291 if(scaled_volume != mTuningSpeakerVolume) 2292 { 2293 mTuningSpeakerVolume = scaled_volume; 2294 mTuningSpeakerVolumeDirty = true; 2295 } 2296} 2297 2298float LLVivoxVoiceClient::tuningGetEnergy(void) 2299{ 2300 return mTuningEnergy; 2301} 2302 2303bool LLVivoxVoiceClient::deviceSettingsAvailable() 2304{ 2305 bool result = true; 2306 2307 if(!mConnected) 2308 result = false; 2309 2310 if(mRenderDevices.empty()) 2311 result = false; 2312 2313 return result; 2314} 2315 2316void LLVivoxVoiceClient::refreshDeviceLists(bool clearCurrentList) 2317{ 2318 if(clearCurrentList) 2319 { 2320 clearCaptureDevices(); 2321 clearRenderDevices(); 2322 } 2323 getCaptureDevicesSendMessage(); 2324 getRenderDevicesSendMessage(); 2325} 2326 2327void LLVivoxVoiceClient::daemonDied() 2328{ 2329 // The daemon died, so the connection is gone. Reset everything and start over. 2330 LL_WARNS("Voice") << "Connection to vivox daemon lost. Resetting state."<< LL_ENDL; 2331 2332 // Try to relaunch the daemon 2333 setState(stateDisableCleanup); 2334} 2335 2336void LLVivoxVoiceClient::giveUp() 2337{ 2338 // All has failed. Clean up and stop trying. 2339 closeSocket(); 2340 cleanUp(); 2341 2342 setState(stateJail); 2343} 2344 2345static void oldSDKTransform (LLVector3 &left, LLVector3 &up, LLVector3 &at, LLVector3d &pos, LLVector3 &vel) 2346{ 2347 F32 nat[3], nup[3], nl[3], nvel[3]; // the new at, up, left vectors and the new position and velocity 2348 F64 npos[3]; 2349 2350 // The original XML command was sent like this: 2351 /* 2352 << "<Position>" 2353 << "<X>" << pos[VX] << "</X>" 2354 << "<Y>" << pos[VZ] << "</Y>" 2355 << "<Z>" << pos[VY] << "</Z>" 2356 << "</Position>" 2357 << "<Velocity>" 2358 << "<X>" << mAvatarVelocity[VX] << "</X>" 2359 << "<Y>" << mAvatarVelocity[VZ] << "</Y>" 2360 << "<Z>" << mAvatarVelocity[VY] << "</Z>" 2361 << "</Velocity>" 2362 << "<AtOrientation>" 2363 << "<X>" << l.mV[VX] << "</X>" 2364 << "<Y>" << u.mV[VX] << "</Y>" 2365 << "<Z>" << a.mV[VX] << "</Z>" 2366 << "</AtOrientation>" 2367 << "<UpOrientation>" 2368 << "<X>" << l.mV[VZ] << "</X>" 2369 << "<Y>" << u.mV[VY] << "</Y>" 2370 << "<Z>" << a.mV[VZ] << "</Z>" 2371 << "</UpOrientation>" 2372 << "<LeftOrientation>" 2373 << "<X>" << l.mV [VY] << "</X>" 2374 << "<Y>" << u.mV [VZ] << "</Y>" 2375 << "<Z>" << a.mV [VY] << "</Z>" 2376