diff options
author | Ansariel <ansariel.hiller@phoenixviewer.com> | 2024-05-22 19:04:52 +0200 |
---|---|---|
committer | Ansariel <ansariel.hiller@phoenixviewer.com> | 2024-05-22 19:04:52 +0200 |
commit | 1b67dd855c41f5a0cda7ec2a68d98071986ca703 (patch) | |
tree | ab243607f74f78200787bba5b9b88f07ef1b966f /indra/newview/llvoicevivox.cpp | |
parent | 6d6eabca44d08d5b97bfe3e941d2b9687c2246ea (diff) | |
parent | e1623bb276f83a43ce7a197e388720c05bdefe61 (diff) |
Merge remote-tracking branch 'origin/main' into DRTVWR-600-maint-A
# Conflicts:
# autobuild.xml
# indra/cmake/CMakeLists.txt
# indra/cmake/GoogleMock.cmake
# indra/llaudio/llaudioengine_fmodstudio.cpp
# indra/llaudio/llaudioengine_fmodstudio.h
# indra/llaudio/lllistener_fmodstudio.cpp
# indra/llaudio/lllistener_fmodstudio.h
# indra/llaudio/llstreamingaudio_fmodstudio.cpp
# indra/llaudio/llstreamingaudio_fmodstudio.h
# indra/llcharacter/llmultigesture.cpp
# indra/llcharacter/llmultigesture.h
# indra/llimage/llimage.cpp
# indra/llimage/llimagepng.cpp
# indra/llimage/llimageworker.cpp
# indra/llimage/tests/llimageworker_test.cpp
# indra/llmessage/tests/llmockhttpclient.h
# indra/llprimitive/llgltfmaterial.h
# indra/llrender/llfontfreetype.cpp
# indra/llui/llcombobox.cpp
# indra/llui/llfolderview.cpp
# indra/llui/llfolderviewmodel.h
# indra/llui/lllineeditor.cpp
# indra/llui/lllineeditor.h
# indra/llui/lltextbase.cpp
# indra/llui/lltextbase.h
# indra/llui/lltexteditor.cpp
# indra/llui/lltextvalidate.cpp
# indra/llui/lltextvalidate.h
# indra/llui/lluictrl.h
# indra/llui/llview.cpp
# indra/llwindow/llwindowmacosx.cpp
# indra/newview/app_settings/settings.xml
# indra/newview/llappearancemgr.cpp
# indra/newview/llappearancemgr.h
# indra/newview/llavatarpropertiesprocessor.cpp
# indra/newview/llavatarpropertiesprocessor.h
# indra/newview/llbreadcrumbview.cpp
# indra/newview/llbreadcrumbview.h
# indra/newview/llbreastmotion.cpp
# indra/newview/llbreastmotion.h
# indra/newview/llconversationmodel.h
# indra/newview/lldensityctrl.cpp
# indra/newview/lldensityctrl.h
# indra/newview/llface.inl
# indra/newview/llfloatereditsky.cpp
# indra/newview/llfloatereditwater.cpp
# indra/newview/llfloateremojipicker.h
# indra/newview/llfloaterimsessiontab.cpp
# indra/newview/llfloaterprofiletexture.cpp
# indra/newview/llfloaterprofiletexture.h
# indra/newview/llgesturemgr.cpp
# indra/newview/llgesturemgr.h
# indra/newview/llimpanel.cpp
# indra/newview/llimpanel.h
# indra/newview/llinventorybridge.cpp
# indra/newview/llinventorybridge.h
# indra/newview/llinventoryclipboard.cpp
# indra/newview/llinventoryclipboard.h
# indra/newview/llinventoryfunctions.cpp
# indra/newview/llinventoryfunctions.h
# indra/newview/llinventorygallery.cpp
# indra/newview/lllistbrowser.cpp
# indra/newview/lllistbrowser.h
# indra/newview/llpanelobjectinventory.cpp
# indra/newview/llpanelprofile.cpp
# indra/newview/llpanelprofile.h
# indra/newview/llpreviewgesture.cpp
# indra/newview/llsavedsettingsglue.cpp
# indra/newview/llsavedsettingsglue.h
# indra/newview/lltooldraganddrop.cpp
# indra/newview/llurllineeditorctrl.cpp
# indra/newview/llvectorperfoptions.cpp
# indra/newview/llvectorperfoptions.h
# indra/newview/llviewerparceloverlay.cpp
# indra/newview/llviewertexlayer.cpp
# indra/newview/llviewertexturelist.cpp
# indra/newview/macmain.h
# indra/test/test.cpp
Diffstat (limited to 'indra/newview/llvoicevivox.cpp')
-rw-r--r-- | indra/newview/llvoicevivox.cpp | 16204 |
1 files changed, 8102 insertions, 8102 deletions
diff --git a/indra/newview/llvoicevivox.cpp b/indra/newview/llvoicevivox.cpp index 429310b5e8..42ea34b706 100644 --- a/indra/newview/llvoicevivox.cpp +++ b/indra/newview/llvoicevivox.cpp @@ -1,8102 +1,8102 @@ - /** - * @file LLVivoxVoiceClient.cpp - * @brief Implementation of LLVivoxVoiceClient class which is the interface to the voice client process. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include <algorithm> -#include "llvoicevivox.h" - -#include "llsdutil.h" - -// Linden library includes -#include "llavatarnamecache.h" -#include "llvoavatarself.h" -#include "llbufferstream.h" -#include "llfile.h" -#include "llmenugl.h" -#ifdef LL_USESYSTEMLIBS -# include "expat.h" -#else -# include "expat/expat.h" -#endif -#include "llcallbacklist.h" -#include "llviewerregion.h" -#include "llviewernetwork.h" // for gGridChoice -#include "llbase64.h" -#include "llviewercontrol.h" -#include "llappviewer.h" // for gDisconnected, gDisableVoice -#include "llprocess.h" - -// Viewer includes -#include "llmutelist.h" // to check for muted avatars -#include "llagent.h" -#include "llcachename.h" -#include "llimview.h" // for LLIMMgr -#include "llparcel.h" -#include "llviewerparcelmgr.h" -#include "llfirstuse.h" -#include "llspeakers.h" -#include "lltrans.h" -#include "llviewerwindow.h" -#include "llviewercamera.h" -#include "llversioninfo.h" - -#include "llviewernetwork.h" -#include "llnotificationsutil.h" - -#include "llcorehttputil.h" -#include "lleventfilter.h" - -#include "stringize.h" - -// for base64 decoding -#include "apr_base64.h" - -#define USE_SESSION_GROUPS 0 -#define VX_NULL_POSITION -2147483648.0 /*The Silence*/ - -extern LLMenuBarGL* gMenuBarView; -extern void handle_voice_morphing_subscribe(); - -namespace { - const F32 VOLUME_SCALE_VIVOX = 0.01f; - - const F32 SPEAKING_TIMEOUT = 1.f; - - static const std::string VOICE_SERVER_TYPE = "Vivox"; - - // Don't retry connecting to the daemon more frequently than this: - const F32 DAEMON_CONNECT_THROTTLE_SECONDS = 1.0f; - const int DAEMON_CONNECT_RETRY_MAX = 3; - - // Don't send positional updates more frequently than this: - const F32 UPDATE_THROTTLE_SECONDS = 0.5f; - - // Timeout for connection to Vivox - const F32 CONNECT_ATTEMPT_TIMEOUT = 300.0f; - const F32 CONNECT_DNS_TIMEOUT = 5.0f; - const int CONNECT_RETRY_MAX = 3; - - const F32 LOGIN_ATTEMPT_TIMEOUT = 30.0f; - const F32 LOGOUT_ATTEMPT_TIMEOUT = 5.0f; - const int LOGIN_RETRY_MAX = 3; - - const F32 PROVISION_RETRY_TIMEOUT = 2.0; - const int PROVISION_RETRY_MAX = 5; - - // Cosine of a "trivially" small angle - const F32 FOUR_DEGREES = 4.0f * (F_PI / 180.0f); - const F32 MINUSCULE_ANGLE_COS = (F32) cos(0.5f * FOUR_DEGREES); - - const F32 SESSION_JOIN_TIMEOUT = 30.0f; - - // Defines the maximum number of times(in a row) "stateJoiningSession" case for spatial channel is reached in stateMachine() - // which is treated as normal. The is the number of frames to wait for a channel join before giving up. This was changed - // from the original count of 50 for two reason. Modern PCs have higher frame rates and sometimes the SLVoice process - // backs up processing join requests. There is a log statement that records when channel joins take longer than 100 frames. - const int MAX_NORMAL_JOINING_SPATIAL_NUM = 1500; - - // How often to check for expired voice fonts in seconds - const F32 VOICE_FONT_EXPIRY_INTERVAL = 10.f; - // Time of day at which Vivox expires voice font subscriptions. - // Used to replace the time portion of received expiry timestamps. - static const std::string VOICE_FONT_EXPIRY_TIME = "T05:00:00Z"; - - // Maximum length of capture buffer recordings in seconds. - const F32 CAPTURE_BUFFER_MAX_TIME = 10.f; - - const int ERROR_VIVOX_OBJECT_NOT_FOUND = 1001; - const int ERROR_VIVOX_NOT_LOGGED_IN = 1007; -} - -static int scale_mic_volume(float volume) -{ - // incoming volume has the range [0.0 ... 2.0], with 1.0 as the default. - // Map it to Vivox levels as follows: 0.0 -> 30, 1.0 -> 50, 2.0 -> 70 - return 30 + (int)(volume * 20.0f); -} - -static int scale_speaker_volume(float volume) -{ - // incoming volume has the range [0.0 ... 1.0], with 0.5 as the default. - // Map it to Vivox levels as follows: 0.0 -> 30, 0.5 -> 50, 1.0 -> 70 - return 30 + (int)(volume * 40.0f); - -} - - -/////////////////////////////////////////////////////////////////////////////////////////////// - -class LLVivoxVoiceClientMuteListObserver : public LLMuteListObserver -{ - /* virtual */ void onChange() { LLVivoxVoiceClient::getInstance()->muteListChanged();} -}; - - -void LLVoiceVivoxStats::reset() -{ - mStartTime = -1.0f; - mConnectCycles = 0; - mConnectTime = -1.0f; - mConnectAttempts = 0; - mProvisionTime = -1.0f; - mProvisionAttempts = 0; - mEstablishTime = -1.0f; - mEstablishAttempts = 0; -} - -LLVoiceVivoxStats::LLVoiceVivoxStats() -{ - reset(); -} - -LLVoiceVivoxStats::~LLVoiceVivoxStats() -{ -} - -void LLVoiceVivoxStats::connectionAttemptStart() -{ - if (!mConnectAttempts) - { - mStartTime = LLTimer::getTotalTime(); - mConnectCycles++; - } - mConnectAttempts++; -} - -void LLVoiceVivoxStats::connectionAttemptEnd(bool success) -{ - if ( success ) - { - mConnectTime = (LLTimer::getTotalTime() - mStartTime) / USEC_PER_SEC; - } -} - -void LLVoiceVivoxStats::provisionAttemptStart() -{ - if (!mProvisionAttempts) - { - mStartTime = LLTimer::getTotalTime(); - } - mProvisionAttempts++; -} - -void LLVoiceVivoxStats::provisionAttemptEnd(bool success) -{ - if ( success ) - { - mProvisionTime = (LLTimer::getTotalTime() - mStartTime) / USEC_PER_SEC; - } -} - -void LLVoiceVivoxStats::establishAttemptStart() -{ - if (!mEstablishAttempts) - { - mStartTime = LLTimer::getTotalTime(); - } - mEstablishAttempts++; -} - -void LLVoiceVivoxStats::establishAttemptEnd(bool success) -{ - if ( success ) - { - mEstablishTime = (LLTimer::getTotalTime() - mStartTime) / USEC_PER_SEC; - } -} - -LLSD LLVoiceVivoxStats::read() -{ - LLSD stats(LLSD::emptyMap()); - - stats["connect_cycles"] = LLSD::Integer(mConnectCycles); - stats["connect_attempts"] = LLSD::Integer(mConnectAttempts); - stats["connect_time"] = LLSD::Real(mConnectTime); - - stats["provision_attempts"] = LLSD::Integer(mProvisionAttempts); - stats["provision_time"] = LLSD::Real(mProvisionTime); - - stats["establish_attempts"] = LLSD::Integer(mEstablishAttempts); - stats["establish_time"] = LLSD::Real(mEstablishTime); - - return stats; -} - -static LLVivoxVoiceClientMuteListObserver mutelist_listener; -static bool sMuteListListener_listening = false; - -/////////////////////////////////////////////////////////////////////////////////////////////// -static LLProcessPtr sGatewayPtr; -static LLEventStream sGatewayPump("VivoxDaemonPump", true); - -static bool isGatewayRunning() -{ - return sGatewayPtr && sGatewayPtr->isRunning(); -} - -static void killGateway() -{ - if (sGatewayPtr) - { - LL_DEBUGS("Voice") << "SLVoice " << sGatewayPtr->getStatusString() << LL_ENDL; - - sGatewayPump.stopListening("VivoxDaemonPump"); - sGatewayPtr->kill(__FUNCTION__); - sGatewayPtr=NULL; - } - else - { - LL_DEBUGS("Voice") << "no gateway" << LL_ENDL; - } -} - -/////////////////////////////////////////////////////////////////////////////////////////////// - -bool LLVivoxVoiceClient::sShuttingDown = false; -bool LLVivoxVoiceClient::sConnected = false; -LLPumpIO *LLVivoxVoiceClient::sPump = nullptr; - -LLVivoxVoiceClient::LLVivoxVoiceClient() : - mSessionTerminateRequested(false), - mRelogRequested(false), - mTerminateDaemon(false), - mSpatialJoiningNum(0), - - mTuningMode(false), - mTuningEnergy(0.0f), - mTuningMicVolume(0), - mTuningMicVolumeDirty(true), - mTuningSpeakerVolume(50), // Set to 50 so the user can hear himself when he sets his mic volume - mTuningSpeakerVolumeDirty(true), - mDevicesListUpdated(false), - - mAreaVoiceDisabled(false), - mAudioSession(), // TBD - should be NULL - mAudioSessionChanged(false), - mNextAudioSession(), - - mCurrentParcelLocalID(0), - mConnectorEstablished(false), - mAccountLoggedIn(false), - mNumberOfAliases(0), - mCommandCookie(0), - mLoginRetryCount(0), - - mBuddyListMapPopulated(false), - mBlockRulesListReceived(false), - mAutoAcceptRulesListReceived(false), - - mCaptureDeviceDirty(false), - mRenderDeviceDirty(false), - mSpatialCoordsDirty(false), - mIsInitialized(false), - - mMuteMic(false), - mMuteMicDirty(false), - mFriendsListDirty(true), - - mEarLocation(0), - mSpeakerVolumeDirty(true), - mSpeakerMuteDirty(true), - mMicVolume(0), - mMicVolumeDirty(true), - - mVoiceEnabled(false), - mWriteInProgress(false), - - mLipSyncEnabled(false), - - mVoiceFontsReceived(false), - mVoiceFontsNew(false), - mVoiceFontListDirty(false), - - mCaptureBufferMode(false), - mCaptureBufferRecording(false), - mCaptureBufferRecorded(false), - mCaptureBufferPlaying(false), - mShutdownComplete(true), - mPlayRequestCount(0), - - mAvatarNameCacheConnection(), - mIsInTuningMode(false), - mIsInChannel(false), - mIsJoiningSession(false), - mIsWaitingForFonts(false), - mIsLoggingIn(false), - mIsLoggedIn(false), - mIsProcessingChannels(false), - mIsCoroutineActive(false), - mVivoxPump("vivoxClientPump") -{ - sShuttingDown = false; - sConnected = false; - sPump = nullptr; - - mSpeakerVolume = scale_speaker_volume(0); - - mVoiceVersion.serverVersion = ""; - mVoiceVersion.serverType = VOICE_SERVER_TYPE; - - // gMuteListp isn't set up at this point, so we defer this until later. -// gMuteListp->addObserver(&mutelist_listener); - - -#if LL_DARWIN || LL_LINUX - // HACK: THIS DOES NOT BELONG HERE - // When the vivox daemon dies, the next write attempt on our socket generates a SIGPIPE, which kills us. - // This should cause us to ignore SIGPIPE and handle the error through proper channels. - // This should really be set up elsewhere. Where should it go? - signal(SIGPIPE, SIG_IGN); - - // Since we're now launching the gateway with fork/exec instead of system(), we need to deal with zombie processes. - // Ignoring SIGCHLD should prevent zombies from being created. Alternately, we could use wait(), but I'd rather not do that. - signal(SIGCHLD, SIG_IGN); -#endif - - - gIdleCallbacks.addFunction(idle, this); -} - -//--------------------------------------------------- - -LLVivoxVoiceClient::~LLVivoxVoiceClient() -{ - if (mAvatarNameCacheConnection.connected()) - { - mAvatarNameCacheConnection.disconnect(); - } - sShuttingDown = true; -} - -//--------------------------------------------------- - -void LLVivoxVoiceClient::init(LLPumpIO *pump) -{ - // constructor will set up LLVoiceClient::getInstance() - sPump = pump; - -// LLCoros::instance().launch("LLVivoxVoiceClient::voiceControlCoro", -// boost::bind(&LLVivoxVoiceClient::voiceControlCoro, LLVivoxVoiceClient::getInstance())); - -} - -void LLVivoxVoiceClient::terminate() -{ - if (sShuttingDown) - { - return; - } - - // needs to be done manually here since we will not get another pass in - // coroutines... that mechanism is long since gone. - if (mIsLoggedIn) - { - logoutOfVivox(false); - } - - if(sConnected) - { - breakVoiceConnection(false); - sConnected = false; - } - else - { - mRelogRequested = false; - killGateway(); - } - - sShuttingDown = true; - sPump = NULL; -} - -//--------------------------------------------------- - -void LLVivoxVoiceClient::cleanUp() -{ - LL_DEBUGS("Voice") << LL_ENDL; - - deleteAllSessions(); - deleteAllVoiceFonts(); - deleteVoiceFontTemplates(); - LL_DEBUGS("Voice") << "exiting" << LL_ENDL; -} - -//--------------------------------------------------- - -const LLVoiceVersionInfo& LLVivoxVoiceClient::getVersion() -{ - return mVoiceVersion; -} - -//--------------------------------------------------- - -void LLVivoxVoiceClient::updateSettings() -{ - setVoiceEnabled(voiceEnabled()); - setEarLocation(gSavedSettings.getS32("VoiceEarLocation")); - - std::string inputDevice = gSavedSettings.getString("VoiceInputAudioDevice"); - setCaptureDevice(inputDevice); - std::string outputDevice = gSavedSettings.getString("VoiceOutputAudioDevice"); - setRenderDevice(outputDevice); - F32 mic_level = gSavedSettings.getF32("AudioLevelMic"); - setMicGain(mic_level); - setLipSyncEnabled(gSavedSettings.getBOOL("LipSyncEnabled")); -} - -///////////////////////////// -// utility functions - -bool LLVivoxVoiceClient::writeString(const std::string &str) -{ - bool result = false; - LL_DEBUGS("LowVoice") << "sending:\n" << str << LL_ENDL; - - if(sConnected) - { - apr_status_t err; - apr_size_t size = (apr_size_t)str.size(); - apr_size_t written = size; - - //MARK: Turn this on to log outgoing XML - // LL_DEBUGS("Voice") << "sending: " << str << LL_ENDL; - - // check return code - sockets will fail (broken, etc.) - err = apr_socket_send( - mSocket->getSocket(), - (const char*)str.data(), - &written); - - if(err == 0 && written == size) - { - // Success. - result = true; - } - else if (err == 0 && written != size) { - // Did a short write, log it for now - LL_WARNS("Voice") << ") short write on socket sending data to vivox daemon." << "Sent " << written << "bytes instead of " << size <<LL_ENDL; - } - else if(APR_STATUS_IS_EAGAIN(err)) - { - char buf[MAX_STRING]; - LL_WARNS("Voice") << "EAGAIN error " << err << " (" << apr_strerror(err, buf, MAX_STRING) << ") sending data to vivox daemon." << LL_ENDL; - } - else - { - // Assume any socket error means something bad. For now, just close the socket. - char buf[MAX_STRING]; - LL_WARNS("Voice") << "apr error " << err << " ("<< apr_strerror(err, buf, MAX_STRING) << ") sending data to vivox daemon." << LL_ENDL; - daemonDied(); - } - } - - return result; -} - - -///////////////////////////// -// session control messages -void LLVivoxVoiceClient::connectorCreate() -{ - std::ostringstream stream; - std::string logdir = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, ""); - - // Transition to stateConnectorStarted when the connector handle comes back. - std::string vivoxLogLevel = gSavedSettings.getString("VivoxDebugLevel"); - if ( vivoxLogLevel.empty() ) - { - vivoxLogLevel = "0"; - } - LL_DEBUGS("Voice") << "creating connector with log level " << vivoxLogLevel << LL_ENDL; - - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.Create.1\">" - << "<ClientName>V2 SDK</ClientName>" - << "<AccountManagementServer>" << mVoiceAccountServerURI << "</AccountManagementServer>" - << "<Mode>Normal</Mode>" - << "<ConnectorHandle>" << LLVivoxSecurity::getInstance()->connectorHandle() << "</ConnectorHandle>" - << "<Logging>" - << "<Folder>" << logdir << "</Folder>" - << "<FileNamePrefix>Connector</FileNamePrefix>" - << "<FileNameSuffix>.log</FileNameSuffix>" - << "<LogLevel>" << vivoxLogLevel << "</LogLevel>" - << "</Logging>" - << "<Application>" << LLVersionInfo::instance().getChannel() << " " << LLVersionInfo::instance().getVersion() << "</Application>" - //<< "<Application></Application>" //Name can cause problems per vivox. - << "<MaxCalls>12</MaxCalls>" - << "</Request>\n\n\n"; - - writeString(stream.str()); -} - -void LLVivoxVoiceClient::connectorShutdown() -{ - if(mConnectorEstablished) - { - std::ostringstream stream; - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.InitiateShutdown.1\">" - << "<ConnectorHandle>" << LLVivoxSecurity::getInstance()->connectorHandle() << "</ConnectorHandle>" - << "</Request>" - << "\n\n\n"; - - mShutdownComplete = false; - mConnectorEstablished = false; - - writeString(stream.str()); - } - else - { - mShutdownComplete = true; - } -} - -void LLVivoxVoiceClient::userAuthorized(const std::string& user_id, const LLUUID &agentID) -{ - - mAccountDisplayName = user_id; - - LL_INFOS("Voice") << "name \"" << mAccountDisplayName << "\" , ID " << agentID << LL_ENDL; - - mAccountName = nameFromID(agentID); -} - -void LLVivoxVoiceClient::setLoginInfo( - const std::string& account_name, - const std::string& password, - const std::string& voice_sip_uri_hostname, - const std::string& voice_account_server_uri) -{ - mVoiceSIPURIHostName = voice_sip_uri_hostname; - mVoiceAccountServerURI = voice_account_server_uri; - - if(mAccountLoggedIn) - { - // Already logged in. - LL_WARNS("Voice") << "Called while already logged in." << LL_ENDL; - - // Don't process another login. - return; - } - else if ( account_name != mAccountName ) - { - LL_WARNS("Voice") << "Mismatched account name! " << account_name - << " instead of " << mAccountName << LL_ENDL; - } - else - { - mAccountPassword = password; - } - - std::string debugSIPURIHostName = gSavedSettings.getString("VivoxDebugSIPURIHostName"); - - if( !debugSIPURIHostName.empty() ) - { - LL_INFOS("Voice") << "Overriding account server based on VivoxDebugSIPURIHostName: " - << debugSIPURIHostName << LL_ENDL; - mVoiceSIPURIHostName = debugSIPURIHostName; - } - - if( mVoiceSIPURIHostName.empty() ) - { - // we have an empty account server name - // so we fall back to hardcoded defaults - - if(LLGridManager::getInstance()->isInProductionGrid()) - { - // Use the release account server - mVoiceSIPURIHostName = "bhr.vivox.com"; - } - else - { - // Use the development account server - mVoiceSIPURIHostName = "bhd.vivox.com"; - } - LL_INFOS("Voice") << "Defaulting SIP URI host: " - << mVoiceSIPURIHostName << LL_ENDL; - - } - - std::string debugAccountServerURI = gSavedSettings.getString("VivoxDebugVoiceAccountServerURI"); - - if( !debugAccountServerURI.empty() ) - { - LL_INFOS("Voice") << "Overriding account server based on VivoxDebugVoiceAccountServerURI: " - << debugAccountServerURI << LL_ENDL; - mVoiceAccountServerURI = debugAccountServerURI; - } - - if( mVoiceAccountServerURI.empty() ) - { - // If the account server URI isn't specified, construct it from the SIP URI hostname - mVoiceAccountServerURI = "https://www." + mVoiceSIPURIHostName + "/api2/"; - LL_INFOS("Voice") << "Inferring account server based on SIP URI Host name: " - << mVoiceAccountServerURI << LL_ENDL; - } -} - -void LLVivoxVoiceClient::idle(void* user_data) -{ -} - -//========================================================================= -// the following are methods to support the coroutine implementation of the -// voice connection and processing. They should only be called in the context -// of a coroutine. -// -// - -typedef enum e_voice_control_coro_state -{ - VOICE_STATE_ERROR = -1, - VOICE_STATE_DONE = 0, - VOICE_STATE_TP_WAIT, // entry point - VOICE_STATE_START_DAEMON, - VOICE_STATE_PROVISION_ACCOUNT, - VOICE_STATE_START_SESSION, - VOICE_STATE_SESSION_RETRY, - VOICE_STATE_SESSION_ESTABLISHED, - VOICE_STATE_WAIT_FOR_CHANNEL, - VOICE_STATE_DISCONNECT, - VOICE_STATE_WAIT_FOR_EXIT, -} EVoiceControlCoroState; - -void LLVivoxVoiceClient::voiceControlCoro() -{ - int state = 0; - try - { - // state is passed as a reference instead of being - // a member due to unresolved issues with coroutine - // surviving longer than LLVivoxVoiceClient - voiceControlStateMachine(state); - } - catch (const LLCoros::Stop&) - { - LL_DEBUGS("LLVivoxVoiceClient") << "Received a shutdown exception" << LL_ENDL; - } - catch (const LLContinueError&) - { - LOG_UNHANDLED_EXCEPTION("LLVivoxVoiceClient"); - } - catch (...) - { - // Ideally for Windows need to log SEH exception instead or to set SEH - // handlers but bugsplat shows local variables for windows, which should - // be enough - LL_WARNS("Voice") << "voiceControlStateMachine crashed in state " << state << LL_ENDL; - throw; - } -} - -void LLVivoxVoiceClient::voiceControlStateMachine(S32 &coro_state) -{ - if (sShuttingDown) - { - return; - } - - LL_DEBUGS("Voice") << "starting" << LL_ENDL; - mIsCoroutineActive = true; - LLCoros::set_consuming(true); - - U32 retry = 0; - - coro_state = VOICE_STATE_TP_WAIT; - - do - { - if (sShuttingDown) - { - // Vivox singleton performed the exit, logged out, - // cleaned sockets, gateway and no longer cares - // about state of coroutine, so just stop - return; - } - - switch (coro_state) - { - case VOICE_STATE_TP_WAIT: - // starting point for voice - if (gAgent.getTeleportState() != LLAgent::TELEPORT_NONE) - { - LL_DEBUGS("Voice") << "Suspending voiceControlCoro() momentarily for teleport. Tuning: " << mTuningMode << ". Relog: " << mRelogRequested << LL_ENDL; - llcoro::suspendUntilTimeout(1.0); - } - else - { - coro_state = VOICE_STATE_START_DAEMON; - } - break; - - case VOICE_STATE_START_DAEMON: - LL_DEBUGS("Voice") << "Launching daemon" << LL_ENDL; - LLVoiceVivoxStats::getInstance()->reset(); - if (startAndLaunchDaemon()) - { - coro_state = VOICE_STATE_PROVISION_ACCOUNT; - } - else - { - coro_state = VOICE_STATE_SESSION_RETRY; - } - break; - - case VOICE_STATE_PROVISION_ACCOUNT: - if (provisionVoiceAccount()) - { - coro_state = VOICE_STATE_START_SESSION; - } - else - { - coro_state = VOICE_STATE_SESSION_RETRY; - } - break; - - case VOICE_STATE_START_SESSION: - if (establishVoiceConnection()) - { - coro_state = VOICE_STATE_SESSION_ESTABLISHED; - } - else - { - coro_state = VOICE_STATE_SESSION_RETRY; - } - break; - - case VOICE_STATE_SESSION_RETRY: - giveUp(); // cleans sockets and session - if (mRelogRequested) - { - // We failed to connect, give it a bit time before retrying. - retry++; - F32 full_delay = llmin(5.f * (F32)retry, 60.f); - F32 current_delay = 0.f; - LL_INFOS("Voice") << "Voice failed to establish session after " << retry - << " tries. Will attempt to reconnect in " << full_delay - << " seconds" << LL_ENDL; - while (current_delay < full_delay && !sShuttingDown) - { - // Assuming that a second has passed is not accurate, - // but we don't need accurancy here, just to make sure - // that some time passed and not to outlive voice itself - current_delay++; - llcoro::suspendUntilTimeout(1.f); - } - coro_state = VOICE_STATE_WAIT_FOR_EXIT; - } - else - { - coro_state = VOICE_STATE_DONE; - } - break; - - case VOICE_STATE_SESSION_ESTABLISHED: - { - // enable/disable the automatic VAD and explicitly set the initial values of - // the VAD variables ourselves when it is off - see SL-15072 for more details - // note: we set the other parameters too even if the auto VAD is on which is ok - unsigned int vad_auto = gSavedSettings.getU32("VivoxVadAuto"); - unsigned int vad_hangover = gSavedSettings.getU32("VivoxVadHangover"); - unsigned int vad_noise_floor = gSavedSettings.getU32("VivoxVadNoiseFloor"); - unsigned int vad_sensitivity = gSavedSettings.getU32("VivoxVadSensitivity"); - setupVADParams(vad_auto, vad_hangover, vad_noise_floor, vad_sensitivity); - - // watch for changes to the VAD settings via Debug Settings UI and act on them accordingly - gSavedSettings.getControl("VivoxVadAuto")->getSignal()->connect(boost::bind(&LLVivoxVoiceClient::onVADSettingsChange, this)); - gSavedSettings.getControl("VivoxVadHangover")->getSignal()->connect(boost::bind(&LLVivoxVoiceClient::onVADSettingsChange, this)); - gSavedSettings.getControl("VivoxVadNoiseFloor")->getSignal()->connect(boost::bind(&LLVivoxVoiceClient::onVADSettingsChange, this)); - gSavedSettings.getControl("VivoxVadSensitivity")->getSignal()->connect(boost::bind(&LLVivoxVoiceClient::onVADSettingsChange, this)); - - if (mTuningMode) - { - performMicTuning(); - } - - coro_state = VOICE_STATE_WAIT_FOR_CHANNEL; - } - break; - - case VOICE_STATE_WAIT_FOR_CHANNEL: - waitForChannel(); // todo: split into more states like login/fonts - coro_state = VOICE_STATE_DISCONNECT; - break; - - case VOICE_STATE_DISCONNECT: - LL_DEBUGS("Voice") << "lost channel RelogRequested=" << mRelogRequested << LL_ENDL; - endAndDisconnectSession(); - retry = 0; // Connected without issues - coro_state = VOICE_STATE_WAIT_FOR_EXIT; - break; - - case VOICE_STATE_WAIT_FOR_EXIT: - if (isGatewayRunning()) - { - LL_INFOS("Voice") << "waiting for SLVoice to exit" << LL_ENDL; - llcoro::suspendUntilTimeout(1.0); - } - else if (mRelogRequested && mVoiceEnabled) - { - LL_INFOS("Voice") << "will attempt to reconnect to voice" << LL_ENDL; - coro_state = VOICE_STATE_TP_WAIT; - } - else - { - coro_state = VOICE_STATE_DONE; - } - break; - - case VOICE_STATE_DONE: - break; - } - } while (coro_state > 0); - - if (sShuttingDown) - { - // LLVivoxVoiceClient might be already dead - return; - } - - mIsCoroutineActive = false; - LL_INFOS("Voice") << "exiting" << LL_ENDL; -} - -bool LLVivoxVoiceClient::endAndDisconnectSession() -{ - LL_DEBUGS("Voice") << LL_ENDL; - - breakVoiceConnection(true); - - killGateway(); - - return true; -} - -bool LLVivoxVoiceClient::callbackEndDaemon(const LLSD& data) -{ - if (!sShuttingDown && mVoiceEnabled) - { - LL_WARNS("Voice") << "SLVoice terminated " << ll_stream_notation_sd(data) << LL_ENDL; - terminateAudioSession(false); - closeSocket(); - cleanUp(); - LLVoiceClient::getInstance()->setUserPTTState(false); - gAgent.setVoiceConnected(false); - mRelogRequested = true; - } - sGatewayPump.stopListening("VivoxDaemonPump"); - return false; -} - -bool LLVivoxVoiceClient::startAndLaunchDaemon() -{ - //--------------------------------------------------------------------- - if (!voiceEnabled()) - { - // Voice is locked out, we must not launch the vivox daemon. - LL_WARNS("Voice") << "voice disabled; not starting daemon" << LL_ENDL; - return false; - } - - if (!isGatewayRunning()) - { -#ifndef VIVOXDAEMON_REMOTEHOST - // Launch the voice daemon -#if LL_WINDOWS - // On windows use exe (not work or RO) directory - std::string exe_path = gDirUtilp->getExecutableDir(); - gDirUtilp->append(exe_path, "SLVoice.exe"); -#elif LL_DARWIN - // On MAC use resource directory - std::string exe_path = gDirUtilp->getAppRODataDir(); - gDirUtilp->append(exe_path, "SLVoice"); -#else - std::string exe_path = gDirUtilp->getExecutableDir(); - gDirUtilp->append(exe_path, "SLVoice"); -#endif - // See if the vivox executable exists - llstat s; - if (!LLFile::stat(exe_path, &s)) - { - // vivox executable exists. Build the command line and launch the daemon. - LLProcess::Params params; - params.executable = exe_path; - - // VOICE-88: Cycle through [portbase..portbase+portrange) on - // successive tries because attempting to relaunch (after manually - // disabling and then re-enabling voice) with the same port can - // cause SLVoice's bind() call to fail with EADDRINUSE. We expect - // that eventually the OS will time out previous ports, which is - // why we cycle instead of incrementing indefinitely. - - static LLCachedControl<U32> portbase(gSavedSettings, "VivoxVoicePort"); - static LLCachedControl<std::string> host(gSavedSettings, "VivoxVoiceHost"); - static LLCachedControl<std::string> loglevel(gSavedSettings, "VivoxDebugLevel"); - static LLCachedControl<std::string> log_folder(gSavedSettings, "VivoxLogDirectory"); - static LLCachedControl<std::string> shutdown_timeout(gSavedSettings, "VivoxShutdownTimeout"); - static const U32 portrange = 100; - static U32 portoffset = 0; - U32 port = 0; - - if (LLAppViewer::instance()->isSecondInstance()) - { - // Ideally need to know amount of instances and - // to increment instance_offset on EADDRINUSE. - // But for now just use rand - static U32 instance_offset = portrange * ll_rand(20); - port = portbase + portoffset + instance_offset; - } - else - { - // leave main thread with exclusive port set - port = portbase + portoffset; - } - portoffset = (portoffset + 1) % portrange; - params.args.add("-i"); - params.args.add(STRINGIZE(host() << ':' << port)); - - params.args.add("-ll"); - if (loglevel().empty()) - { - params.args.add("0"); - } - else - { - params.args.add(loglevel); - } - - params.args.add("-lf"); - if (log_folder().empty()) - { - params.args.add(gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "")); - } - else - { - params.args.add(log_folder); - } - - // set log file basename and .log - params.args.add("-lp"); - params.args.add("SLVoice"); - params.args.add("-ls"); - params.args.add(".log"); - - // rotate any existing log - std::string new_log = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "SLVoice.log"); - std::string old_log = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "SLVoice.old"); - if (gDirUtilp->fileExists(new_log)) - { - LLFile::rename(new_log, old_log); - } - - if (!shutdown_timeout().empty()) - { - params.args.add("-st"); - params.args.add(shutdown_timeout); - } - params.cwd = gDirUtilp->getAppRODataDir(); - -# ifdef VIVOX_HANDLE_ARGS - params.args.add("-ah"); - params.args.add(LLVivoxSecurity::getInstance()->accountHandle()); - - params.args.add("-ch"); - params.args.add(LLVivoxSecurity::getInstance()->connectorHandle()); -# endif // VIVOX_HANDLE_ARGS - - params.postend = sGatewayPump.getName(); - sGatewayPump.listen("VivoxDaemonPump", boost::bind(&LLVivoxVoiceClient::callbackEndDaemon, this, _1)); - - LL_INFOS("Voice") << "Launching SLVoice" << LL_ENDL; - LL_DEBUGS("Voice") << "SLVoice params " << params << LL_ENDL; - - sGatewayPtr = LLProcess::create(params); - - mDaemonHost = LLHost(host().c_str(), port); - } - else - { - LL_WARNS("Voice") << exe_path << " not found." << LL_ENDL; - return false; - } -#else - // SLIM SDK: port changed from 44124 to 44125. - // We can connect to a client gateway running on another host. This is useful for testing. - // To do this, launch the gateway on a nearby host like this: - // vivox-gw.exe -p tcp -i 0.0.0.0:44125 - // and put that host's IP address here. - mDaemonHost = LLHost(gSavedSettings.getString("VivoxVoiceHost"), gSavedSettings.getU32("VivoxVoicePort")); -#endif - - // Dirty the states we'll need to sync with the daemon when it comes up. - mMuteMicDirty = true; - mMicVolumeDirty = true; - mSpeakerVolumeDirty = true; - mSpeakerMuteDirty = true; - // These only need to be set if they're not default (i.e. empty string). - mCaptureDeviceDirty = !mCaptureDevice.empty(); - mRenderDeviceDirty = !mRenderDevice.empty(); - - mMainSessionGroupHandle.clear(); - } - else - { - LL_DEBUGS("Voice") << " gateway running; not attempting to start" << LL_ENDL; - } - - //--------------------------------------------------------------------- - llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS); - - LL_DEBUGS("Voice") << "Connecting to vivox daemon:" << mDaemonHost << LL_ENDL; - - int retryCount(0); - LLVoiceVivoxStats::getInstance()->reset(); - while (!sConnected && !sShuttingDown && retryCount++ <= DAEMON_CONNECT_RETRY_MAX) - { - LLVoiceVivoxStats::getInstance()->connectionAttemptStart(); - LL_DEBUGS("Voice") << "Attempting to connect to vivox daemon: " << mDaemonHost << LL_ENDL; - closeSocket(); - if (!mSocket) - { - mSocket = LLSocket::create(gAPRPoolp, LLSocket::STREAM_TCP); - } - - sConnected = mSocket->blockingConnect(mDaemonHost); - LLVoiceVivoxStats::getInstance()->connectionAttemptEnd(sConnected); - if (!sConnected) - { - llcoro::suspendUntilTimeout(DAEMON_CONNECT_THROTTLE_SECONDS); - } - } - - //--------------------------------------------------------------------- - if (sShuttingDown && !sConnected) - { - return false; - } - - llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS); - - while (!sPump && !sShuttingDown) - { // Can't use the pump until we have it available. - llcoro::suspend(); - } - - if (sShuttingDown) - { - return false; - } - - // MBW -- Note to self: pumps and pipes examples in - // indra/test/io.cpp - // indra/test/llpipeutil.{cpp|h} - - // Attach the pumps and pipes - - LLPumpIO::chain_t readChain; - - readChain.push_back(LLIOPipe::ptr_t(new LLIOSocketReader(mSocket))); - readChain.push_back(LLIOPipe::ptr_t(new LLVivoxProtocolParser())); - - - sPump->addChain(readChain, NEVER_CHAIN_EXPIRY_SECS); - - - //--------------------------------------------------------------------- - llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS); - - // Initial devices query - getCaptureDevicesSendMessage(); - getRenderDevicesSendMessage(); - - mLoginRetryCount = 0; - - return true; -} - -bool LLVivoxVoiceClient::provisionVoiceAccount() -{ - LL_INFOS("Voice") << "Provisioning voice account." << LL_ENDL; - - while ((!gAgent.getRegion() || !gAgent.getRegion()->capabilitiesReceived()) && !sShuttingDown) - { - LL_DEBUGS("Voice") << "no capabilities for voice provisioning; waiting " << LL_ENDL; - // *TODO* Pump a message for wake up. - llcoro::suspend(); - } - - if (sShuttingDown) - { - return false; - } - - std::string url = gAgent.getRegionCapability("ProvisionVoiceAccountRequest"); - - LL_DEBUGS("Voice") << "region ready for voice provisioning; url=" << url << LL_ENDL; - - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("voiceAccountProvision", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - LLCore::HttpOptions::ptr_t httpOpts = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions); - int retryCount(0); - - LLSD result; - bool provisioned = false; - do - { - LLVoiceVivoxStats::getInstance()->provisionAttemptStart(); - result = httpAdapter->postAndSuspend(httpRequest, url, LLSD(), httpOpts); - - if (sShuttingDown) - { - return false; - } - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (status == LLCore::HttpStatus(404)) - { - F32 timeout = pow(PROVISION_RETRY_TIMEOUT, static_cast<float>(retryCount)); - LL_WARNS("Voice") << "Provision CAP 404. Retrying in " << timeout << " seconds. Retries: " << (S32)retryCount << LL_ENDL; - llcoro::suspendUntilTimeout(timeout); - - if (sShuttingDown) - { - return false; - } - } - else if (!status) - { - LL_WARNS("Voice") << "Unable to provision voice account." << LL_ENDL; - LLVoiceVivoxStats::getInstance()->provisionAttemptEnd(false); - return false; - } - else - { - provisioned = true; - } - } while (!provisioned && ++retryCount <= PROVISION_RETRY_MAX && !sShuttingDown); - - if (sShuttingDown && !provisioned) - { - return false; - } - - LLVoiceVivoxStats::getInstance()->provisionAttemptEnd(provisioned); - if (!provisioned) - { - LL_WARNS("Voice") << "Could not access voice provision cap after " << retryCount << " attempts." << LL_ENDL; - return false; - } - - std::string voiceSipUriHostname; - std::string voiceAccountServerUri; - std::string voiceUserName = result["username"].asString(); - std::string voicePassword = result["password"].asString(); - - if (result.has("voice_sip_uri_hostname")) - { - voiceSipUriHostname = result["voice_sip_uri_hostname"].asString(); - } - - // this key is actually misnamed -- it will be an entire URI, not just a hostname. - if (result.has("voice_account_server_name")) - { - voiceAccountServerUri = result["voice_account_server_name"].asString(); - } - - LL_DEBUGS("Voice") << "ProvisionVoiceAccountRequest response" - << " user " << (voiceUserName.empty() ? "not set" : "set") - << " password " << (voicePassword.empty() ? "not set" : "set") - << " sip uri " << voiceSipUriHostname - << " account uri " << voiceAccountServerUri - << LL_ENDL; - - setLoginInfo(voiceUserName, voicePassword, voiceSipUriHostname, voiceAccountServerUri); - - return true; -} - -bool LLVivoxVoiceClient::establishVoiceConnection() -{ - if (!mVoiceEnabled && mIsInitialized) - { - LL_WARNS("Voice") << "cannot establish connection; enabled "<<mVoiceEnabled<<" initialized "<<mIsInitialized<<LL_ENDL; - return false; - } - - if (sShuttingDown) - { - return false; - } - - LLSD result; - bool connected(false); - bool giving_up(false); - int retries = 0; - LL_INFOS("Voice") << "Requesting connection to voice service" << LL_ENDL; - - LLVoiceVivoxStats::getInstance()->establishAttemptStart(); - connectorCreate(); - do - { - result = llcoro::suspendUntilEventOn(mVivoxPump); - LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; - - if (result.has("connector")) - { - connected = LLSD::Boolean(result["connector"]); - LLVoiceVivoxStats::getInstance()->establishAttemptEnd(connected); - if (!connected) - { - if (result.has("retry") && ++retries <= CONNECT_RETRY_MAX && !sShuttingDown) - { - F32 timeout = LLSD::Real(result["retry"]); - timeout *= retries; - LL_INFOS("Voice") << "Retry connection to voice service in " << timeout << " seconds" << LL_ENDL; - llcoro::suspendUntilTimeout(timeout); - - if (mVoiceEnabled) // user may have switched it off - { - // try again - LLVoiceVivoxStats::getInstance()->establishAttemptStart(); - connectorCreate(); - } - else - { - // stop if they've turned off voice - giving_up = true; - } - } - else - { - giving_up=true; - } - } - } - LL_DEBUGS("Voice") << (connected ? "" : "not ") << "connected, " - << (giving_up ? "" : "not ") << "giving up" - << LL_ENDL; - } while (!connected && !giving_up && !sShuttingDown); - - if (giving_up) - { - LLSD args; - args["HOSTID"] = LLURI(mVoiceAccountServerURI).authority(); - LLNotificationsUtil::add("NoVoiceConnect", args); - } - - return connected; -} - -bool LLVivoxVoiceClient::breakVoiceConnection(bool corowait) -{ - LL_DEBUGS("Voice") << "( wait=" << corowait << ")" << LL_ENDL; - bool retval(true); - - mShutdownComplete = false; - connectorShutdown(); - - if (corowait) - { - LLSD timeoutResult(LLSDMap("connector", "timeout")); - - LLSD result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, LOGOUT_ATTEMPT_TIMEOUT, timeoutResult); - LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; - - retval = result.has("connector"); - } - else - { - mRelogRequested = false; //stop the control coro - // If we are not doing a corowait then we must sleep until the connector has responded - // otherwise we may very well close the socket too early. -#if LL_WINDOWS - if (!mShutdownComplete) - { - // The situation that brings us here is a call from ::terminate() - // At this point message system is already down so we can't wait for - // the message, yet we need to receive "connector shutdown response". - // Either wait a bit and emulate it or check gMessageSystem for specific message - _sleep(1000); - if (sConnected) - { - sConnected = false; - LLSD vivoxevent(LLSDMap("connector", LLSD::Boolean(false))); - mVivoxPump.post(vivoxevent); - } - mShutdownComplete = true; - } -#endif - } - - LL_DEBUGS("Voice") << "closing SLVoice socket" << LL_ENDL; - closeSocket(); // Need to do this now -- bad things happen if the destructor does it later. - cleanUp(); - sConnected = false; - - return retval; -} - -bool LLVivoxVoiceClient::loginToVivox() -{ - LLSD timeoutResult(LLSDMap("login", "timeout")); - - int loginRetryCount(0); - - bool response_ok(false); - bool account_login(false); - bool send_login(true); - - do - { - mIsLoggingIn = true; - if (send_login) - { - loginSendMessage(); - send_login = false; - } - - LLSD result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, LOGIN_ATTEMPT_TIMEOUT, timeoutResult); - - if (sShuttingDown) - { - return false; - } - - LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; - - if (result.has("login")) - { - std::string loginresp = result["login"]; - - if (((loginresp == "retry") || (loginresp == "timeout")) && !sShuttingDown) - { - LL_WARNS("Voice") << "login failed with status '" << loginresp << "' " - << " count " << loginRetryCount << "/" << LOGIN_RETRY_MAX - << LL_ENDL; - if (++loginRetryCount > LOGIN_RETRY_MAX) - { - // We've run out of retries - tell the user - LL_WARNS("Voice") << "too many login retries (" << loginRetryCount << "); giving up." << LL_ENDL; - LLSD args; - args["HOSTID"] = LLURI(mVoiceAccountServerURI).authority(); - mTerminateDaemon = true; - LLNotificationsUtil::add("NoVoiceConnect", args); - - mIsLoggingIn = false; - return false; - } - response_ok = false; - account_login = false; - send_login = true; - - // an exponential backoff gets too long too quickly; stretch it out, but not too much - F32 timeout = loginRetryCount * LOGIN_ATTEMPT_TIMEOUT; - - // tell the user there is a problem - LL_WARNS("Voice") << "login " << loginresp << " will retry login in " << timeout << " seconds." << LL_ENDL; - - if (!sShuttingDown) - { - // Todo: this is way to long, viewer can get stuck waiting during shutdown - // either make it listen to pump or split in smaller waits with checks for shutdown - llcoro::suspendUntilTimeout(timeout); - } - } - else if (loginresp == "failed") - { - mIsLoggingIn = false; - return false; - } - else if (loginresp == "response_ok") - { - response_ok = true; - } - else if (loginresp == "account_login") - { - account_login = true; - } - else if (sShuttingDown) - { - mIsLoggingIn = false; - return false; - } - } - - } while ((!response_ok || !account_login) && !sShuttingDown); - - if (sShuttingDown) - { - return false; - } - - mRelogRequested = false; - mIsLoggedIn = true; - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LOGGED_IN); - - // Set up the mute list observer if it hasn't been set up already. - if ((!sMuteListListener_listening)) - { - LLMuteList::getInstance()->addObserver(&mutelist_listener); - sMuteListListener_listening = true; - } - - // Set the initial state of mic mute, local speaker volume, etc. - sendLocalAudioUpdates(); - mIsLoggingIn = false; - - return true; -} - -void LLVivoxVoiceClient::logoutOfVivox(bool wait) -{ - if (mIsLoggedIn) - { - // Ensure that we'll re-request provisioning before logging in again - mAccountPassword.clear(); - mVoiceAccountServerURI.clear(); - - logoutSendMessage(); - - if (wait) - { - LLSD timeoutResult(LLSDMap("logout", "timeout")); - LLSD result; - - do - { - LL_DEBUGS("Voice") - << "waiting for logout response on " - << mVivoxPump.getName() - << LL_ENDL; - - result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, LOGOUT_ATTEMPT_TIMEOUT, timeoutResult); - - if (sShuttingDown) - { - break; - } - - LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; - // Don't get confused by prior queued events -- note that it's - // very important that mVivoxPump is an LLEventMailDrop, which - // does queue events. - } while (! result["logout"]); - } - else - { - LL_DEBUGS("Voice") << "not waiting for logout" << LL_ENDL; - } - - mIsLoggedIn = false; - } -} - - -bool LLVivoxVoiceClient::retrieveVoiceFonts() -{ - // Request the set of available voice fonts. - refreshVoiceEffectLists(true); - - mIsWaitingForFonts = true; - LLSD result; - do - { - result = llcoro::suspendUntilEventOn(mVivoxPump); - - LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; - if (result.has("voice_fonts")) - break; - } while (true); - mIsWaitingForFonts = false; - - mVoiceFontExpiryTimer.start(); - mVoiceFontExpiryTimer.setTimerExpirySec(VOICE_FONT_EXPIRY_INTERVAL); - - return result["voice_fonts"].asBoolean(); -} - -bool LLVivoxVoiceClient::requestParcelVoiceInfo() -{ - //_INFOS("Voice") << "Requesting voice info for Parcel" << LL_ENDL; - - LLViewerRegion * region = gAgent.getRegion(); - if (region == NULL || !region->capabilitiesReceived()) - { - LL_DEBUGS("Voice") << "ParcelVoiceInfoRequest capability not yet available, deferring" << LL_ENDL; - return false; - } - - // grab the cap. - std::string url = gAgent.getRegion()->getCapability("ParcelVoiceInfoRequest"); - if (url.empty()) - { - // Region dosn't have the cap. Stop probing. - LL_DEBUGS("Voice") << "ParcelVoiceInfoRequest capability not available in this region" << LL_ENDL; - return false; - } - - // update the parcel - checkParcelChanged(true); - - LL_DEBUGS("Voice") << "sending ParcelVoiceInfoRequest (" << mCurrentRegionName << ", " << mCurrentParcelLocalID << ")" << LL_ENDL; - - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("parcelVoiceInfoRequest", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - - LLSD result = httpAdapter->postAndSuspend(httpRequest, url, LLSD()); - - if (sShuttingDown) - { - return false; - } - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (mSessionTerminateRequested || (!mVoiceEnabled && mIsInitialized)) - { - // if a terminate request has been received, - // bail and go to the stateSessionTerminated - // state. If the cap request is still pending, - // the responder will check to see if we've moved - // to a new session and won't change any state. - LL_DEBUGS("Voice") << "terminate requested " << mSessionTerminateRequested - << " enabled " << mVoiceEnabled - << " initialized " << mIsInitialized - << LL_ENDL; - terminateAudioSession(true); - return false; - } - - if ((!status) || (mSessionTerminateRequested || (!mVoiceEnabled && mIsInitialized))) - { - if (mSessionTerminateRequested || (!mVoiceEnabled && mIsInitialized)) - { - LL_WARNS("Voice") << "Session terminated." << LL_ENDL; - } - - LL_WARNS("Voice") << "No voice on parcel" << LL_ENDL; - sessionTerminate(); - return false; - } - - std::string uri; - std::string credentials; - - if (result.has("voice_credentials")) - { - LLSD voice_credentials = result["voice_credentials"]; - if (voice_credentials.has("channel_uri")) - { - LL_DEBUGS("Voice") << "got voice channel uri" << LL_ENDL; - uri = voice_credentials["channel_uri"].asString(); - } - else - { - LL_WARNS("Voice") << "No voice channel uri" << LL_ENDL; - } - - if (voice_credentials.has("channel_credentials")) - { - LL_DEBUGS("Voice") << "got voice channel credentials" << LL_ENDL; - credentials = - voice_credentials["channel_credentials"].asString(); - } - else - { - LLVoiceChannel* channel = LLVoiceChannel::getCurrentVoiceChannel(); - if (channel != NULL) - { - if (channel->getSessionName().empty() && channel->getSessionID().isNull()) - { - if (LLViewerParcelMgr::getInstance()->allowAgentVoice()) - { - LL_WARNS("Voice") << "No channel credentials for default channel" << LL_ENDL; - } - } - else - { - LL_WARNS("Voice") << "No voice channel credentials" << LL_ENDL; - } - } - } - } - else - { - if (LLViewerParcelMgr::getInstance()->allowAgentVoice()) - { - LL_WARNS("Voice") << "No voice credentials" << LL_ENDL; - } - else - { - LL_DEBUGS("Voice") << "No voice credentials" << LL_ENDL; - } - } - - // set the spatial channel. If no voice credentials or uri are - // available, then we simply drop out of voice spatially. - return !setSpatialChannel(uri, credentials); -} - -bool LLVivoxVoiceClient::addAndJoinSession(const sessionStatePtr_t &nextSession) -{ - mIsJoiningSession = true; - - sessionStatePtr_t oldSession = mAudioSession; - - LL_INFOS("Voice") << "Adding or joining voice session " << nextSession->mHandle << LL_ENDL; - - mAudioSession = nextSession; - mAudioSessionChanged = true; - if (!mAudioSession || !mAudioSession->mReconnect) - { - mNextAudioSession.reset(); - } - - // The old session may now need to be deleted. - reapSession(oldSession); - - if (mAudioSession) - { - if (!mAudioSession->mHandle.empty()) - { - // Connect to a session by session handle - - sessionMediaConnectSendMessage(mAudioSession); - } - else - { - // Connect to a session by URI - sessionCreateSendMessage(mAudioSession, true, false); - } - } - - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINING); - - llcoro::suspend(); - - if (sShuttingDown) - { - return false; - } - - LLSD result; - - if (mSpatialJoiningNum == MAX_NORMAL_JOINING_SPATIAL_NUM) - { - // Notify observers to let them know there is problem with voice - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED); - LL_WARNS() << "There seems to be problem with connection to voice server. Disabling voice chat abilities." << LL_ENDL; - } - - // Increase mSpatialJoiningNum only for spatial sessions- it's normal to reach this case for - // example for p2p many times while waiting for response, so it can't be used to detect errors - if (mAudioSession && mAudioSession->mIsSpatial) - { - mSpatialJoiningNum++; - } - - if (!mVoiceEnabled && mIsInitialized) - { - LL_DEBUGS("Voice") << "Voice no longer enabled. Exiting" - << " enabled " << mVoiceEnabled - << " initialized " << mIsInitialized - << LL_ENDL; - mIsJoiningSession = false; - // User bailed out during connect -- jump straight to teardown. - terminateAudioSession(true); - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED); - return false; - } - else if (mSessionTerminateRequested) - { - LL_DEBUGS("Voice") << "Terminate requested" << LL_ENDL; - if (mAudioSession && !mAudioSession->mHandle.empty()) - { - // Only allow direct exits from this state in p2p calls (for cancelling an invite). - // Terminating a half-connected session on other types of calls seems to break something in the vivox gateway. - if (mAudioSession->mIsP2P) - { - terminateAudioSession(true); - mIsJoiningSession = false; - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL); - return false; - } - } - } - - bool added(true); - bool joined(false); - - LLSD timeoutResult(LLSDMap("session", "timeout")); - - // We are about to start a whole new session. Anything that MIGHT still be in our - // maildrop is going to be stale and cause us much wailing and gnashing of teeth. - // Just flush it all out and start new. - mVivoxPump.discard(); - - // It appears that I need to wait for BOTH the SessionGroup.AddSession response and the SessionStateChangeEvent with state 4 - // before continuing from this state. They can happen in either order, and if I don't wait for both, things can get stuck. - // For now, the SessionGroup.AddSession response handler sets mSessionHandle and the SessionStateChangeEvent handler transitions to stateSessionJoined. - // This is a cheap way to make sure both have happened before proceeding. - do - { - result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, SESSION_JOIN_TIMEOUT, timeoutResult); - - if (sShuttingDown) - { - return false; - } - - LL_INFOS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; - if (result.has("session")) - { - if (!mAudioSession) - { - LL_WARNS("Voice") << "Message for session handle \"" << result["handle"] << "\" while session is not initialized." << LL_ENDL; - continue; - } - if (result.has("handle") && result["handle"] != mAudioSession->mHandle) - { - LL_WARNS("Voice") << "Message for session handle \"" << result["handle"] << "\" while waiting for \"" << mAudioSession->mHandle << "\"." << LL_ENDL; - continue; - } - - std::string message = result["session"].asString(); - - if ((message == "added") || (message == "created")) - { - added = true; - } - else if (message == "joined") - { - joined = true; - } - else if ((message == "failed") || (message == "removed") || (message == "timeout")) - { // we will get a removed message if a voice call is declined. - - if (message == "failed") - { - int reason = result["reason"].asInteger(); - LL_WARNS("Voice") << "Add and join failed for reason " << reason << LL_ENDL; - - if ( (reason == ERROR_VIVOX_NOT_LOGGED_IN) - || (reason == ERROR_VIVOX_OBJECT_NOT_FOUND)) - { - LL_DEBUGS("Voice") << "Requesting reprovision and login." << LL_ENDL; - requestRelog(); - } - } - else - { - LL_WARNS("Voice") << "session '" << message << "' " - << LL_ENDL; - } - - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL); - mIsJoiningSession = false; - return false; - } - } - } while (!added || !joined); - - mIsJoiningSession = false; - - if (mSpatialJoiningNum > 100) - { - LL_WARNS("Voice") << "There seems to be problem with connecting to a voice channel. Frames to join were " << mSpatialJoiningNum << LL_ENDL; - } - - mSpatialJoiningNum = 0; - - // Events that need to happen when a session is joined could go here. - // send an initial positional information immediately upon joining. - // - // do an initial update for position and the camera position, then send a - // positional update. - updatePosition(); - enforceTether(); - - // Dirty state that may need to be sync'ed with the daemon. - mMuteMicDirty = true; - mSpeakerVolumeDirty = true; - mSpatialCoordsDirty = true; - - sendPositionAndVolumeUpdate(); - - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINED); - - return true; -} - -bool LLVivoxVoiceClient::terminateAudioSession(bool wait) -{ - - if (mAudioSession) - { - LL_INFOS("Voice") << "terminateAudioSession(" << wait << ") Terminating current voice session " << mAudioSession->mHandle << LL_ENDL; - - if (mIsLoggedIn) - { - if (!mAudioSession->mHandle.empty()) - { - -#if RECORD_EVERYTHING - // HACK: for testing only - // Save looped recording - std::string savepath("/tmp/vivoxrecording"); - { - time_t now = time(NULL); - const size_t BUF_SIZE = 64; - char time_str[BUF_SIZE]; /* Flawfinder: ignore */ - - strftime(time_str, BUF_SIZE, "%Y-%m-%dT%H:%M:%SZ", gmtime(&now)); - savepath += time_str; - } - recordingLoopSave(savepath); -#endif - - sessionMediaDisconnectSendMessage(mAudioSession); - - if (wait) - { - LLSD result; - do - { - LLSD timeoutResult(LLSDMap("session", "timeout")); - - result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, LOGOUT_ATTEMPT_TIMEOUT, timeoutResult); - - if (sShuttingDown) - { - return false; - } - - LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; - if (result.has("session")) - { - if (result.has("handle")) - { - if (result["handle"] != mAudioSession->mHandle) - { - LL_WARNS("Voice") << "Message for session handle \"" << result["handle"] << "\" while waiting for \"" << mAudioSession->mHandle << "\"." << LL_ENDL; - continue; - } - } - - std::string message = result["session"].asString(); - if (message == "removed" || message == "timeout") - break; - } - } while (true); - - } - } - else - { - LL_WARNS("Voice") << "called with no session handle" << LL_ENDL; - } - } - else - { - LL_WARNS("Voice") << "Session " << mAudioSession->mHandle << " already terminated by logout." << LL_ENDL; - } - - sessionStatePtr_t oldSession = mAudioSession; - - mAudioSession.reset(); - // We just notified status observers about this change. Don't do it again. - mAudioSessionChanged = false; - - // The old session may now need to be deleted. - reapSession(oldSession); - } - else - { - LL_WARNS("Voice") << "terminateAudioSession(" << wait << ") with NULL mAudioSession" << LL_ENDL; - } - - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL); - - // Always reset the terminate request flag when we get here. - // Some slower PCs have a race condition where they can switch to an incoming P2P call faster than the state machine leaves - // the region chat. - mSessionTerminateRequested = false; - - bool status=((mVoiceEnabled || !mIsInitialized) && !mRelogRequested && !sShuttingDown); - LL_DEBUGS("Voice") << "exiting" - << " VoiceEnabled " << mVoiceEnabled - << " IsInitialized " << mIsInitialized - << " RelogRequested " << mRelogRequested - << " ShuttingDown " << (sShuttingDown ? "true" : "false") - << " returning " << status - << LL_ENDL; - return status; -} - - -typedef enum e_voice_wait_for_channel_state -{ - VOICE_CHANNEL_STATE_LOGIN = 0, // entry point - VOICE_CHANNEL_STATE_CHECK_EFFECTS, - VOICE_CHANNEL_STATE_START_CHANNEL_PROCESSING, - VOICE_CHANNEL_STATE_PROCESS_CHANNEL, - VOICE_CHANNEL_STATE_NEXT_CHANNEL_DELAY, - VOICE_CHANNEL_STATE_NEXT_CHANNEL_CHECK, - VOICE_CHANNEL_STATE_LOGOUT, - VOICE_CHANNEL_STATE_RELOG, - VOICE_CHANNEL_STATE_DONE, -} EVoiceWaitForChannelState; - -bool LLVivoxVoiceClient::waitForChannel() -{ - LL_INFOS("Voice") << "Waiting for channel" << LL_ENDL; - - EVoiceWaitForChannelState state = VOICE_CHANNEL_STATE_LOGIN; - - do - { - if (sShuttingDown) - { - // terminate() forcefully disconects voice, no need for cleanup - return false; - } - - switch (state) - { - case VOICE_CHANNEL_STATE_LOGIN: - if (!loginToVivox()) - { - return false; - } - state = VOICE_CHANNEL_STATE_CHECK_EFFECTS; - break; - - case VOICE_CHANNEL_STATE_CHECK_EFFECTS: - if (LLVoiceClient::instance().getVoiceEffectEnabled()) - { - retrieveVoiceFonts(); - - if (sShuttingDown) - { - return false; - } - - // Request the set of available voice fonts. - refreshVoiceEffectLists(false); - } - -#if USE_SESSION_GROUPS - // Rider: This code is completely unchanged from the original state machine - // It does not seem to be in active use... but I'd rather not rip it out. - // create the main session group - setState(stateCreatingSessionGroup); - sessionGroupCreateSendMessage(); -#endif - - state = VOICE_CHANNEL_STATE_START_CHANNEL_PROCESSING; - break; - - case VOICE_CHANNEL_STATE_START_CHANNEL_PROCESSING: - mIsProcessingChannels = true; - llcoro::suspend(); - state = VOICE_CHANNEL_STATE_PROCESS_CHANNEL; - break; - - case VOICE_CHANNEL_STATE_PROCESS_CHANNEL: - if (mTuningMode) - { - performMicTuning(); - } - else if (mCaptureBufferMode) - { - recordingAndPlaybackMode(); - } - else if (checkParcelChanged() || (mNextAudioSession == NULL)) - { - // the parcel is changed, or we have no pending audio sessions, - // so try to request the parcel voice info - // if we have the cap, we move to the appropriate state - requestParcelVoiceInfo(); //suspends for http reply - } - else if (sessionNeedsRelog(mNextAudioSession)) - { - LL_INFOS("Voice") << "Session requesting reprovision and login." << LL_ENDL; - requestRelog(); - break; - } - else if (mNextAudioSession) - { - sessionStatePtr_t joinSession = mNextAudioSession; - mNextAudioSession.reset(); - if (!runSession(joinSession)) //suspends - { - LL_DEBUGS("Voice") << "runSession returned false; leaving inner loop" << LL_ENDL; - break; - } - else - { - LL_DEBUGS("Voice") - << "runSession returned true to inner loop" - << " RelogRequested=" << mRelogRequested - << " VoiceEnabled=" << mVoiceEnabled - << LL_ENDL; - } - } - - state = VOICE_CHANNEL_STATE_NEXT_CHANNEL_DELAY; - break; - - case VOICE_CHANNEL_STATE_NEXT_CHANNEL_DELAY: - if (!mNextAudioSession) - { - llcoro::suspendUntilTimeout(1.0); - } - state = VOICE_CHANNEL_STATE_NEXT_CHANNEL_CHECK; - break; - - case VOICE_CHANNEL_STATE_NEXT_CHANNEL_CHECK: - if (mVoiceEnabled && !mRelogRequested) - { - state = VOICE_CHANNEL_STATE_START_CHANNEL_PROCESSING; - break; - } - else - { - mIsProcessingChannels = false; - LL_DEBUGS("Voice") - << "leaving inner waitForChannel loop" - << " RelogRequested=" << mRelogRequested - << " VoiceEnabled=" << mVoiceEnabled - << LL_ENDL; - state = VOICE_CHANNEL_STATE_LOGOUT; - break; - } - - case VOICE_CHANNEL_STATE_LOGOUT: - logoutOfVivox(true /*bool wait*/); - if (mRelogRequested) - { - state = VOICE_CHANNEL_STATE_RELOG; - } - else - { - state = VOICE_CHANNEL_STATE_DONE; - } - break; - - case VOICE_CHANNEL_STATE_RELOG: - LL_DEBUGS("Voice") << "Relog Requested, restarting provisioning" << LL_ENDL; - if (!provisionVoiceAccount()) - { - if (sShuttingDown) - { - return false; - } - LL_WARNS("Voice") << "provisioning voice failed; giving up" << LL_ENDL; - giveUp(); - return false; - } - if (mVoiceEnabled && mRelogRequested && isGatewayRunning()) - { - state = VOICE_CHANNEL_STATE_LOGIN; - } - else - { - state = VOICE_CHANNEL_STATE_DONE; - } - break; - case VOICE_CHANNEL_STATE_DONE: - LL_DEBUGS("Voice") - << "exiting" - << " RelogRequested=" << mRelogRequested - << " VoiceEnabled=" << mVoiceEnabled - << LL_ENDL; - return !sShuttingDown; - } - } while (true); -} - -bool LLVivoxVoiceClient::runSession(const sessionStatePtr_t &session) -{ - LL_INFOS("Voice") << "running new voice session " << session->mHandle << LL_ENDL; - - bool joined_session = addAndJoinSession(session); - - if (sShuttingDown) - { - return false; - } - - if (!joined_session) - { - notifyStatusObservers(LLVoiceClientStatusObserver::ERROR_UNKNOWN); - - if (mSessionTerminateRequested) - { - LL_DEBUGS("Voice") << "runSession terminate requested " << LL_ENDL; - terminateAudioSession(true); - } - // if a relog has been requested then addAndJoineSession - // failed in a spectacular way and we need to back out. - // If this is not the case then we were simply trying to - // make a call and the other party rejected it. - return !mRelogRequested; - } - - notifyParticipantObservers(); - notifyVoiceFontObservers(); - - LLSD timeoutEvent(LLSDMap("timeout", LLSD::Boolean(true))); - - mIsInChannel = true; - mMuteMicDirty = true; - - while (!sShuttingDown - && mVoiceEnabled - && isGatewayRunning() - && !mSessionTerminateRequested - && !mTuningMode) - { - sendCaptureAndRenderDevices(); // suspends - - if (sShuttingDown) - { - return false; - } - - if (mSessionTerminateRequested) - { - break; - } - - if (mAudioSession && mAudioSession->mParticipantsChanged) - { - mAudioSession->mParticipantsChanged = false; - notifyParticipantObservers(); - } - - if (!inSpatialChannel()) - { - // When in a non-spatial channel, never send positional updates. - mSpatialCoordsDirty = false; - } - else - { - updatePosition(); - - if (checkParcelChanged()) - { - // *RIDER: I think I can just return here if the parcel has changed - // and grab the new voice channel from the outside loop. - // - // if the parcel has changed, attempted to request the - // cap for the parcel voice info. If we can't request it - // then we don't have the cap URL so we do nothing and will - // recheck next time around - if (requestParcelVoiceInfo()) // suspends - { // The parcel voice URI has changed.. break out and reconnect. - break; - } - - if (sShuttingDown) - { - return false; - } - } - // Do the calculation that enforces the listener<->speaker tether (and also updates the real camera position) - enforceTether(); - } - sendPositionAndVolumeUpdate(); - - // Do notifications for expiring Voice Fonts. - if (mVoiceFontExpiryTimer.hasExpired()) - { - expireVoiceFonts(); - mVoiceFontExpiryTimer.setTimerExpirySec(VOICE_FONT_EXPIRY_INTERVAL); - } - - // send any requests to adjust mic and speaker settings if they have changed - sendLocalAudioUpdates(); - - mIsInitialized = true; - LLSD result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, UPDATE_THROTTLE_SECONDS, timeoutEvent); - - if (sShuttingDown) - { - return false; - } - - if (!result.has("timeout")) // logging the timeout event spams the log - { - LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; - } - if (result.has("session")) - { - if (result.has("handle")) - { - if (!mAudioSession) - { - LL_WARNS("Voice") << "Message for session handle \"" << result["handle"] << "\" while session is not initiated." << LL_ENDL; - continue; - } - if (result["handle"] != mAudioSession->mHandle) - { - LL_WARNS("Voice") << "Message for session handle \"" << result["handle"] << "\" while waiting for \"" << mAudioSession->mHandle << "\"." << LL_ENDL; - continue; - } - } - - std::string message = result["session"]; - - if (message == "removed") - { - LL_DEBUGS("Voice") << "session removed" << LL_ENDL; - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL); - break; - } - } - else if (result.has("login")) - { - std::string message = result["login"]; - if (message == "account_logout") - { - LL_DEBUGS("Voice") << "logged out" << LL_ENDL; - mIsLoggedIn = false; - mRelogRequested = true; - break; - } - } - } - - if (sShuttingDown) - { - return false; - } - - mIsInChannel = false; - LL_DEBUGS("Voice") << "terminating at end of runSession" << LL_ENDL; - terminateAudioSession(true); - - return true; -} - -void LLVivoxVoiceClient::sendCaptureAndRenderDevices() -{ - if (mCaptureDeviceDirty || mRenderDeviceDirty) - { - std::ostringstream stream; - - buildSetCaptureDevice(stream); - buildSetRenderDevice(stream); - - if (!stream.str().empty()) - { - writeString(stream.str()); - } - - llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS); - } -} - -void LLVivoxVoiceClient::recordingAndPlaybackMode() -{ - LL_INFOS("Voice") << "In voice capture/playback mode." << LL_ENDL; - - while (true) - { - LLSD command; - do - { - command = llcoro::suspendUntilEventOn(mVivoxPump); - LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(command) << LL_ENDL; - } while (!command.has("recplay")); - - if (command["recplay"].asString() == "quit") - { - mCaptureBufferMode = false; - break; - } - else if (command["recplay"].asString() == "record") - { - voiceRecordBuffer(); - } - else if (command["recplay"].asString() == "playback") - { - voicePlaybackBuffer(); - } - } - - LL_INFOS("Voice") << "Leaving capture/playback mode." << LL_ENDL; - mCaptureBufferRecording = false; - mCaptureBufferRecorded = false; - mCaptureBufferPlaying = false; - - return; -} - -int LLVivoxVoiceClient::voiceRecordBuffer() -{ - LLSD timeoutResult(LLSDMap("recplay", "stop")); - - LL_INFOS("Voice") << "Recording voice buffer" << LL_ENDL; - - LLSD result; - - captureBufferRecordStartSendMessage(); - notifyVoiceFontObservers(); - - do - { - result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, CAPTURE_BUFFER_MAX_TIME, timeoutResult); - LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; - } while (!result.has("recplay")); - - mCaptureBufferRecorded = true; - - captureBufferRecordStopSendMessage(); - mCaptureBufferRecording = false; - - // Update UI, should really use a separate callback. - notifyVoiceFontObservers(); - - return true; - /*TODO expand return to move directly into play*/ -} - -int LLVivoxVoiceClient::voicePlaybackBuffer() -{ - LLSD timeoutResult(LLSDMap("recplay", "stop")); - - LL_INFOS("Voice") << "Playing voice buffer" << LL_ENDL; - - LLSD result; - - do - { - captureBufferPlayStartSendMessage(mPreviewVoiceFont); - - // Store the voice font being previewed, so that we know to restart if it changes. - mPreviewVoiceFontLast = mPreviewVoiceFont; - - do - { - // Update UI, should really use a separate callback. - notifyVoiceFontObservers(); - - result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, CAPTURE_BUFFER_MAX_TIME, timeoutResult); - LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; - } while (!result.has("recplay")); - - if (result["recplay"] == "playback") - continue; // restart playback... May be a font change. - - break; - } while (true); - - // Stop playing. - captureBufferPlayStopSendMessage(); - mCaptureBufferPlaying = false; - - // Update UI, should really use a separate callback. - notifyVoiceFontObservers(); - - return true; -} - - -bool LLVivoxVoiceClient::performMicTuning() -{ - LL_INFOS("Voice") << "Entering voice tuning mode." << LL_ENDL; - - mIsInTuningMode = true; - llcoro::suspend(); - - while (mTuningMode && !sShuttingDown) - { - - if (mCaptureDeviceDirty || mRenderDeviceDirty) - { - // These can't be changed while in tuning mode. Set them before starting. - std::ostringstream stream; - - buildSetCaptureDevice(stream); - buildSetRenderDevice(stream); - - if (!stream.str().empty()) - { - writeString(stream.str()); - } - - llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS); - } - - // loop mic back to render device. - //setMuteMic(0); // make sure the mic is not muted - std::ostringstream stream; - - stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.MuteLocalMic.1\">" - << "<ConnectorHandle>" << LLVivoxSecurity::getInstance()->connectorHandle() << "</ConnectorHandle>" - << "<Value>false</Value>" - << "</Request>\n\n\n"; - - // Dirty the mute mic state so that it will get reset when we finishing previewing - mMuteMicDirty = true; - mTuningSpeakerVolumeDirty = true; - - writeString(stream.str()); - tuningCaptureStartSendMessage(1); // 1-loop, zero, don't loop - - //--------------------------------------------------------------------- - if (!sShuttingDown) - { - llcoro::suspend(); - } - - while (mTuningMode && !mCaptureDeviceDirty && !mRenderDeviceDirty && !sShuttingDown) - { - // process mic/speaker volume changes - if (mTuningMicVolumeDirty || mTuningSpeakerVolumeDirty) - { - std::ostringstream stream; - - if (mTuningMicVolumeDirty) - { - LL_INFOS("Voice") << "setting tuning mic level to " << mTuningMicVolume << LL_ENDL; - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetMicLevel.1\">" - << "<Level>" << mTuningMicVolume << "</Level>" - << "</Request>\n\n\n"; - } - - if (mTuningSpeakerVolumeDirty) - { - LL_INFOS("Voice") << "setting tuning speaker level to " << mTuningSpeakerVolume << LL_ENDL; - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetSpeakerLevel.1\">" - << "<Level>" << mTuningSpeakerVolume << "</Level>" - << "</Request>\n\n\n"; - } - - mTuningMicVolumeDirty = false; - mTuningSpeakerVolumeDirty = false; - - if (!stream.str().empty()) - { - writeString(stream.str()); - } - } - llcoro::suspend(); - } - - //--------------------------------------------------------------------- - - // transition out of mic tuning - tuningCaptureStopSendMessage(); - if ((mCaptureDeviceDirty || mRenderDeviceDirty) && !sShuttingDown) - { - llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS); - } - } - - mIsInTuningMode = false; - - //--------------------------------------------------------------------- - return true; -} - -//========================================================================= - -void LLVivoxVoiceClient::closeSocket(void) -{ - mSocket.reset(); - sConnected = false; - mConnectorEstablished = false; - mAccountLoggedIn = false; -} - -void LLVivoxVoiceClient::loginSendMessage() -{ - std::ostringstream stream; - - bool autoPostCrashDumps = gSavedSettings.getBOOL("VivoxAutoPostCrashDumps"); - - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.Login.1\">" - << "<ConnectorHandle>" << LLVivoxSecurity::getInstance()->connectorHandle() << "</ConnectorHandle>" - << "<AccountName>" << mAccountName << "</AccountName>" - << "<AccountPassword>" << mAccountPassword << "</AccountPassword>" - << "<AccountHandle>" << LLVivoxSecurity::getInstance()->accountHandle() << "</AccountHandle>" - << "<AudioSessionAnswerMode>VerifyAnswer</AudioSessionAnswerMode>" - << "<EnableBuddiesAndPresence>false</EnableBuddiesAndPresence>" - << "<EnablePresencePersistence>0</EnablePresencePersistence>" - << "<BuddyManagementMode>Application</BuddyManagementMode>" - << "<ParticipantPropertyFrequency>5</ParticipantPropertyFrequency>" - << (autoPostCrashDumps?"<AutopostCrashDumps>true</AutopostCrashDumps>":"") - << "</Request>\n\n\n"; - - LL_INFOS("Voice") << "Attempting voice login" << LL_ENDL; - writeString(stream.str()); -} - -void LLVivoxVoiceClient::logout() -{ - // Ensure that we'll re-request provisioning before logging in again - mAccountPassword.clear(); - mVoiceAccountServerURI.clear(); - - logoutSendMessage(); -} - -void LLVivoxVoiceClient::logoutSendMessage() -{ - if(mAccountLoggedIn) - { - LL_INFOS("Voice") << "Attempting voice logout" << LL_ENDL; - std::ostringstream stream; - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.Logout.1\">" - << "<AccountHandle>" << LLVivoxSecurity::getInstance()->accountHandle() << "</AccountHandle>" - << "</Request>" - << "\n\n\n"; - - mAccountLoggedIn = false; - - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::sessionGroupCreateSendMessage() -{ - if(mAccountLoggedIn) - { - std::ostringstream stream; - - LL_DEBUGS("Voice") << "creating session group" << LL_ENDL; - - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.Create.1\">" - << "<AccountHandle>" << LLVivoxSecurity::getInstance()->accountHandle() << "</AccountHandle>" - << "<Type>Normal</Type>" - << "</Request>" - << "\n\n\n"; - - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::sessionCreateSendMessage(const sessionStatePtr_t &session, bool startAudio, bool startText) -{ - S32 font_index = getVoiceFontIndex(session->mVoiceFontID); - LL_DEBUGS("Voice") << "Requesting create: " << session->mSIPURI - << " with voice font: " << session->mVoiceFontID << " (" << font_index << ")" - << LL_ENDL; - - session->mCreateInProgress = true; - if(startAudio) - { - session->mMediaConnectInProgress = true; - } - - std::ostringstream stream; - stream - << "<Request requestId=\"" << session->mSIPURI << "\" action=\"Session.Create.1\">" - << "<AccountHandle>" << LLVivoxSecurity::getInstance()->accountHandle() << "</AccountHandle>" - << "<URI>" << session->mSIPURI << "</URI>"; - - static const std::string allowed_chars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" - "0123456789" - "-._~"; - - if(!session->mHash.empty()) - { - stream - << "<Password>" << LLURI::escape(session->mHash, allowed_chars) << "</Password>" - << "<PasswordHashAlgorithm>SHA1UserName</PasswordHashAlgorithm>"; - } - - stream - << "<ConnectAudio>" << (startAudio?"true":"false") << "</ConnectAudio>" - << "<ConnectText>" << (startText?"true":"false") << "</ConnectText>" - << "<VoiceFontID>" << font_index << "</VoiceFontID>" - << "<Name>" << mChannelName << "</Name>" - << "</Request>\n\n\n"; - writeString(stream.str()); -} - -void LLVivoxVoiceClient::sessionGroupAddSessionSendMessage(const sessionStatePtr_t &session, bool startAudio, bool startText) -{ - LL_DEBUGS("Voice") << "Requesting create: " << session->mSIPURI << LL_ENDL; - - S32 font_index = getVoiceFontIndex(session->mVoiceFontID); - LL_DEBUGS("Voice") << "With voice font: " << session->mVoiceFontID << " (" << font_index << ")" << LL_ENDL; - - session->mCreateInProgress = true; - if(startAudio) - { - session->mMediaConnectInProgress = true; - } - - std::string password; - if(!session->mHash.empty()) - { - static const std::string allowed_chars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" - "0123456789" - "-._~" - ; - password = LLURI::escape(session->mHash, allowed_chars); - } - - std::ostringstream stream; - stream - << "<Request requestId=\"" << session->mSIPURI << "\" action=\"SessionGroup.AddSession.1\">" - << "<SessionGroupHandle>" << session->mGroupHandle << "</SessionGroupHandle>" - << "<URI>" << session->mSIPURI << "</URI>" - << "<Name>" << mChannelName << "</Name>" - << "<ConnectAudio>" << (startAudio?"true":"false") << "</ConnectAudio>" - << "<ConnectText>" << (startText?"true":"false") << "</ConnectText>" - << "<VoiceFontID>" << font_index << "</VoiceFontID>" - << "<Password>" << password << "</Password>" - << "<PasswordHashAlgorithm>SHA1UserName</PasswordHashAlgorithm>" - << "</Request>\n\n\n" - ; - - writeString(stream.str()); -} - -void LLVivoxVoiceClient::sessionMediaConnectSendMessage(const sessionStatePtr_t &session) -{ - S32 font_index = getVoiceFontIndex(session->mVoiceFontID); - LL_DEBUGS("Voice") << "Connecting audio to session handle: " << session->mHandle - << " with voice font: " << session->mVoiceFontID << " (" << font_index << ")" - << LL_ENDL; - - session->mMediaConnectInProgress = true; - - std::ostringstream stream; - - stream - << "<Request requestId=\"" << session->mHandle << "\" action=\"Session.MediaConnect.1\">" - << "<SessionGroupHandle>" << session->mGroupHandle << "</SessionGroupHandle>" - << "<SessionHandle>" << session->mHandle << "</SessionHandle>" - << "<VoiceFontID>" << font_index << "</VoiceFontID>" - << "<Media>Audio</Media>" - << "</Request>\n\n\n"; - - writeString(stream.str()); -} - -void LLVivoxVoiceClient::sessionTextConnectSendMessage(const sessionStatePtr_t &session) -{ - LL_DEBUGS("Voice") << "connecting text to session handle: " << session->mHandle << LL_ENDL; - - std::ostringstream stream; - - stream - << "<Request requestId=\"" << session->mHandle << "\" action=\"Session.TextConnect.1\">" - << "<SessionGroupHandle>" << session->mGroupHandle << "</SessionGroupHandle>" - << "<SessionHandle>" << session->mHandle << "</SessionHandle>" - << "</Request>\n\n\n"; - - writeString(stream.str()); -} - -void LLVivoxVoiceClient::sessionTerminate() -{ - mSessionTerminateRequested = true; -} - -void LLVivoxVoiceClient::requestRelog() -{ - mSessionTerminateRequested = true; - mRelogRequested = true; -} - - -void LLVivoxVoiceClient::leaveAudioSession() -{ - if(mAudioSession) - { - LL_DEBUGS("Voice") << "leaving session: " << mAudioSession->mSIPURI << LL_ENDL; - - if(!mAudioSession->mHandle.empty()) - { - -#if RECORD_EVERYTHING - // HACK: for testing only - // Save looped recording - std::string savepath("/tmp/vivoxrecording"); - { - time_t now = time(NULL); - const size_t BUF_SIZE = 64; - char time_str[BUF_SIZE]; /* Flawfinder: ignore */ - - strftime(time_str, BUF_SIZE, "%Y-%m-%dT%H:%M:%SZ", gmtime(&now)); - savepath += time_str; - } - recordingLoopSave(savepath); -#endif - - sessionMediaDisconnectSendMessage(mAudioSession); - } - else - { - LL_WARNS("Voice") << "called with no session handle" << LL_ENDL; - } - } - else - { - LL_WARNS("Voice") << "called with no active session" << LL_ENDL; - } - sessionTerminate(); -} - -void LLVivoxVoiceClient::sessionTerminateSendMessage(const sessionStatePtr_t &session) -{ - std::ostringstream stream; - - sessionGroupTerminateSendMessage(session); - return; - /* - LL_DEBUGS("Voice") << "Sending Session.Terminate with handle " << session->mHandle << LL_ENDL; - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.Terminate.1\">" - << "<SessionHandle>" << session->mHandle << "</SessionHandle>" - << "</Request>\n\n\n"; - - writeString(stream.str()); - */ -} - -void LLVivoxVoiceClient::sessionGroupTerminateSendMessage(const sessionStatePtr_t &session) -{ - std::ostringstream stream; - - LL_DEBUGS("Voice") << "Sending SessionGroup.Terminate with handle " << session->mGroupHandle << LL_ENDL; - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.Terminate.1\">" - << "<SessionGroupHandle>" << session->mGroupHandle << "</SessionGroupHandle>" - << "</Request>\n\n\n"; - - writeString(stream.str()); -} - -void LLVivoxVoiceClient::sessionMediaDisconnectSendMessage(const sessionStatePtr_t &session) -{ - std::ostringstream stream; - sessionGroupTerminateSendMessage(session); - return; - /* - LL_DEBUGS("Voice") << "Sending Session.MediaDisconnect with handle " << session->mHandle << LL_ENDL; - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.MediaDisconnect.1\">" - << "<SessionGroupHandle>" << session->mGroupHandle << "</SessionGroupHandle>" - << "<SessionHandle>" << session->mHandle << "</SessionHandle>" - << "<Media>Audio</Media>" - << "</Request>\n\n\n"; - - writeString(stream.str()); - */ - -} - - -void LLVivoxVoiceClient::getCaptureDevicesSendMessage() -{ - std::ostringstream stream; - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.GetCaptureDevices.1\">" - << "</Request>\n\n\n"; - - writeString(stream.str()); -} - -void LLVivoxVoiceClient::getRenderDevicesSendMessage() -{ - std::ostringstream stream; - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.GetRenderDevices.1\">" - << "</Request>\n\n\n"; - - writeString(stream.str()); -} - -void LLVivoxVoiceClient::clearCaptureDevices() -{ - LL_DEBUGS("Voice") << "called" << LL_ENDL; - mCaptureDevices.clear(); -} - -void LLVivoxVoiceClient::addCaptureDevice(const LLVoiceDevice& device) -{ - LL_DEBUGS("Voice") << "display: '" << device.display_name << "' device: '" << device.full_name << "'" << LL_ENDL; - mCaptureDevices.push_back(device); -} - -LLVoiceDeviceList& LLVivoxVoiceClient::getCaptureDevices() -{ - return mCaptureDevices; -} - -void LLVivoxVoiceClient::setCaptureDevice(const std::string& name) -{ - if(name == "Default") - { - if(!mCaptureDevice.empty()) - { - mCaptureDevice.clear(); - mCaptureDeviceDirty = true; - } - } - else - { - if(mCaptureDevice != name) - { - mCaptureDevice = name; - mCaptureDeviceDirty = true; - } - } -} -void LLVivoxVoiceClient::setDevicesListUpdated(bool state) -{ - mDevicesListUpdated = state; -} - -void LLVivoxVoiceClient::clearRenderDevices() -{ - LL_DEBUGS("Voice") << "called" << LL_ENDL; - mRenderDevices.clear(); -} - -void LLVivoxVoiceClient::addRenderDevice(const LLVoiceDevice& device) -{ - LL_DEBUGS("Voice") << "display: '" << device.display_name << "' device: '" << device.full_name << "'" << LL_ENDL; - mRenderDevices.push_back(device); -} - -LLVoiceDeviceList& LLVivoxVoiceClient::getRenderDevices() -{ - return mRenderDevices; -} - -void LLVivoxVoiceClient::setRenderDevice(const std::string& name) -{ - if(name == "Default") - { - if(!mRenderDevice.empty()) - { - mRenderDevice.clear(); - mRenderDeviceDirty = true; - } - } - else - { - if(mRenderDevice != name) - { - mRenderDevice = name; - mRenderDeviceDirty = true; - } - } - -} - -void LLVivoxVoiceClient::tuningStart() -{ - LL_DEBUGS("Voice") << "Starting tuning" << LL_ENDL; - mTuningMode = true; - if (!mIsCoroutineActive) - { - LLCoros::instance().launch("LLVivoxVoiceClient::voiceControlCoro", - boost::bind(&LLVivoxVoiceClient::voiceControlCoro, LLVivoxVoiceClient::getInstance())); - } - else if (mIsInChannel) - { - LL_DEBUGS("Voice") << "no channel" << LL_ENDL; - sessionTerminate(); - } -} - -void LLVivoxVoiceClient::tuningStop() -{ - mTuningMode = false; -} - -bool LLVivoxVoiceClient::inTuningMode() -{ - return mIsInTuningMode; -} - -void LLVivoxVoiceClient::tuningRenderStartSendMessage(const std::string& name, bool loop) -{ - mTuningAudioFile = name; - std::ostringstream stream; - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.RenderAudioStart.1\">" - << "<SoundFilePath>" << mTuningAudioFile << "</SoundFilePath>" - << "<Loop>" << (loop?"1":"0") << "</Loop>" - << "</Request>\n\n\n"; - - writeString(stream.str()); -} - -void LLVivoxVoiceClient::tuningRenderStopSendMessage() -{ - std::ostringstream stream; - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.RenderAudioStop.1\">" - << "<SoundFilePath>" << mTuningAudioFile << "</SoundFilePath>" - << "</Request>\n\n\n"; - - writeString(stream.str()); -} - -void LLVivoxVoiceClient::tuningCaptureStartSendMessage(int loop) -{ - LL_DEBUGS("Voice") << "sending CaptureAudioStart" << LL_ENDL; - - std::ostringstream stream; - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.CaptureAudioStart.1\">" - << "<Duration>-1</Duration>" - << "<LoopToRenderDevice>" << loop << "</LoopToRenderDevice>" - << "</Request>\n\n\n"; - - writeString(stream.str()); -} - -void LLVivoxVoiceClient::tuningCaptureStopSendMessage() -{ - LL_DEBUGS("Voice") << "sending CaptureAudioStop" << LL_ENDL; - - std::ostringstream stream; - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.CaptureAudioStop.1\">" - << "</Request>\n\n\n"; - - writeString(stream.str()); - - mTuningEnergy = 0.0f; -} - -void LLVivoxVoiceClient::tuningSetMicVolume(float volume) -{ - int scaled_volume = scale_mic_volume(volume); - - if(scaled_volume != mTuningMicVolume) - { - mTuningMicVolume = scaled_volume; - mTuningMicVolumeDirty = true; - } -} - -void LLVivoxVoiceClient::tuningSetSpeakerVolume(float volume) -{ - int scaled_volume = scale_speaker_volume(volume); - - if(scaled_volume != mTuningSpeakerVolume) - { - mTuningSpeakerVolume = scaled_volume; - mTuningSpeakerVolumeDirty = true; - } -} - -float LLVivoxVoiceClient::tuningGetEnergy(void) -{ - return mTuningEnergy; -} - -bool LLVivoxVoiceClient::deviceSettingsAvailable() -{ - bool result = true; - - if(!sConnected) - result = false; - - if(mRenderDevices.empty()) - result = false; - - return result; -} -bool LLVivoxVoiceClient::deviceSettingsUpdated() -{ - bool updated = mDevicesListUpdated; - if (mDevicesListUpdated) - { - // a hot swap event or a polling of the audio devices has been parsed since the last redraw of the input and output device panel. - mDevicesListUpdated = false; // toggle the setting - } - return updated; -} - -void LLVivoxVoiceClient::refreshDeviceLists(bool clearCurrentList) -{ - if(clearCurrentList) - { - clearCaptureDevices(); - clearRenderDevices(); - } - getCaptureDevicesSendMessage(); - getRenderDevicesSendMessage(); -} - -void LLVivoxVoiceClient::daemonDied() -{ - // The daemon died, so the connection is gone. Reset everything and start over. - LL_WARNS("Voice") << "Connection to vivox daemon lost. Resetting state."<< LL_ENDL; - - //TODO: Try to relaunch the daemon -} - -void LLVivoxVoiceClient::giveUp() -{ - // All has failed. Clean up and stop trying. - LL_WARNS("Voice") << "Terminating Voice Service" << LL_ENDL; - closeSocket(); - cleanUp(); -} - -static void oldSDKTransform (LLVector3 &left, LLVector3 &up, LLVector3 &at, LLVector3d &pos, LLVector3 &vel) -{ - F32 nat[3], nup[3], nl[3]; // the new at, up, left vectors and the new position and velocity -// F32 nvel[3]; - F64 npos[3]; - - // The original XML command was sent like this: - /* - << "<Position>" - << "<X>" << pos[VX] << "</X>" - << "<Y>" << pos[VZ] << "</Y>" - << "<Z>" << pos[VY] << "</Z>" - << "</Position>" - << "<Velocity>" - << "<X>" << mAvatarVelocity[VX] << "</X>" - << "<Y>" << mAvatarVelocity[VZ] << "</Y>" - << "<Z>" << mAvatarVelocity[VY] << "</Z>" - << "</Velocity>" - << "<AtOrientation>" - << "<X>" << l.mV[VX] << "</X>" - << "<Y>" << u.mV[VX] << "</Y>" - << "<Z>" << a.mV[VX] << "</Z>" - << "</AtOrientation>" - << "<UpOrientation>" - << "<X>" << l.mV[VZ] << "</X>" - << "<Y>" << u.mV[VY] << "</Y>" - << "<Z>" << a.mV[VZ] << "</Z>" - << "</UpOrientation>" - << "<LeftOrientation>" - << "<X>" << l.mV [VY] << "</X>" - << "<Y>" << u.mV [VZ] << "</Y>" - << "<Z>" << a.mV [VY] << "</Z>" - << "</LeftOrientation>"; - */ - -#if 1 - // This was the original transform done when building the XML command - nat[0] = left.mV[VX]; - nat[1] = up.mV[VX]; - nat[2] = at.mV[VX]; - - nup[0] = left.mV[VZ]; - nup[1] = up.mV[VY]; - nup[2] = at.mV[VZ]; - - nl[0] = left.mV[VY]; - nl[1] = up.mV[VZ]; - nl[2] = at.mV[VY]; - - npos[0] = pos.mdV[VX]; - npos[1] = pos.mdV[VZ]; - npos[2] = pos.mdV[VY]; - -// nvel[0] = vel.mV[VX]; -// nvel[1] = vel.mV[VZ]; -// nvel[2] = vel.mV[VY]; - - for(int i=0;i<3;++i) { - at.mV[i] = nat[i]; - up.mV[i] = nup[i]; - left.mV[i] = nl[i]; - pos.mdV[i] = npos[i]; - } - - // This was the original transform done in the SDK - nat[0] = at.mV[2]; - nat[1] = 0; // y component of at vector is always 0, this was up[2] - nat[2] = -1 * left.mV[2]; - - // We override whatever the application gives us - nup[0] = 0; // x component of up vector is always 0 - nup[1] = 1; // y component of up vector is always 1 - nup[2] = 0; // z component of up vector is always 0 - - nl[0] = at.mV[0]; - nl[1] = 0; // y component of left vector is always zero, this was up[0] - nl[2] = -1 * left.mV[0]; - - npos[2] = pos.mdV[2] * -1.0; - npos[1] = pos.mdV[1]; - npos[0] = pos.mdV[0]; - - for(int i=0;i<3;++i) { - at.mV[i] = nat[i]; - up.mV[i] = nup[i]; - left.mV[i] = nl[i]; - pos.mdV[i] = npos[i]; - } -#else - // This is the compose of the two transforms (at least, that's what I'm trying for) - nat[0] = at.mV[VX]; - nat[1] = 0; // y component of at vector is always 0, this was up[2] - nat[2] = -1 * up.mV[VZ]; - - // We override whatever the application gives us - nup[0] = 0; // x component of up vector is always 0 - nup[1] = 1; // y component of up vector is always 1 - nup[2] = 0; // z component of up vector is always 0 - - nl[0] = left.mV[VX]; - nl[1] = 0; // y component of left vector is always zero, this was up[0] - nl[2] = -1 * left.mV[VY]; - - npos[0] = pos.mdV[VX]; - npos[1] = pos.mdV[VZ]; - npos[2] = pos.mdV[VY] * -1.0; - - nvel[0] = vel.mV[VX]; - nvel[1] = vel.mV[VZ]; - nvel[2] = vel.mV[VY]; - - for(int i=0;i<3;++i) { - at.mV[i] = nat[i]; - up.mV[i] = nup[i]; - left.mV[i] = nl[i]; - pos.mdV[i] = npos[i]; - } - -#endif -} - -void LLVivoxVoiceClient::setHidden(bool hidden) -{ - mHidden = hidden; - - if (mHidden && inSpatialChannel()) - { - // get out of the channel entirely - leaveAudioSession(); - } - else - { - sendPositionAndVolumeUpdate(); - } -} - -void LLVivoxVoiceClient::sendPositionAndVolumeUpdate(void) -{ - std::ostringstream stream; - - if (mSpatialCoordsDirty && inSpatialChannel()) - { - LLVector3 l, u, a, vel; - LLVector3d pos; - - mSpatialCoordsDirty = false; - - // Always send both speaker and listener positions together. - stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.Set3DPosition.1\">" - << "<SessionHandle>" << getAudioSessionHandle() << "</SessionHandle>"; - - stream << "<SpeakerPosition>"; - - LLMatrix3 avatarRot = mAvatarRot.getMatrix3(); - -// LL_DEBUGS("Voice") << "Sending speaker position " << mAvatarPosition << LL_ENDL; - l = avatarRot.getLeftRow(); - u = avatarRot.getUpRow(); - a = avatarRot.getFwdRow(); - - pos = mAvatarPosition; - vel = mAvatarVelocity; - - // SLIM SDK: the old SDK was doing a transform on the passed coordinates that the new one doesn't do anymore. - // The old transform is replicated by this function. - oldSDKTransform(l, u, a, pos, vel); - - if (mHidden) - { - for (int i=0;i<3;++i) - { - pos.mdV[i] = VX_NULL_POSITION; - } - } - - stream - << "<Position>" - << "<X>" << pos.mdV[VX] << "</X>" - << "<Y>" << pos.mdV[VY] << "</Y>" - << "<Z>" << pos.mdV[VZ] << "</Z>" - << "</Position>" - << "<Velocity>" - << "<X>" << vel.mV[VX] << "</X>" - << "<Y>" << vel.mV[VY] << "</Y>" - << "<Z>" << vel.mV[VZ] << "</Z>" - << "</Velocity>" - << "<AtOrientation>" - << "<X>" << a.mV[VX] << "</X>" - << "<Y>" << a.mV[VY] << "</Y>" - << "<Z>" << a.mV[VZ] << "</Z>" - << "</AtOrientation>" - << "<UpOrientation>" - << "<X>" << u.mV[VX] << "</X>" - << "<Y>" << u.mV[VY] << "</Y>" - << "<Z>" << u.mV[VZ] << "</Z>" - << "</UpOrientation>" - << "<LeftOrientation>" - << "<X>" << l.mV [VX] << "</X>" - << "<Y>" << l.mV [VY] << "</Y>" - << "<Z>" << l.mV [VZ] << "</Z>" - << "</LeftOrientation>" - ; - - stream << "</SpeakerPosition>"; - - stream << "<ListenerPosition>"; - - LLVector3d earPosition; - LLVector3 earVelocity; - LLMatrix3 earRot; - - switch(mEarLocation) - { - case earLocCamera: - default: - earPosition = mCameraPosition; - earVelocity = mCameraVelocity; - earRot = mCameraRot; - break; - - case earLocAvatar: - earPosition = mAvatarPosition; - earVelocity = mAvatarVelocity; - earRot = avatarRot; - break; - - case earLocMixed: - earPosition = mAvatarPosition; - earVelocity = mAvatarVelocity; - earRot = mCameraRot; - break; - } - - l = earRot.getLeftRow(); - u = earRot.getUpRow(); - a = earRot.getFwdRow(); - - pos = earPosition; - vel = earVelocity; - - - oldSDKTransform(l, u, a, pos, vel); - - if (mHidden) - { - for (int i=0;i<3;++i) - { - pos.mdV[i] = VX_NULL_POSITION; - } - } - - stream - << "<Position>" - << "<X>" << pos.mdV[VX] << "</X>" - << "<Y>" << pos.mdV[VY] << "</Y>" - << "<Z>" << pos.mdV[VZ] << "</Z>" - << "</Position>" - << "<Velocity>" - << "<X>" << vel.mV[VX] << "</X>" - << "<Y>" << vel.mV[VY] << "</Y>" - << "<Z>" << vel.mV[VZ] << "</Z>" - << "</Velocity>" - << "<AtOrientation>" - << "<X>" << a.mV[VX] << "</X>" - << "<Y>" << a.mV[VY] << "</Y>" - << "<Z>" << a.mV[VZ] << "</Z>" - << "</AtOrientation>" - << "<UpOrientation>" - << "<X>" << u.mV[VX] << "</X>" - << "<Y>" << u.mV[VY] << "</Y>" - << "<Z>" << u.mV[VZ] << "</Z>" - << "</UpOrientation>" - << "<LeftOrientation>" - << "<X>" << l.mV [VX] << "</X>" - << "<Y>" << l.mV [VY] << "</Y>" - << "<Z>" << l.mV [VZ] << "</Z>" - << "</LeftOrientation>" - ; - - stream << "</ListenerPosition>"; - - stream << "<ReqDispositionType>1</ReqDispositionType>"; //do not generate responses for update requests - stream << "</Request>\n\n\n"; - } - - if(mAudioSession && (mAudioSession->mVolumeDirty || mAudioSession->mMuteDirty)) - { - participantMap::iterator iter = mAudioSession->mParticipantsByURI.begin(); - - mAudioSession->mVolumeDirty = false; - mAudioSession->mMuteDirty = false; - - for(; iter != mAudioSession->mParticipantsByURI.end(); iter++) - { - participantStatePtr_t p(iter->second); - - if(p->mVolumeDirty) - { - // Can't set volume/mute for yourself - if(!p->mIsSelf) - { - // scale from the range 0.0-1.0 to vivox volume in the range 0-100 - S32 volume = ll_round(p->mVolume / VOLUME_SCALE_VIVOX); - bool mute = p->mOnMuteList; - - if(mute) - { - // SetParticipantMuteForMe doesn't work in p2p sessions. - // If we want the user to be muted, set their volume to 0 as well. - // This isn't perfect, but it will at least reduce their volume to a minimum. - volume = 0; - // Mark the current volume level as set to prevent incoming events - // changing it to 0, so that we can return to it when unmuting. - p->mVolumeSet = true; - } - - if(volume == 0) - { - mute = true; - } - - LL_DEBUGS("Voice") << "Setting volume/mute for avatar " << p->mAvatarID << " to " << volume << (mute?"/true":"/false") << LL_ENDL; - - // SLIM SDK: Send both volume and mute commands. - - // Send a "volume for me" command for the user. - stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.SetParticipantVolumeForMe.1\">" - << "<SessionHandle>" << getAudioSessionHandle() << "</SessionHandle>" - << "<ParticipantURI>" << p->mURI << "</ParticipantURI>" - << "<Volume>" << volume << "</Volume>" - << "</Request>\n\n\n"; - - if(!mAudioSession->mIsP2P) - { - // Send a "mute for me" command for the user - // Doesn't work in P2P sessions - stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.SetParticipantMuteForMe.1\">" - << "<SessionHandle>" << getAudioSessionHandle() << "</SessionHandle>" - << "<ParticipantURI>" << p->mURI << "</ParticipantURI>" - << "<Mute>" << (mute?"1":"0") << "</Mute>" - << "<Scope>Audio</Scope>" - << "</Request>\n\n\n"; - } - } - - p->mVolumeDirty = false; - } - } - } - - std::string update(stream.str()); - if(!update.empty()) - { - LL_DEBUGS("VoiceUpdate") << "sending update " << update << LL_ENDL; - writeString(update); - } - -} - -void LLVivoxVoiceClient::buildSetCaptureDevice(std::ostringstream &stream) -{ - if(mCaptureDeviceDirty) - { - LL_DEBUGS("Voice") << "Setting input device = \"" << mCaptureDevice << "\"" << LL_ENDL; - - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetCaptureDevice.1\">" - << "<CaptureDeviceSpecifier>" << mCaptureDevice << "</CaptureDeviceSpecifier>" - << "</Request>" - << "\n\n\n"; - - mCaptureDeviceDirty = false; - } -} - -void LLVivoxVoiceClient::buildSetRenderDevice(std::ostringstream &stream) -{ - if(mRenderDeviceDirty) - { - LL_DEBUGS("Voice") << "Setting output device = \"" << mRenderDevice << "\"" << LL_ENDL; - - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetRenderDevice.1\">" - << "<RenderDeviceSpecifier>" << mRenderDevice << "</RenderDeviceSpecifier>" - << "</Request>" - << "\n\n\n"; - mRenderDeviceDirty = false; - } -} - -void LLVivoxVoiceClient::sendLocalAudioUpdates() -{ - // Check all of the dirty states and then send messages to those needing to be changed. - // Tuningmode hands its own mute settings. - std::ostringstream stream; - - if (mMuteMicDirty && !mTuningMode) - { - mMuteMicDirty = false; - - // Send a local mute command. - - LL_INFOS("Voice") << "Sending MuteLocalMic command with parameter " << (mMuteMic ? "true" : "false") << LL_ENDL; - - stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.MuteLocalMic.1\">" - << "<ConnectorHandle>" << LLVivoxSecurity::getInstance()->connectorHandle() << "</ConnectorHandle>" - << "<Value>" << (mMuteMic ? "true" : "false") << "</Value>" - << "</Request>\n\n\n"; - - } - - if (mSpeakerMuteDirty && !mTuningMode) - { - const char *muteval = ((mSpeakerVolume <= scale_speaker_volume(0)) ? "true" : "false"); - - mSpeakerMuteDirty = false; - - LL_INFOS("Voice") << "Setting speaker mute to " << muteval << LL_ENDL; - - stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.MuteLocalSpeaker.1\">" - << "<ConnectorHandle>" << LLVivoxSecurity::getInstance()->connectorHandle() << "</ConnectorHandle>" - << "<Value>" << muteval << "</Value>" - << "</Request>\n\n\n"; - - } - - if (mSpeakerVolumeDirty) - { - mSpeakerVolumeDirty = false; - - LL_INFOS("Voice") << "Setting speaker volume to " << mSpeakerVolume << LL_ENDL; - - stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.SetLocalSpeakerVolume.1\">" - << "<ConnectorHandle>" << LLVivoxSecurity::getInstance()->connectorHandle() << "</ConnectorHandle>" - << "<Value>" << mSpeakerVolume << "</Value>" - << "</Request>\n\n\n"; - - } - - if (mMicVolumeDirty) - { - mMicVolumeDirty = false; - - LL_INFOS("Voice") << "Setting mic volume to " << mMicVolume << LL_ENDL; - - stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.SetLocalMicVolume.1\">" - << "<ConnectorHandle>" << LLVivoxSecurity::getInstance()->connectorHandle() << "</ConnectorHandle>" - << "<Value>" << mMicVolume << "</Value>" - << "</Request>\n\n\n"; - } - - - if (!stream.str().empty()) - { - writeString(stream.str()); - } -} - -/** - * Because of the recurring voice cutout issues (SL-15072) we are going to try - * to disable the automatic VAD (Voice Activity Detection) and set the associated - * parameters directly. We will expose them via Debug Settings and that should - * let us iterate on a collection of values that work for us. Hopefully! - * - * From the VIVOX Docs: - * - * VadAuto: A flag indicating if the automatic VAD is enabled (1) or disabled (0) - * - * VadHangover: The time (in milliseconds) that it takes - * for the VAD to switch back to silence from speech mode after the last speech - * frame has been detected. - * - * VadNoiseFloor: A dimensionless value between 0 and - * 20000 (default 576) that controls the maximum level at which the noise floor - * may be set at by the VAD's noise tracking. Too low of a value will make noise - * tracking ineffective (A value of 0 disables noise tracking and the VAD then - * relies purely on the sensitivity property). Too high of a value will make - * long speech classifiable as noise. - * - * VadSensitivity: A dimensionless value between 0 and - * 100, indicating the 'sensitivity of the VAD'. Increasing this value corresponds - * to decreasing the sensitivity of the VAD (i.e. '0' is most sensitive, - * while 100 is 'least sensitive') - */ -void LLVivoxVoiceClient::setupVADParams(unsigned int vad_auto, - unsigned int vad_hangover, - unsigned int vad_noise_floor, - unsigned int vad_sensitivity) -{ - std::ostringstream stream; - - LL_INFOS("Voice") << "Setting the automatic VAD to " - << (vad_auto ? "True" : "False") - << " and discrete values to" - << " VadHangover = " << vad_hangover - << ", VadSensitivity = " << vad_sensitivity - << ", VadNoiseFloor = " << vad_noise_floor - << LL_ENDL; - - // Create a request to set the VAD parameters: - stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetVadProperties.1\">" - << "<VadAuto>" << vad_auto << "</VadAuto>" - << "<VadHangover>" << vad_hangover << "</VadHangover>" - << "<VadSensitivity>" << vad_sensitivity << "</VadSensitivity>" - << "<VadNoiseFloor>" << vad_noise_floor << "</VadNoiseFloor>" - << "</Request>\n\n\n"; - - if (!stream.str().empty()) - { - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::onVADSettingsChange() -{ - // pick up the VAD variables (one of which was changed) - unsigned int vad_auto = gSavedSettings.getU32("VivoxVadAuto"); - unsigned int vad_hangover = gSavedSettings.getU32("VivoxVadHangover"); - unsigned int vad_noise_floor = gSavedSettings.getU32("VivoxVadNoiseFloor"); - unsigned int vad_sensitivity = gSavedSettings.getU32("VivoxVadSensitivity"); - - // build a VAD params change request and send it to SLVoice - setupVADParams(vad_auto, vad_hangover, vad_noise_floor, vad_sensitivity); -} - -///////////////////////////// -// Response/Event handlers - -void LLVivoxVoiceClient::connectorCreateResponse(int statusCode, std::string &statusString, std::string &connectorHandle, std::string &versionID) -{ - LLSD result = LLSD::emptyMap(); - - if(statusCode == 0) - { - // Connector created, move forward. - if (connectorHandle == LLVivoxSecurity::getInstance()->connectorHandle()) - { - LL_INFOS("Voice") << "Voice connector succeeded, Vivox SDK version is " << versionID << " connector handle " << connectorHandle << LL_ENDL; - mVoiceVersion.serverVersion = versionID; - mConnectorEstablished = true; - mTerminateDaemon = false; - - result["connector"] = LLSD::Boolean(true); - } - else - { - // This shouldn't happen - we are somehow out of sync with SLVoice - // or possibly there are two things trying to run SLVoice at once - // or someone is trying to hack into it. - LL_WARNS("Voice") << "Connector returned wrong handle " - << "(" << connectorHandle << ")" - << " expected (" << LLVivoxSecurity::getInstance()->connectorHandle() << ")" - << LL_ENDL; - result["connector"] = LLSD::Boolean(false); - // Give up. - mTerminateDaemon = true; - } - } - else if (statusCode == 10028) // web request timeout prior to login - { - // this is usually fatal, but a long timeout might work - result["connector"] = LLSD::Boolean(false); - result["retry"] = LLSD::Real(CONNECT_ATTEMPT_TIMEOUT); - - LL_WARNS("Voice") << "Voice connection failed" << LL_ENDL; - } - else if (statusCode == 10006) // name resolution failure - a shorter retry may work - { - // some networks have slower DNS, but a short timeout might let it catch up - result["connector"] = LLSD::Boolean(false); - result["retry"] = LLSD::Real(CONNECT_DNS_TIMEOUT); - - LL_WARNS("Voice") << "Voice connection DNS lookup failed" << LL_ENDL; - } - else // unknown failure - give up - { - LL_WARNS("Voice") << "Voice connection failure ("<< statusCode << "): " << statusString << LL_ENDL; - mTerminateDaemon = true; - result["connector"] = LLSD::Boolean(false); - } - - mVivoxPump.post(result); -} - -void LLVivoxVoiceClient::loginResponse(int statusCode, std::string &statusString, std::string &accountHandle, int numberOfAliases) -{ - LLSD result = LLSD::emptyMap(); - - LL_DEBUGS("Voice") << "Account.Login response (" << statusCode << "): " << statusString << LL_ENDL; - - // Status code of 20200 means "bad password". We may want to special-case that at some point. - - if ( statusCode == HTTP_UNAUTHORIZED ) - { - // Login failure which is probably caused by the delay after a user's password being updated. - LL_INFOS("Voice") << "Account.Login response failure (" << statusCode << "): " << statusString << LL_ENDL; - result["login"] = LLSD::String("retry"); - } - else if(statusCode != 0) - { - LL_WARNS("Voice") << "Account.Login response failure (" << statusCode << "): " << statusString << LL_ENDL; - result["login"] = LLSD::String("failed"); - } - else - { - // Login succeeded, move forward. - mAccountLoggedIn = true; - mNumberOfAliases = numberOfAliases; - result["login"] = LLSD::String("response_ok"); - } - - mVivoxPump.post(result); - -} - -void LLVivoxVoiceClient::sessionCreateResponse(std::string &requestId, int statusCode, std::string &statusString, std::string &sessionHandle) -{ - sessionStatePtr_t session(findSessionBeingCreatedByURI(requestId)); - - if(session) - { - session->mCreateInProgress = false; - } - - if(statusCode != 0) - { - LL_WARNS("Voice") << "Session.Create response failure (" << statusCode << "): " << statusString << LL_ENDL; - if(session) - { - session->mErrorStatusCode = statusCode; - session->mErrorStatusString = statusString; - if(session == mAudioSession) - { - LLSD vivoxevent(LLSDMap("handle", LLSD::String(sessionHandle)) - ("session", "failed") - ("reason", LLSD::Integer(statusCode))); - - mVivoxPump.post(vivoxevent); - } - else - { - reapSession(session); - } - } - } - else - { - LL_INFOS("Voice") << "Session.Create response received (success), session handle is " << sessionHandle << LL_ENDL; - if(session) - { - setSessionHandle(session, sessionHandle); - } - LLSD vivoxevent(LLSDMap("handle", LLSD::String(sessionHandle)) - ("session", "created")); - - mVivoxPump.post(vivoxevent); - } -} - -void LLVivoxVoiceClient::sessionGroupAddSessionResponse(std::string &requestId, int statusCode, std::string &statusString, std::string &sessionHandle) -{ - sessionStatePtr_t session(findSessionBeingCreatedByURI(requestId)); - - if(session) - { - session->mCreateInProgress = false; - } - - if(statusCode != 0) - { - LL_WARNS("Voice") << "SessionGroup.AddSession response failure (" << statusCode << "): " << statusString << LL_ENDL; - if(session) - { - session->mErrorStatusCode = statusCode; - session->mErrorStatusString = statusString; - if(session == mAudioSession) - { - LLSD vivoxevent(LLSDMap("handle", LLSD::String(sessionHandle)) - ("session", "failed")); - - mVivoxPump.post(vivoxevent); - } - else - { - reapSession(session); - } - } - } - else - { - LL_DEBUGS("Voice") << "SessionGroup.AddSession response received (success), session handle is " << sessionHandle << LL_ENDL; - if(session) - { - setSessionHandle(session, sessionHandle); - } - - LLSD vivoxevent(LLSDMap("handle", LLSD::String(sessionHandle)) - ("session", "added")); - - mVivoxPump.post(vivoxevent); - - } -} - -void LLVivoxVoiceClient::sessionConnectResponse(std::string &requestId, int statusCode, std::string &statusString) -{ - sessionStatePtr_t session(findSession(requestId)); - // 1026 is session already has media, somehow mediaconnect was called twice on the same session. - // set the session info to reflect that the user is already connected. - if (statusCode == 1026) - { - session->mVoiceActive = true; - session->mMediaConnectInProgress = false; - session->mMediaStreamState = streamStateConnected; - //session->mTextStreamState = streamStateConnected; - session->mErrorStatusCode = 0; - } - else if (statusCode != 0) - { - LL_WARNS("Voice") << "Session.Connect response failure (" << statusCode << "): " << statusString << LL_ENDL; - if (session) - { - session->mMediaConnectInProgress = false; - session->mErrorStatusCode = statusCode; - session->mErrorStatusString = statusString; - } - } - else - { - LL_DEBUGS("Voice") << "Session.Connect response received (success)" << LL_ENDL; - } -} - -void LLVivoxVoiceClient::logoutResponse(int statusCode, std::string &statusString) -{ - if(statusCode != 0) - { - LL_WARNS("Voice") << "Account.Logout response failure: " << statusString << LL_ENDL; - // Should this ever fail? do we care if it does? - } - LLSD vivoxevent(LLSDMap("logout", LLSD::Boolean(true))); - - mVivoxPump.post(vivoxevent); -} - -void LLVivoxVoiceClient::connectorShutdownResponse(int statusCode, std::string &statusString) -{ - if(statusCode != 0) - { - LL_WARNS("Voice") << "Connector.InitiateShutdown response failure: " << statusString << LL_ENDL; - // Should this ever fail? do we care if it does? - } - - sConnected = false; - mShutdownComplete = true; - - LLSD vivoxevent(LLSDMap("connector", LLSD::Boolean(false))); - - mVivoxPump.post(vivoxevent); -} - -void LLVivoxVoiceClient::sessionAddedEvent( - std::string &uriString, - std::string &alias, - std::string &sessionHandle, - std::string &sessionGroupHandle, - bool isChannel, - bool incoming, - std::string &nameString, - std::string &applicationString) -{ - sessionStatePtr_t session; - - LL_INFOS("Voice") << "session " << uriString << ", alias " << alias << ", name " << nameString << " handle " << sessionHandle << LL_ENDL; - - session = addSession(uriString, sessionHandle); - if(session) - { - session->mGroupHandle = sessionGroupHandle; - session->mIsChannel = isChannel; - session->mIncoming = incoming; - session->mAlias = alias; - - // Generate a caller UUID -- don't need to do this for channels - if(!session->mIsChannel) - { - if(IDFromName(session->mSIPURI, session->mCallerID)) - { - // Normal URI(base64-encoded UUID) - } - else if(!session->mAlias.empty() && IDFromName(session->mAlias, session->mCallerID)) - { - // Wrong URI, but an alias is available. Stash the incoming URI as an alternate - session->mAlternateSIPURI = session->mSIPURI; - - // and generate a proper URI from the ID. - setSessionURI(session, sipURIFromID(session->mCallerID)); - } - else - { - LL_INFOS("Voice") << "Could not generate caller id from uri, using hash of uri " << session->mSIPURI << LL_ENDL; - session->mCallerID.generate(session->mSIPURI); - session->mSynthesizedCallerID = true; - - // Can't look up the name in this case -- we have to extract it from the URI. - std::string namePortion = nameFromsipURI(session->mSIPURI); - if(namePortion.empty()) - { - // Didn't seem to be a SIP URI, just use the whole provided name. - namePortion = nameString; - } - - // Some incoming names may be separated with an underscore instead of a space. Fix this. - LLStringUtil::replaceChar(namePortion, '_', ' '); - - // Act like we just finished resolving the name (this stores it in all the right places) - avatarNameResolved(session->mCallerID, namePortion); - } - - LL_INFOS("Voice") << "caller ID: " << session->mCallerID << LL_ENDL; - - if(!session->mSynthesizedCallerID) - { - // If we got here, we don't have a proper name. Initiate a lookup. - lookupName(session->mCallerID); - } - } - } -} - -void LLVivoxVoiceClient::sessionGroupAddedEvent(std::string &sessionGroupHandle) -{ - LL_DEBUGS("Voice") << "handle " << sessionGroupHandle << LL_ENDL; - -#if USE_SESSION_GROUPS - if(mMainSessionGroupHandle.empty()) - { - // This is the first (i.e. "main") session group. Save its handle. - mMainSessionGroupHandle = sessionGroupHandle; - } - else - { - LL_DEBUGS("Voice") << "Already had a session group handle " << mMainSessionGroupHandle << LL_ENDL; - } -#endif -} - -void LLVivoxVoiceClient::joinedAudioSession(const sessionStatePtr_t &session) -{ - LL_DEBUGS("Voice") << "Joined Audio Session" << LL_ENDL; - if(mAudioSession != session) - { - sessionStatePtr_t oldSession = mAudioSession; - - mAudioSession = session; - mAudioSessionChanged = true; - - // The old session may now need to be deleted. - reapSession(oldSession); - } - - // This is the session we're joining. - if(mIsJoiningSession) - { - LLSD vivoxevent(LLSDMap("handle", LLSD::String(session->mHandle)) - ("session", "joined")); - - mVivoxPump.post(vivoxevent); - - // Add the current user as a participant here. - participantStatePtr_t participant(session->addParticipant(sipURIFromName(mAccountName))); - if(participant) - { - participant->mIsSelf = true; - lookupName(participant->mAvatarID); - - LL_INFOS("Voice") << "added self as participant \"" << participant->mAccountName - << "\" (" << participant->mAvatarID << ")"<< LL_ENDL; - } - - if(!session->mIsChannel) - { - // this is a p2p session. Make sure the other end is added as a participant. - participantStatePtr_t participant(session->addParticipant(session->mSIPURI)); - if(participant) - { - if(participant->mAvatarIDValid) - { - lookupName(participant->mAvatarID); - } - else if(!session->mName.empty()) - { - participant->mDisplayName = session->mName; - avatarNameResolved(participant->mAvatarID, session->mName); - } - - // TODO: Question: Do we need to set up mAvatarID/mAvatarIDValid here? - LL_INFOS("Voice") << "added caller as participant \"" << participant->mAccountName - << "\" (" << participant->mAvatarID << ")"<< LL_ENDL; - } - } - } -} - -void LLVivoxVoiceClient::sessionRemovedEvent( - std::string &sessionHandle, - std::string &sessionGroupHandle) -{ - LL_INFOS("Voice") << "handle " << sessionHandle << LL_ENDL; - - sessionStatePtr_t session(findSession(sessionHandle)); - if(session) - { - leftAudioSession(session); - - // This message invalidates the session's handle. Set it to empty. - clearSessionHandle(session); - - // This also means that the session's session group is now empty. - // Terminate the session group so it doesn't leak. - sessionGroupTerminateSendMessage(session); - - // Reset the media state (we now have no info) - session->mMediaStreamState = streamStateUnknown; - //session->mTextStreamState = streamStateUnknown; - - // Conditionally delete the session - reapSession(session); - } - else - { - // Already reaped this session. - LL_DEBUGS("Voice") << "unknown session " << sessionHandle << " removed" << LL_ENDL; - } - -} - -void LLVivoxVoiceClient::reapSession(const sessionStatePtr_t &session) -{ - if(session) - { - - if(session->mCreateInProgress) - { - LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (create in progress)" << LL_ENDL; - } - else if(session->mMediaConnectInProgress) - { - LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (connect in progress)" << LL_ENDL; - } - else if(session == mAudioSession) - { - LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (it's the current session)" << LL_ENDL; - } - else if(session == mNextAudioSession) - { - LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (it's the next session)" << LL_ENDL; - } - else - { - // We don't have a reason to keep tracking this session, so just delete it. - LL_DEBUGS("Voice") << "deleting session " << session->mSIPURI << LL_ENDL; - deleteSession(session); - } - } - else - { -// LL_DEBUGS("Voice") << "session is NULL" << LL_ENDL; - } -} - -// Returns true if the session seems to indicate we've moved to a region on a different voice server -bool LLVivoxVoiceClient::sessionNeedsRelog(const sessionStatePtr_t &session) -{ - bool result = false; - - if(session) - { - // Only make this check for spatial channels (so it won't happen for group or p2p calls) - if(session->mIsSpatial) - { - std::string::size_type atsign; - - atsign = session->mSIPURI.find("@"); - - if(atsign != std::string::npos) - { - std::string urihost = session->mSIPURI.substr(atsign + 1); - if(stricmp(urihost.c_str(), mVoiceSIPURIHostName.c_str())) - { - // The hostname in this URI is different from what we expect. This probably means we need to relog. - - // We could make a ProvisionVoiceAccountRequest and compare the result with the current values of - // mVoiceSIPURIHostName and mVoiceAccountServerURI to be really sure, but this is a pretty good indicator. - - result = true; - } - } - } - } - - return result; -} - -void LLVivoxVoiceClient::leftAudioSession(const sessionStatePtr_t &session) -{ - if (mAudioSession == session) - { - LLSD vivoxevent(LLSDMap("handle", LLSD::String(session->mHandle)) - ("session", "removed")); - - mVivoxPump.post(vivoxevent); - } -} - -void LLVivoxVoiceClient::accountLoginStateChangeEvent( - std::string &accountHandle, - int statusCode, - std::string &statusString, - int state) -{ - LLSD levent = LLSD::emptyMap(); - - /* - According to Mike S., status codes for this event are: - login_state_logged_out=0, - login_state_logged_in = 1, - login_state_logging_in = 2, - login_state_logging_out = 3, - login_state_resetting = 4, - login_state_error=100 - */ - - LL_DEBUGS("Voice") << "state change event: " << state << LL_ENDL; - switch(state) - { - case 1: - levent["login"] = LLSD::String("account_login"); - - mVivoxPump.post(levent); - break; - case 2: - break; - - case 3: - levent["login"] = LLSD::String("account_loggingOut"); - - mVivoxPump.post(levent); - break; - - case 4: - break; - - case 100: - LL_WARNS("Voice") << "account state event error" << LL_ENDL; - break; - - case 0: - levent["login"] = LLSD::String("account_logout"); - - mVivoxPump.post(levent); - break; - - default: - //Used to be a commented out warning - LL_WARNS("Voice") << "unknown account state event: " << state << LL_ENDL; - break; - } -} - -void LLVivoxVoiceClient::mediaCompletionEvent(std::string &sessionGroupHandle, std::string &mediaCompletionType) -{ - LLSD result; - - if (mediaCompletionType == "AuxBufferAudioCapture") - { - mCaptureBufferRecording = false; - result["recplay"] = "end"; - } - else if (mediaCompletionType == "AuxBufferAudioRender") - { - // Ignore all but the last stop event - if (--mPlayRequestCount <= 0) - { - mCaptureBufferPlaying = false; - result["recplay"] = "end"; -// result["recplay"] = "done"; - } - } - else - { - LL_WARNS("Voice") << "Unknown MediaCompletionType: " << mediaCompletionType << LL_ENDL; - } - - if (!result.isUndefined()) - mVivoxPump.post(result); -} - -void LLVivoxVoiceClient::mediaStreamUpdatedEvent( - std::string &sessionHandle, - std::string &sessionGroupHandle, - int statusCode, - std::string &statusString, - int state, - bool incoming) -{ - sessionStatePtr_t session(findSession(sessionHandle)); - - LL_DEBUGS("Voice") << "session " << sessionHandle << ", status code " << statusCode << ", string \"" << statusString << "\"" << LL_ENDL; - - if(session) - { - // We know about this session - - // Save the state for later use - session->mMediaStreamState = state; - - switch(statusCode) - { - case 0: - case HTTP_OK: - // generic success - // Don't change the saved error code (it may have been set elsewhere) - break; - default: - // save the status code for later - session->mErrorStatusCode = statusCode; - break; - } - - switch(state) - { - case streamStateDisconnecting: - case streamStateIdle: - // Standard "left audio session", Vivox state 'disconnected' - session->mVoiceActive = false; - session->mMediaConnectInProgress = false; - leftAudioSession(session); - break; - - case streamStateConnected: - session->mVoiceActive = true; - session->mMediaConnectInProgress = false; - joinedAudioSession(session); - case streamStateConnecting: // do nothing, but prevents a warning getting into the logs. - break; - - case streamStateRinging: - if(incoming) - { - // Send the voice chat invite to the GUI layer - // TODO: Question: Should we correlate with the mute list here? - session->mIMSessionID = LLIMMgr::computeSessionID(IM_SESSION_P2P_INVITE, session->mCallerID); - session->mVoiceInvitePending = true; - if(session->mName.empty()) - { - lookupName(session->mCallerID); - } - else - { - // Act like we just finished resolving the name - avatarNameResolved(session->mCallerID, session->mName); - } - } - break; - - default: - LL_WARNS("Voice") << "unknown state " << state << LL_ENDL; - break; - - } - - } - else - { - // session disconnectintg and disconnected events arriving after we have already left the session. - LL_DEBUGS("Voice") << "session " << sessionHandle << " not found"<< LL_ENDL; - } -} - -void LLVivoxVoiceClient::participantAddedEvent( - std::string &sessionHandle, - std::string &sessionGroupHandle, - std::string &uriString, - std::string &alias, - std::string &nameString, - std::string &displayNameString, - int participantType) -{ - sessionStatePtr_t session(findSession(sessionHandle)); - if(session) - { - participantStatePtr_t participant(session->addParticipant(uriString)); - if(participant) - { - participant->mAccountName = nameString; - - LL_DEBUGS("Voice") << "added participant \"" << participant->mAccountName - << "\" (" << participant->mAvatarID << ")"<< LL_ENDL; - - if(participant->mAvatarIDValid) - { - // Initiate a lookup - lookupName(participant->mAvatarID); - } - else - { - // If we don't have a valid avatar UUID, we need to fill in the display name to make the active speakers floater work. - std::string namePortion = nameFromsipURI(uriString); - if(namePortion.empty()) - { - // Problem with the SIP URI, fall back to the display name - namePortion = displayNameString; - } - if(namePortion.empty()) - { - // Problems with both of the above, fall back to the account name - namePortion = nameString; - } - - // Set the display name (which is a hint to the active speakers window not to do its own lookup) - participant->mDisplayName = namePortion; - avatarNameResolved(participant->mAvatarID, namePortion); - } - } - } -} - -void LLVivoxVoiceClient::participantRemovedEvent( - std::string &sessionHandle, - std::string &sessionGroupHandle, - std::string &uriString, - std::string &alias, - std::string &nameString) -{ - sessionStatePtr_t session(findSession(sessionHandle)); - if(session) - { - participantStatePtr_t participant(session->findParticipant(uriString)); - if(participant) - { - session->removeParticipant(participant); - } - else - { - LL_DEBUGS("Voice") << "unknown participant " << uriString << LL_ENDL; - } - } - else - { - // a late arriving event on a session we have already left. - LL_DEBUGS("Voice") << "unknown session " << sessionHandle << LL_ENDL; - } -} - - -void LLVivoxVoiceClient::participantUpdatedEvent( - std::string &sessionHandle, - std::string &sessionGroupHandle, - std::string &uriString, - std::string &alias, - bool isModeratorMuted, - bool isSpeaking, - int volume, - F32 energy) -{ - sessionStatePtr_t session(findSession(sessionHandle)); - if(session) - { - participantStatePtr_t participant(session->findParticipant(uriString)); - - if(participant) - { - //LL_INFOS("Voice") << "Participant Update for " << participant->mDisplayName << LL_ENDL; - - participant->mIsSpeaking = isSpeaking; - participant->mIsModeratorMuted = isModeratorMuted; - - // SLIM SDK: convert range: ensure that energy is set to zero if is_speaking is false - if (isSpeaking) - { - participant->mSpeakingTimeout.reset(); - participant->mPower = energy; - } - else - { - participant->mPower = 0.0f; - } - - // Ignore incoming volume level if it has been explicitly set, or there - // is a volume or mute change pending. - if ( !participant->mVolumeSet && !participant->mVolumeDirty) - { - participant->mVolume = (F32)volume * VOLUME_SCALE_VIVOX; - } - - // *HACK: mantipov: added while working on EXT-3544 - /* - Sometimes LLVoiceClient::participantUpdatedEvent callback is called BEFORE - LLViewerChatterBoxSessionAgentListUpdates::post() sometimes AFTER. - - participantUpdatedEvent updates voice participant state in particular participantState::mIsModeratorMuted - Originally we wanted to update session Speaker Manager to fire LLSpeakerVoiceModerationEvent to fix the EXT-3544 bug. - Calling of the LLSpeakerMgr::update() method was added into LLIMMgr::processAgentListUpdates. - - But in case participantUpdatedEvent() is called after LLViewerChatterBoxSessionAgentListUpdates::post() - voice participant mIsModeratorMuted is changed after speakers are updated in Speaker Manager - and event is not fired. - - So, we have to call LLSpeakerMgr::update() here. - */ - LLVoiceChannel* voice_cnl = LLVoiceChannel::getCurrentVoiceChannel(); - - // ignore session ID of local chat - if (voice_cnl && voice_cnl->getSessionID().notNull()) - { - LLSpeakerMgr* speaker_manager = LLIMModel::getInstance()->getSpeakerManager(voice_cnl->getSessionID()); - if (speaker_manager) - { - speaker_manager->update(true); - - // also initialize voice moderate_mode depend on Agent's participant. See EXT-6937. - // *TODO: remove once a way to request the current voice channel moderation mode is implemented. - if (gAgent.getID() == participant->mAvatarID) - { - speaker_manager->initVoiceModerateMode(); - } - } - } - } - else - { - LL_WARNS("Voice") << "unknown participant: " << uriString << LL_ENDL; - } - } - else - { - LL_DEBUGS("Voice") << "unknown session " << sessionHandle << LL_ENDL; - } -} - -void LLVivoxVoiceClient::messageEvent( - std::string &sessionHandle, - std::string &uriString, - std::string &alias, - std::string &messageHeader, - std::string &messageBody, - std::string &applicationString) -{ - LL_DEBUGS("Voice") << "Message event, session " << sessionHandle << " from " << uriString << LL_ENDL; -// LL_DEBUGS("Voice") << " header " << messageHeader << ", body: \n" << messageBody << LL_ENDL; - - LL_INFOS("Voice") << "Vivox raw message:" << std::endl << messageBody << LL_ENDL; - - if(messageHeader.find(HTTP_CONTENT_TEXT_HTML) != std::string::npos) - { - std::string message; - - { - const std::string startMarker = "<body"; - const std::string startMarker2 = ">"; - const std::string endMarker = "</body>"; - const std::string startSpan = "<span"; - const std::string endSpan = "</span>"; - std::string::size_type start; - std::string::size_type end; - - // Default to displaying the raw string, so the message gets through. - message = messageBody; - - // Find the actual message text within the XML fragment - start = messageBody.find(startMarker); - start = messageBody.find(startMarker2, start); - end = messageBody.find(endMarker); - - if(start != std::string::npos) - { - start += startMarker2.size(); - - if(end != std::string::npos) - end -= start; - - message.assign(messageBody, start, end); - } - else - { - // Didn't find a <body>, try looking for a <span> instead. - start = messageBody.find(startSpan); - start = messageBody.find(startMarker2, start); - end = messageBody.find(endSpan); - - if(start != std::string::npos) - { - start += startMarker2.size(); - - if(end != std::string::npos) - end -= start; - - message.assign(messageBody, start, end); - } - } - } - -// LL_DEBUGS("Voice") << " raw message = \n" << message << LL_ENDL; - - // strip formatting tags - { - std::string::size_type start; - std::string::size_type end; - - while((start = message.find('<')) != std::string::npos) - { - if((end = message.find('>', start + 1)) != std::string::npos) - { - // Strip out the tag - message.erase(start, (end + 1) - start); - } - else - { - // Avoid an infinite loop - break; - } - } - } - - // Decode ampersand-escaped chars - { - std::string::size_type mark = 0; - - // The text may contain text encoded with <, >, and & - mark = 0; - while((mark = message.find("<", mark)) != std::string::npos) - { - message.replace(mark, 4, "<"); - mark += 1; - } - - mark = 0; - while((mark = message.find(">", mark)) != std::string::npos) - { - message.replace(mark, 4, ">"); - mark += 1; - } - - mark = 0; - while((mark = message.find("&", mark)) != std::string::npos) - { - message.replace(mark, 5, "&"); - mark += 1; - } - } - - // strip leading/trailing whitespace (since we always seem to get a couple newlines) - LLStringUtil::trim(message); - -// LL_DEBUGS("Voice") << " stripped message = \n" << message << LL_ENDL; - - sessionStatePtr_t session(findSession(sessionHandle)); - if(session) - { - bool is_do_not_disturb = gAgent.isDoNotDisturb(); - bool is_muted = LLMuteList::getInstance()->isMuted(session->mCallerID, session->mName, LLMute::flagTextChat); - bool is_linden = LLMuteList::isLinden(session->mName); - LLChat chat; - - chat.mMuted = is_muted && !is_linden; - - if(!chat.mMuted) - { - chat.mFromID = session->mCallerID; - chat.mFromName = session->mName; - chat.mSourceType = CHAT_SOURCE_AGENT; - - if(is_do_not_disturb && !is_linden) - { - // TODO: Question: Return do not disturb mode response here? Or maybe when session is started instead? - } - - LL_DEBUGS("Voice") << "adding message, name " << session->mName << " session " << session->mIMSessionID << ", target " << session->mCallerID << LL_ENDL; - LLIMMgr::getInstance()->addMessage(session->mIMSessionID, - session->mCallerID, - session->mName.c_str(), - message.c_str(), - false, - LLStringUtil::null, // default arg - IM_NOTHING_SPECIAL, // default arg - 0, // default arg - LLUUID::null, // default arg - LLVector3::zero); // default arg - } - } - } -} - -void LLVivoxVoiceClient::sessionNotificationEvent(std::string &sessionHandle, std::string &uriString, std::string ¬ificationType) -{ - sessionStatePtr_t session(findSession(sessionHandle)); - - if(session) - { - participantStatePtr_t participant(session->findParticipant(uriString)); - if(participant) - { - if (!stricmp(notificationType.c_str(), "Typing")) - { - // Other end started typing - // TODO: The proper way to add a typing notification seems to be LLIMMgr::processIMTypingStart(). - // It requires some info for the message, which we don't have here. - } - else if (!stricmp(notificationType.c_str(), "NotTyping")) - { - // Other end stopped typing - // TODO: The proper way to remove a typing notification seems to be LLIMMgr::processIMTypingStop(). - // It requires some info for the message, which we don't have here. - } - else - { - LL_DEBUGS("Voice") << "Unknown notification type " << notificationType << "for participant " << uriString << " in session " << session->mSIPURI << LL_ENDL; - } - } - else - { - LL_DEBUGS("Voice") << "Unknown participant " << uriString << " in session " << session->mSIPURI << LL_ENDL; - } - } - else - { - LL_DEBUGS("Voice") << "Unknown session handle " << sessionHandle << LL_ENDL; - } -} - -void LLVivoxVoiceClient::voiceServiceConnectionStateChangedEvent(int statusCode, std::string &statusString, std::string &build_id) -{ - // We don't generally need to process this. However, one occurence is when we first connect, and so it is the - // earliest opportunity to learn what we're connected to. - if (statusCode) - { - LL_WARNS("Voice") << "VoiceServiceConnectionStateChangedEvent statusCode: " << statusCode << - "statusString: " << statusString << LL_ENDL; - return; - } - if (build_id.empty()) - { - return; - } - mVoiceVersion.mBuildVersion = build_id; -} - -void LLVivoxVoiceClient::auxAudioPropertiesEvent(F32 energy) -{ - LL_DEBUGS("VoiceEnergy") << "got energy " << energy << LL_ENDL; - mTuningEnergy = energy; -} - -void LLVivoxVoiceClient::muteListChanged() -{ - // The user's mute list has been updated. Go through the current participant list and sync it with the mute list. - if(mAudioSession) - { - participantMap::iterator iter = mAudioSession->mParticipantsByURI.begin(); - - for(; iter != mAudioSession->mParticipantsByURI.end(); iter++) - { - participantStatePtr_t p(iter->second); - - // Check to see if this participant is on the mute list already - if(p->updateMuteState()) - mAudioSession->mVolumeDirty = true; - } - } -} - -///////////////////////////// -// Managing list of participants -LLVivoxVoiceClient::participantState::participantState(const std::string &uri) : - mURI(uri), - mPTT(false), - mIsSpeaking(false), - mIsModeratorMuted(false), - mLastSpokeTimestamp(0.f), - mPower(0.f), - mVolume(LLVoiceClient::VOLUME_DEFAULT), - mUserVolume(0), - mOnMuteList(false), - mVolumeSet(false), - mVolumeDirty(false), - mAvatarIDValid(false), - mIsSelf(false) -{ -} - -LLVivoxVoiceClient::participantStatePtr_t LLVivoxVoiceClient::sessionState::addParticipant(const std::string &uri) -{ - participantStatePtr_t result; - bool useAlternateURI = false; - - // Note: this is mostly the body of LLVivoxVoiceClient::sessionState::findParticipant(), but since we need to know if it - // matched the alternate SIP URI (so we can add it properly), we need to reproduce it here. - { - participantMap::iterator iter = mParticipantsByURI.find(uri); - - if(iter == mParticipantsByURI.end()) - { - if(!mAlternateSIPURI.empty() && (uri == mAlternateSIPURI)) - { - // This is a p2p session (probably with the SLIM client) with an alternate URI for the other participant. - // Use mSIPURI instead, since it will be properly encoded. - iter = mParticipantsByURI.find(mSIPURI); - useAlternateURI = true; - } - } - - if(iter != mParticipantsByURI.end()) - { - result = iter->second; - } - } - - if(!result) - { - // participant isn't already in one list or the other. - result.reset(new participantState(useAlternateURI?mSIPURI:uri)); - mParticipantsByURI.insert(participantMap::value_type(result->mURI, result)); - mParticipantsChanged = true; - - // Try to do a reverse transform on the URI to get the GUID back. - { - LLUUID id; - if(LLVivoxVoiceClient::getInstance()->IDFromName(result->mURI, id)) - { - result->mAvatarIDValid = true; - result->mAvatarID = id; - } - else - { - // Create a UUID by hashing the URI, but do NOT set mAvatarIDValid. - // This indicates that the ID will not be in the name cache. - result->mAvatarID.generate(uri); - } - } - - if(result->updateMuteState()) - { - mMuteDirty = true; - } - - mParticipantsByUUID.insert(participantUUIDMap::value_type(result->mAvatarID, result)); - - if (LLSpeakerVolumeStorage::getInstance()->getSpeakerVolume(result->mAvatarID, result->mVolume)) - { - result->mVolumeDirty = true; - mVolumeDirty = true; - } - - LL_DEBUGS("Voice") << "participant \"" << result->mURI << "\" added." << LL_ENDL; - } - - return result; -} - -bool LLVivoxVoiceClient::participantState::updateMuteState() -{ - bool result = false; - - bool isMuted = LLMuteList::getInstance()->isMuted(mAvatarID, LLMute::flagVoiceChat); - if(mOnMuteList != isMuted) - { - mOnMuteList = isMuted; - mVolumeDirty = true; - result = true; - } - return result; -} - -bool LLVivoxVoiceClient::participantState::isAvatar() -{ - return mAvatarIDValid; -} - -void LLVivoxVoiceClient::sessionState::removeParticipant(const LLVivoxVoiceClient::participantStatePtr_t &participant) -{ - if(participant) - { - participantMap::iterator iter = mParticipantsByURI.find(participant->mURI); - participantUUIDMap::iterator iter2 = mParticipantsByUUID.find(participant->mAvatarID); - - LL_DEBUGS("Voice") << "participant \"" << participant->mURI << "\" (" << participant->mAvatarID << ") removed." << LL_ENDL; - - if(iter == mParticipantsByURI.end()) - { - LL_WARNS("Voice") << "Internal error: participant " << participant->mURI << " not in URI map" << LL_ENDL; - } - else if(iter2 == mParticipantsByUUID.end()) - { - LL_WARNS("Voice") << "Internal error: participant ID " << participant->mAvatarID << " not in UUID map" << LL_ENDL; - } - else if(iter->second != iter2->second) - { - LL_WARNS("Voice") << "Internal error: participant mismatch!" << LL_ENDL; - } - else - { - mParticipantsByURI.erase(iter); - mParticipantsByUUID.erase(iter2); - - mParticipantsChanged = true; - } - } -} - -void LLVivoxVoiceClient::sessionState::removeAllParticipants() -{ - LL_DEBUGS("Voice") << "called" << LL_ENDL; - - while(!mParticipantsByURI.empty()) - { - removeParticipant(mParticipantsByURI.begin()->second); - } - - if(!mParticipantsByUUID.empty()) - { - LL_WARNS("Voice") << "Internal error: empty URI map, non-empty UUID map" << LL_ENDL; - } -} - -/*static*/ -void LLVivoxVoiceClient::sessionState::VerifySessions() -{ - std::set<wptr_t>::iterator it = mSession.begin(); - while (it != mSession.end()) - { - if ((*it).expired()) - { - LL_WARNS("Voice") << "Expired session found! removing" << LL_ENDL; - it = mSession.erase(it); - } - else - ++it; - } -} - - -void LLVivoxVoiceClient::getParticipantList(std::set<LLUUID> &participants) -{ - if(mAudioSession) - { - for(participantUUIDMap::iterator iter = mAudioSession->mParticipantsByUUID.begin(); - iter != mAudioSession->mParticipantsByUUID.end(); - iter++) - { - participants.insert(iter->first); - } - } -} - -bool LLVivoxVoiceClient::isParticipant(const LLUUID &speaker_id) -{ - if(mAudioSession) - { - return (mAudioSession->mParticipantsByUUID.find(speaker_id) != mAudioSession->mParticipantsByUUID.end()); - } - return false; -} - - -LLVivoxVoiceClient::participantStatePtr_t LLVivoxVoiceClient::sessionState::findParticipant(const std::string &uri) -{ - participantStatePtr_t result; - - participantMap::iterator iter = mParticipantsByURI.find(uri); - - if(iter == mParticipantsByURI.end()) - { - if(!mAlternateSIPURI.empty() && (uri == mAlternateSIPURI)) - { - // This is a p2p session (probably with the SLIM client) with an alternate URI for the other participant. - // Look up the other URI - iter = mParticipantsByURI.find(mSIPURI); - } - } - - if(iter != mParticipantsByURI.end()) - { - result = iter->second; - } - - return result; -} - -LLVivoxVoiceClient::participantStatePtr_t LLVivoxVoiceClient::sessionState::findParticipantByID(const LLUUID& id) -{ - participantStatePtr_t result; - participantUUIDMap::iterator iter = mParticipantsByUUID.find(id); - - if(iter != mParticipantsByUUID.end()) - { - result = iter->second; - } - - return result; -} - -LLVivoxVoiceClient::participantStatePtr_t LLVivoxVoiceClient::findParticipantByID(const LLUUID& id) -{ - participantStatePtr_t result; - - if(mAudioSession) - { - result = mAudioSession->findParticipantByID(id); - } - - return result; -} - - - -// Check for parcel boundary crossing -bool LLVivoxVoiceClient::checkParcelChanged(bool update) -{ - LLViewerRegion *region = gAgent.getRegion(); - LLParcel *parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); - - if(region && parcel) - { - S32 parcelLocalID = parcel->getLocalID(); - std::string regionName = region->getName(); - - // LL_DEBUGS("Voice") << "Region name = \"" << regionName << "\", parcel local ID = " << parcelLocalID << ", cap URI = \"" << capURI << "\"" << LL_ENDL; - - // The region name starts out empty and gets filled in later. - // Also, the cap gets filled in a short time after the region cross, but a little too late for our purposes. - // If either is empty, wait for the next time around. - if(!regionName.empty()) - { - if((parcelLocalID != mCurrentParcelLocalID) || (regionName != mCurrentRegionName)) - { - // We have changed parcels. Initiate a parcel channel lookup. - if (update) - { - mCurrentParcelLocalID = parcelLocalID; - mCurrentRegionName = regionName; - } - return true; - } - } - } - return false; -} - -bool LLVivoxVoiceClient::switchChannel( - std::string uri, - bool spatial, - bool no_reconnect, - bool is_p2p, - std::string hash) -{ - bool needsSwitch = !mIsInChannel; - - if (mIsInChannel) - { - if (mSessionTerminateRequested) - { - // If a terminate has been requested, we need to compare against where the URI we're already headed to. - if(mNextAudioSession) - { - if(mNextAudioSession->mSIPURI != uri) - needsSwitch = true; - } - else - { - // mNextAudioSession is null -- this probably means we're on our way back to spatial. - if(!uri.empty()) - { - // We do want to process a switch in this case. - needsSwitch = true; - } - } - } - else - { - // Otherwise, compare against the URI we're in now. - if(mAudioSession) - { - if(mAudioSession->mSIPURI != uri) - { - needsSwitch = true; - } - } - else - { - if(!uri.empty()) - { - // mAudioSession is null -- it's not clear what case would cause this. - // For now, log it as a warning and see if it ever crops up. - LL_WARNS("Voice") << "No current audio session... Forcing switch" << LL_ENDL; - needsSwitch = true; - } - } - } - } - - if(needsSwitch) - { - if(uri.empty()) - { - // Leave any channel we may be in - LL_DEBUGS("Voice") << "leaving channel" << LL_ENDL; - - sessionStatePtr_t oldSession = mNextAudioSession; - mNextAudioSession.reset(); - - // The old session may now need to be deleted. - reapSession(oldSession); - - // If voice was on, turn it off - if (LLVoiceClient::getInstance()->getUserPTTState()) - { - LLVoiceClient::getInstance()->setUserPTTState(false); - } - - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED); - } - else - { - LL_DEBUGS("Voice") << "switching to channel " << uri << LL_ENDL; - - mNextAudioSession = addSession(uri); - mNextAudioSession->mHash = hash; - mNextAudioSession->mIsSpatial = spatial; - mNextAudioSession->mReconnect = !no_reconnect; - mNextAudioSession->mIsP2P = is_p2p; - } - - if (mIsInChannel) - { - // If we're already in a channel, or if we're joining one, terminate - // so we can rejoin with the new session data. - sessionTerminate(); - } - } - - return needsSwitch; -} - -void LLVivoxVoiceClient::joinSession(const sessionStatePtr_t &session) -{ - mNextAudioSession = session; - - if (mIsInChannel) - { - // If we're already in a channel, or if we're joining one, terminate - // so we can rejoin with the new session data. - sessionTerminate(); - } -} - -void LLVivoxVoiceClient::setNonSpatialChannel( - const std::string &uri, - const std::string &credentials) -{ - switchChannel(uri, false, false, false, credentials); -} - -bool LLVivoxVoiceClient::setSpatialChannel( - const std::string &uri, - const std::string &credentials) -{ - mSpatialSessionURI = uri; - mSpatialSessionCredentials = credentials; - mAreaVoiceDisabled = mSpatialSessionURI.empty(); - - LL_DEBUGS("Voice") << "got spatial channel uri: \"" << uri << "\"" << LL_ENDL; - - if((mIsInChannel && mAudioSession && !(mAudioSession->mIsSpatial)) || (mNextAudioSession && !(mNextAudioSession->mIsSpatial))) - { - // User is in a non-spatial chat or joining a non-spatial chat. Don't switch channels. - LL_INFOS("Voice") << "in non-spatial chat, not switching channels" << LL_ENDL; - return false; - } - else - { - return switchChannel(mSpatialSessionURI, true, false, false, mSpatialSessionCredentials); - } -} - -void LLVivoxVoiceClient::callUser(const LLUUID &uuid) -{ - std::string userURI = sipURIFromID(uuid); - - switchChannel(userURI, false, true, true); -} - -#if 0 -// Vivox text IMs are not in use. -LLVivoxVoiceClient::sessionStatePtr_t LLVivoxVoiceClient::startUserIMSession(const LLUUID &uuid) -{ - // Figure out if a session with the user already exists - sessionStatePtr_t session(findSession(uuid)); - if(!session) - { - // No session with user, need to start one. - std::string uri = sipURIFromID(uuid); - session = addSession(uri); - - llassert(session); - if (!session) - return session; - - session->mIsSpatial = false; - session->mReconnect = false; - session->mIsP2P = true; - session->mCallerID = uuid; - } - - if(session->mHandle.empty()) - { - // Session isn't active -- start it up. - sessionCreateSendMessage(session, false, false); - } - else - { - // Session is already active -- start up text. - sessionTextConnectSendMessage(session); - } - - return session; -} -#endif - -void LLVivoxVoiceClient::endUserIMSession(const LLUUID &uuid) -{ -#if 0 - // Vivox text IMs are not in use. - - // Figure out if a session with the user exists - sessionStatePtr_t session(findSession(uuid)); - if(session) - { - // found the session - if(!session->mHandle.empty()) - { - // sessionTextDisconnectSendMessage(session); // a SLim leftover, not used any more. - } - } - else - { - LL_DEBUGS("Voice") << "Session not found for participant ID " << uuid << LL_ENDL; - } -#endif -} -bool LLVivoxVoiceClient::isValidChannel(std::string &sessionHandle) -{ - return(findSession(sessionHandle) != NULL); - -} -bool LLVivoxVoiceClient::answerInvite(std::string &sessionHandle) -{ - // this is only ever used to answer incoming p2p call invites. - - sessionStatePtr_t session(findSession(sessionHandle)); - if(session) - { - session->mIsSpatial = false; - session->mReconnect = false; - session->mIsP2P = true; - - joinSession(session); - return true; - } - - return false; -} - -bool LLVivoxVoiceClient::isVoiceWorking() const -{ - - //Added stateSessionTerminated state to avoid problems with call in parcels with disabled voice (EXT-4758) - // Condition with joining spatial num was added to take into account possible problems with connection to voice - // server(EXT-4313). See bug descriptions and comments for MAX_NORMAL_JOINING_SPATIAL_NUM for more info. - return (mSpatialJoiningNum < MAX_NORMAL_JOINING_SPATIAL_NUM) && mIsProcessingChannels; -// return (mSpatialJoiningNum < MAX_NORMAL_JOINING_SPATIAL_NUM) && (stateLoggedIn <= mState) && (mState <= stateSessionTerminated); -} - -// Returns true if the indicated participant in the current audio session is really an SL avatar. -// Currently this will be false only for PSTN callers into group chats, and PSTN p2p calls. -bool LLVivoxVoiceClient::isParticipantAvatar(const LLUUID &id) -{ - bool result = true; - sessionStatePtr_t session(findSession(id)); - - if(session) - { - // this is a p2p session with the indicated caller, or the session with the specified UUID. - if(session->mSynthesizedCallerID) - result = false; - } - else - { - // Didn't find a matching session -- check the current audio session for a matching participant - if(mAudioSession) - { - participantStatePtr_t participant(findParticipantByID(id)); - if(participant) - { - result = participant->isAvatar(); - } - } - } - - return result; -} - -// Returns true if calling back the session URI after the session has closed is possible. -// Currently this will be false only for PSTN P2P calls. -bool LLVivoxVoiceClient::isSessionCallBackPossible(const LLUUID &session_id) -{ - bool result = true; - sessionStatePtr_t session(findSession(session_id)); - - if(session != NULL) - { - result = session->isCallBackPossible(); - } - - return result; -} - -// Returns true if the session can accept text IM's. -// Currently this will be false only for PSTN P2P calls. -bool LLVivoxVoiceClient::isSessionTextIMPossible(const LLUUID &session_id) -{ - bool result = true; - sessionStatePtr_t session(findSession(session_id)); - - if(session != NULL) - { - result = session->isTextIMPossible(); - } - - return result; -} - - -void LLVivoxVoiceClient::declineInvite(std::string &sessionHandle) -{ - sessionStatePtr_t session(findSession(sessionHandle)); - if(session) - { - sessionMediaDisconnectSendMessage(session); - } -} - -void LLVivoxVoiceClient::leaveNonSpatialChannel() -{ - LL_DEBUGS("Voice") << "Request to leave spacial channel." << LL_ENDL; - - // Make sure we don't rejoin the current session. - sessionStatePtr_t oldNextSession(mNextAudioSession); - mNextAudioSession.reset(); - - // Most likely this will still be the current session at this point, but check it anyway. - reapSession(oldNextSession); - - verifySessionState(); - - sessionTerminate(); -} - -std::string LLVivoxVoiceClient::getCurrentChannel() -{ - std::string result; - - if (mIsInChannel && !mSessionTerminateRequested) - { - result = getAudioSessionURI(); - } - - return result; -} - -bool LLVivoxVoiceClient::inProximalChannel() -{ - bool result = false; - - if (mIsInChannel && !mSessionTerminateRequested) - { - result = inSpatialChannel(); - } - - return result; -} - -std::string LLVivoxVoiceClient::sipURIFromID(const LLUUID &id) -{ - std::string result; - result = "sip:"; - result += nameFromID(id); - result += "@"; - result += mVoiceSIPURIHostName; - - return result; -} - -std::string LLVivoxVoiceClient::sipURIFromAvatar(LLVOAvatar *avatar) -{ - std::string result; - if(avatar) - { - result = "sip:"; - result += nameFromID(avatar->getID()); - result += "@"; - result += mVoiceSIPURIHostName; - } - - return result; -} - -std::string LLVivoxVoiceClient::nameFromAvatar(LLVOAvatar *avatar) -{ - std::string result; - if(avatar) - { - result = nameFromID(avatar->getID()); - } - return result; -} - -std::string LLVivoxVoiceClient::nameFromID(const LLUUID &uuid) -{ - std::string result; - - if (uuid.isNull()) { - //VIVOX, the uuid emtpy look for the mURIString and return that instead. - //result.assign(uuid.mURIStringName); - LLStringUtil::replaceChar(result, '_', ' '); - return result; - } - // Prepending this apparently prevents conflicts with reserved names inside the vivox code. - result = "x"; - - // Base64 encode and replace the pieces of base64 that are less compatible - // with e-mail local-parts. - // See RFC-4648 "Base 64 Encoding with URL and Filename Safe Alphabet" - result += LLBase64::encode(uuid.mData, UUID_BYTES); - LLStringUtil::replaceChar(result, '+', '-'); - LLStringUtil::replaceChar(result, '/', '_'); - - // If you need to transform a GUID to this form on the macOS command line, this will do so: - // echo -n x && (echo e669132a-6c43-4ee1-a78d-6c82fff59f32 |xxd -r -p |openssl base64|tr '/+' '_-') - - // The reverse transform can be done with: - // echo 'x5mkTKmxDTuGnjWyC__WfMg==' |cut -b 2- -|tr '_-' '/+' |openssl base64 -d|xxd -p - - return result; -} - -bool LLVivoxVoiceClient::IDFromName(const std::string inName, LLUUID &uuid) -{ - bool result = false; - - // SLIM SDK: The "name" may actually be a SIP URI such as: "sip:xFnPP04IpREWNkuw1cOXlhw==@bhr.vivox.com" - // If it is, convert to a bare name before doing the transform. - std::string name = nameFromsipURI(inName); - - // Doesn't look like a SIP URI, assume it's an actual name. - if(name.empty()) - name = inName; - - // This will only work if the name is of the proper form. - // As an example, the account name for Monroe Linden (UUID 1673cfd3-8229-4445-8d92-ec3570e5e587) is: - // "xFnPP04IpREWNkuw1cOXlhw==" - - if((name.size() == 25) && (name[0] == 'x') && (name[23] == '=') && (name[24] == '=')) - { - // The name appears to have the right form. - - // Reverse the transforms done by nameFromID - std::string temp = name; - LLStringUtil::replaceChar(temp, '-', '+'); - LLStringUtil::replaceChar(temp, '_', '/'); - - U8 rawuuid[UUID_BYTES + 1]; - int len = apr_base64_decode_binary(rawuuid, temp.c_str() + 1); - if(len == UUID_BYTES) - { - // The decode succeeded. Stuff the bits into the result's UUID - memcpy(uuid.mData, rawuuid, UUID_BYTES); - result = true; - } - } - - if(!result) - { - // VIVOX: not a standard account name, just copy the URI name mURIString field - // and hope for the best. bpj - uuid.setNull(); // VIVOX, set the uuid field to nulls - } - - return result; -} - -std::string LLVivoxVoiceClient::displayNameFromAvatar(LLVOAvatar *avatar) -{ - return avatar->getFullname(); -} - -std::string LLVivoxVoiceClient::sipURIFromName(std::string &name) -{ - std::string result; - result = "sip:"; - result += name; - result += "@"; - result += mVoiceSIPURIHostName; - -// LLStringUtil::toLower(result); - - return result; -} - -std::string LLVivoxVoiceClient::nameFromsipURI(const std::string &uri) -{ - std::string result; - - std::string::size_type sipOffset, atOffset; - sipOffset = uri.find("sip:"); - atOffset = uri.find("@"); - if((sipOffset != std::string::npos) && (atOffset != std::string::npos)) - { - result = uri.substr(sipOffset + 4, atOffset - (sipOffset + 4)); - } - - return result; -} - -bool LLVivoxVoiceClient::inSpatialChannel(void) -{ - bool result = false; - - if(mAudioSession) - { - result = mAudioSession->mIsSpatial; - } - - return result; -} - -std::string LLVivoxVoiceClient::getAudioSessionURI() -{ - std::string result; - - if(mAudioSession) - result = mAudioSession->mSIPURI; - - return result; -} - -std::string LLVivoxVoiceClient::getAudioSessionHandle() -{ - std::string result; - - if(mAudioSession) - result = mAudioSession->mHandle; - - return result; -} - - -///////////////////////////// -// Sending updates of current state - -void LLVivoxVoiceClient::enforceTether(void) -{ - LLVector3d tethered = mCameraRequestedPosition; - - // constrain 'tethered' to within 50m of mAvatarPosition. - { - F32 max_dist = 50.0f; - LLVector3d camera_offset = mCameraRequestedPosition - mAvatarPosition; - F32 camera_distance = (F32)camera_offset.magVec(); - if(camera_distance > max_dist) - { - tethered = mAvatarPosition + - (max_dist / camera_distance) * camera_offset; - } - } - - if(dist_vec_squared(mCameraPosition, tethered) > 0.01) - { - mCameraPosition = tethered; - mSpatialCoordsDirty = true; - } -} - -void LLVivoxVoiceClient::updatePosition(void) -{ - - LLViewerRegion *region = gAgent.getRegion(); - if(region && isAgentAvatarValid()) - { - LLMatrix3 rot; - LLVector3d pos; - LLQuaternion qrot; - - // TODO: If camera and avatar velocity are actually used by the voice system, we could compute them here... - // They're currently always set to zero. - - // Send the current camera position to the voice code - rot.setRows(LLViewerCamera::getInstance()->getAtAxis(), LLViewerCamera::getInstance()->getLeftAxis (), LLViewerCamera::getInstance()->getUpAxis()); - pos = gAgent.getRegion()->getPosGlobalFromRegion(LLViewerCamera::getInstance()->getOrigin()); - - LLVivoxVoiceClient::getInstance()->setCameraPosition( - pos, // position - LLVector3::zero, // velocity - rot); // rotation matrix - - // Send the current avatar position to the voice code - qrot = gAgentAvatarp->getRootJoint()->getWorldRotation(); - pos = gAgentAvatarp->getPositionGlobal(); - - // TODO: Can we get the head offset from outside the LLVOAvatar? - // pos += LLVector3d(mHeadOffset); - pos += LLVector3d(0.f, 0.f, 1.f); - - LLVivoxVoiceClient::getInstance()->setAvatarPosition( - pos, // position - LLVector3::zero, // velocity - qrot); // rotation matrix - } -} - -void LLVivoxVoiceClient::setCameraPosition(const LLVector3d &position, const LLVector3 &velocity, const LLMatrix3 &rot) -{ - mCameraRequestedPosition = position; - - if(mCameraVelocity != velocity) - { - mCameraVelocity = velocity; - mSpatialCoordsDirty = true; - } - - if(mCameraRot != rot) - { - mCameraRot = rot; - mSpatialCoordsDirty = true; - } -} - -void LLVivoxVoiceClient::setAvatarPosition(const LLVector3d &position, const LLVector3 &velocity, const LLQuaternion &rot) -{ - if(dist_vec_squared(mAvatarPosition, position) > 0.01) - { - mAvatarPosition = position; - mSpatialCoordsDirty = true; - } - - if(mAvatarVelocity != velocity) - { - mAvatarVelocity = velocity; - mSpatialCoordsDirty = true; - } - - // If the two rotations are not exactly equal test their dot product - // to get the cos of the angle between them. - // If it is too small, don't update. - F32 rot_cos_diff = llabs(dot(mAvatarRot, rot)); - if ((mAvatarRot != rot) && (rot_cos_diff < MINUSCULE_ANGLE_COS)) - { - mAvatarRot = rot; - mSpatialCoordsDirty = true; - } -} - -bool LLVivoxVoiceClient::channelFromRegion(LLViewerRegion *region, std::string &name) -{ - bool result = false; - - if(region) - { - name = region->getName(); - } - - if(!name.empty()) - result = true; - - return result; -} - -void LLVivoxVoiceClient::leaveChannel(void) -{ - if (mIsInChannel) - { - LL_DEBUGS("Voice") << "leaving channel for teleport/logout" << LL_ENDL; - mChannelName.clear(); - sessionTerminate(); - } -} - -void LLVivoxVoiceClient::setMuteMic(bool muted) -{ - if(mMuteMic != muted) - { - mMuteMic = muted; - mMuteMicDirty = true; - } -} - -void LLVivoxVoiceClient::setVoiceEnabled(bool enabled) -{ - LL_DEBUGS("Voice") - << "( " << (enabled ? "enabled" : "disabled") << " )" - << " was "<< (mVoiceEnabled ? "enabled" : "disabled") - << " coro "<< (mIsCoroutineActive ? "active" : "inactive") - << LL_ENDL; - - if (enabled != mVoiceEnabled) - { - // TODO: Refactor this so we don't call into LLVoiceChannel, but simply - // use the status observer - mVoiceEnabled = enabled; - LLVoiceClientStatusObserver::EStatusType status; - - if (enabled) - { - LL_DEBUGS("Voice") << "enabling" << LL_ENDL; - LLVoiceChannel::getCurrentVoiceChannel()->activate(); - status = LLVoiceClientStatusObserver::STATUS_VOICE_ENABLED; - - if (!mIsCoroutineActive) - { - LLCoros::instance().launch("LLVivoxVoiceClient::voiceControlCoro", - boost::bind(&LLVivoxVoiceClient::voiceControlCoro, LLVivoxVoiceClient::getInstance())); - } - else - { - LL_DEBUGS("Voice") << "coro should be active.. not launching" << LL_ENDL; - } - } - else - { - // Turning voice off looses your current channel -- this makes sure the UI isn't out of sync when you re-enable it. - LLVoiceChannel::getCurrentVoiceChannel()->deactivate(); - gAgent.setVoiceConnected(false); - status = LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED; - } - - notifyStatusObservers(status); - } - else - { - LL_DEBUGS("Voice") << " no-op" << LL_ENDL; - } -} - -bool LLVivoxVoiceClient::voiceEnabled() -{ - return gSavedSettings.getBOOL("EnableVoiceChat") && - !gSavedSettings.getBOOL("CmdLineDisableVoice") && - !gNonInteractive; -} - -void LLVivoxVoiceClient::setLipSyncEnabled(bool enabled) -{ - mLipSyncEnabled = enabled; -} - -bool LLVivoxVoiceClient::lipSyncEnabled() -{ - - if ( mVoiceEnabled ) - { - return mLipSyncEnabled; - } - else - { - return false; - } -} - - -void LLVivoxVoiceClient::setEarLocation(S32 loc) -{ - if(mEarLocation != loc) - { - LL_DEBUGS("Voice") << "Setting mEarLocation to " << loc << LL_ENDL; - - mEarLocation = loc; - mSpatialCoordsDirty = true; - } -} - -void LLVivoxVoiceClient::setVoiceVolume(F32 volume) -{ - int scaled_volume = scale_speaker_volume(volume); - - if(scaled_volume != mSpeakerVolume) - { - int min_volume = scale_speaker_volume(0); - if((scaled_volume == min_volume) || (mSpeakerVolume == min_volume)) - { - mSpeakerMuteDirty = true; - } - - mSpeakerVolume = scaled_volume; - mSpeakerVolumeDirty = true; - } -} - -void LLVivoxVoiceClient::setMicGain(F32 volume) -{ - int scaled_volume = scale_mic_volume(volume); - - if(scaled_volume != mMicVolume) - { - mMicVolume = scaled_volume; - mMicVolumeDirty = true; - } -} - -///////////////////////////// -// Accessors for data related to nearby speakers -bool LLVivoxVoiceClient::getVoiceEnabled(const LLUUID& id) -{ - bool result = false; - participantStatePtr_t participant(findParticipantByID(id)); - if(participant) - { - // I'm not sure what the semantics of this should be. - // For now, if we have any data about the user that came through the chat channel, assume they're voice-enabled. - result = true; - } - - return result; -} - -std::string LLVivoxVoiceClient::getDisplayName(const LLUUID& id) -{ - std::string result; - participantStatePtr_t participant(findParticipantByID(id)); - if(participant) - { - result = participant->mDisplayName; - } - - return result; -} - - - -bool LLVivoxVoiceClient::getIsSpeaking(const LLUUID& id) -{ - bool result = false; - - participantStatePtr_t participant(findParticipantByID(id)); - if(participant) - { - if (participant->mSpeakingTimeout.getElapsedTimeF32() > SPEAKING_TIMEOUT) - { - participant->mIsSpeaking = false; - } - result = participant->mIsSpeaking; - } - - return result; -} - -bool LLVivoxVoiceClient::getIsModeratorMuted(const LLUUID& id) -{ - bool result = false; - - participantStatePtr_t participant(findParticipantByID(id)); - if(participant) - { - result = participant->mIsModeratorMuted; - } - - return result; -} - -F32 LLVivoxVoiceClient::getCurrentPower(const LLUUID& id) -{ - F32 result = 0; - participantStatePtr_t participant(findParticipantByID(id)); - if(participant) - { - result = participant->mPower; - } - - return result; -} - - - -bool LLVivoxVoiceClient::getUsingPTT(const LLUUID& id) -{ - bool result = false; - - participantStatePtr_t participant(findParticipantByID(id)); - if(participant) - { - // I'm not sure what the semantics of this should be. - // Does "using PTT" mean they're configured with a push-to-talk button? - // For now, we know there's no PTT mechanism in place, so nobody is using it. - } - - return result; -} - -bool LLVivoxVoiceClient::getOnMuteList(const LLUUID& id) -{ - bool result = false; - - participantStatePtr_t participant(findParticipantByID(id)); - if(participant) - { - result = participant->mOnMuteList; - } - - return result; -} - -// External accessors. -F32 LLVivoxVoiceClient::getUserVolume(const LLUUID& id) -{ - // Minimum volume will be returned for users with voice disabled - F32 result = LLVoiceClient::VOLUME_MIN; - - participantStatePtr_t participant(findParticipantByID(id)); - if(participant) - { - result = participant->mVolume; - - // Enable this when debugging voice slider issues. It's way to spammy even for debug-level logging. - // LL_DEBUGS("Voice") << "mVolume = " << result << " for " << id << LL_ENDL; - } - - return result; -} - -void LLVivoxVoiceClient::setUserVolume(const LLUUID& id, F32 volume) -{ - if(mAudioSession) - { - participantStatePtr_t participant(findParticipantByID(id)); - if (participant && !participant->mIsSelf) - { - if (!is_approx_equal(volume, LLVoiceClient::VOLUME_DEFAULT)) - { - // Store this volume setting for future sessions if it has been - // changed from the default - LLSpeakerVolumeStorage::getInstance()->storeSpeakerVolume(id, volume); - } - else - { - // Remove stored volume setting if it is returned to the default - LLSpeakerVolumeStorage::getInstance()->removeSpeakerVolume(id); - } - - participant->mVolume = llclamp(volume, LLVoiceClient::VOLUME_MIN, LLVoiceClient::VOLUME_MAX); - participant->mVolumeDirty = true; - mAudioSession->mVolumeDirty = true; - } - } -} - -std::string LLVivoxVoiceClient::getGroupID(const LLUUID& id) -{ - std::string result; - - participantStatePtr_t participant(findParticipantByID(id)); - if(participant) - { - result = participant->mGroupID; - } - - return result; -} - -bool LLVivoxVoiceClient::getAreaVoiceDisabled() -{ - return mAreaVoiceDisabled; -} - -void LLVivoxVoiceClient::recordingLoopStart(int seconds, int deltaFramesPerControlFrame) -{ -// LL_DEBUGS("Voice") << "sending SessionGroup.ControlRecording (Start)" << LL_ENDL; - - if(!mMainSessionGroupHandle.empty()) - { - std::ostringstream stream; - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.ControlRecording.1\">" - << "<SessionGroupHandle>" << mMainSessionGroupHandle << "</SessionGroupHandle>" - << "<RecordingControlType>Start</RecordingControlType>" - << "<DeltaFramesPerControlFrame>" << deltaFramesPerControlFrame << "</DeltaFramesPerControlFrame>" - << "<Filename>" << "" << "</Filename>" - << "<EnableAudioRecordingEvents>false</EnableAudioRecordingEvents>" - << "<LoopModeDurationSeconds>" << seconds << "</LoopModeDurationSeconds>" - << "</Request>\n\n\n"; - - - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::recordingLoopSave(const std::string& filename) -{ -// LL_DEBUGS("Voice") << "sending SessionGroup.ControlRecording (Flush)" << LL_ENDL; - - if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty()) - { - std::ostringstream stream; - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.ControlRecording.1\">" - << "<SessionGroupHandle>" << mMainSessionGroupHandle << "</SessionGroupHandle>" - << "<RecordingControlType>Flush</RecordingControlType>" - << "<Filename>" << filename << "</Filename>" - << "</Request>\n\n\n"; - - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::recordingStop() -{ -// LL_DEBUGS("Voice") << "sending SessionGroup.ControlRecording (Stop)" << LL_ENDL; - - if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty()) - { - std::ostringstream stream; - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.ControlRecording.1\">" - << "<SessionGroupHandle>" << mMainSessionGroupHandle << "</SessionGroupHandle>" - << "<RecordingControlType>Stop</RecordingControlType>" - << "</Request>\n\n\n"; - - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::filePlaybackStart(const std::string& filename) -{ -// LL_DEBUGS("Voice") << "sending SessionGroup.ControlPlayback (Start)" << LL_ENDL; - - if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty()) - { - std::ostringstream stream; - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.ControlPlayback.1\">" - << "<SessionGroupHandle>" << mMainSessionGroupHandle << "</SessionGroupHandle>" - << "<RecordingControlType>Start</RecordingControlType>" - << "<Filename>" << filename << "</Filename>" - << "</Request>\n\n\n"; - - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::filePlaybackStop() -{ -// LL_DEBUGS("Voice") << "sending SessionGroup.ControlPlayback (Stop)" << LL_ENDL; - - if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty()) - { - std::ostringstream stream; - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.ControlPlayback.1\">" - << "<SessionGroupHandle>" << mMainSessionGroupHandle << "</SessionGroupHandle>" - << "<RecordingControlType>Stop</RecordingControlType>" - << "</Request>\n\n\n"; - - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::filePlaybackSetPaused(bool paused) -{ - // TODO: Implement once Vivox gives me a sample -} - -void LLVivoxVoiceClient::filePlaybackSetMode(bool vox, float speed) -{ - // TODO: Implement once Vivox gives me a sample -} - -//------------------------------------------------------------------------ -std::set<LLVivoxVoiceClient::sessionState::wptr_t, std::owner_less<LLVivoxVoiceClient::sessionState::wptr_t>> LLVivoxVoiceClient::sessionState::mSession; - - -LLVivoxVoiceClient::sessionState::sessionState() : - mErrorStatusCode(0), - mMediaStreamState(streamStateUnknown), - mCreateInProgress(false), - mMediaConnectInProgress(false), - mVoiceInvitePending(false), - mTextInvitePending(false), - mSynthesizedCallerID(false), - mIsChannel(false), - mIsSpatial(false), - mIsP2P(false), - mIncoming(false), - mVoiceActive(false), - mReconnect(false), - mVolumeDirty(false), - mMuteDirty(false), - mParticipantsChanged(false) -{ -} - -/*static*/ -LLVivoxVoiceClient::sessionState::ptr_t LLVivoxVoiceClient::sessionState::createSession() -{ - sessionState::ptr_t ptr(new sessionState()); - - std::pair<std::set<wptr_t>::iterator, bool> result = mSession.insert(ptr); - - if (result.second) - ptr->mMyIterator = result.first; - - return ptr; -} - -LLVivoxVoiceClient::sessionState::~sessionState() -{ - LL_INFOS("Voice") << "Destroying session handle=" << mHandle << " SIP=" << mSIPURI << LL_ENDL; - if (mMyIterator != mSession.end()) - mSession.erase(mMyIterator); - - removeAllParticipants(); -} - -bool LLVivoxVoiceClient::sessionState::isCallBackPossible() -{ - // This may change to be explicitly specified by vivox in the future... - // Currently, only PSTN P2P calls cannot be returned. - // Conveniently, this is also the only case where we synthesize a caller UUID. - return !mSynthesizedCallerID; -} - -bool LLVivoxVoiceClient::sessionState::isTextIMPossible() -{ - // This may change to be explicitly specified by vivox in the future... - return !mSynthesizedCallerID; -} - - -/*static*/ -LLVivoxVoiceClient::sessionState::ptr_t LLVivoxVoiceClient::sessionState::matchSessionByHandle(const std::string &handle) -{ - sessionStatePtr_t result; - - // *TODO: My kingdom for a lambda! - std::set<wptr_t>::iterator it = std::find_if(mSession.begin(), mSession.end(), boost::bind(testByHandle, _1, handle)); - - if (it != mSession.end()) - result = (*it).lock(); - - return result; -} - -/*static*/ -LLVivoxVoiceClient::sessionState::ptr_t LLVivoxVoiceClient::sessionState::matchCreatingSessionByURI(const std::string &uri) -{ - sessionStatePtr_t result; - - // *TODO: My kingdom for a lambda! - std::set<wptr_t>::iterator it = std::find_if(mSession.begin(), mSession.end(), boost::bind(testByCreatingURI, _1, uri)); - - if (it != mSession.end()) - result = (*it).lock(); - - return result; -} - -/*static*/ -LLVivoxVoiceClient::sessionState::ptr_t LLVivoxVoiceClient::sessionState::matchSessionByURI(const std::string &uri) -{ - sessionStatePtr_t result; - - // *TODO: My kingdom for a lambda! - std::set<wptr_t>::iterator it = std::find_if(mSession.begin(), mSession.end(), boost::bind(testBySIPOrAlterateURI, _1, uri)); - - if (it != mSession.end()) - result = (*it).lock(); - - return result; -} - -/*static*/ -LLVivoxVoiceClient::sessionState::ptr_t LLVivoxVoiceClient::sessionState::matchSessionByParticipant(const LLUUID &participant_id) -{ - sessionStatePtr_t result; - - // *TODO: My kingdom for a lambda! - std::set<wptr_t>::iterator it = std::find_if(mSession.begin(), mSession.end(), boost::bind(testByCallerId, _1, participant_id)); - - if (it != mSession.end()) - result = (*it).lock(); - - return result; -} - -void LLVivoxVoiceClient::sessionState::for_each(sessionFunc_t func) -{ - std::for_each(mSession.begin(), mSession.end(), boost::bind(for_eachPredicate, _1, func)); -} - -// simple test predicates. -// *TODO: These should be made into lambdas when we can pull the trigger on newer C++ features. -bool LLVivoxVoiceClient::sessionState::testByHandle(const LLVivoxVoiceClient::sessionState::wptr_t &a, std::string handle) -{ - ptr_t aLock(a.lock()); - - return aLock ? aLock->mHandle == handle : false; -} - -bool LLVivoxVoiceClient::sessionState::testByCreatingURI(const LLVivoxVoiceClient::sessionState::wptr_t &a, std::string uri) -{ - ptr_t aLock(a.lock()); - - return aLock ? (aLock->mCreateInProgress && (aLock->mSIPURI == uri)) : false; -} - -bool LLVivoxVoiceClient::sessionState::testBySIPOrAlterateURI(const LLVivoxVoiceClient::sessionState::wptr_t &a, std::string uri) -{ - ptr_t aLock(a.lock()); - - return aLock ? ((aLock->mSIPURI == uri) || (aLock->mAlternateSIPURI == uri)) : false; -} - - -bool LLVivoxVoiceClient::sessionState::testByCallerId(const LLVivoxVoiceClient::sessionState::wptr_t &a, LLUUID participantId) -{ - ptr_t aLock(a.lock()); - - return aLock ? ((aLock->mCallerID == participantId) || (aLock->mIMSessionID == participantId)) : false; -} - -/*static*/ -void LLVivoxVoiceClient::sessionState::for_eachPredicate(const LLVivoxVoiceClient::sessionState::wptr_t &a, sessionFunc_t func) -{ - ptr_t aLock(a.lock()); - - if (aLock) - func(aLock); - else - { - LL_WARNS("Voice") << "Stale handle in session map!" << LL_ENDL; - } -} - - - -LLVivoxVoiceClient::sessionStatePtr_t LLVivoxVoiceClient::findSession(const std::string &handle) -{ - sessionStatePtr_t result; - sessionMap::iterator iter = mSessionsByHandle.find(handle); - if(iter != mSessionsByHandle.end()) - { - result = iter->second; - } - - return result; -} - -LLVivoxVoiceClient::sessionStatePtr_t LLVivoxVoiceClient::findSessionBeingCreatedByURI(const std::string &uri) -{ - sessionStatePtr_t result = sessionState::matchCreatingSessionByURI(uri); - - return result; -} - -LLVivoxVoiceClient::sessionStatePtr_t LLVivoxVoiceClient::findSession(const LLUUID &participant_id) -{ - sessionStatePtr_t result = sessionState::matchSessionByParticipant(participant_id); - - return result; -} - -LLVivoxVoiceClient::sessionStatePtr_t LLVivoxVoiceClient::addSession(const std::string &uri, const std::string &handle) -{ - sessionStatePtr_t result; - - if(handle.empty()) - { - // No handle supplied. - // Check whether there's already a session with this URI - result = sessionState::matchSessionByURI(uri); - } - else // (!handle.empty()) - { - // Check for an existing session with this handle - sessionMap::iterator iter = mSessionsByHandle.find(handle); - - if(iter != mSessionsByHandle.end()) - { - result = iter->second; - } - } - - if(!result) - { - // No existing session found. - - LL_DEBUGS("Voice") << "adding new session: handle \"" << handle << "\" URI " << uri << LL_ENDL; - result = sessionState::createSession(); - result->mSIPURI = uri; - result->mHandle = handle; - - if (LLVoiceClient::instance().getVoiceEffectEnabled()) - { - result->mVoiceFontID = LLVoiceClient::instance().getVoiceEffectDefault(); - } - - if(!result->mHandle.empty()) - { - // *TODO: Rider: This concerns me. There is a path (via switchChannel) where - // we do not track the session. In theory this means that we could end up with - // a mAuidoSession that does not match the session tracked in mSessionsByHandle - mSessionsByHandle.insert(sessionMap::value_type(result->mHandle, result)); - } - } - else - { - // Found an existing session - - if(uri != result->mSIPURI) - { - // TODO: Should this be an internal error? - LL_DEBUGS("Voice") << "changing uri from " << result->mSIPURI << " to " << uri << LL_ENDL; - setSessionURI(result, uri); - } - - if(handle != result->mHandle) - { - if(handle.empty()) - { - // There's at least one race condition where where addSession was clearing an existing session handle, which caused things to break. - LL_DEBUGS("Voice") << "NOT clearing handle " << result->mHandle << LL_ENDL; - } - else - { - // TODO: Should this be an internal error? - LL_DEBUGS("Voice") << "changing handle from " << result->mHandle << " to " << handle << LL_ENDL; - setSessionHandle(result, handle); - } - } - - LL_DEBUGS("Voice") << "returning existing session: handle " << handle << " URI " << uri << LL_ENDL; - } - - verifySessionState(); - - return result; -} - -void LLVivoxVoiceClient::clearSessionHandle(const sessionStatePtr_t &session) -{ - if (session) - { - if (!session->mHandle.empty()) - { - sessionMap::iterator iter = mSessionsByHandle.find(session->mHandle); - if (iter != mSessionsByHandle.end()) - { - mSessionsByHandle.erase(iter); - } - } - else - { - LL_WARNS("Voice") << "Session has empty handle!" << LL_ENDL; - } - } - else - { - LL_WARNS("Voice") << "Attempt to clear NULL session!" << LL_ENDL; - } - -} - -void LLVivoxVoiceClient::setSessionHandle(const sessionStatePtr_t &session, const std::string &handle) -{ - // Have to remove the session from the handle-indexed map before changing the handle, or things will break badly. - - if(!session->mHandle.empty()) - { - // Remove session from the map if it should have been there. - sessionMap::iterator iter = mSessionsByHandle.find(session->mHandle); - if(iter != mSessionsByHandle.end()) - { - if(iter->second != session) - { - LL_WARNS("Voice") << "Internal error: session mismatch! Session may have been duplicated. Removing version in map." << LL_ENDL; - } - - mSessionsByHandle.erase(iter); - } - else - { - LL_WARNS("Voice") << "Attempt to remove session with handle " << session->mHandle << " not found in map!" << LL_ENDL; - } - } - - session->mHandle = handle; - - if(!handle.empty()) - { - mSessionsByHandle.insert(sessionMap::value_type(session->mHandle, session)); - } - - verifySessionState(); -} - -void LLVivoxVoiceClient::setSessionURI(const sessionStatePtr_t &session, const std::string &uri) -{ - // There used to be a map of session URIs to sessions, which made this complex.... - session->mSIPURI = uri; - - verifySessionState(); -} - -void LLVivoxVoiceClient::deleteSession(const sessionStatePtr_t &session) -{ - // Remove the session from the handle map - if(!session->mHandle.empty()) - { - sessionMap::iterator iter = mSessionsByHandle.find(session->mHandle); - if(iter != mSessionsByHandle.end()) - { - if(iter->second != session) - { - LL_WARNS("Voice") << "Internal error: session mismatch, removing session in map." << LL_ENDL; - } - mSessionsByHandle.erase(iter); - } - } - - // At this point, the session should be unhooked from all lists and all state should be consistent. - verifySessionState(); - - // If this is the current audio session, clean up the pointer which will soon be dangling. - if(mAudioSession == session) - { - mAudioSession.reset(); - mAudioSessionChanged = true; - } - - // ditto for the next audio session - if(mNextAudioSession == session) - { - mNextAudioSession.reset(); - } - -} - -void LLVivoxVoiceClient::deleteAllSessions() -{ - LL_DEBUGS("Voice") << LL_ENDL; - - while (!mSessionsByHandle.empty()) - { - const sessionStatePtr_t session = mSessionsByHandle.begin()->second; - deleteSession(session); - } - -} - -void LLVivoxVoiceClient::verifySessionState(void) -{ - LL_DEBUGS("Voice") << "Sessions in handle map=" << mSessionsByHandle.size() << LL_ENDL; - sessionState::VerifySessions(); -} - -void LLVivoxVoiceClient::addObserver(LLVoiceClientParticipantObserver* observer) -{ - mParticipantObservers.insert(observer); -} - -void LLVivoxVoiceClient::removeObserver(LLVoiceClientParticipantObserver* observer) -{ - mParticipantObservers.erase(observer); -} - -void LLVivoxVoiceClient::notifyParticipantObservers() -{ - for (observer_set_t::iterator it = mParticipantObservers.begin(); - it != mParticipantObservers.end(); - ) - { - LLVoiceClientParticipantObserver* observer = *it; - observer->onParticipantsChanged(); - // In case onParticipantsChanged() deleted an entry. - it = mParticipantObservers.upper_bound(observer); - } -} - -void LLVivoxVoiceClient::addObserver(LLVoiceClientStatusObserver* observer) -{ - mStatusObservers.insert(observer); -} - -void LLVivoxVoiceClient::removeObserver(LLVoiceClientStatusObserver* observer) -{ - mStatusObservers.erase(observer); -} - -void LLVivoxVoiceClient::notifyStatusObservers(LLVoiceClientStatusObserver::EStatusType status) -{ - LL_DEBUGS("Voice") << "( " << LLVoiceClientStatusObserver::status2string(status) << " )" - << " mAudioSession=" << mAudioSession - << LL_ENDL; - - if(mAudioSession) - { - if(status == LLVoiceClientStatusObserver::ERROR_UNKNOWN) - { - switch(mAudioSession->mErrorStatusCode) - { - case 20713: status = LLVoiceClientStatusObserver::ERROR_CHANNEL_FULL; break; - case 20714: status = LLVoiceClientStatusObserver::ERROR_CHANNEL_LOCKED; break; - case 20715: - //invalid channel, we may be using a set of poorly cached - //info - status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE; - break; - case 1009: - //invalid username and password - status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE; - break; - } - - // Reset the error code to make sure it won't be reused later by accident. - mAudioSession->mErrorStatusCode = 0; - } - else if(status == LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL) - { - switch(mAudioSession->mErrorStatusCode) - { - case HTTP_NOT_FOUND: // NOT_FOUND - // *TODO: Should this be 503? - case 480: // TEMPORARILY_UNAVAILABLE - case HTTP_REQUEST_TIME_OUT: // REQUEST_TIMEOUT - // call failed because other user was not available - // treat this as an error case - status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE; - - // Reset the error code to make sure it won't be reused later by accident. - mAudioSession->mErrorStatusCode = 0; - break; - } - } - } - - LL_DEBUGS("Voice") - << " " << LLVoiceClientStatusObserver::status2string(status) - << ", session URI " << getAudioSessionURI() - << ", proximal is " << inSpatialChannel() - << LL_ENDL; - - for (status_observer_set_t::iterator it = mStatusObservers.begin(); - it != mStatusObservers.end(); - ) - { - LLVoiceClientStatusObserver* observer = *it; - observer->onChange(status, getAudioSessionURI(), inSpatialChannel()); - // In case onError() deleted an entry. - it = mStatusObservers.upper_bound(observer); - } - - // skipped to avoid speak button blinking - if ( status != LLVoiceClientStatusObserver::STATUS_JOINING - && status != LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL - && status != LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED) - { - bool voice_status = LLVoiceClient::getInstance()->voiceEnabled() && LLVoiceClient::getInstance()->isVoiceWorking(); - - gAgent.setVoiceConnected(voice_status); - - if (voice_status) - { - LLFirstUse::speak(true); - } - } -} - -void LLVivoxVoiceClient::addObserver(LLFriendObserver* observer) -{ - mFriendObservers.insert(observer); -} - -void LLVivoxVoiceClient::removeObserver(LLFriendObserver* observer) -{ - mFriendObservers.erase(observer); -} - -void LLVivoxVoiceClient::notifyFriendObservers() -{ - for (friend_observer_set_t::iterator it = mFriendObservers.begin(); - it != mFriendObservers.end(); - ) - { - LLFriendObserver* observer = *it; - it++; - // The only friend-related thing we notify on is online/offline transitions. - observer->changed(LLFriendObserver::ONLINE); - } -} - -void LLVivoxVoiceClient::lookupName(const LLUUID &id) -{ - if (mAvatarNameCacheConnection.connected()) - { - mAvatarNameCacheConnection.disconnect(); - } - mAvatarNameCacheConnection = LLAvatarNameCache::get(id, boost::bind(&LLVivoxVoiceClient::onAvatarNameCache, this, _1, _2)); -} - -void LLVivoxVoiceClient::onAvatarNameCache(const LLUUID& agent_id, - const LLAvatarName& av_name) -{ - mAvatarNameCacheConnection.disconnect(); - std::string display_name = av_name.getDisplayName(); - avatarNameResolved(agent_id, display_name); -} - -void LLVivoxVoiceClient::predAvatarNameResolution(const LLVivoxVoiceClient::sessionStatePtr_t &session, LLUUID id, std::string name) -{ - participantStatePtr_t participant(session->findParticipantByID(id)); - if (participant) - { - // Found -- fill in the name - participant->mAccountName = name; - // and post a "participants updated" message to listeners later. - session->mParticipantsChanged = true; - } - - // Check whether this is a p2p session whose caller name just resolved - if (session->mCallerID == id) - { - // this session's "caller ID" just resolved. Fill in the name. - session->mName = name; - if (session->mTextInvitePending) - { - session->mTextInvitePending = false; - - // We don't need to call LLIMMgr::getInstance()->addP2PSession() here. The first incoming message will create the panel. - } - if (session->mVoiceInvitePending) - { - session->mVoiceInvitePending = false; - - LLIMMgr::getInstance()->inviteToSession( - session->mIMSessionID, - session->mName, - session->mCallerID, - session->mName, - IM_SESSION_P2P_INVITE, - LLIMMgr::INVITATION_TYPE_VOICE, - session->mHandle, - session->mSIPURI); - } - - } -} - -void LLVivoxVoiceClient::avatarNameResolved(const LLUUID &id, const std::string &name) -{ - sessionState::for_each(boost::bind(predAvatarNameResolution, _1, id, name)); -} - -bool LLVivoxVoiceClient::setVoiceEffect(const LLUUID& id) -{ - if (!mAudioSession) - { - return false; - } - - if (!id.isNull()) - { - if (mVoiceFontMap.empty()) - { - LL_DEBUGS("Voice") << "Voice fonts not available." << LL_ENDL; - return false; - } - else if (mVoiceFontMap.find(id) == mVoiceFontMap.end()) - { - LL_DEBUGS("Voice") << "Invalid voice font " << id << LL_ENDL; - return false; - } - } - - // *TODO: Check for expired fonts? - mAudioSession->mVoiceFontID = id; - - // *TODO: Separate voice font defaults for spatial chat and IM? - gSavedPerAccountSettings.setString("VoiceEffectDefault", id.asString()); - - sessionSetVoiceFontSendMessage(mAudioSession); - notifyVoiceFontObservers(); - - return true; -} - -const LLUUID LLVivoxVoiceClient::getVoiceEffect() -{ - return mAudioSession ? mAudioSession->mVoiceFontID : LLUUID::null; -} - -LLSD LLVivoxVoiceClient::getVoiceEffectProperties(const LLUUID& id) -{ - LLSD sd; - - voice_font_map_t::iterator iter = mVoiceFontMap.find(id); - if (iter != mVoiceFontMap.end()) - { - sd["template_only"] = false; - } - else - { - // Voice effect is not in the voice font map, see if there is a template - iter = mVoiceFontTemplateMap.find(id); - if (iter == mVoiceFontTemplateMap.end()) - { - LL_WARNS("Voice") << "Voice effect " << id << "not found." << LL_ENDL; - return sd; - } - sd["template_only"] = true; - } - - voiceFontEntry *font = iter->second; - sd["name"] = font->mName; - sd["expiry_date"] = font->mExpirationDate; - sd["is_new"] = font->mIsNew; - - return sd; -} - -LLVivoxVoiceClient::voiceFontEntry::voiceFontEntry(LLUUID& id) : - mID(id), - mFontIndex(0), - mFontType(VOICE_FONT_TYPE_NONE), - mFontStatus(VOICE_FONT_STATUS_NONE), - mIsNew(false) -{ - mExpiryTimer.stop(); - mExpiryWarningTimer.stop(); -} - -LLVivoxVoiceClient::voiceFontEntry::~voiceFontEntry() -{ -} - -void LLVivoxVoiceClient::refreshVoiceEffectLists(bool clear_lists) -{ - if (clear_lists) - { - mVoiceFontsReceived = false; - deleteAllVoiceFonts(); - deleteVoiceFontTemplates(); - } - - accountGetSessionFontsSendMessage(); - accountGetTemplateFontsSendMessage(); -} - -const voice_effect_list_t& LLVivoxVoiceClient::getVoiceEffectList() const -{ - return mVoiceFontList; -} - -const voice_effect_list_t& LLVivoxVoiceClient::getVoiceEffectTemplateList() const -{ - return mVoiceFontTemplateList; -} - -void LLVivoxVoiceClient::addVoiceFont(const S32 font_index, - const std::string &name, - const std::string &description, - const LLDate &expiration_date, - bool has_expired, - const S32 font_type, - const S32 font_status, - const bool template_font) -{ - // Vivox SessionFontIDs are not guaranteed to remain the same between - // sessions or grids so use a UUID for the name. - - // If received name is not a UUID, fudge one by hashing the name and type. - LLUUID font_id; - if (LLUUID::validate(name)) - { - font_id = LLUUID(name); - } - else - { - font_id.generate(STRINGIZE(font_type << ":" << name)); - } - - voiceFontEntry *font = NULL; - - voice_font_map_t& font_map = template_font ? mVoiceFontTemplateMap : mVoiceFontMap; - voice_effect_list_t& font_list = template_font ? mVoiceFontTemplateList : mVoiceFontList; - - // Check whether we've seen this font before. - voice_font_map_t::iterator iter = font_map.find(font_id); - bool new_font = (iter == font_map.end()); - - // Override the has_expired flag if we have passed the expiration_date as a double check. - if (expiration_date.secondsSinceEpoch() < (LLDate::now().secondsSinceEpoch() + VOICE_FONT_EXPIRY_INTERVAL)) - { - has_expired = true; - } - - if (has_expired) - { - LL_DEBUGS("VoiceFont") << "Expired " << (template_font ? "Template " : "") - << expiration_date.asString() << " " << font_id - << " (" << font_index << ") " << name << LL_ENDL; - - // Remove existing session fonts that have expired since we last saw them. - if (!new_font && !template_font) - { - deleteVoiceFont(font_id); - } - return; - } - - if (new_font) - { - // If it is a new font create a new entry. - font = new voiceFontEntry(font_id); - } - else - { - // Not a new font, update the existing entry - font = iter->second; - } - - if (font) - { - font->mFontIndex = font_index; - // Use the description for the human readable name if available, as the - // "name" may be a UUID. - font->mName = description.empty() ? name : description; - font->mFontType = font_type; - font->mFontStatus = font_status; - - // If the font is new or the expiration date has changed the expiry timers need updating. - if (!template_font && (new_font || font->mExpirationDate != expiration_date)) - { - font->mExpirationDate = expiration_date; - - // Set the expiry timer to trigger a notification when the voice font can no longer be used. - font->mExpiryTimer.start(); - font->mExpiryTimer.setExpiryAt(expiration_date.secondsSinceEpoch() - VOICE_FONT_EXPIRY_INTERVAL); - - // Set the warning timer to some interval before actual expiry. - S32 warning_time = gSavedSettings.getS32("VoiceEffectExpiryWarningTime"); - if (warning_time != 0) - { - font->mExpiryWarningTimer.start(); - F64 expiry_time = (expiration_date.secondsSinceEpoch() - (F64)warning_time); - font->mExpiryWarningTimer.setExpiryAt(expiry_time - VOICE_FONT_EXPIRY_INTERVAL); - } - else - { - // Disable the warning timer. - font->mExpiryWarningTimer.stop(); - } - - // Only flag new session fonts after the first time we have fetched the list. - if (mVoiceFontsReceived) - { - font->mIsNew = true; - mVoiceFontsNew = true; - } - } - - LL_DEBUGS("VoiceFont") << (template_font ? "Template " : "") - << font->mExpirationDate.asString() << " " << font->mID - << " (" << font->mFontIndex << ") " << name << LL_ENDL; - - if (new_font) - { - font_map.insert(voice_font_map_t::value_type(font->mID, font)); - font_list.insert(voice_effect_list_t::value_type(font->mName, font->mID)); - } - - mVoiceFontListDirty = true; - - // Debugging stuff - - if (font_type < VOICE_FONT_TYPE_NONE || font_type >= VOICE_FONT_TYPE_UNKNOWN) - { - LL_WARNS("VoiceFont") << "Unknown voice font type: " << font_type << LL_ENDL; - } - if (font_status < VOICE_FONT_STATUS_NONE || font_status >= VOICE_FONT_STATUS_UNKNOWN) - { - LL_WARNS("VoiceFont") << "Unknown voice font status: " << font_status << LL_ENDL; - } - } -} - -void LLVivoxVoiceClient::expireVoiceFonts() -{ - // *TODO: If we are selling voice fonts in packs, there are probably - // going to be a number of fonts with the same expiration time, so would - // be more efficient to just keep a list of expiration times rather - // than checking each font individually. - - bool have_expired = false; - bool will_expire = false; - bool expired_in_use = false; - - LLUUID current_effect = LLVoiceClient::instance().getVoiceEffectDefault(); - - voice_font_map_t::iterator iter; - for (iter = mVoiceFontMap.begin(); iter != mVoiceFontMap.end(); ++iter) - { - voiceFontEntry* voice_font = iter->second; - LLFrameTimer& expiry_timer = voice_font->mExpiryTimer; - LLFrameTimer& warning_timer = voice_font->mExpiryWarningTimer; - - // Check for expired voice fonts - if (expiry_timer.getStarted() && expiry_timer.hasExpired()) - { - // Check whether it is the active voice font - if (voice_font->mID == current_effect) - { - // Reset to no voice effect. - setVoiceEffect(LLUUID::null); - expired_in_use = true; - } - - LL_DEBUGS("Voice") << "Voice Font " << voice_font->mName << " has expired." << LL_ENDL; - deleteVoiceFont(voice_font->mID); - have_expired = true; - } - - // Check for voice fonts that will expire in less that the warning time - if (warning_timer.getStarted() && warning_timer.hasExpired()) - { - LL_DEBUGS("VoiceFont") << "Voice Font " << voice_font->mName << " will expire soon." << LL_ENDL; - will_expire = true; - warning_timer.stop(); - } - } - - LLSD args; - args["URL"] = LLTrans::getString("voice_morphing_url"); - args["PREMIUM_URL"] = LLTrans::getString("premium_voice_morphing_url"); - - // Give a notification if any voice fonts have expired. - if (have_expired) - { - if (expired_in_use) - { - LLNotificationsUtil::add("VoiceEffectsExpiredInUse", args); - } - else - { - LLNotificationsUtil::add("VoiceEffectsExpired", args); - } - - // Refresh voice font lists in the UI. - notifyVoiceFontObservers(); - } - - // Give a warning notification if any voice fonts are due to expire. - if (will_expire) - { - S32Seconds seconds(gSavedSettings.getS32("VoiceEffectExpiryWarningTime")); - args["INTERVAL"] = llformat("%d", LLUnit<S32, LLUnits::Days>(seconds).value()); - - LLNotificationsUtil::add("VoiceEffectsWillExpire", args); - } -} - -void LLVivoxVoiceClient::deleteVoiceFont(const LLUUID& id) -{ - // Remove the entry from the voice font list. - voice_effect_list_t::iterator list_iter = mVoiceFontList.begin(); - while (list_iter != mVoiceFontList.end()) - { - if (list_iter->second == id) - { - LL_DEBUGS("VoiceFont") << "Removing " << id << " from the voice font list." << LL_ENDL; - list_iter = mVoiceFontList.erase(list_iter); - mVoiceFontListDirty = true; - } - else - { - ++list_iter; - } - } - - // Find the entry in the voice font map and erase its data. - voice_font_map_t::iterator map_iter = mVoiceFontMap.find(id); - if (map_iter != mVoiceFontMap.end()) - { - delete map_iter->second; - } - - // Remove the entry from the voice font map. - mVoiceFontMap.erase(map_iter); -} - -void LLVivoxVoiceClient::deleteAllVoiceFonts() -{ - mVoiceFontList.clear(); - - voice_font_map_t::iterator iter; - for (iter = mVoiceFontMap.begin(); iter != mVoiceFontMap.end(); ++iter) - { - delete iter->second; - } - mVoiceFontMap.clear(); -} - -void LLVivoxVoiceClient::deleteVoiceFontTemplates() -{ - mVoiceFontTemplateList.clear(); - - voice_font_map_t::iterator iter; - for (iter = mVoiceFontTemplateMap.begin(); iter != mVoiceFontTemplateMap.end(); ++iter) - { - delete iter->second; - } - mVoiceFontTemplateMap.clear(); -} - -S32 LLVivoxVoiceClient::getVoiceFontIndex(const LLUUID& id) const -{ - S32 result = 0; - if (!id.isNull()) - { - voice_font_map_t::const_iterator it = mVoiceFontMap.find(id); - if (it != mVoiceFontMap.end()) - { - result = it->second->mFontIndex; - } - else - { - LL_WARNS("VoiceFont") << "Selected voice font " << id << " is not available." << LL_ENDL; - } - } - return result; -} - -S32 LLVivoxVoiceClient::getVoiceFontTemplateIndex(const LLUUID& id) const -{ - S32 result = 0; - if (!id.isNull()) - { - voice_font_map_t::const_iterator it = mVoiceFontTemplateMap.find(id); - if (it != mVoiceFontTemplateMap.end()) - { - result = it->second->mFontIndex; - } - else - { - LL_WARNS("VoiceFont") << "Selected voice font template " << id << " is not available." << LL_ENDL; - } - } - return result; -} - -void LLVivoxVoiceClient::accountGetSessionFontsSendMessage() -{ - if(mAccountLoggedIn) - { - std::ostringstream stream; - - LL_DEBUGS("VoiceFont") << "Requesting voice font list." << LL_ENDL; - - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.GetSessionFonts.1\">" - << "<AccountHandle>" << LLVivoxSecurity::getInstance()->accountHandle() << "</AccountHandle>" - << "</Request>" - << "\n\n\n"; - - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::accountGetTemplateFontsSendMessage() -{ - if(mAccountLoggedIn) - { - std::ostringstream stream; - - LL_DEBUGS("VoiceFont") << "Requesting voice font template list." << LL_ENDL; - - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.GetTemplateFonts.1\">" - << "<AccountHandle>" << LLVivoxSecurity::getInstance()->accountHandle() << "</AccountHandle>" - << "</Request>" - << "\n\n\n"; - - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::sessionSetVoiceFontSendMessage(const sessionStatePtr_t &session) -{ - S32 font_index = getVoiceFontIndex(session->mVoiceFontID); - LL_DEBUGS("VoiceFont") << "Requesting voice font: " << session->mVoiceFontID << " (" << font_index << "), session handle: " << session->mHandle << LL_ENDL; - - std::ostringstream stream; - - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.SetVoiceFont.1\">" - << "<SessionHandle>" << session->mHandle << "</SessionHandle>" - << "<SessionFontID>" << font_index << "</SessionFontID>" - << "</Request>\n\n\n"; - - writeString(stream.str()); -} - -void LLVivoxVoiceClient::accountGetSessionFontsResponse(int statusCode, const std::string &statusString) -{ - if (mIsWaitingForFonts) - { - // *TODO: We seem to get multiple events of this type. Should figure a way to advance only after - // receiving the last one. - LLSD result(LLSDMap("voice_fonts", LLSD::Boolean(true))); - - mVivoxPump.post(result); - } - notifyVoiceFontObservers(); - mVoiceFontsReceived = true; -} - -void LLVivoxVoiceClient::accountGetTemplateFontsResponse(int statusCode, const std::string &statusString) -{ - // Voice font list entries were updated via addVoiceFont() during parsing. - notifyVoiceFontObservers(); -} -void LLVivoxVoiceClient::addObserver(LLVoiceEffectObserver* observer) -{ - mVoiceFontObservers.insert(observer); -} - -void LLVivoxVoiceClient::removeObserver(LLVoiceEffectObserver* observer) -{ - mVoiceFontObservers.erase(observer); -} - -// method checks the item in VoiceMorphing menu for appropriate current voice font -bool LLVivoxVoiceClient::onCheckVoiceEffect(const std::string& voice_effect_name) -{ - LLVoiceEffectInterface * effect_interfacep = LLVoiceClient::instance().getVoiceEffectInterface(); - if (NULL != effect_interfacep) - { - const LLUUID& currect_voice_effect_id = effect_interfacep->getVoiceEffect(); - - if (currect_voice_effect_id.isNull()) - { - if (voice_effect_name == "NoVoiceMorphing") - { - return true; - } - } - else - { - const LLSD& voice_effect_props = effect_interfacep->getVoiceEffectProperties(currect_voice_effect_id); - if (voice_effect_props["name"].asString() == voice_effect_name) - { - return true; - } - } - } - - return false; -} - -// method changes voice font for selected VoiceMorphing menu item -void LLVivoxVoiceClient::onClickVoiceEffect(const std::string& voice_effect_name) -{ - LLVoiceEffectInterface * effect_interfacep = LLVoiceClient::instance().getVoiceEffectInterface(); - if (NULL != effect_interfacep) - { - if (voice_effect_name == "NoVoiceMorphing") - { - effect_interfacep->setVoiceEffect(LLUUID()); - return; - } - const voice_effect_list_t& effect_list = effect_interfacep->getVoiceEffectList(); - if (!effect_list.empty()) - { - for (voice_effect_list_t::const_iterator it = effect_list.begin(); it != effect_list.end(); ++it) - { - if (voice_effect_name == it->first) - { - effect_interfacep->setVoiceEffect(it->second); - return; - } - } - } - } -} - -// it updates VoiceMorphing menu items in accordance with purchased properties -void LLVivoxVoiceClient::updateVoiceMorphingMenu() -{ - if (mVoiceFontListDirty) - { - LLVoiceEffectInterface * effect_interfacep = LLVoiceClient::instance().getVoiceEffectInterface(); - if (effect_interfacep) - { - const voice_effect_list_t& effect_list = effect_interfacep->getVoiceEffectList(); - if (!effect_list.empty()) - { - LLMenuGL * voice_morphing_menup = gMenuBarView->findChildMenuByName("VoiceMorphing", true); - - if (NULL != voice_morphing_menup) - { - S32 items = voice_morphing_menup->getItemCount(); - if (items > 0) - { - voice_morphing_menup->erase(1, items - 3, false); - - S32 pos = 1; - for (voice_effect_list_t::const_iterator it = effect_list.begin(); it != effect_list.end(); ++it) - { - LLMenuItemCheckGL::Params p; - p.name = it->first; - p.label = it->first; - p.on_check.function(boost::bind(&LLVivoxVoiceClient::onCheckVoiceEffect, this, it->first)); - p.on_click.function(boost::bind(&LLVivoxVoiceClient::onClickVoiceEffect, this, it->first)); - LLMenuItemCheckGL * voice_effect_itemp = LLUICtrlFactory::create<LLMenuItemCheckGL>(p); - voice_morphing_menup->insert(pos++, voice_effect_itemp, false); - } - - voice_morphing_menup->needsArrange(); - } - } - } - } - } -} -void LLVivoxVoiceClient::notifyVoiceFontObservers() -{ - LL_DEBUGS("VoiceFont") << "Notifying voice effect observers. Lists changed: " << mVoiceFontListDirty << LL_ENDL; - - updateVoiceMorphingMenu(); - - for (voice_font_observer_set_t::iterator it = mVoiceFontObservers.begin(); - it != mVoiceFontObservers.end();) - { - LLVoiceEffectObserver* observer = *it; - observer->onVoiceEffectChanged(mVoiceFontListDirty); - // In case onVoiceEffectChanged() deleted an entry. - it = mVoiceFontObservers.upper_bound(observer); - } - mVoiceFontListDirty = false; - - // If new Voice Fonts have been added notify the user. - if (mVoiceFontsNew) - { - if (mVoiceFontsReceived) - { - LLNotificationsUtil::add("VoiceEffectsNew"); - } - mVoiceFontsNew = false; - } -} - -void LLVivoxVoiceClient::enablePreviewBuffer(bool enable) -{ - LLSD result; - mCaptureBufferMode = enable; - - if (enable) - result["recplay"] = "start"; - else - result["recplay"] = "quit"; - - mVivoxPump.post(result); - - if(mCaptureBufferMode && mIsInChannel) - { - LL_DEBUGS("Voice") << "no channel" << LL_ENDL; - sessionTerminate(); - } -} - -void LLVivoxVoiceClient::recordPreviewBuffer() -{ - if (!mCaptureBufferMode) - { - LL_DEBUGS("Voice") << "Not in voice effect preview mode, cannot start recording." << LL_ENDL; - mCaptureBufferRecording = false; - return; - } - - mCaptureBufferRecording = true; - - LLSD result(LLSDMap("recplay", "record")); - mVivoxPump.post(result); -} - -void LLVivoxVoiceClient::playPreviewBuffer(const LLUUID& effect_id) -{ - if (!mCaptureBufferMode) - { - LL_DEBUGS("Voice") << "Not in voice effect preview mode, no buffer to play." << LL_ENDL; - mCaptureBufferRecording = false; - return; - } - - if (!mCaptureBufferRecorded) - { - // Can't play until we have something recorded! - mCaptureBufferPlaying = false; - return; - } - - mPreviewVoiceFont = effect_id; - mCaptureBufferPlaying = true; - - LLSD result(LLSDMap("recplay", "playback")); - mVivoxPump.post(result); -} - -void LLVivoxVoiceClient::stopPreviewBuffer() -{ - mCaptureBufferRecording = false; - mCaptureBufferPlaying = false; - - LLSD result(LLSDMap("recplay", "quit")); - mVivoxPump.post(result); -} - -bool LLVivoxVoiceClient::isPreviewRecording() -{ - return (mCaptureBufferMode && mCaptureBufferRecording); -} - -bool LLVivoxVoiceClient::isPreviewPlaying() -{ - return (mCaptureBufferMode && mCaptureBufferPlaying); -} - -void LLVivoxVoiceClient::captureBufferRecordStartSendMessage() -{ if(mAccountLoggedIn) - { - std::ostringstream stream; - - LL_DEBUGS("Voice") << "Starting audio capture to buffer." << LL_ENDL; - - // Start capture - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.StartBufferCapture.1\">" - << "</Request>" - << "\n\n\n"; - - // Unmute the mic - stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.MuteLocalMic.1\">" - << "<ConnectorHandle>" << LLVivoxSecurity::getInstance()->connectorHandle() << "</ConnectorHandle>" - << "<Value>false</Value>" - << "</Request>\n\n\n"; - - // Dirty the mute mic state so that it will get reset when we finishing previewing - mMuteMicDirty = true; - - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::captureBufferRecordStopSendMessage() -{ - if(mAccountLoggedIn) - { - std::ostringstream stream; - - LL_DEBUGS("Voice") << "Stopping audio capture to buffer." << LL_ENDL; - - // Mute the mic. Mic mute state was dirtied at recording start, so will be reset when finished previewing. - stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.MuteLocalMic.1\">" - << "<ConnectorHandle>" << LLVivoxSecurity::getInstance()->connectorHandle() << "</ConnectorHandle>" - << "<Value>true</Value>" - << "</Request>\n\n\n"; - - // Stop capture - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.CaptureAudioStop.1\">" - << "<AccountHandle>" << LLVivoxSecurity::getInstance()->accountHandle() << "</AccountHandle>" - << "</Request>" - << "\n\n\n"; - - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::captureBufferPlayStartSendMessage(const LLUUID& voice_font_id) -{ - if(mAccountLoggedIn) - { - // Track how may play requests are sent, so we know how many stop events to - // expect before play actually stops. - ++mPlayRequestCount; - - std::ostringstream stream; - - LL_DEBUGS("Voice") << "Starting audio buffer playback." << LL_ENDL; - - S32 font_index = getVoiceFontTemplateIndex(voice_font_id); - LL_DEBUGS("Voice") << "With voice font: " << voice_font_id << " (" << font_index << ")" << LL_ENDL; - - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.PlayAudioBuffer.1\">" - << "<AccountHandle>" << LLVivoxSecurity::getInstance()->accountHandle() << "</AccountHandle>" - << "<TemplateFontID>" << font_index << "</TemplateFontID>" - << "<FontDelta />" - << "</Request>" - << "\n\n\n"; - - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::captureBufferPlayStopSendMessage() -{ - if(mAccountLoggedIn) - { - std::ostringstream stream; - - LL_DEBUGS("Voice") << "Stopping audio buffer playback." << LL_ENDL; - - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.RenderAudioStop.1\">" - << "<AccountHandle>" << LLVivoxSecurity::getInstance()->accountHandle() << "</AccountHandle>" - << "</Request>" - << "\n\n\n"; - - writeString(stream.str()); - } -} - -LLVivoxProtocolParser::LLVivoxProtocolParser() -{ - parser = XML_ParserCreate(NULL); - - reset(); -} - -void LLVivoxProtocolParser::reset() -{ - responseDepth = 0; - ignoringTags = false; - accumulateText = false; - energy = 0.f; - hasText = false; - hasAudio = false; - hasVideo = false; - terminated = false; - ignoreDepth = 0; - isChannel = false; - incoming = false; - enabled = false; - isEvent = false; - isLocallyMuted = false; - isModeratorMuted = false; - isSpeaking = false; - participantType = 0; - returnCode = -1; - state = 0; - statusCode = 0; - volume = 0; - textBuffer.clear(); - alias.clear(); - numberOfAliases = 0; - applicationString.clear(); -} - -//virtual -LLVivoxProtocolParser::~LLVivoxProtocolParser() -{ - if (parser) - XML_ParserFree(parser); -} - -static LLTrace::BlockTimerStatHandle FTM_VIVOX_PROCESS("Vivox Process"); - -// virtual -LLIOPipe::EStatus LLVivoxProtocolParser::process_impl( - const LLChannelDescriptors& channels, - buffer_ptr_t& buffer, - bool& eos, - LLSD& context, - LLPumpIO* pump) -{ - LL_RECORD_BLOCK_TIME(FTM_VIVOX_PROCESS); - LLBufferStream istr(channels, buffer.get()); - std::ostringstream ostr; - while (istr.good()) - { - char buf[1024]; - istr.read(buf, sizeof(buf)); - mInput.append(buf, istr.gcount()); - } - - // Look for input delimiter(s) in the input buffer. If one is found, send the message to the xml parser. - int start = 0; - int delim; - while((delim = mInput.find("\n\n\n", start)) != std::string::npos) - { - - // Reset internal state of the LLVivoxProtocolParser (no effect on the expat parser) - reset(); - - XML_ParserReset(parser, NULL); - XML_SetElementHandler(parser, ExpatStartTag, ExpatEndTag); - XML_SetCharacterDataHandler(parser, ExpatCharHandler); - XML_SetUserData(parser, this); - XML_Parse(parser, mInput.data() + start, delim - start, false); - - LL_DEBUGS("VivoxProtocolParser") << "parsing: " << mInput.substr(start, delim - start) << LL_ENDL; - start = delim + 3; - } - - if(start != 0) - mInput = mInput.substr(start); - - LL_DEBUGS("VivoxProtocolParser") << "at end, mInput is: " << mInput << LL_ENDL; - - if(!LLVivoxVoiceClient::sConnected) - { - // If voice has been disabled, we just want to close the socket. This does so. - LL_INFOS("Voice") << "returning STATUS_STOP" << LL_ENDL; - return STATUS_STOP; - } - - return STATUS_OK; -} - -void XMLCALL LLVivoxProtocolParser::ExpatStartTag(void *data, const char *el, const char **attr) -{ - if (data) - { - LLVivoxProtocolParser *object = (LLVivoxProtocolParser*)data; - object->StartTag(el, attr); - } -} - -// -------------------------------------------------------------------------------- - -void XMLCALL LLVivoxProtocolParser::ExpatEndTag(void *data, const char *el) -{ - if (data) - { - LLVivoxProtocolParser *object = (LLVivoxProtocolParser*)data; - object->EndTag(el); - } -} - -// -------------------------------------------------------------------------------- - -void XMLCALL LLVivoxProtocolParser::ExpatCharHandler(void *data, const XML_Char *s, int len) -{ - if (data) - { - LLVivoxProtocolParser *object = (LLVivoxProtocolParser*)data; - object->CharData(s, len); - } -} - -// -------------------------------------------------------------------------------- - - -void LLVivoxProtocolParser::StartTag(const char *tag, const char **attr) -{ - // Reset the text accumulator. We shouldn't have strings that are inturrupted by new tags - textBuffer.clear(); - // only accumulate text if we're not ignoring tags. - accumulateText = !ignoringTags; - - if (responseDepth == 0) - { - isEvent = !stricmp("Event", tag); - - if (!stricmp("Response", tag) || isEvent) - { - // Grab the attributes - while (*attr) - { - const char *key = *attr++; - const char *value = *attr++; - - if (!stricmp("requestId", key)) - { - requestId = value; - } - else if (!stricmp("action", key)) - { - actionString = value; - } - else if (!stricmp("type", key)) - { - eventTypeString = value; - } - } - } - LL_DEBUGS("VivoxProtocolParser") << tag << " (" << responseDepth << ")" << LL_ENDL; - } - else - { - if (ignoringTags) - { - LL_DEBUGS("VivoxProtocolParser") << "ignoring tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL; - } - else - { - LL_DEBUGS("VivoxProtocolParser") << tag << " (" << responseDepth << ")" << LL_ENDL; - - // Ignore the InputXml stuff so we don't get confused - if (!stricmp("InputXml", tag)) - { - ignoringTags = true; - ignoreDepth = responseDepth; - accumulateText = false; - - LL_DEBUGS("VivoxProtocolParser") << "starting ignore, ignoreDepth is " << ignoreDepth << LL_ENDL; - } - else if (!stricmp("CaptureDevices", tag)) - { - LLVivoxVoiceClient::getInstance()->clearCaptureDevices(); - } - else if (!stricmp("RenderDevices", tag)) - { - LLVivoxVoiceClient::getInstance()->clearRenderDevices(); - } - else if (!stricmp("CaptureDevice", tag)) - { - deviceString.clear(); - } - else if (!stricmp("RenderDevice", tag)) - { - deviceString.clear(); - } - else if (!stricmp("SessionFont", tag)) - { - id = 0; - nameString.clear(); - descriptionString.clear(); - expirationDate = LLDate(); - hasExpired = false; - fontType = 0; - fontStatus = 0; - } - else if (!stricmp("TemplateFont", tag)) - { - id = 0; - nameString.clear(); - descriptionString.clear(); - expirationDate = LLDate(); - hasExpired = false; - fontType = 0; - fontStatus = 0; - } - else if (!stricmp("MediaCompletionType", tag)) - { - mediaCompletionType.clear(); - } - } - } - responseDepth++; -} - -// -------------------------------------------------------------------------------- - -void LLVivoxProtocolParser::EndTag(const char *tag) -{ - const std::string& string = textBuffer; - - responseDepth--; - - if (ignoringTags) - { - if (ignoreDepth == responseDepth) - { - LL_DEBUGS("VivoxProtocolParser") << "end of ignore" << LL_ENDL; - ignoringTags = false; - } - else - { - LL_DEBUGS("VivoxProtocolParser") << "ignoring tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL; - } - } - - if (!ignoringTags) - { - LL_DEBUGS("VivoxProtocolParser") << "processing tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL; - - // Closing a tag. Finalize the text we've accumulated and reset - if (!stricmp("ReturnCode", tag)) - returnCode = strtol(string.c_str(), NULL, 10); - else if (!stricmp("SessionHandle", tag)) - sessionHandle = string; - else if (!stricmp("SessionGroupHandle", tag)) - sessionGroupHandle = string; - else if (!stricmp("StatusCode", tag)) - statusCode = strtol(string.c_str(), NULL, 10); - else if (!stricmp("StatusString", tag)) - statusString = string; - else if (!stricmp("ParticipantURI", tag)) - uriString = string; - else if (!stricmp("Volume", tag)) - volume = strtol(string.c_str(), NULL, 10); - else if (!stricmp("Energy", tag)) - energy = (F32)strtod(string.c_str(), NULL); - else if (!stricmp("IsModeratorMuted", tag)) - isModeratorMuted = !stricmp(string.c_str(), "true"); - else if (!stricmp("IsSpeaking", tag)) - isSpeaking = !stricmp(string.c_str(), "true"); - else if (!stricmp("Alias", tag)) - alias = string; - else if (!stricmp("NumberOfAliases", tag)) - numberOfAliases = strtol(string.c_str(), NULL, 10); - else if (!stricmp("Application", tag)) - applicationString = string; - else if (!stricmp("ConnectorHandle", tag)) - connectorHandle = string; - else if (!stricmp("VersionID", tag)) - versionID = string; - else if (!stricmp("Version", tag)) - mBuildID = string; - else if (!stricmp("AccountHandle", tag)) - accountHandle = string; - else if (!stricmp("State", tag)) - state = strtol(string.c_str(), NULL, 10); - else if (!stricmp("URI", tag)) - uriString = string; - else if (!stricmp("IsChannel", tag)) - isChannel = !stricmp(string.c_str(), "true"); - else if (!stricmp("Incoming", tag)) - incoming = !stricmp(string.c_str(), "true"); - else if (!stricmp("Enabled", tag)) - enabled = !stricmp(string.c_str(), "true"); - else if (!stricmp("Name", tag)) - nameString = string; - else if (!stricmp("AudioMedia", tag)) - audioMediaString = string; - else if (!stricmp("ChannelName", tag)) - nameString = string; - else if (!stricmp("DisplayName", tag)) - displayNameString = string; - else if (!stricmp("Device", tag)) - deviceString = string; - else if (!stricmp("AccountName", tag)) - nameString = string; - else if (!stricmp("ParticipantType", tag)) - participantType = strtol(string.c_str(), NULL, 10); - else if (!stricmp("IsLocallyMuted", tag)) - isLocallyMuted = !stricmp(string.c_str(), "true"); - else if (!stricmp("MicEnergy", tag)) - energy = (F32)strtod(string.c_str(), NULL); - else if (!stricmp("ChannelName", tag)) - nameString = string; - else if (!stricmp("ChannelURI", tag)) - uriString = string; - else if (!stricmp("BuddyURI", tag)) - uriString = string; - else if (!stricmp("Presence", tag)) - statusString = string; - else if (!stricmp("CaptureDevices", tag)) - { - LLVivoxVoiceClient::getInstance()->setDevicesListUpdated(true); - } - else if (!stricmp("RenderDevices", tag)) - { - LLVivoxVoiceClient::getInstance()->setDevicesListUpdated(true); - } - else if (!stricmp("CaptureDevice", tag)) - { - LLVivoxVoiceClient::getInstance()->addCaptureDevice(LLVoiceDevice(displayNameString, deviceString)); - } - else if (!stricmp("RenderDevice", tag)) - { - LLVivoxVoiceClient::getInstance()->addRenderDevice(LLVoiceDevice(displayNameString, deviceString)); - } - else if (!stricmp("BlockMask", tag)) - blockMask = string; - else if (!stricmp("PresenceOnly", tag)) - presenceOnly = string; - else if (!stricmp("AutoAcceptMask", tag)) - autoAcceptMask = string; - else if (!stricmp("AutoAddAsBuddy", tag)) - autoAddAsBuddy = string; - else if (!stricmp("MessageHeader", tag)) - messageHeader = string; - else if (!stricmp("MessageBody", tag)) - messageBody = string; - else if (!stricmp("NotificationType", tag)) - notificationType = string; - else if (!stricmp("HasText", tag)) - hasText = !stricmp(string.c_str(), "true"); - else if (!stricmp("HasAudio", tag)) - hasAudio = !stricmp(string.c_str(), "true"); - else if (!stricmp("HasVideo", tag)) - hasVideo = !stricmp(string.c_str(), "true"); - else if (!stricmp("Terminated", tag)) - terminated = !stricmp(string.c_str(), "true"); - else if (!stricmp("SubscriptionHandle", tag)) - subscriptionHandle = string; - else if (!stricmp("SubscriptionType", tag)) - subscriptionType = string; - else if (!stricmp("SessionFont", tag)) - { - LLVivoxVoiceClient::getInstance()->addVoiceFont(id, nameString, descriptionString, expirationDate, hasExpired, fontType, fontStatus, false); - } - else if (!stricmp("TemplateFont", tag)) - { - LLVivoxVoiceClient::getInstance()->addVoiceFont(id, nameString, descriptionString, expirationDate, hasExpired, fontType, fontStatus, true); - } - else if (!stricmp("ID", tag)) - { - id = strtol(string.c_str(), NULL, 10); - } - else if (!stricmp("Description", tag)) - { - descriptionString = string; - } - else if (!stricmp("ExpirationDate", tag)) - { - expirationDate = expiryTimeStampToLLDate(string); - } - else if (!stricmp("Expired", tag)) - { - hasExpired = !stricmp(string.c_str(), "1"); - } - else if (!stricmp("Type", tag)) - { - fontType = strtol(string.c_str(), NULL, 10); - } - else if (!stricmp("Status", tag)) - { - fontStatus = strtol(string.c_str(), NULL, 10); - } - else if (!stricmp("MediaCompletionType", tag)) - { - mediaCompletionType = string;; - } - - textBuffer.clear(); - accumulateText= false; - - if (responseDepth == 0) - { - // We finished all of the XML, process the data - processResponse(tag); - } - } -} - -// -------------------------------------------------------------------------------- - -void LLVivoxProtocolParser::CharData(const char *buffer, int length) -{ - /* - This method is called for anything that isn't a tag, which can be text you - want that lies between tags, and a lot of stuff you don't want like file formatting - (tabs, spaces, CR/LF, etc). - - Only copy text if we are in accumulate mode... - */ - if (accumulateText) - textBuffer.append(buffer, length); -} - -// -------------------------------------------------------------------------------- - -LLDate LLVivoxProtocolParser::expiryTimeStampToLLDate(const std::string& vivox_ts) -{ - // *HACK: Vivox reports the time incorrectly. LLDate also only parses a - // subset of valid ISO 8601 dates (only handles Z, not offsets). - // So just use the date portion and fix the time here. - std::string time_stamp = vivox_ts.substr(0, 10); - time_stamp += VOICE_FONT_EXPIRY_TIME; - - LL_DEBUGS("VivoxProtocolParser") << "Vivox timestamp " << vivox_ts << " modified to: " << time_stamp << LL_ENDL; - - return LLDate(time_stamp); -} - -// -------------------------------------------------------------------------------- - -void LLVivoxProtocolParser::processResponse(std::string tag) -{ - LL_DEBUGS("VivoxProtocolParser") << tag << LL_ENDL; - - // SLIM SDK: the SDK now returns a statusCode of "200" (OK) for success. This is a change vs. previous SDKs. - // According to Mike S., "The actual API convention is that responses with return codes of 0 are successful, regardless of the status code returned", - // so I believe this will give correct behavior. - - if(returnCode == 0) - statusCode = 0; - - if (isEvent) - { - const char *eventTypeCstr = eventTypeString.c_str(); - LL_DEBUGS("LowVoice") << eventTypeCstr << LL_ENDL; - - if (!stricmp(eventTypeCstr, "ParticipantUpdatedEvent")) - { - // These happen so often that logging them is pretty useless. - LL_DEBUGS("LowVoice") << "Updated Params: " << sessionHandle << ", " << sessionGroupHandle << ", " << uriString << ", " << alias << ", " << isModeratorMuted << ", " << isSpeaking << ", " << volume << ", " << energy << LL_ENDL; - LLVivoxVoiceClient::getInstance()->participantUpdatedEvent(sessionHandle, sessionGroupHandle, uriString, alias, isModeratorMuted, isSpeaking, volume, energy); - } - else if (!stricmp(eventTypeCstr, "AccountLoginStateChangeEvent")) - { - LLVivoxVoiceClient::getInstance()->accountLoginStateChangeEvent(accountHandle, statusCode, statusString, state); - } - else if (!stricmp(eventTypeCstr, "SessionAddedEvent")) - { - /* - <Event type="SessionAddedEvent"> - <SessionGroupHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0</SessionGroupHandle> - <SessionHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==0</SessionHandle> - <Uri>sip:confctl-1408789@bhr.vivox.com</Uri> - <IsChannel>true</IsChannel> - <Incoming>false</Incoming> - <ChannelName /> - </Event> - */ - LLVivoxVoiceClient::getInstance()->sessionAddedEvent(uriString, alias, sessionHandle, sessionGroupHandle, isChannel, incoming, nameString, applicationString); - } - else if (!stricmp(eventTypeCstr, "SessionRemovedEvent")) - { - LLVivoxVoiceClient::getInstance()->sessionRemovedEvent(sessionHandle, sessionGroupHandle); - } - else if (!stricmp(eventTypeCstr, "SessionGroupUpdatedEvent")) - { - //nothng useful to process for this event, but we should not WARN that we have received it. - } - else if (!stricmp(eventTypeCstr, "SessionGroupAddedEvent")) - { - LLVivoxVoiceClient::getInstance()->sessionGroupAddedEvent(sessionGroupHandle); - } - else if (!stricmp(eventTypeCstr, "MediaStreamUpdatedEvent")) - { - /* - <Event type="MediaStreamUpdatedEvent"> - <SessionGroupHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0</SessionGroupHandle> - <SessionHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==0</SessionHandle> - <StatusCode>200</StatusCode> - <StatusString>OK</StatusString> - <State>2</State> - <Incoming>false</Incoming> - </Event> - */ - LLVivoxVoiceClient::getInstance()->mediaStreamUpdatedEvent(sessionHandle, sessionGroupHandle, statusCode, statusString, state, incoming); - } - else if (!stricmp(eventTypeCstr, "MediaCompletionEvent")) - { - /* - <Event type="MediaCompletionEvent"> - <SessionGroupHandle /> - <MediaCompletionType>AuxBufferAudioCapture</MediaCompletionType> - </Event> - */ - LLVivoxVoiceClient::getInstance()->mediaCompletionEvent(sessionGroupHandle, mediaCompletionType); - } - else if (!stricmp(eventTypeCstr, "ParticipantAddedEvent")) - { - /* - <Event type="ParticipantAddedEvent"> - <SessionGroupHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg4</SessionGroupHandle> - <SessionHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==4</SessionHandle> - <ParticipantUri>sip:xI5auBZ60SJWIk606-1JGRQ==@bhr.vivox.com</ParticipantUri> - <AccountName>xI5auBZ60SJWIk606-1JGRQ==</AccountName> - <DisplayName /> - <ParticipantType>0</ParticipantType> - </Event> - */ - LL_DEBUGS("LowVoice") << "Added Params: " << sessionHandle << ", " << sessionGroupHandle << ", " << uriString << ", " << alias << ", " << nameString << ", " << displayNameString << ", " << participantType << LL_ENDL; - LLVivoxVoiceClient::getInstance()->participantAddedEvent(sessionHandle, sessionGroupHandle, uriString, alias, nameString, displayNameString, participantType); - } - else if (!stricmp(eventTypeCstr, "ParticipantRemovedEvent")) - { - /* - <Event type="ParticipantRemovedEvent"> - <SessionGroupHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg4</SessionGroupHandle> - <SessionHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==4</SessionHandle> - <ParticipantUri>sip:xtx7YNV-3SGiG7rA1fo5Ndw==@bhr.vivox.com</ParticipantUri> - <AccountName>xtx7YNV-3SGiG7rA1fo5Ndw==</AccountName> - </Event> - */ - LL_DEBUGS("LowVoice") << "Removed params:" << sessionHandle << ", " << sessionGroupHandle << ", " << uriString << ", " << alias << ", " << nameString << LL_ENDL; - - LLVivoxVoiceClient::getInstance()->participantRemovedEvent(sessionHandle, sessionGroupHandle, uriString, alias, nameString); - } - else if (!stricmp(eventTypeCstr, "AuxAudioPropertiesEvent")) - { - // These are really spammy in tuning mode - LLVivoxVoiceClient::getInstance()->auxAudioPropertiesEvent(energy); - } - else if (!stricmp(eventTypeCstr, "MessageEvent")) - { - //TODO: This probably is not received any more, it was used to support SLim clients - LLVivoxVoiceClient::getInstance()->messageEvent(sessionHandle, uriString, alias, messageHeader, messageBody, applicationString); - } - else if (!stricmp(eventTypeCstr, "SessionNotificationEvent")) - { - //TODO: This probably is not received any more, it was used to support SLim clients - LLVivoxVoiceClient::getInstance()->sessionNotificationEvent(sessionHandle, uriString, notificationType); - } - else if (!stricmp(eventTypeCstr, "SessionUpdatedEvent")) - { - /* - <Event type="SessionUpdatedEvent"> - <SessionGroupHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0</SessionGroupHandle> - <SessionHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==0</SessionHandle> - <Uri>sip:confctl-9@bhd.vivox.com</Uri> - <IsMuted>0</IsMuted> - <Volume>50</Volume> - <TransmitEnabled>1</TransmitEnabled> - <IsFocused>0</IsFocused> - <SpeakerPosition><Position><X>0</X><Y>0</Y><Z>0</Z></Position></SpeakerPosition> - <SessionFontID>0</SessionFontID> - </Event> - */ - // We don't need to process this, but we also shouldn't warn on it, since that confuses people. - } - else if (!stricmp(eventTypeCstr, "SessionGroupRemovedEvent")) - { - // We don't need to process this, but we also shouldn't warn on it, since that confuses people. - } - else if (!stricmp(eventTypeCstr, "VoiceServiceConnectionStateChangedEvent")) - { - LLVivoxVoiceClient::getInstance()->voiceServiceConnectionStateChangedEvent(statusCode, statusString, mBuildID); - } - else if (!stricmp(eventTypeCstr, "AudioDeviceHotSwapEvent")) - { - /* - <Event type = "AudioDeviceHotSwapEvent"> - <EventType>RenderDeviceChanged< / EventType> - <RelevantDevice> - <Device>Speakers(Turtle Beach P11 Headset)< / Device> - <DisplayName>Speakers(Turtle Beach P11 Headset)< / DisplayName> - <Type>SpecificDevice< / Type> - < / RelevantDevice> - < / Event> - */ - // an audio device was removed or added, fetch and update the local list of audio devices. - LLVivoxVoiceClient::getInstance()->getCaptureDevicesSendMessage(); - LLVivoxVoiceClient::getInstance()->getRenderDevicesSendMessage(); - } - else - { - LL_WARNS("VivoxProtocolParser") << "Unknown event type " << eventTypeString << LL_ENDL; - } - } - else - { - const char *actionCstr = actionString.c_str(); - LL_DEBUGS("LowVoice") << actionCstr << LL_ENDL; - - if (!stricmp(actionCstr, "Session.Set3DPosition.1")) - { - // We don't need to process these - } - else if (!stricmp(actionCstr, "Connector.Create.1")) - { - LLVivoxVoiceClient::getInstance()->connectorCreateResponse(statusCode, statusString, connectorHandle, versionID); - } - else if (!stricmp(actionCstr, "Account.Login.1")) - { - LLVivoxVoiceClient::getInstance()->loginResponse(statusCode, statusString, accountHandle, numberOfAliases); - } - else if (!stricmp(actionCstr, "Session.Create.1")) - { - LLVivoxVoiceClient::getInstance()->sessionCreateResponse(requestId, statusCode, statusString, sessionHandle); - } - else if (!stricmp(actionCstr, "SessionGroup.AddSession.1")) - { - LLVivoxVoiceClient::getInstance()->sessionGroupAddSessionResponse(requestId, statusCode, statusString, sessionHandle); - } - else if (!stricmp(actionCstr, "Session.Connect.1")) - { - LLVivoxVoiceClient::getInstance()->sessionConnectResponse(requestId, statusCode, statusString); - } - else if (!stricmp(actionCstr, "Account.Logout.1")) - { - LLVivoxVoiceClient::getInstance()->logoutResponse(statusCode, statusString); - } - else if (!stricmp(actionCstr, "Connector.InitiateShutdown.1")) - { - LLVivoxVoiceClient::getInstance()->connectorShutdownResponse(statusCode, statusString); - } - else if (!stricmp(actionCstr, "Account.GetSessionFonts.1")) - { - LLVivoxVoiceClient::getInstance()->accountGetSessionFontsResponse(statusCode, statusString); - } - else if (!stricmp(actionCstr, "Account.GetTemplateFonts.1")) - { - LLVivoxVoiceClient::getInstance()->accountGetTemplateFontsResponse(statusCode, statusString); - } - else if (!stricmp(actionCstr, "Aux.SetVadProperties.1")) - { - // both values of statusCode (old and more recent) indicate valid requests - if (statusCode != 0 && statusCode != 200) - { - LL_WARNS("Voice") << "Aux.SetVadProperties.1 request failed: " - << "statusCode: " << statusCode - << " and " - << "statusString: " << statusString - << LL_ENDL; - } - } - /* - else if (!stricmp(actionCstr, "Account.ChannelGetList.1")) - { - LLVoiceClient::getInstance()->channelGetListResponse(statusCode, statusString); - } - else if (!stricmp(actionCstr, "Connector.AccountCreate.1")) - { - - } - else if (!stricmp(actionCstr, "Connector.MuteLocalMic.1")) - { - - } - else if (!stricmp(actionCstr, "Connector.MuteLocalSpeaker.1")) - { - - } - else if (!stricmp(actionCstr, "Connector.SetLocalMicVolume.1")) - { - - } - else if (!stricmp(actionCstr, "Connector.SetLocalSpeakerVolume.1")) - { - - } - else if (!stricmp(actionCstr, "Session.ListenerSetPosition.1")) - { - - } - else if (!stricmp(actionCstr, "Session.SpeakerSetPosition.1")) - { - - } - else if (!stricmp(actionCstr, "Session.AudioSourceSetPosition.1")) - { - - } - else if (!stricmp(actionCstr, "Session.GetChannelParticipants.1")) - { - - } - else if (!stricmp(actionCstr, "Account.ChannelCreate.1")) - { - - } - else if (!stricmp(actionCstr, "Account.ChannelUpdate.1")) - { - - } - else if (!stricmp(actionCstr, "Account.ChannelDelete.1")) - { - - } - else if (!stricmp(actionCstr, "Account.ChannelCreateAndInvite.1")) - { - - } - else if (!stricmp(actionCstr, "Account.ChannelFolderCreate.1")) - { - - } - else if (!stricmp(actionCstr, "Account.ChannelFolderUpdate.1")) - { - - } - else if (!stricmp(actionCstr, "Account.ChannelFolderDelete.1")) - { - - } - else if (!stricmp(actionCstr, "Account.ChannelAddModerator.1")) - { - - } - else if (!stricmp(actionCstr, "Account.ChannelDeleteModerator.1")) - { - - } - */ - } -} - -LLVivoxSecurity::LLVivoxSecurity() -{ - // This size is an arbitrary choice; Vivox does not care - // Use a multiple of three so that there is no '=' padding in the base64 (purely an esthetic choice) - #define VIVOX_TOKEN_BYTES 9 - U8 random_value[VIVOX_TOKEN_BYTES]; - - for (int b = 0; b < VIVOX_TOKEN_BYTES; b++) - { - random_value[b] = ll_rand() & 0xff; - } - mConnectorHandle = LLBase64::encode(random_value, VIVOX_TOKEN_BYTES); - - for (int b = 0; b < VIVOX_TOKEN_BYTES; b++) - { - random_value[b] = ll_rand() & 0xff; - } - mAccountHandle = LLBase64::encode(random_value, VIVOX_TOKEN_BYTES); -} - -LLVivoxSecurity::~LLVivoxSecurity() -{ -} + /**
+ * @file LLVivoxVoiceClient.cpp
+ * @brief Implementation of LLVivoxVoiceClient class which is the interface to the voice client process.
+ *
+ * $LicenseInfo:firstyear=2001&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#include "llviewerprecompiledheaders.h"
+#include <algorithm>
+#include "llvoicevivox.h"
+
+#include "llsdutil.h"
+
+// Linden library includes
+#include "llavatarnamecache.h"
+#include "llvoavatarself.h"
+#include "llbufferstream.h"
+#include "llfile.h"
+#include "llmenugl.h"
+#ifdef LL_USESYSTEMLIBS
+# include "expat.h"
+#else
+# include "expat/expat.h"
+#endif
+#include "llcallbacklist.h"
+#include "llviewerregion.h"
+#include "llviewernetwork.h" // for gGridChoice
+#include "llbase64.h"
+#include "llviewercontrol.h"
+#include "llappviewer.h" // for gDisconnected, gDisableVoice
+#include "llprocess.h"
+
+// Viewer includes
+#include "llmutelist.h" // to check for muted avatars
+#include "llagent.h"
+#include "llcachename.h"
+#include "llimview.h" // for LLIMMgr
+#include "llparcel.h"
+#include "llviewerparcelmgr.h"
+#include "llfirstuse.h"
+#include "llspeakers.h"
+#include "lltrans.h"
+#include "llviewerwindow.h"
+#include "llviewercamera.h"
+#include "llversioninfo.h"
+
+#include "llviewernetwork.h"
+#include "llnotificationsutil.h"
+
+#include "llcorehttputil.h"
+#include "lleventfilter.h"
+
+#include "stringize.h"
+
+// for base64 decoding
+#include "apr_base64.h"
+
+#define USE_SESSION_GROUPS 0
+#define VX_NULL_POSITION -2147483648.0 /*The Silence*/
+
+extern LLMenuBarGL* gMenuBarView;
+extern void handle_voice_morphing_subscribe();
+
+namespace {
+ const F32 VOLUME_SCALE_VIVOX = 0.01f;
+
+ const F32 SPEAKING_TIMEOUT = 1.f;
+
+ static const std::string VOICE_SERVER_TYPE = "Vivox";
+
+ // Don't retry connecting to the daemon more frequently than this:
+ const F32 DAEMON_CONNECT_THROTTLE_SECONDS = 1.0f;
+ const int DAEMON_CONNECT_RETRY_MAX = 3;
+
+ // Don't send positional updates more frequently than this:
+ const F32 UPDATE_THROTTLE_SECONDS = 0.5f;
+
+ // Timeout for connection to Vivox
+ const F32 CONNECT_ATTEMPT_TIMEOUT = 300.0f;
+ const F32 CONNECT_DNS_TIMEOUT = 5.0f;
+ const int CONNECT_RETRY_MAX = 3;
+
+ const F32 LOGIN_ATTEMPT_TIMEOUT = 30.0f;
+ const F32 LOGOUT_ATTEMPT_TIMEOUT = 5.0f;
+ const int LOGIN_RETRY_MAX = 3;
+
+ const F32 PROVISION_RETRY_TIMEOUT = 2.0;
+ const int PROVISION_RETRY_MAX = 5;
+
+ // Cosine of a "trivially" small angle
+ const F32 FOUR_DEGREES = 4.0f * (F_PI / 180.0f);
+ const F32 MINUSCULE_ANGLE_COS = (F32) cos(0.5f * FOUR_DEGREES);
+
+ const F32 SESSION_JOIN_TIMEOUT = 30.0f;
+
+ // Defines the maximum number of times(in a row) "stateJoiningSession" case for spatial channel is reached in stateMachine()
+ // which is treated as normal. The is the number of frames to wait for a channel join before giving up. This was changed
+ // from the original count of 50 for two reason. Modern PCs have higher frame rates and sometimes the SLVoice process
+ // backs up processing join requests. There is a log statement that records when channel joins take longer than 100 frames.
+ const int MAX_NORMAL_JOINING_SPATIAL_NUM = 1500;
+
+ // How often to check for expired voice fonts in seconds
+ const F32 VOICE_FONT_EXPIRY_INTERVAL = 10.f;
+ // Time of day at which Vivox expires voice font subscriptions.
+ // Used to replace the time portion of received expiry timestamps.
+ static const std::string VOICE_FONT_EXPIRY_TIME = "T05:00:00Z";
+
+ // Maximum length of capture buffer recordings in seconds.
+ const F32 CAPTURE_BUFFER_MAX_TIME = 10.f;
+
+ const int ERROR_VIVOX_OBJECT_NOT_FOUND = 1001;
+ const int ERROR_VIVOX_NOT_LOGGED_IN = 1007;
+}
+
+static int scale_mic_volume(float volume)
+{
+ // incoming volume has the range [0.0 ... 2.0], with 1.0 as the default.
+ // Map it to Vivox levels as follows: 0.0 -> 30, 1.0 -> 50, 2.0 -> 70
+ return 30 + (int)(volume * 20.0f);
+}
+
+static int scale_speaker_volume(float volume)
+{
+ // incoming volume has the range [0.0 ... 1.0], with 0.5 as the default.
+ // Map it to Vivox levels as follows: 0.0 -> 30, 0.5 -> 50, 1.0 -> 70
+ return 30 + (int)(volume * 40.0f);
+
+}
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+class LLVivoxVoiceClientMuteListObserver : public LLMuteListObserver
+{
+ /* virtual */ void onChange() { LLVivoxVoiceClient::getInstance()->muteListChanged();}
+};
+
+
+void LLVoiceVivoxStats::reset()
+{
+ mStartTime = -1.0f;
+ mConnectCycles = 0;
+ mConnectTime = -1.0f;
+ mConnectAttempts = 0;
+ mProvisionTime = -1.0f;
+ mProvisionAttempts = 0;
+ mEstablishTime = -1.0f;
+ mEstablishAttempts = 0;
+}
+
+LLVoiceVivoxStats::LLVoiceVivoxStats()
+{
+ reset();
+}
+
+LLVoiceVivoxStats::~LLVoiceVivoxStats()
+{
+}
+
+void LLVoiceVivoxStats::connectionAttemptStart()
+{
+ if (!mConnectAttempts)
+ {
+ mStartTime = LLTimer::getTotalTime();
+ mConnectCycles++;
+ }
+ mConnectAttempts++;
+}
+
+void LLVoiceVivoxStats::connectionAttemptEnd(bool success)
+{
+ if ( success )
+ {
+ mConnectTime = (LLTimer::getTotalTime() - mStartTime) / USEC_PER_SEC;
+ }
+}
+
+void LLVoiceVivoxStats::provisionAttemptStart()
+{
+ if (!mProvisionAttempts)
+ {
+ mStartTime = LLTimer::getTotalTime();
+ }
+ mProvisionAttempts++;
+}
+
+void LLVoiceVivoxStats::provisionAttemptEnd(bool success)
+{
+ if ( success )
+ {
+ mProvisionTime = (LLTimer::getTotalTime() - mStartTime) / USEC_PER_SEC;
+ }
+}
+
+void LLVoiceVivoxStats::establishAttemptStart()
+{
+ if (!mEstablishAttempts)
+ {
+ mStartTime = LLTimer::getTotalTime();
+ }
+ mEstablishAttempts++;
+}
+
+void LLVoiceVivoxStats::establishAttemptEnd(bool success)
+{
+ if ( success )
+ {
+ mEstablishTime = (LLTimer::getTotalTime() - mStartTime) / USEC_PER_SEC;
+ }
+}
+
+LLSD LLVoiceVivoxStats::read()
+{
+ LLSD stats(LLSD::emptyMap());
+
+ stats["connect_cycles"] = LLSD::Integer(mConnectCycles);
+ stats["connect_attempts"] = LLSD::Integer(mConnectAttempts);
+ stats["connect_time"] = LLSD::Real(mConnectTime);
+
+ stats["provision_attempts"] = LLSD::Integer(mProvisionAttempts);
+ stats["provision_time"] = LLSD::Real(mProvisionTime);
+
+ stats["establish_attempts"] = LLSD::Integer(mEstablishAttempts);
+ stats["establish_time"] = LLSD::Real(mEstablishTime);
+
+ return stats;
+}
+
+static LLVivoxVoiceClientMuteListObserver mutelist_listener;
+static bool sMuteListListener_listening = false;
+
+///////////////////////////////////////////////////////////////////////////////////////////////
+static LLProcessPtr sGatewayPtr;
+static LLEventStream sGatewayPump("VivoxDaemonPump", true);
+
+static bool isGatewayRunning()
+{
+ return sGatewayPtr && sGatewayPtr->isRunning();
+}
+
+static void killGateway()
+{
+ if (sGatewayPtr)
+ {
+ LL_DEBUGS("Voice") << "SLVoice " << sGatewayPtr->getStatusString() << LL_ENDL;
+
+ sGatewayPump.stopListening("VivoxDaemonPump");
+ sGatewayPtr->kill(__FUNCTION__);
+ sGatewayPtr=NULL;
+ }
+ else
+ {
+ LL_DEBUGS("Voice") << "no gateway" << LL_ENDL;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+bool LLVivoxVoiceClient::sShuttingDown = false;
+bool LLVivoxVoiceClient::sConnected = false;
+LLPumpIO *LLVivoxVoiceClient::sPump = nullptr;
+
+LLVivoxVoiceClient::LLVivoxVoiceClient() :
+ mSessionTerminateRequested(false),
+ mRelogRequested(false),
+ mTerminateDaemon(false),
+ mSpatialJoiningNum(0),
+
+ mTuningMode(false),
+ mTuningEnergy(0.0f),
+ mTuningMicVolume(0),
+ mTuningMicVolumeDirty(true),
+ mTuningSpeakerVolume(50), // Set to 50 so the user can hear himself when he sets his mic volume
+ mTuningSpeakerVolumeDirty(true),
+ mDevicesListUpdated(false),
+
+ mAreaVoiceDisabled(false),
+ mAudioSession(), // TBD - should be NULL
+ mAudioSessionChanged(false),
+ mNextAudioSession(),
+
+ mCurrentParcelLocalID(0),
+ mConnectorEstablished(false),
+ mAccountLoggedIn(false),
+ mNumberOfAliases(0),
+ mCommandCookie(0),
+ mLoginRetryCount(0),
+
+ mBuddyListMapPopulated(false),
+ mBlockRulesListReceived(false),
+ mAutoAcceptRulesListReceived(false),
+
+ mCaptureDeviceDirty(false),
+ mRenderDeviceDirty(false),
+ mSpatialCoordsDirty(false),
+ mIsInitialized(false),
+
+ mMuteMic(false),
+ mMuteMicDirty(false),
+ mFriendsListDirty(true),
+
+ mEarLocation(0),
+ mSpeakerVolumeDirty(true),
+ mSpeakerMuteDirty(true),
+ mMicVolume(0),
+ mMicVolumeDirty(true),
+
+ mVoiceEnabled(false),
+ mWriteInProgress(false),
+
+ mLipSyncEnabled(false),
+
+ mVoiceFontsReceived(false),
+ mVoiceFontsNew(false),
+ mVoiceFontListDirty(false),
+
+ mCaptureBufferMode(false),
+ mCaptureBufferRecording(false),
+ mCaptureBufferRecorded(false),
+ mCaptureBufferPlaying(false),
+ mShutdownComplete(true),
+ mPlayRequestCount(0),
+
+ mAvatarNameCacheConnection(),
+ mIsInTuningMode(false),
+ mIsInChannel(false),
+ mIsJoiningSession(false),
+ mIsWaitingForFonts(false),
+ mIsLoggingIn(false),
+ mIsLoggedIn(false),
+ mIsProcessingChannels(false),
+ mIsCoroutineActive(false),
+ mVivoxPump("vivoxClientPump")
+{
+ sShuttingDown = false;
+ sConnected = false;
+ sPump = nullptr;
+
+ mSpeakerVolume = scale_speaker_volume(0);
+
+ mVoiceVersion.serverVersion = "";
+ mVoiceVersion.serverType = VOICE_SERVER_TYPE;
+
+ // gMuteListp isn't set up at this point, so we defer this until later.
+// gMuteListp->addObserver(&mutelist_listener);
+
+
+#if LL_DARWIN || LL_LINUX
+ // HACK: THIS DOES NOT BELONG HERE
+ // When the vivox daemon dies, the next write attempt on our socket generates a SIGPIPE, which kills us.
+ // This should cause us to ignore SIGPIPE and handle the error through proper channels.
+ // This should really be set up elsewhere. Where should it go?
+ signal(SIGPIPE, SIG_IGN);
+
+ // Since we're now launching the gateway with fork/exec instead of system(), we need to deal with zombie processes.
+ // Ignoring SIGCHLD should prevent zombies from being created. Alternately, we could use wait(), but I'd rather not do that.
+ signal(SIGCHLD, SIG_IGN);
+#endif
+
+
+ gIdleCallbacks.addFunction(idle, this);
+}
+
+//---------------------------------------------------
+
+LLVivoxVoiceClient::~LLVivoxVoiceClient()
+{
+ if (mAvatarNameCacheConnection.connected())
+ {
+ mAvatarNameCacheConnection.disconnect();
+ }
+ sShuttingDown = true;
+}
+
+//---------------------------------------------------
+
+void LLVivoxVoiceClient::init(LLPumpIO *pump)
+{
+ // constructor will set up LLVoiceClient::getInstance()
+ sPump = pump;
+
+// LLCoros::instance().launch("LLVivoxVoiceClient::voiceControlCoro",
+// boost::bind(&LLVivoxVoiceClient::voiceControlCoro, LLVivoxVoiceClient::getInstance()));
+
+}
+
+void LLVivoxVoiceClient::terminate()
+{
+ if (sShuttingDown)
+ {
+ return;
+ }
+
+ // needs to be done manually here since we will not get another pass in
+ // coroutines... that mechanism is long since gone.
+ if (mIsLoggedIn)
+ {
+ logoutOfVivox(false);
+ }
+
+ if(sConnected)
+ {
+ breakVoiceConnection(false);
+ sConnected = false;
+ }
+ else
+ {
+ mRelogRequested = false;
+ killGateway();
+ }
+
+ sShuttingDown = true;
+ sPump = NULL;
+}
+
+//---------------------------------------------------
+
+void LLVivoxVoiceClient::cleanUp()
+{
+ LL_DEBUGS("Voice") << LL_ENDL;
+
+ deleteAllSessions();
+ deleteAllVoiceFonts();
+ deleteVoiceFontTemplates();
+ LL_DEBUGS("Voice") << "exiting" << LL_ENDL;
+}
+
+//---------------------------------------------------
+
+const LLVoiceVersionInfo& LLVivoxVoiceClient::getVersion()
+{
+ return mVoiceVersion;
+}
+
+//---------------------------------------------------
+
+void LLVivoxVoiceClient::updateSettings()
+{
+ setVoiceEnabled(voiceEnabled());
+ setEarLocation(gSavedSettings.getS32("VoiceEarLocation"));
+
+ std::string inputDevice = gSavedSettings.getString("VoiceInputAudioDevice");
+ setCaptureDevice(inputDevice);
+ std::string outputDevice = gSavedSettings.getString("VoiceOutputAudioDevice");
+ setRenderDevice(outputDevice);
+ F32 mic_level = gSavedSettings.getF32("AudioLevelMic");
+ setMicGain(mic_level);
+ setLipSyncEnabled(gSavedSettings.getBOOL("LipSyncEnabled"));
+}
+
+/////////////////////////////
+// utility functions
+
+bool LLVivoxVoiceClient::writeString(const std::string &str)
+{
+ bool result = false;
+ LL_DEBUGS("LowVoice") << "sending:\n" << str << LL_ENDL;
+
+ if(sConnected)
+ {
+ apr_status_t err;
+ apr_size_t size = (apr_size_t)str.size();
+ apr_size_t written = size;
+
+ //MARK: Turn this on to log outgoing XML
+ // LL_DEBUGS("Voice") << "sending: " << str << LL_ENDL;
+
+ // check return code - sockets will fail (broken, etc.)
+ err = apr_socket_send(
+ mSocket->getSocket(),
+ (const char*)str.data(),
+ &written);
+
+ if(err == 0 && written == size)
+ {
+ // Success.
+ result = true;
+ }
+ else if (err == 0 && written != size) {
+ // Did a short write, log it for now
+ LL_WARNS("Voice") << ") short write on socket sending data to vivox daemon." << "Sent " << written << "bytes instead of " << size <<LL_ENDL;
+ }
+ else if(APR_STATUS_IS_EAGAIN(err))
+ {
+ char buf[MAX_STRING];
+ LL_WARNS("Voice") << "EAGAIN error " << err << " (" << apr_strerror(err, buf, MAX_STRING) << ") sending data to vivox daemon." << LL_ENDL;
+ }
+ else
+ {
+ // Assume any socket error means something bad. For now, just close the socket.
+ char buf[MAX_STRING];
+ LL_WARNS("Voice") << "apr error " << err << " ("<< apr_strerror(err, buf, MAX_STRING) << ") sending data to vivox daemon." << LL_ENDL;
+ daemonDied();
+ }
+ }
+
+ return result;
+}
+
+
+/////////////////////////////
+// session control messages
+void LLVivoxVoiceClient::connectorCreate()
+{
+ std::ostringstream stream;
+ std::string logdir = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "");
+
+ // Transition to stateConnectorStarted when the connector handle comes back.
+ std::string vivoxLogLevel = gSavedSettings.getString("VivoxDebugLevel");
+ if ( vivoxLogLevel.empty() )
+ {
+ vivoxLogLevel = "0";
+ }
+ LL_DEBUGS("Voice") << "creating connector with log level " << vivoxLogLevel << LL_ENDL;
+
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.Create.1\">"
+ << "<ClientName>V2 SDK</ClientName>"
+ << "<AccountManagementServer>" << mVoiceAccountServerURI << "</AccountManagementServer>"
+ << "<Mode>Normal</Mode>"
+ << "<ConnectorHandle>" << LLVivoxSecurity::getInstance()->connectorHandle() << "</ConnectorHandle>"
+ << "<Logging>"
+ << "<Folder>" << logdir << "</Folder>"
+ << "<FileNamePrefix>Connector</FileNamePrefix>"
+ << "<FileNameSuffix>.log</FileNameSuffix>"
+ << "<LogLevel>" << vivoxLogLevel << "</LogLevel>"
+ << "</Logging>"
+ << "<Application>" << LLVersionInfo::instance().getChannel() << " " << LLVersionInfo::instance().getVersion() << "</Application>"
+ //<< "<Application></Application>" //Name can cause problems per vivox.
+ << "<MaxCalls>12</MaxCalls>"
+ << "</Request>\n\n\n";
+
+ writeString(stream.str());
+}
+
+void LLVivoxVoiceClient::connectorShutdown()
+{
+ if(mConnectorEstablished)
+ {
+ std::ostringstream stream;
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.InitiateShutdown.1\">"
+ << "<ConnectorHandle>" << LLVivoxSecurity::getInstance()->connectorHandle() << "</ConnectorHandle>"
+ << "</Request>"
+ << "\n\n\n";
+
+ mShutdownComplete = false;
+ mConnectorEstablished = false;
+
+ writeString(stream.str());
+ }
+ else
+ {
+ mShutdownComplete = true;
+ }
+}
+
+void LLVivoxVoiceClient::userAuthorized(const std::string& user_id, const LLUUID &agentID)
+{
+
+ mAccountDisplayName = user_id;
+
+ LL_INFOS("Voice") << "name \"" << mAccountDisplayName << "\" , ID " << agentID << LL_ENDL;
+
+ mAccountName = nameFromID(agentID);
+}
+
+void LLVivoxVoiceClient::setLoginInfo(
+ const std::string& account_name,
+ const std::string& password,
+ const std::string& voice_sip_uri_hostname,
+ const std::string& voice_account_server_uri)
+{
+ mVoiceSIPURIHostName = voice_sip_uri_hostname;
+ mVoiceAccountServerURI = voice_account_server_uri;
+
+ if(mAccountLoggedIn)
+ {
+ // Already logged in.
+ LL_WARNS("Voice") << "Called while already logged in." << LL_ENDL;
+
+ // Don't process another login.
+ return;
+ }
+ else if ( account_name != mAccountName )
+ {
+ LL_WARNS("Voice") << "Mismatched account name! " << account_name
+ << " instead of " << mAccountName << LL_ENDL;
+ }
+ else
+ {
+ mAccountPassword = password;
+ }
+
+ std::string debugSIPURIHostName = gSavedSettings.getString("VivoxDebugSIPURIHostName");
+
+ if( !debugSIPURIHostName.empty() )
+ {
+ LL_INFOS("Voice") << "Overriding account server based on VivoxDebugSIPURIHostName: "
+ << debugSIPURIHostName << LL_ENDL;
+ mVoiceSIPURIHostName = debugSIPURIHostName;
+ }
+
+ if( mVoiceSIPURIHostName.empty() )
+ {
+ // we have an empty account server name
+ // so we fall back to hardcoded defaults
+
+ if(LLGridManager::getInstance()->isInProductionGrid())
+ {
+ // Use the release account server
+ mVoiceSIPURIHostName = "bhr.vivox.com";
+ }
+ else
+ {
+ // Use the development account server
+ mVoiceSIPURIHostName = "bhd.vivox.com";
+ }
+ LL_INFOS("Voice") << "Defaulting SIP URI host: "
+ << mVoiceSIPURIHostName << LL_ENDL;
+
+ }
+
+ std::string debugAccountServerURI = gSavedSettings.getString("VivoxDebugVoiceAccountServerURI");
+
+ if( !debugAccountServerURI.empty() )
+ {
+ LL_INFOS("Voice") << "Overriding account server based on VivoxDebugVoiceAccountServerURI: "
+ << debugAccountServerURI << LL_ENDL;
+ mVoiceAccountServerURI = debugAccountServerURI;
+ }
+
+ if( mVoiceAccountServerURI.empty() )
+ {
+ // If the account server URI isn't specified, construct it from the SIP URI hostname
+ mVoiceAccountServerURI = "https://www." + mVoiceSIPURIHostName + "/api2/";
+ LL_INFOS("Voice") << "Inferring account server based on SIP URI Host name: "
+ << mVoiceAccountServerURI << LL_ENDL;
+ }
+}
+
+void LLVivoxVoiceClient::idle(void* user_data)
+{
+}
+
+//=========================================================================
+// the following are methods to support the coroutine implementation of the
+// voice connection and processing. They should only be called in the context
+// of a coroutine.
+//
+//
+
+typedef enum e_voice_control_coro_state
+{
+ VOICE_STATE_ERROR = -1,
+ VOICE_STATE_DONE = 0,
+ VOICE_STATE_TP_WAIT, // entry point
+ VOICE_STATE_START_DAEMON,
+ VOICE_STATE_PROVISION_ACCOUNT,
+ VOICE_STATE_START_SESSION,
+ VOICE_STATE_SESSION_RETRY,
+ VOICE_STATE_SESSION_ESTABLISHED,
+ VOICE_STATE_WAIT_FOR_CHANNEL,
+ VOICE_STATE_DISCONNECT,
+ VOICE_STATE_WAIT_FOR_EXIT,
+} EVoiceControlCoroState;
+
+void LLVivoxVoiceClient::voiceControlCoro()
+{
+ int state = 0;
+ try
+ {
+ // state is passed as a reference instead of being
+ // a member due to unresolved issues with coroutine
+ // surviving longer than LLVivoxVoiceClient
+ voiceControlStateMachine(state);
+ }
+ catch (const LLCoros::Stop&)
+ {
+ LL_DEBUGS("LLVivoxVoiceClient") << "Received a shutdown exception" << LL_ENDL;
+ }
+ catch (const LLContinueError&)
+ {
+ LOG_UNHANDLED_EXCEPTION("LLVivoxVoiceClient");
+ }
+ catch (...)
+ {
+ // Ideally for Windows need to log SEH exception instead or to set SEH
+ // handlers but bugsplat shows local variables for windows, which should
+ // be enough
+ LL_WARNS("Voice") << "voiceControlStateMachine crashed in state " << state << LL_ENDL;
+ throw;
+ }
+}
+
+void LLVivoxVoiceClient::voiceControlStateMachine(S32 &coro_state)
+{
+ if (sShuttingDown)
+ {
+ return;
+ }
+
+ LL_DEBUGS("Voice") << "starting" << LL_ENDL;
+ mIsCoroutineActive = true;
+ LLCoros::set_consuming(true);
+
+ U32 retry = 0;
+
+ coro_state = VOICE_STATE_TP_WAIT;
+
+ do
+ {
+ if (sShuttingDown)
+ {
+ // Vivox singleton performed the exit, logged out,
+ // cleaned sockets, gateway and no longer cares
+ // about state of coroutine, so just stop
+ return;
+ }
+
+ switch (coro_state)
+ {
+ case VOICE_STATE_TP_WAIT:
+ // starting point for voice
+ if (gAgent.getTeleportState() != LLAgent::TELEPORT_NONE)
+ {
+ LL_DEBUGS("Voice") << "Suspending voiceControlCoro() momentarily for teleport. Tuning: " << mTuningMode << ". Relog: " << mRelogRequested << LL_ENDL;
+ llcoro::suspendUntilTimeout(1.0);
+ }
+ else
+ {
+ coro_state = VOICE_STATE_START_DAEMON;
+ }
+ break;
+
+ case VOICE_STATE_START_DAEMON:
+ LL_DEBUGS("Voice") << "Launching daemon" << LL_ENDL;
+ LLVoiceVivoxStats::getInstance()->reset();
+ if (startAndLaunchDaemon())
+ {
+ coro_state = VOICE_STATE_PROVISION_ACCOUNT;
+ }
+ else
+ {
+ coro_state = VOICE_STATE_SESSION_RETRY;
+ }
+ break;
+
+ case VOICE_STATE_PROVISION_ACCOUNT:
+ if (provisionVoiceAccount())
+ {
+ coro_state = VOICE_STATE_START_SESSION;
+ }
+ else
+ {
+ coro_state = VOICE_STATE_SESSION_RETRY;
+ }
+ break;
+
+ case VOICE_STATE_START_SESSION:
+ if (establishVoiceConnection())
+ {
+ coro_state = VOICE_STATE_SESSION_ESTABLISHED;
+ }
+ else
+ {
+ coro_state = VOICE_STATE_SESSION_RETRY;
+ }
+ break;
+
+ case VOICE_STATE_SESSION_RETRY:
+ giveUp(); // cleans sockets and session
+ if (mRelogRequested)
+ {
+ // We failed to connect, give it a bit time before retrying.
+ retry++;
+ F32 full_delay = llmin(5.f * (F32)retry, 60.f);
+ F32 current_delay = 0.f;
+ LL_INFOS("Voice") << "Voice failed to establish session after " << retry
+ << " tries. Will attempt to reconnect in " << full_delay
+ << " seconds" << LL_ENDL;
+ while (current_delay < full_delay && !sShuttingDown)
+ {
+ // Assuming that a second has passed is not accurate,
+ // but we don't need accurancy here, just to make sure
+ // that some time passed and not to outlive voice itself
+ current_delay++;
+ llcoro::suspendUntilTimeout(1.f);
+ }
+ coro_state = VOICE_STATE_WAIT_FOR_EXIT;
+ }
+ else
+ {
+ coro_state = VOICE_STATE_DONE;
+ }
+ break;
+
+ case VOICE_STATE_SESSION_ESTABLISHED:
+ {
+ // enable/disable the automatic VAD and explicitly set the initial values of
+ // the VAD variables ourselves when it is off - see SL-15072 for more details
+ // note: we set the other parameters too even if the auto VAD is on which is ok
+ unsigned int vad_auto = gSavedSettings.getU32("VivoxVadAuto");
+ unsigned int vad_hangover = gSavedSettings.getU32("VivoxVadHangover");
+ unsigned int vad_noise_floor = gSavedSettings.getU32("VivoxVadNoiseFloor");
+ unsigned int vad_sensitivity = gSavedSettings.getU32("VivoxVadSensitivity");
+ setupVADParams(vad_auto, vad_hangover, vad_noise_floor, vad_sensitivity);
+
+ // watch for changes to the VAD settings via Debug Settings UI and act on them accordingly
+ gSavedSettings.getControl("VivoxVadAuto")->getSignal()->connect(boost::bind(&LLVivoxVoiceClient::onVADSettingsChange, this));
+ gSavedSettings.getControl("VivoxVadHangover")->getSignal()->connect(boost::bind(&LLVivoxVoiceClient::onVADSettingsChange, this));
+ gSavedSettings.getControl("VivoxVadNoiseFloor")->getSignal()->connect(boost::bind(&LLVivoxVoiceClient::onVADSettingsChange, this));
+ gSavedSettings.getControl("VivoxVadSensitivity")->getSignal()->connect(boost::bind(&LLVivoxVoiceClient::onVADSettingsChange, this));
+
+ if (mTuningMode)
+ {
+ performMicTuning();
+ }
+
+ coro_state = VOICE_STATE_WAIT_FOR_CHANNEL;
+ }
+ break;
+
+ case VOICE_STATE_WAIT_FOR_CHANNEL:
+ waitForChannel(); // todo: split into more states like login/fonts
+ coro_state = VOICE_STATE_DISCONNECT;
+ break;
+
+ case VOICE_STATE_DISCONNECT:
+ LL_DEBUGS("Voice") << "lost channel RelogRequested=" << mRelogRequested << LL_ENDL;
+ endAndDisconnectSession();
+ retry = 0; // Connected without issues
+ coro_state = VOICE_STATE_WAIT_FOR_EXIT;
+ break;
+
+ case VOICE_STATE_WAIT_FOR_EXIT:
+ if (isGatewayRunning())
+ {
+ LL_INFOS("Voice") << "waiting for SLVoice to exit" << LL_ENDL;
+ llcoro::suspendUntilTimeout(1.0);
+ }
+ else if (mRelogRequested && mVoiceEnabled)
+ {
+ LL_INFOS("Voice") << "will attempt to reconnect to voice" << LL_ENDL;
+ coro_state = VOICE_STATE_TP_WAIT;
+ }
+ else
+ {
+ coro_state = VOICE_STATE_DONE;
+ }
+ break;
+
+ case VOICE_STATE_DONE:
+ break;
+ }
+ } while (coro_state > 0);
+
+ if (sShuttingDown)
+ {
+ // LLVivoxVoiceClient might be already dead
+ return;
+ }
+
+ mIsCoroutineActive = false;
+ LL_INFOS("Voice") << "exiting" << LL_ENDL;
+}
+
+bool LLVivoxVoiceClient::endAndDisconnectSession()
+{
+ LL_DEBUGS("Voice") << LL_ENDL;
+
+ breakVoiceConnection(true);
+
+ killGateway();
+
+ return true;
+}
+
+bool LLVivoxVoiceClient::callbackEndDaemon(const LLSD& data)
+{
+ if (!sShuttingDown && mVoiceEnabled)
+ {
+ LL_WARNS("Voice") << "SLVoice terminated " << ll_stream_notation_sd(data) << LL_ENDL;
+ terminateAudioSession(false);
+ closeSocket();
+ cleanUp();
+ LLVoiceClient::getInstance()->setUserPTTState(false);
+ gAgent.setVoiceConnected(false);
+ mRelogRequested = true;
+ }
+ sGatewayPump.stopListening("VivoxDaemonPump");
+ return false;
+}
+
+bool LLVivoxVoiceClient::startAndLaunchDaemon()
+{
+ //---------------------------------------------------------------------
+ if (!voiceEnabled())
+ {
+ // Voice is locked out, we must not launch the vivox daemon.
+ LL_WARNS("Voice") << "voice disabled; not starting daemon" << LL_ENDL;
+ return false;
+ }
+
+ if (!isGatewayRunning())
+ {
+#ifndef VIVOXDAEMON_REMOTEHOST
+ // Launch the voice daemon
+#if LL_WINDOWS
+ // On windows use exe (not work or RO) directory
+ std::string exe_path = gDirUtilp->getExecutableDir();
+ gDirUtilp->append(exe_path, "SLVoice.exe");
+#elif LL_DARWIN
+ // On MAC use resource directory
+ std::string exe_path = gDirUtilp->getAppRODataDir();
+ gDirUtilp->append(exe_path, "SLVoice");
+#else
+ std::string exe_path = gDirUtilp->getExecutableDir();
+ gDirUtilp->append(exe_path, "SLVoice");
+#endif
+ // See if the vivox executable exists
+ llstat s;
+ if (!LLFile::stat(exe_path, &s))
+ {
+ // vivox executable exists. Build the command line and launch the daemon.
+ LLProcess::Params params;
+ params.executable = exe_path;
+
+ // VOICE-88: Cycle through [portbase..portbase+portrange) on
+ // successive tries because attempting to relaunch (after manually
+ // disabling and then re-enabling voice) with the same port can
+ // cause SLVoice's bind() call to fail with EADDRINUSE. We expect
+ // that eventually the OS will time out previous ports, which is
+ // why we cycle instead of incrementing indefinitely.
+
+ static LLCachedControl<U32> portbase(gSavedSettings, "VivoxVoicePort");
+ static LLCachedControl<std::string> host(gSavedSettings, "VivoxVoiceHost");
+ static LLCachedControl<std::string> loglevel(gSavedSettings, "VivoxDebugLevel");
+ static LLCachedControl<std::string> log_folder(gSavedSettings, "VivoxLogDirectory");
+ static LLCachedControl<std::string> shutdown_timeout(gSavedSettings, "VivoxShutdownTimeout");
+ static const U32 portrange = 100;
+ static U32 portoffset = 0;
+ U32 port = 0;
+
+ if (LLAppViewer::instance()->isSecondInstance())
+ {
+ // Ideally need to know amount of instances and
+ // to increment instance_offset on EADDRINUSE.
+ // But for now just use rand
+ static U32 instance_offset = portrange * ll_rand(20);
+ port = portbase + portoffset + instance_offset;
+ }
+ else
+ {
+ // leave main thread with exclusive port set
+ port = portbase + portoffset;
+ }
+ portoffset = (portoffset + 1) % portrange;
+ params.args.add("-i");
+ params.args.add(STRINGIZE(host() << ':' << port));
+
+ params.args.add("-ll");
+ if (loglevel().empty())
+ {
+ params.args.add("0");
+ }
+ else
+ {
+ params.args.add(loglevel);
+ }
+
+ params.args.add("-lf");
+ if (log_folder().empty())
+ {
+ params.args.add(gDirUtilp->getExpandedFilename(LL_PATH_LOGS, ""));
+ }
+ else
+ {
+ params.args.add(log_folder);
+ }
+
+ // set log file basename and .log
+ params.args.add("-lp");
+ params.args.add("SLVoice");
+ params.args.add("-ls");
+ params.args.add(".log");
+
+ // rotate any existing log
+ std::string new_log = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "SLVoice.log");
+ std::string old_log = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "SLVoice.old");
+ if (gDirUtilp->fileExists(new_log))
+ {
+ LLFile::rename(new_log, old_log);
+ }
+
+ if (!shutdown_timeout().empty())
+ {
+ params.args.add("-st");
+ params.args.add(shutdown_timeout);
+ }
+ params.cwd = gDirUtilp->getAppRODataDir();
+
+# ifdef VIVOX_HANDLE_ARGS
+ params.args.add("-ah");
+ params.args.add(LLVivoxSecurity::getInstance()->accountHandle());
+
+ params.args.add("-ch");
+ params.args.add(LLVivoxSecurity::getInstance()->connectorHandle());
+# endif // VIVOX_HANDLE_ARGS
+
+ params.postend = sGatewayPump.getName();
+ sGatewayPump.listen("VivoxDaemonPump", boost::bind(&LLVivoxVoiceClient::callbackEndDaemon, this, _1));
+
+ LL_INFOS("Voice") << "Launching SLVoice" << LL_ENDL;
+ LL_DEBUGS("Voice") << "SLVoice params " << params << LL_ENDL;
+
+ sGatewayPtr = LLProcess::create(params);
+
+ mDaemonHost = LLHost(host().c_str(), port);
+ }
+ else
+ {
+ LL_WARNS("Voice") << exe_path << " not found." << LL_ENDL;
+ return false;
+ }
+#else
+ // SLIM SDK: port changed from 44124 to 44125.
+ // We can connect to a client gateway running on another host. This is useful for testing.
+ // To do this, launch the gateway on a nearby host like this:
+ // vivox-gw.exe -p tcp -i 0.0.0.0:44125
+ // and put that host's IP address here.
+ mDaemonHost = LLHost(gSavedSettings.getString("VivoxVoiceHost"), gSavedSettings.getU32("VivoxVoicePort"));
+#endif
+
+ // Dirty the states we'll need to sync with the daemon when it comes up.
+ mMuteMicDirty = true;
+ mMicVolumeDirty = true;
+ mSpeakerVolumeDirty = true;
+ mSpeakerMuteDirty = true;
+ // These only need to be set if they're not default (i.e. empty string).
+ mCaptureDeviceDirty = !mCaptureDevice.empty();
+ mRenderDeviceDirty = !mRenderDevice.empty();
+
+ mMainSessionGroupHandle.clear();
+ }
+ else
+ {
+ LL_DEBUGS("Voice") << " gateway running; not attempting to start" << LL_ENDL;
+ }
+
+ //---------------------------------------------------------------------
+ llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS);
+
+ LL_DEBUGS("Voice") << "Connecting to vivox daemon:" << mDaemonHost << LL_ENDL;
+
+ int retryCount(0);
+ LLVoiceVivoxStats::getInstance()->reset();
+ while (!sConnected && !sShuttingDown && retryCount++ <= DAEMON_CONNECT_RETRY_MAX)
+ {
+ LLVoiceVivoxStats::getInstance()->connectionAttemptStart();
+ LL_DEBUGS("Voice") << "Attempting to connect to vivox daemon: " << mDaemonHost << LL_ENDL;
+ closeSocket();
+ if (!mSocket)
+ {
+ mSocket = LLSocket::create(gAPRPoolp, LLSocket::STREAM_TCP);
+ }
+
+ sConnected = mSocket->blockingConnect(mDaemonHost);
+ LLVoiceVivoxStats::getInstance()->connectionAttemptEnd(sConnected);
+ if (!sConnected)
+ {
+ llcoro::suspendUntilTimeout(DAEMON_CONNECT_THROTTLE_SECONDS);
+ }
+ }
+
+ //---------------------------------------------------------------------
+ if (sShuttingDown && !sConnected)
+ {
+ return false;
+ }
+
+ llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS);
+
+ while (!sPump && !sShuttingDown)
+ { // Can't use the pump until we have it available.
+ llcoro::suspend();
+ }
+
+ if (sShuttingDown)
+ {
+ return false;
+ }
+
+ // MBW -- Note to self: pumps and pipes examples in
+ // indra/test/io.cpp
+ // indra/test/llpipeutil.{cpp|h}
+
+ // Attach the pumps and pipes
+
+ LLPumpIO::chain_t readChain;
+
+ readChain.push_back(LLIOPipe::ptr_t(new LLIOSocketReader(mSocket)));
+ readChain.push_back(LLIOPipe::ptr_t(new LLVivoxProtocolParser()));
+
+
+ sPump->addChain(readChain, NEVER_CHAIN_EXPIRY_SECS);
+
+
+ //---------------------------------------------------------------------
+ llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS);
+
+ // Initial devices query
+ getCaptureDevicesSendMessage();
+ getRenderDevicesSendMessage();
+
+ mLoginRetryCount = 0;
+
+ return true;
+}
+
+bool LLVivoxVoiceClient::provisionVoiceAccount()
+{
+ LL_INFOS("Voice") << "Provisioning voice account." << LL_ENDL;
+
+ while ((!gAgent.getRegion() || !gAgent.getRegion()->capabilitiesReceived()) && !sShuttingDown)
+ {
+ LL_DEBUGS("Voice") << "no capabilities for voice provisioning; waiting " << LL_ENDL;
+ // *TODO* Pump a message for wake up.
+ llcoro::suspend();
+ }
+
+ if (sShuttingDown)
+ {
+ return false;
+ }
+
+ std::string url = gAgent.getRegionCapability("ProvisionVoiceAccountRequest");
+
+ LL_DEBUGS("Voice") << "region ready for voice provisioning; url=" << url << LL_ENDL;
+
+ LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
+ LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t
+ httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("voiceAccountProvision", httpPolicy));
+ LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest);
+ LLCore::HttpOptions::ptr_t httpOpts = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions);
+ int retryCount(0);
+
+ LLSD result;
+ bool provisioned = false;
+ do
+ {
+ LLVoiceVivoxStats::getInstance()->provisionAttemptStart();
+ result = httpAdapter->postAndSuspend(httpRequest, url, LLSD(), httpOpts);
+
+ if (sShuttingDown)
+ {
+ return false;
+ }
+
+ LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
+ LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
+
+ if (status == LLCore::HttpStatus(404))
+ {
+ F32 timeout = pow(PROVISION_RETRY_TIMEOUT, static_cast<float>(retryCount));
+ LL_WARNS("Voice") << "Provision CAP 404. Retrying in " << timeout << " seconds. Retries: " << (S32)retryCount << LL_ENDL;
+ llcoro::suspendUntilTimeout(timeout);
+
+ if (sShuttingDown)
+ {
+ return false;
+ }
+ }
+ else if (!status)
+ {
+ LL_WARNS("Voice") << "Unable to provision voice account." << LL_ENDL;
+ LLVoiceVivoxStats::getInstance()->provisionAttemptEnd(false);
+ return false;
+ }
+ else
+ {
+ provisioned = true;
+ }
+ } while (!provisioned && ++retryCount <= PROVISION_RETRY_MAX && !sShuttingDown);
+
+ if (sShuttingDown && !provisioned)
+ {
+ return false;
+ }
+
+ LLVoiceVivoxStats::getInstance()->provisionAttemptEnd(provisioned);
+ if (!provisioned)
+ {
+ LL_WARNS("Voice") << "Could not access voice provision cap after " << retryCount << " attempts." << LL_ENDL;
+ return false;
+ }
+
+ std::string voiceSipUriHostname;
+ std::string voiceAccountServerUri;
+ std::string voiceUserName = result["username"].asString();
+ std::string voicePassword = result["password"].asString();
+
+ if (result.has("voice_sip_uri_hostname"))
+ {
+ voiceSipUriHostname = result["voice_sip_uri_hostname"].asString();
+ }
+
+ // this key is actually misnamed -- it will be an entire URI, not just a hostname.
+ if (result.has("voice_account_server_name"))
+ {
+ voiceAccountServerUri = result["voice_account_server_name"].asString();
+ }
+
+ LL_DEBUGS("Voice") << "ProvisionVoiceAccountRequest response"
+ << " user " << (voiceUserName.empty() ? "not set" : "set")
+ << " password " << (voicePassword.empty() ? "not set" : "set")
+ << " sip uri " << voiceSipUriHostname
+ << " account uri " << voiceAccountServerUri
+ << LL_ENDL;
+
+ setLoginInfo(voiceUserName, voicePassword, voiceSipUriHostname, voiceAccountServerUri);
+
+ return true;
+}
+
+bool LLVivoxVoiceClient::establishVoiceConnection()
+{
+ if (!mVoiceEnabled && mIsInitialized)
+ {
+ LL_WARNS("Voice") << "cannot establish connection; enabled "<<mVoiceEnabled<<" initialized "<<mIsInitialized<<LL_ENDL;
+ return false;
+ }
+
+ if (sShuttingDown)
+ {
+ return false;
+ }
+
+ LLSD result;
+ bool connected(false);
+ bool giving_up(false);
+ int retries = 0;
+ LL_INFOS("Voice") << "Requesting connection to voice service" << LL_ENDL;
+
+ LLVoiceVivoxStats::getInstance()->establishAttemptStart();
+ connectorCreate();
+ do
+ {
+ result = llcoro::suspendUntilEventOn(mVivoxPump);
+ LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL;
+
+ if (result.has("connector"))
+ {
+ connected = LLSD::Boolean(result["connector"]);
+ LLVoiceVivoxStats::getInstance()->establishAttemptEnd(connected);
+ if (!connected)
+ {
+ if (result.has("retry") && ++retries <= CONNECT_RETRY_MAX && !sShuttingDown)
+ {
+ F32 timeout = LLSD::Real(result["retry"]);
+ timeout *= retries;
+ LL_INFOS("Voice") << "Retry connection to voice service in " << timeout << " seconds" << LL_ENDL;
+ llcoro::suspendUntilTimeout(timeout);
+
+ if (mVoiceEnabled) // user may have switched it off
+ {
+ // try again
+ LLVoiceVivoxStats::getInstance()->establishAttemptStart();
+ connectorCreate();
+ }
+ else
+ {
+ // stop if they've turned off voice
+ giving_up = true;
+ }
+ }
+ else
+ {
+ giving_up=true;
+ }
+ }
+ }
+ LL_DEBUGS("Voice") << (connected ? "" : "not ") << "connected, "
+ << (giving_up ? "" : "not ") << "giving up"
+ << LL_ENDL;
+ } while (!connected && !giving_up && !sShuttingDown);
+
+ if (giving_up)
+ {
+ LLSD args;
+ args["HOSTID"] = LLURI(mVoiceAccountServerURI).authority();
+ LLNotificationsUtil::add("NoVoiceConnect", args);
+ }
+
+ return connected;
+}
+
+bool LLVivoxVoiceClient::breakVoiceConnection(bool corowait)
+{
+ LL_DEBUGS("Voice") << "( wait=" << corowait << ")" << LL_ENDL;
+ bool retval(true);
+
+ mShutdownComplete = false;
+ connectorShutdown();
+
+ if (corowait)
+ {
+ LLSD timeoutResult(LLSDMap("connector", "timeout"));
+
+ LLSD result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, LOGOUT_ATTEMPT_TIMEOUT, timeoutResult);
+ LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL;
+
+ retval = result.has("connector");
+ }
+ else
+ {
+ mRelogRequested = false; //stop the control coro
+ // If we are not doing a corowait then we must sleep until the connector has responded
+ // otherwise we may very well close the socket too early.
+#if LL_WINDOWS
+ if (!mShutdownComplete)
+ {
+ // The situation that brings us here is a call from ::terminate()
+ // At this point message system is already down so we can't wait for
+ // the message, yet we need to receive "connector shutdown response".
+ // Either wait a bit and emulate it or check gMessageSystem for specific message
+ _sleep(1000);
+ if (sConnected)
+ {
+ sConnected = false;
+ LLSD vivoxevent(LLSDMap("connector", LLSD::Boolean(false)));
+ mVivoxPump.post(vivoxevent);
+ }
+ mShutdownComplete = true;
+ }
+#endif
+ }
+
+ LL_DEBUGS("Voice") << "closing SLVoice socket" << LL_ENDL;
+ closeSocket(); // Need to do this now -- bad things happen if the destructor does it later.
+ cleanUp();
+ sConnected = false;
+
+ return retval;
+}
+
+bool LLVivoxVoiceClient::loginToVivox()
+{
+ LLSD timeoutResult(LLSDMap("login", "timeout"));
+
+ int loginRetryCount(0);
+
+ bool response_ok(false);
+ bool account_login(false);
+ bool send_login(true);
+
+ do
+ {
+ mIsLoggingIn = true;
+ if (send_login)
+ {
+ loginSendMessage();
+ send_login = false;
+ }
+
+ LLSD result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, LOGIN_ATTEMPT_TIMEOUT, timeoutResult);
+
+ if (sShuttingDown)
+ {
+ return false;
+ }
+
+ LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL;
+
+ if (result.has("login"))
+ {
+ std::string loginresp = result["login"];
+
+ if (((loginresp == "retry") || (loginresp == "timeout")) && !sShuttingDown)
+ {
+ LL_WARNS("Voice") << "login failed with status '" << loginresp << "' "
+ << " count " << loginRetryCount << "/" << LOGIN_RETRY_MAX
+ << LL_ENDL;
+ if (++loginRetryCount > LOGIN_RETRY_MAX)
+ {
+ // We've run out of retries - tell the user
+ LL_WARNS("Voice") << "too many login retries (" << loginRetryCount << "); giving up." << LL_ENDL;
+ LLSD args;
+ args["HOSTID"] = LLURI(mVoiceAccountServerURI).authority();
+ mTerminateDaemon = true;
+ LLNotificationsUtil::add("NoVoiceConnect", args);
+
+ mIsLoggingIn = false;
+ return false;
+ }
+ response_ok = false;
+ account_login = false;
+ send_login = true;
+
+ // an exponential backoff gets too long too quickly; stretch it out, but not too much
+ F32 timeout = loginRetryCount * LOGIN_ATTEMPT_TIMEOUT;
+
+ // tell the user there is a problem
+ LL_WARNS("Voice") << "login " << loginresp << " will retry login in " << timeout << " seconds." << LL_ENDL;
+
+ if (!sShuttingDown)
+ {
+ // Todo: this is way to long, viewer can get stuck waiting during shutdown
+ // either make it listen to pump or split in smaller waits with checks for shutdown
+ llcoro::suspendUntilTimeout(timeout);
+ }
+ }
+ else if (loginresp == "failed")
+ {
+ mIsLoggingIn = false;
+ return false;
+ }
+ else if (loginresp == "response_ok")
+ {
+ response_ok = true;
+ }
+ else if (loginresp == "account_login")
+ {
+ account_login = true;
+ }
+ else if (sShuttingDown)
+ {
+ mIsLoggingIn = false;
+ return false;
+ }
+ }
+
+ } while ((!response_ok || !account_login) && !sShuttingDown);
+
+ if (sShuttingDown)
+ {
+ return false;
+ }
+
+ mRelogRequested = false;
+ mIsLoggedIn = true;
+ notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LOGGED_IN);
+
+ // Set up the mute list observer if it hasn't been set up already.
+ if ((!sMuteListListener_listening))
+ {
+ LLMuteList::getInstance()->addObserver(&mutelist_listener);
+ sMuteListListener_listening = true;
+ }
+
+ // Set the initial state of mic mute, local speaker volume, etc.
+ sendLocalAudioUpdates();
+ mIsLoggingIn = false;
+
+ return true;
+}
+
+void LLVivoxVoiceClient::logoutOfVivox(bool wait)
+{
+ if (mIsLoggedIn)
+ {
+ // Ensure that we'll re-request provisioning before logging in again
+ mAccountPassword.clear();
+ mVoiceAccountServerURI.clear();
+
+ logoutSendMessage();
+
+ if (wait)
+ {
+ LLSD timeoutResult(LLSDMap("logout", "timeout"));
+ LLSD result;
+
+ do
+ {
+ LL_DEBUGS("Voice")
+ << "waiting for logout response on "
+ << mVivoxPump.getName()
+ << LL_ENDL;
+
+ result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, LOGOUT_ATTEMPT_TIMEOUT, timeoutResult);
+
+ if (sShuttingDown)
+ {
+ break;
+ }
+
+ LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL;
+ // Don't get confused by prior queued events -- note that it's
+ // very important that mVivoxPump is an LLEventMailDrop, which
+ // does queue events.
+ } while (! result["logout"]);
+ }
+ else
+ {
+ LL_DEBUGS("Voice") << "not waiting for logout" << LL_ENDL;
+ }
+
+ mIsLoggedIn = false;
+ }
+}
+
+
+bool LLVivoxVoiceClient::retrieveVoiceFonts()
+{
+ // Request the set of available voice fonts.
+ refreshVoiceEffectLists(true);
+
+ mIsWaitingForFonts = true;
+ LLSD result;
+ do
+ {
+ result = llcoro::suspendUntilEventOn(mVivoxPump);
+
+ LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL;
+ if (result.has("voice_fonts"))
+ break;
+ } while (true);
+ mIsWaitingForFonts = false;
+
+ mVoiceFontExpiryTimer.start();
+ mVoiceFontExpiryTimer.setTimerExpirySec(VOICE_FONT_EXPIRY_INTERVAL);
+
+ return result["voice_fonts"].asBoolean();
+}
+
+bool LLVivoxVoiceClient::requestParcelVoiceInfo()
+{
+ //_INFOS("Voice") << "Requesting voice info for Parcel" << LL_ENDL;
+
+ LLViewerRegion * region = gAgent.getRegion();
+ if (region == NULL || !region->capabilitiesReceived())
+ {
+ LL_DEBUGS("Voice") << "ParcelVoiceInfoRequest capability not yet available, deferring" << LL_ENDL;
+ return false;
+ }
+
+ // grab the cap.
+ std::string url = gAgent.getRegion()->getCapability("ParcelVoiceInfoRequest");
+ if (url.empty())
+ {
+ // Region dosn't have the cap. Stop probing.
+ LL_DEBUGS("Voice") << "ParcelVoiceInfoRequest capability not available in this region" << LL_ENDL;
+ return false;
+ }
+
+ // update the parcel
+ checkParcelChanged(true);
+
+ LL_DEBUGS("Voice") << "sending ParcelVoiceInfoRequest (" << mCurrentRegionName << ", " << mCurrentParcelLocalID << ")" << LL_ENDL;
+
+ LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
+ LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t
+ httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("parcelVoiceInfoRequest", httpPolicy));
+ LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest);
+
+ LLSD result = httpAdapter->postAndSuspend(httpRequest, url, LLSD());
+
+ if (sShuttingDown)
+ {
+ return false;
+ }
+
+ LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
+ LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
+
+ if (mSessionTerminateRequested || (!mVoiceEnabled && mIsInitialized))
+ {
+ // if a terminate request has been received,
+ // bail and go to the stateSessionTerminated
+ // state. If the cap request is still pending,
+ // the responder will check to see if we've moved
+ // to a new session and won't change any state.
+ LL_DEBUGS("Voice") << "terminate requested " << mSessionTerminateRequested
+ << " enabled " << mVoiceEnabled
+ << " initialized " << mIsInitialized
+ << LL_ENDL;
+ terminateAudioSession(true);
+ return false;
+ }
+
+ if ((!status) || (mSessionTerminateRequested || (!mVoiceEnabled && mIsInitialized)))
+ {
+ if (mSessionTerminateRequested || (!mVoiceEnabled && mIsInitialized))
+ {
+ LL_WARNS("Voice") << "Session terminated." << LL_ENDL;
+ }
+
+ LL_WARNS("Voice") << "No voice on parcel" << LL_ENDL;
+ sessionTerminate();
+ return false;
+ }
+
+ std::string uri;
+ std::string credentials;
+
+ if (result.has("voice_credentials"))
+ {
+ LLSD voice_credentials = result["voice_credentials"];
+ if (voice_credentials.has("channel_uri"))
+ {
+ LL_DEBUGS("Voice") << "got voice channel uri" << LL_ENDL;
+ uri = voice_credentials["channel_uri"].asString();
+ }
+ else
+ {
+ LL_WARNS("Voice") << "No voice channel uri" << LL_ENDL;
+ }
+
+ if (voice_credentials.has("channel_credentials"))
+ {
+ LL_DEBUGS("Voice") << "got voice channel credentials" << LL_ENDL;
+ credentials =
+ voice_credentials["channel_credentials"].asString();
+ }
+ else
+ {
+ LLVoiceChannel* channel = LLVoiceChannel::getCurrentVoiceChannel();
+ if (channel != NULL)
+ {
+ if (channel->getSessionName().empty() && channel->getSessionID().isNull())
+ {
+ if (LLViewerParcelMgr::getInstance()->allowAgentVoice())
+ {
+ LL_WARNS("Voice") << "No channel credentials for default channel" << LL_ENDL;
+ }
+ }
+ else
+ {
+ LL_WARNS("Voice") << "No voice channel credentials" << LL_ENDL;
+ }
+ }
+ }
+ }
+ else
+ {
+ if (LLViewerParcelMgr::getInstance()->allowAgentVoice())
+ {
+ LL_WARNS("Voice") << "No voice credentials" << LL_ENDL;
+ }
+ else
+ {
+ LL_DEBUGS("Voice") << "No voice credentials" << LL_ENDL;
+ }
+ }
+
+ // set the spatial channel. If no voice credentials or uri are
+ // available, then we simply drop out of voice spatially.
+ return !setSpatialChannel(uri, credentials);
+}
+
+bool LLVivoxVoiceClient::addAndJoinSession(const sessionStatePtr_t &nextSession)
+{
+ mIsJoiningSession = true;
+
+ sessionStatePtr_t oldSession = mAudioSession;
+
+ LL_INFOS("Voice") << "Adding or joining voice session " << nextSession->mHandle << LL_ENDL;
+
+ mAudioSession = nextSession;
+ mAudioSessionChanged = true;
+ if (!mAudioSession || !mAudioSession->mReconnect)
+ {
+ mNextAudioSession.reset();
+ }
+
+ // The old session may now need to be deleted.
+ reapSession(oldSession);
+
+ if (mAudioSession)
+ {
+ if (!mAudioSession->mHandle.empty())
+ {
+ // Connect to a session by session handle
+
+ sessionMediaConnectSendMessage(mAudioSession);
+ }
+ else
+ {
+ // Connect to a session by URI
+ sessionCreateSendMessage(mAudioSession, true, false);
+ }
+ }
+
+ notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINING);
+
+ llcoro::suspend();
+
+ if (sShuttingDown)
+ {
+ return false;
+ }
+
+ LLSD result;
+
+ if (mSpatialJoiningNum == MAX_NORMAL_JOINING_SPATIAL_NUM)
+ {
+ // Notify observers to let them know there is problem with voice
+ notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED);
+ LL_WARNS() << "There seems to be problem with connection to voice server. Disabling voice chat abilities." << LL_ENDL;
+ }
+
+ // Increase mSpatialJoiningNum only for spatial sessions- it's normal to reach this case for
+ // example for p2p many times while waiting for response, so it can't be used to detect errors
+ if (mAudioSession && mAudioSession->mIsSpatial)
+ {
+ mSpatialJoiningNum++;
+ }
+
+ if (!mVoiceEnabled && mIsInitialized)
+ {
+ LL_DEBUGS("Voice") << "Voice no longer enabled. Exiting"
+ << " enabled " << mVoiceEnabled
+ << " initialized " << mIsInitialized
+ << LL_ENDL;
+ mIsJoiningSession = false;
+ // User bailed out during connect -- jump straight to teardown.
+ terminateAudioSession(true);
+ notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED);
+ return false;
+ }
+ else if (mSessionTerminateRequested)
+ {
+ LL_DEBUGS("Voice") << "Terminate requested" << LL_ENDL;
+ if (mAudioSession && !mAudioSession->mHandle.empty())
+ {
+ // Only allow direct exits from this state in p2p calls (for cancelling an invite).
+ // Terminating a half-connected session on other types of calls seems to break something in the vivox gateway.
+ if (mAudioSession->mIsP2P)
+ {
+ terminateAudioSession(true);
+ mIsJoiningSession = false;
+ notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL);
+ return false;
+ }
+ }
+ }
+
+ bool added(true);
+ bool joined(false);
+
+ LLSD timeoutResult(LLSDMap("session", "timeout"));
+
+ // We are about to start a whole new session. Anything that MIGHT still be in our
+ // maildrop is going to be stale and cause us much wailing and gnashing of teeth.
+ // Just flush it all out and start new.
+ mVivoxPump.discard();
+
+ // It appears that I need to wait for BOTH the SessionGroup.AddSession response and the SessionStateChangeEvent with state 4
+ // before continuing from this state. They can happen in either order, and if I don't wait for both, things can get stuck.
+ // For now, the SessionGroup.AddSession response handler sets mSessionHandle and the SessionStateChangeEvent handler transitions to stateSessionJoined.
+ // This is a cheap way to make sure both have happened before proceeding.
+ do
+ {
+ result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, SESSION_JOIN_TIMEOUT, timeoutResult);
+
+ if (sShuttingDown)
+ {
+ return false;
+ }
+
+ LL_INFOS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL;
+ if (result.has("session"))
+ {
+ if (!mAudioSession)
+ {
+ LL_WARNS("Voice") << "Message for session handle \"" << result["handle"] << "\" while session is not initialized." << LL_ENDL;
+ continue;
+ }
+ if (result.has("handle") && result["handle"] != mAudioSession->mHandle)
+ {
+ LL_WARNS("Voice") << "Message for session handle \"" << result["handle"] << "\" while waiting for \"" << mAudioSession->mHandle << "\"." << LL_ENDL;
+ continue;
+ }
+
+ std::string message = result["session"].asString();
+
+ if ((message == "added") || (message == "created"))
+ {
+ added = true;
+ }
+ else if (message == "joined")
+ {
+ joined = true;
+ }
+ else if ((message == "failed") || (message == "removed") || (message == "timeout"))
+ { // we will get a removed message if a voice call is declined.
+
+ if (message == "failed")
+ {
+ int reason = result["reason"].asInteger();
+ LL_WARNS("Voice") << "Add and join failed for reason " << reason << LL_ENDL;
+
+ if ( (reason == ERROR_VIVOX_NOT_LOGGED_IN)
+ || (reason == ERROR_VIVOX_OBJECT_NOT_FOUND))
+ {
+ LL_DEBUGS("Voice") << "Requesting reprovision and login." << LL_ENDL;
+ requestRelog();
+ }
+ }
+ else
+ {
+ LL_WARNS("Voice") << "session '" << message << "' "
+ << LL_ENDL;
+ }
+
+ notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL);
+ mIsJoiningSession = false;
+ return false;
+ }
+ }
+ } while (!added || !joined);
+
+ mIsJoiningSession = false;
+
+ if (mSpatialJoiningNum > 100)
+ {
+ LL_WARNS("Voice") << "There seems to be problem with connecting to a voice channel. Frames to join were " << mSpatialJoiningNum << LL_ENDL;
+ }
+
+ mSpatialJoiningNum = 0;
+
+ // Events that need to happen when a session is joined could go here.
+ // send an initial positional information immediately upon joining.
+ //
+ // do an initial update for position and the camera position, then send a
+ // positional update.
+ updatePosition();
+ enforceTether();
+
+ // Dirty state that may need to be sync'ed with the daemon.
+ mMuteMicDirty = true;
+ mSpeakerVolumeDirty = true;
+ mSpatialCoordsDirty = true;
+
+ sendPositionAndVolumeUpdate();
+
+ notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINED);
+
+ return true;
+}
+
+bool LLVivoxVoiceClient::terminateAudioSession(bool wait)
+{
+
+ if (mAudioSession)
+ {
+ LL_INFOS("Voice") << "terminateAudioSession(" << wait << ") Terminating current voice session " << mAudioSession->mHandle << LL_ENDL;
+
+ if (mIsLoggedIn)
+ {
+ if (!mAudioSession->mHandle.empty())
+ {
+
+#if RECORD_EVERYTHING
+ // HACK: for testing only
+ // Save looped recording
+ std::string savepath("/tmp/vivoxrecording");
+ {
+ time_t now = time(NULL);
+ const size_t BUF_SIZE = 64;
+ char time_str[BUF_SIZE]; /* Flawfinder: ignore */
+
+ strftime(time_str, BUF_SIZE, "%Y-%m-%dT%H:%M:%SZ", gmtime(&now));
+ savepath += time_str;
+ }
+ recordingLoopSave(savepath);
+#endif
+
+ sessionMediaDisconnectSendMessage(mAudioSession);
+
+ if (wait)
+ {
+ LLSD result;
+ do
+ {
+ LLSD timeoutResult(LLSDMap("session", "timeout"));
+
+ result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, LOGOUT_ATTEMPT_TIMEOUT, timeoutResult);
+
+ if (sShuttingDown)
+ {
+ return false;
+ }
+
+ LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL;
+ if (result.has("session"))
+ {
+ if (result.has("handle"))
+ {
+ if (result["handle"] != mAudioSession->mHandle)
+ {
+ LL_WARNS("Voice") << "Message for session handle \"" << result["handle"] << "\" while waiting for \"" << mAudioSession->mHandle << "\"." << LL_ENDL;
+ continue;
+ }
+ }
+
+ std::string message = result["session"].asString();
+ if (message == "removed" || message == "timeout")
+ break;
+ }
+ } while (true);
+
+ }
+ }
+ else
+ {
+ LL_WARNS("Voice") << "called with no session handle" << LL_ENDL;
+ }
+ }
+ else
+ {
+ LL_WARNS("Voice") << "Session " << mAudioSession->mHandle << " already terminated by logout." << LL_ENDL;
+ }
+
+ sessionStatePtr_t oldSession = mAudioSession;
+
+ mAudioSession.reset();
+ // We just notified status observers about this change. Don't do it again.
+ mAudioSessionChanged = false;
+
+ // The old session may now need to be deleted.
+ reapSession(oldSession);
+ }
+ else
+ {
+ LL_WARNS("Voice") << "terminateAudioSession(" << wait << ") with NULL mAudioSession" << LL_ENDL;
+ }
+
+ notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL);
+
+ // Always reset the terminate request flag when we get here.
+ // Some slower PCs have a race condition where they can switch to an incoming P2P call faster than the state machine leaves
+ // the region chat.
+ mSessionTerminateRequested = false;
+
+ bool status=((mVoiceEnabled || !mIsInitialized) && !mRelogRequested && !sShuttingDown);
+ LL_DEBUGS("Voice") << "exiting"
+ << " VoiceEnabled " << mVoiceEnabled
+ << " IsInitialized " << mIsInitialized
+ << " RelogRequested " << mRelogRequested
+ << " ShuttingDown " << (sShuttingDown ? "true" : "false")
+ << " returning " << status
+ << LL_ENDL;
+ return status;
+}
+
+
+typedef enum e_voice_wait_for_channel_state
+{
+ VOICE_CHANNEL_STATE_LOGIN = 0, // entry point
+ VOICE_CHANNEL_STATE_CHECK_EFFECTS,
+ VOICE_CHANNEL_STATE_START_CHANNEL_PROCESSING,
+ VOICE_CHANNEL_STATE_PROCESS_CHANNEL,
+ VOICE_CHANNEL_STATE_NEXT_CHANNEL_DELAY,
+ VOICE_CHANNEL_STATE_NEXT_CHANNEL_CHECK,
+ VOICE_CHANNEL_STATE_LOGOUT,
+ VOICE_CHANNEL_STATE_RELOG,
+ VOICE_CHANNEL_STATE_DONE,
+} EVoiceWaitForChannelState;
+
+bool LLVivoxVoiceClient::waitForChannel()
+{
+ LL_INFOS("Voice") << "Waiting for channel" << LL_ENDL;
+
+ EVoiceWaitForChannelState state = VOICE_CHANNEL_STATE_LOGIN;
+
+ do
+ {
+ if (sShuttingDown)
+ {
+ // terminate() forcefully disconects voice, no need for cleanup
+ return false;
+ }
+
+ switch (state)
+ {
+ case VOICE_CHANNEL_STATE_LOGIN:
+ if (!loginToVivox())
+ {
+ return false;
+ }
+ state = VOICE_CHANNEL_STATE_CHECK_EFFECTS;
+ break;
+
+ case VOICE_CHANNEL_STATE_CHECK_EFFECTS:
+ if (LLVoiceClient::instance().getVoiceEffectEnabled())
+ {
+ retrieveVoiceFonts();
+
+ if (sShuttingDown)
+ {
+ return false;
+ }
+
+ // Request the set of available voice fonts.
+ refreshVoiceEffectLists(false);
+ }
+
+#if USE_SESSION_GROUPS
+ // Rider: This code is completely unchanged from the original state machine
+ // It does not seem to be in active use... but I'd rather not rip it out.
+ // create the main session group
+ setState(stateCreatingSessionGroup);
+ sessionGroupCreateSendMessage();
+#endif
+
+ state = VOICE_CHANNEL_STATE_START_CHANNEL_PROCESSING;
+ break;
+
+ case VOICE_CHANNEL_STATE_START_CHANNEL_PROCESSING:
+ mIsProcessingChannels = true;
+ llcoro::suspend();
+ state = VOICE_CHANNEL_STATE_PROCESS_CHANNEL;
+ break;
+
+ case VOICE_CHANNEL_STATE_PROCESS_CHANNEL:
+ if (mTuningMode)
+ {
+ performMicTuning();
+ }
+ else if (mCaptureBufferMode)
+ {
+ recordingAndPlaybackMode();
+ }
+ else if (checkParcelChanged() || (mNextAudioSession == NULL))
+ {
+ // the parcel is changed, or we have no pending audio sessions,
+ // so try to request the parcel voice info
+ // if we have the cap, we move to the appropriate state
+ requestParcelVoiceInfo(); //suspends for http reply
+ }
+ else if (sessionNeedsRelog(mNextAudioSession))
+ {
+ LL_INFOS("Voice") << "Session requesting reprovision and login." << LL_ENDL;
+ requestRelog();
+ break;
+ }
+ else if (mNextAudioSession)
+ {
+ sessionStatePtr_t joinSession = mNextAudioSession;
+ mNextAudioSession.reset();
+ if (!runSession(joinSession)) //suspends
+ {
+ LL_DEBUGS("Voice") << "runSession returned false; leaving inner loop" << LL_ENDL;
+ break;
+ }
+ else
+ {
+ LL_DEBUGS("Voice")
+ << "runSession returned true to inner loop"
+ << " RelogRequested=" << mRelogRequested
+ << " VoiceEnabled=" << mVoiceEnabled
+ << LL_ENDL;
+ }
+ }
+
+ state = VOICE_CHANNEL_STATE_NEXT_CHANNEL_DELAY;
+ break;
+
+ case VOICE_CHANNEL_STATE_NEXT_CHANNEL_DELAY:
+ if (!mNextAudioSession)
+ {
+ llcoro::suspendUntilTimeout(1.0);
+ }
+ state = VOICE_CHANNEL_STATE_NEXT_CHANNEL_CHECK;
+ break;
+
+ case VOICE_CHANNEL_STATE_NEXT_CHANNEL_CHECK:
+ if (mVoiceEnabled && !mRelogRequested)
+ {
+ state = VOICE_CHANNEL_STATE_START_CHANNEL_PROCESSING;
+ break;
+ }
+ else
+ {
+ mIsProcessingChannels = false;
+ LL_DEBUGS("Voice")
+ << "leaving inner waitForChannel loop"
+ << " RelogRequested=" << mRelogRequested
+ << " VoiceEnabled=" << mVoiceEnabled
+ << LL_ENDL;
+ state = VOICE_CHANNEL_STATE_LOGOUT;
+ break;
+ }
+
+ case VOICE_CHANNEL_STATE_LOGOUT:
+ logoutOfVivox(true /*bool wait*/);
+ if (mRelogRequested)
+ {
+ state = VOICE_CHANNEL_STATE_RELOG;
+ }
+ else
+ {
+ state = VOICE_CHANNEL_STATE_DONE;
+ }
+ break;
+
+ case VOICE_CHANNEL_STATE_RELOG:
+ LL_DEBUGS("Voice") << "Relog Requested, restarting provisioning" << LL_ENDL;
+ if (!provisionVoiceAccount())
+ {
+ if (sShuttingDown)
+ {
+ return false;
+ }
+ LL_WARNS("Voice") << "provisioning voice failed; giving up" << LL_ENDL;
+ giveUp();
+ return false;
+ }
+ if (mVoiceEnabled && mRelogRequested && isGatewayRunning())
+ {
+ state = VOICE_CHANNEL_STATE_LOGIN;
+ }
+ else
+ {
+ state = VOICE_CHANNEL_STATE_DONE;
+ }
+ break;
+ case VOICE_CHANNEL_STATE_DONE:
+ LL_DEBUGS("Voice")
+ << "exiting"
+ << " RelogRequested=" << mRelogRequested
+ << " VoiceEnabled=" << mVoiceEnabled
+ << LL_ENDL;
+ return !sShuttingDown;
+ }
+ } while (true);
+}
+
+bool LLVivoxVoiceClient::runSession(const sessionStatePtr_t &session)
+{
+ LL_INFOS("Voice") << "running new voice session " << session->mHandle << LL_ENDL;
+
+ bool joined_session = addAndJoinSession(session);
+
+ if (sShuttingDown)
+ {
+ return false;
+ }
+
+ if (!joined_session)
+ {
+ notifyStatusObservers(LLVoiceClientStatusObserver::ERROR_UNKNOWN);
+
+ if (mSessionTerminateRequested)
+ {
+ LL_DEBUGS("Voice") << "runSession terminate requested " << LL_ENDL;
+ terminateAudioSession(true);
+ }
+ // if a relog has been requested then addAndJoineSession
+ // failed in a spectacular way and we need to back out.
+ // If this is not the case then we were simply trying to
+ // make a call and the other party rejected it.
+ return !mRelogRequested;
+ }
+
+ notifyParticipantObservers();
+ notifyVoiceFontObservers();
+
+ LLSD timeoutEvent(LLSDMap("timeout", LLSD::Boolean(true)));
+
+ mIsInChannel = true;
+ mMuteMicDirty = true;
+
+ while (!sShuttingDown
+ && mVoiceEnabled
+ && isGatewayRunning()
+ && !mSessionTerminateRequested
+ && !mTuningMode)
+ {
+ sendCaptureAndRenderDevices(); // suspends
+
+ if (sShuttingDown)
+ {
+ return false;
+ }
+
+ if (mSessionTerminateRequested)
+ {
+ break;
+ }
+
+ if (mAudioSession && mAudioSession->mParticipantsChanged)
+ {
+ mAudioSession->mParticipantsChanged = false;
+ notifyParticipantObservers();
+ }
+
+ if (!inSpatialChannel())
+ {
+ // When in a non-spatial channel, never send positional updates.
+ mSpatialCoordsDirty = false;
+ }
+ else
+ {
+ updatePosition();
+
+ if (checkParcelChanged())
+ {
+ // *RIDER: I think I can just return here if the parcel has changed
+ // and grab the new voice channel from the outside loop.
+ //
+ // if the parcel has changed, attempted to request the
+ // cap for the parcel voice info. If we can't request it
+ // then we don't have the cap URL so we do nothing and will
+ // recheck next time around
+ if (requestParcelVoiceInfo()) // suspends
+ { // The parcel voice URI has changed.. break out and reconnect.
+ break;
+ }
+
+ if (sShuttingDown)
+ {
+ return false;
+ }
+ }
+ // Do the calculation that enforces the listener<->speaker tether (and also updates the real camera position)
+ enforceTether();
+ }
+ sendPositionAndVolumeUpdate();
+
+ // Do notifications for expiring Voice Fonts.
+ if (mVoiceFontExpiryTimer.hasExpired())
+ {
+ expireVoiceFonts();
+ mVoiceFontExpiryTimer.setTimerExpirySec(VOICE_FONT_EXPIRY_INTERVAL);
+ }
+
+ // send any requests to adjust mic and speaker settings if they have changed
+ sendLocalAudioUpdates();
+
+ mIsInitialized = true;
+ LLSD result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, UPDATE_THROTTLE_SECONDS, timeoutEvent);
+
+ if (sShuttingDown)
+ {
+ return false;
+ }
+
+ if (!result.has("timeout")) // logging the timeout event spams the log
+ {
+ LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL;
+ }
+ if (result.has("session"))
+ {
+ if (result.has("handle"))
+ {
+ if (!mAudioSession)
+ {
+ LL_WARNS("Voice") << "Message for session handle \"" << result["handle"] << "\" while session is not initiated." << LL_ENDL;
+ continue;
+ }
+ if (result["handle"] != mAudioSession->mHandle)
+ {
+ LL_WARNS("Voice") << "Message for session handle \"" << result["handle"] << "\" while waiting for \"" << mAudioSession->mHandle << "\"." << LL_ENDL;
+ continue;
+ }
+ }
+
+ std::string message = result["session"];
+
+ if (message == "removed")
+ {
+ LL_DEBUGS("Voice") << "session removed" << LL_ENDL;
+ notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL);
+ break;
+ }
+ }
+ else if (result.has("login"))
+ {
+ std::string message = result["login"];
+ if (message == "account_logout")
+ {
+ LL_DEBUGS("Voice") << "logged out" << LL_ENDL;
+ mIsLoggedIn = false;
+ mRelogRequested = true;
+ break;
+ }
+ }
+ }
+
+ if (sShuttingDown)
+ {
+ return false;
+ }
+
+ mIsInChannel = false;
+ LL_DEBUGS("Voice") << "terminating at end of runSession" << LL_ENDL;
+ terminateAudioSession(true);
+
+ return true;
+}
+
+void LLVivoxVoiceClient::sendCaptureAndRenderDevices()
+{
+ if (mCaptureDeviceDirty || mRenderDeviceDirty)
+ {
+ std::ostringstream stream;
+
+ buildSetCaptureDevice(stream);
+ buildSetRenderDevice(stream);
+
+ if (!stream.str().empty())
+ {
+ writeString(stream.str());
+ }
+
+ llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS);
+ }
+}
+
+void LLVivoxVoiceClient::recordingAndPlaybackMode()
+{
+ LL_INFOS("Voice") << "In voice capture/playback mode." << LL_ENDL;
+
+ while (true)
+ {
+ LLSD command;
+ do
+ {
+ command = llcoro::suspendUntilEventOn(mVivoxPump);
+ LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(command) << LL_ENDL;
+ } while (!command.has("recplay"));
+
+ if (command["recplay"].asString() == "quit")
+ {
+ mCaptureBufferMode = false;
+ break;
+ }
+ else if (command["recplay"].asString() == "record")
+ {
+ voiceRecordBuffer();
+ }
+ else if (command["recplay"].asString() == "playback")
+ {
+ voicePlaybackBuffer();
+ }
+ }
+
+ LL_INFOS("Voice") << "Leaving capture/playback mode." << LL_ENDL;
+ mCaptureBufferRecording = false;
+ mCaptureBufferRecorded = false;
+ mCaptureBufferPlaying = false;
+
+ return;
+}
+
+int LLVivoxVoiceClient::voiceRecordBuffer()
+{
+ LLSD timeoutResult(LLSDMap("recplay", "stop"));
+
+ LL_INFOS("Voice") << "Recording voice buffer" << LL_ENDL;
+
+ LLSD result;
+
+ captureBufferRecordStartSendMessage();
+ notifyVoiceFontObservers();
+
+ do
+ {
+ result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, CAPTURE_BUFFER_MAX_TIME, timeoutResult);
+ LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL;
+ } while (!result.has("recplay"));
+
+ mCaptureBufferRecorded = true;
+
+ captureBufferRecordStopSendMessage();
+ mCaptureBufferRecording = false;
+
+ // Update UI, should really use a separate callback.
+ notifyVoiceFontObservers();
+
+ return true;
+ /*TODO expand return to move directly into play*/
+}
+
+int LLVivoxVoiceClient::voicePlaybackBuffer()
+{
+ LLSD timeoutResult(LLSDMap("recplay", "stop"));
+
+ LL_INFOS("Voice") << "Playing voice buffer" << LL_ENDL;
+
+ LLSD result;
+
+ do
+ {
+ captureBufferPlayStartSendMessage(mPreviewVoiceFont);
+
+ // Store the voice font being previewed, so that we know to restart if it changes.
+ mPreviewVoiceFontLast = mPreviewVoiceFont;
+
+ do
+ {
+ // Update UI, should really use a separate callback.
+ notifyVoiceFontObservers();
+
+ result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, CAPTURE_BUFFER_MAX_TIME, timeoutResult);
+ LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL;
+ } while (!result.has("recplay"));
+
+ if (result["recplay"] == "playback")
+ continue; // restart playback... May be a font change.
+
+ break;
+ } while (true);
+
+ // Stop playing.
+ captureBufferPlayStopSendMessage();
+ mCaptureBufferPlaying = false;
+
+ // Update UI, should really use a separate callback.
+ notifyVoiceFontObservers();
+
+ return true;
+}
+
+
+bool LLVivoxVoiceClient::performMicTuning()
+{
+ LL_INFOS("Voice") << "Entering voice tuning mode." << LL_ENDL;
+
+ mIsInTuningMode = true;
+ llcoro::suspend();
+
+ while (mTuningMode && !sShuttingDown)
+ {
+
+ if (mCaptureDeviceDirty || mRenderDeviceDirty)
+ {
+ // These can't be changed while in tuning mode. Set them before starting.
+ std::ostringstream stream;
+
+ buildSetCaptureDevice(stream);
+ buildSetRenderDevice(stream);
+
+ if (!stream.str().empty())
+ {
+ writeString(stream.str());
+ }
+
+ llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS);
+ }
+
+ // loop mic back to render device.
+ //setMuteMic(0); // make sure the mic is not muted
+ std::ostringstream stream;
+
+ stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.MuteLocalMic.1\">"
+ << "<ConnectorHandle>" << LLVivoxSecurity::getInstance()->connectorHandle() << "</ConnectorHandle>"
+ << "<Value>false</Value>"
+ << "</Request>\n\n\n";
+
+ // Dirty the mute mic state so that it will get reset when we finishing previewing
+ mMuteMicDirty = true;
+ mTuningSpeakerVolumeDirty = true;
+
+ writeString(stream.str());
+ tuningCaptureStartSendMessage(1); // 1-loop, zero, don't loop
+
+ //---------------------------------------------------------------------
+ if (!sShuttingDown)
+ {
+ llcoro::suspend();
+ }
+
+ while (mTuningMode && !mCaptureDeviceDirty && !mRenderDeviceDirty && !sShuttingDown)
+ {
+ // process mic/speaker volume changes
+ if (mTuningMicVolumeDirty || mTuningSpeakerVolumeDirty)
+ {
+ std::ostringstream stream;
+
+ if (mTuningMicVolumeDirty)
+ {
+ LL_INFOS("Voice") << "setting tuning mic level to " << mTuningMicVolume << LL_ENDL;
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetMicLevel.1\">"
+ << "<Level>" << mTuningMicVolume << "</Level>"
+ << "</Request>\n\n\n";
+ }
+
+ if (mTuningSpeakerVolumeDirty)
+ {
+ LL_INFOS("Voice") << "setting tuning speaker level to " << mTuningSpeakerVolume << LL_ENDL;
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetSpeakerLevel.1\">"
+ << "<Level>" << mTuningSpeakerVolume << "</Level>"
+ << "</Request>\n\n\n";
+ }
+
+ mTuningMicVolumeDirty = false;
+ mTuningSpeakerVolumeDirty = false;
+
+ if (!stream.str().empty())
+ {
+ writeString(stream.str());
+ }
+ }
+ llcoro::suspend();
+ }
+
+ //---------------------------------------------------------------------
+
+ // transition out of mic tuning
+ tuningCaptureStopSendMessage();
+ if ((mCaptureDeviceDirty || mRenderDeviceDirty) && !sShuttingDown)
+ {
+ llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS);
+ }
+ }
+
+ mIsInTuningMode = false;
+
+ //---------------------------------------------------------------------
+ return true;
+}
+
+//=========================================================================
+
+void LLVivoxVoiceClient::closeSocket(void)
+{
+ mSocket.reset();
+ sConnected = false;
+ mConnectorEstablished = false;
+ mAccountLoggedIn = false;
+}
+
+void LLVivoxVoiceClient::loginSendMessage()
+{
+ std::ostringstream stream;
+
+ bool autoPostCrashDumps = gSavedSettings.getBOOL("VivoxAutoPostCrashDumps");
+
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.Login.1\">"
+ << "<ConnectorHandle>" << LLVivoxSecurity::getInstance()->connectorHandle() << "</ConnectorHandle>"
+ << "<AccountName>" << mAccountName << "</AccountName>"
+ << "<AccountPassword>" << mAccountPassword << "</AccountPassword>"
+ << "<AccountHandle>" << LLVivoxSecurity::getInstance()->accountHandle() << "</AccountHandle>"
+ << "<AudioSessionAnswerMode>VerifyAnswer</AudioSessionAnswerMode>"
+ << "<EnableBuddiesAndPresence>false</EnableBuddiesAndPresence>"
+ << "<EnablePresencePersistence>0</EnablePresencePersistence>"
+ << "<BuddyManagementMode>Application</BuddyManagementMode>"
+ << "<ParticipantPropertyFrequency>5</ParticipantPropertyFrequency>"
+ << (autoPostCrashDumps?"<AutopostCrashDumps>true</AutopostCrashDumps>":"")
+ << "</Request>\n\n\n";
+
+ LL_INFOS("Voice") << "Attempting voice login" << LL_ENDL;
+ writeString(stream.str());
+}
+
+void LLVivoxVoiceClient::logout()
+{
+ // Ensure that we'll re-request provisioning before logging in again
+ mAccountPassword.clear();
+ mVoiceAccountServerURI.clear();
+
+ logoutSendMessage();
+}
+
+void LLVivoxVoiceClient::logoutSendMessage()
+{
+ if(mAccountLoggedIn)
+ {
+ LL_INFOS("Voice") << "Attempting voice logout" << LL_ENDL;
+ std::ostringstream stream;
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.Logout.1\">"
+ << "<AccountHandle>" << LLVivoxSecurity::getInstance()->accountHandle() << "</AccountHandle>"
+ << "</Request>"
+ << "\n\n\n";
+
+ mAccountLoggedIn = false;
+
+ writeString(stream.str());
+ }
+}
+
+void LLVivoxVoiceClient::sessionGroupCreateSendMessage()
+{
+ if(mAccountLoggedIn)
+ {
+ std::ostringstream stream;
+
+ LL_DEBUGS("Voice") << "creating session group" << LL_ENDL;
+
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.Create.1\">"
+ << "<AccountHandle>" << LLVivoxSecurity::getInstance()->accountHandle() << "</AccountHandle>"
+ << "<Type>Normal</Type>"
+ << "</Request>"
+ << "\n\n\n";
+
+ writeString(stream.str());
+ }
+}
+
+void LLVivoxVoiceClient::sessionCreateSendMessage(const sessionStatePtr_t &session, bool startAudio, bool startText)
+{
+ S32 font_index = getVoiceFontIndex(session->mVoiceFontID);
+ LL_DEBUGS("Voice") << "Requesting create: " << session->mSIPURI
+ << " with voice font: " << session->mVoiceFontID << " (" << font_index << ")"
+ << LL_ENDL;
+
+ session->mCreateInProgress = true;
+ if(startAudio)
+ {
+ session->mMediaConnectInProgress = true;
+ }
+
+ std::ostringstream stream;
+ stream
+ << "<Request requestId=\"" << session->mSIPURI << "\" action=\"Session.Create.1\">"
+ << "<AccountHandle>" << LLVivoxSecurity::getInstance()->accountHandle() << "</AccountHandle>"
+ << "<URI>" << session->mSIPURI << "</URI>";
+
+ static const std::string allowed_chars =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
+ "0123456789"
+ "-._~";
+
+ if(!session->mHash.empty())
+ {
+ stream
+ << "<Password>" << LLURI::escape(session->mHash, allowed_chars) << "</Password>"
+ << "<PasswordHashAlgorithm>SHA1UserName</PasswordHashAlgorithm>";
+ }
+
+ stream
+ << "<ConnectAudio>" << (startAudio?"true":"false") << "</ConnectAudio>"
+ << "<ConnectText>" << (startText?"true":"false") << "</ConnectText>"
+ << "<VoiceFontID>" << font_index << "</VoiceFontID>"
+ << "<Name>" << mChannelName << "</Name>"
+ << "</Request>\n\n\n";
+ writeString(stream.str());
+}
+
+void LLVivoxVoiceClient::sessionGroupAddSessionSendMessage(const sessionStatePtr_t &session, bool startAudio, bool startText)
+{
+ LL_DEBUGS("Voice") << "Requesting create: " << session->mSIPURI << LL_ENDL;
+
+ S32 font_index = getVoiceFontIndex(session->mVoiceFontID);
+ LL_DEBUGS("Voice") << "With voice font: " << session->mVoiceFontID << " (" << font_index << ")" << LL_ENDL;
+
+ session->mCreateInProgress = true;
+ if(startAudio)
+ {
+ session->mMediaConnectInProgress = true;
+ }
+
+ std::string password;
+ if(!session->mHash.empty())
+ {
+ static const std::string allowed_chars =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
+ "0123456789"
+ "-._~"
+ ;
+ password = LLURI::escape(session->mHash, allowed_chars);
+ }
+
+ std::ostringstream stream;
+ stream
+ << "<Request requestId=\"" << session->mSIPURI << "\" action=\"SessionGroup.AddSession.1\">"
+ << "<SessionGroupHandle>" << session->mGroupHandle << "</SessionGroupHandle>"
+ << "<URI>" << session->mSIPURI << "</URI>"
+ << "<Name>" << mChannelName << "</Name>"
+ << "<ConnectAudio>" << (startAudio?"true":"false") << "</ConnectAudio>"
+ << "<ConnectText>" << (startText?"true":"false") << "</ConnectText>"
+ << "<VoiceFontID>" << font_index << "</VoiceFontID>"
+ << "<Password>" << password << "</Password>"
+ << "<PasswordHashAlgorithm>SHA1UserName</PasswordHashAlgorithm>"
+ << "</Request>\n\n\n"
+ ;
+
+ writeString(stream.str());
+}
+
+void LLVivoxVoiceClient::sessionMediaConnectSendMessage(const sessionStatePtr_t &session)
+{
+ S32 font_index = getVoiceFontIndex(session->mVoiceFontID);
+ LL_DEBUGS("Voice") << "Connecting audio to session handle: " << session->mHandle
+ << " with voice font: " << session->mVoiceFontID << " (" << font_index << ")"
+ << LL_ENDL;
+
+ session->mMediaConnectInProgress = true;
+
+ std::ostringstream stream;
+
+ stream
+ << "<Request requestId=\"" << session->mHandle << "\" action=\"Session.MediaConnect.1\">"
+ << "<SessionGroupHandle>" << session->mGroupHandle << "</SessionGroupHandle>"
+ << "<SessionHandle>" << session->mHandle << "</SessionHandle>"
+ << "<VoiceFontID>" << font_index << "</VoiceFontID>"
+ << "<Media>Audio</Media>"
+ << "</Request>\n\n\n";
+
+ writeString(stream.str());
+}
+
+void LLVivoxVoiceClient::sessionTextConnectSendMessage(const sessionStatePtr_t &session)
+{
+ LL_DEBUGS("Voice") << "connecting text to session handle: " << session->mHandle << LL_ENDL;
+
+ std::ostringstream stream;
+
+ stream
+ << "<Request requestId=\"" << session->mHandle << "\" action=\"Session.TextConnect.1\">"
+ << "<SessionGroupHandle>" << session->mGroupHandle << "</SessionGroupHandle>"
+ << "<SessionHandle>" << session->mHandle << "</SessionHandle>"
+ << "</Request>\n\n\n";
+
+ writeString(stream.str());
+}
+
+void LLVivoxVoiceClient::sessionTerminate()
+{
+ mSessionTerminateRequested = true;
+}
+
+void LLVivoxVoiceClient::requestRelog()
+{
+ mSessionTerminateRequested = true;
+ mRelogRequested = true;
+}
+
+
+void LLVivoxVoiceClient::leaveAudioSession()
+{
+ if(mAudioSession)
+ {
+ LL_DEBUGS("Voice") << "leaving session: " << mAudioSession->mSIPURI << LL_ENDL;
+
+ if(!mAudioSession->mHandle.empty())
+ {
+
+#if RECORD_EVERYTHING
+ // HACK: for testing only
+ // Save looped recording
+ std::string savepath("/tmp/vivoxrecording");
+ {
+ time_t now = time(NULL);
+ const size_t BUF_SIZE = 64;
+ char time_str[BUF_SIZE]; /* Flawfinder: ignore */
+
+ strftime(time_str, BUF_SIZE, "%Y-%m-%dT%H:%M:%SZ", gmtime(&now));
+ savepath += time_str;
+ }
+ recordingLoopSave(savepath);
+#endif
+
+ sessionMediaDisconnectSendMessage(mAudioSession);
+ }
+ else
+ {
+ LL_WARNS("Voice") << "called with no session handle" << LL_ENDL;
+ }
+ }
+ else
+ {
+ LL_WARNS("Voice") << "called with no active session" << LL_ENDL;
+ }
+ sessionTerminate();
+}
+
+void LLVivoxVoiceClient::sessionTerminateSendMessage(const sessionStatePtr_t &session)
+{
+ std::ostringstream stream;
+
+ sessionGroupTerminateSendMessage(session);
+ return;
+ /*
+ LL_DEBUGS("Voice") << "Sending Session.Terminate with handle " << session->mHandle << LL_ENDL;
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.Terminate.1\">"
+ << "<SessionHandle>" << session->mHandle << "</SessionHandle>"
+ << "</Request>\n\n\n";
+
+ writeString(stream.str());
+ */
+}
+
+void LLVivoxVoiceClient::sessionGroupTerminateSendMessage(const sessionStatePtr_t &session)
+{
+ std::ostringstream stream;
+
+ LL_DEBUGS("Voice") << "Sending SessionGroup.Terminate with handle " << session->mGroupHandle << LL_ENDL;
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.Terminate.1\">"
+ << "<SessionGroupHandle>" << session->mGroupHandle << "</SessionGroupHandle>"
+ << "</Request>\n\n\n";
+
+ writeString(stream.str());
+}
+
+void LLVivoxVoiceClient::sessionMediaDisconnectSendMessage(const sessionStatePtr_t &session)
+{
+ std::ostringstream stream;
+ sessionGroupTerminateSendMessage(session);
+ return;
+ /*
+ LL_DEBUGS("Voice") << "Sending Session.MediaDisconnect with handle " << session->mHandle << LL_ENDL;
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.MediaDisconnect.1\">"
+ << "<SessionGroupHandle>" << session->mGroupHandle << "</SessionGroupHandle>"
+ << "<SessionHandle>" << session->mHandle << "</SessionHandle>"
+ << "<Media>Audio</Media>"
+ << "</Request>\n\n\n";
+
+ writeString(stream.str());
+ */
+
+}
+
+
+void LLVivoxVoiceClient::getCaptureDevicesSendMessage()
+{
+ std::ostringstream stream;
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.GetCaptureDevices.1\">"
+ << "</Request>\n\n\n";
+
+ writeString(stream.str());
+}
+
+void LLVivoxVoiceClient::getRenderDevicesSendMessage()
+{
+ std::ostringstream stream;
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.GetRenderDevices.1\">"
+ << "</Request>\n\n\n";
+
+ writeString(stream.str());
+}
+
+void LLVivoxVoiceClient::clearCaptureDevices()
+{
+ LL_DEBUGS("Voice") << "called" << LL_ENDL;
+ mCaptureDevices.clear();
+}
+
+void LLVivoxVoiceClient::addCaptureDevice(const LLVoiceDevice& device)
+{
+ LL_DEBUGS("Voice") << "display: '" << device.display_name << "' device: '" << device.full_name << "'" << LL_ENDL;
+ mCaptureDevices.push_back(device);
+}
+
+LLVoiceDeviceList& LLVivoxVoiceClient::getCaptureDevices()
+{
+ return mCaptureDevices;
+}
+
+void LLVivoxVoiceClient::setCaptureDevice(const std::string& name)
+{
+ if(name == "Default")
+ {
+ if(!mCaptureDevice.empty())
+ {
+ mCaptureDevice.clear();
+ mCaptureDeviceDirty = true;
+ }
+ }
+ else
+ {
+ if(mCaptureDevice != name)
+ {
+ mCaptureDevice = name;
+ mCaptureDeviceDirty = true;
+ }
+ }
+}
+void LLVivoxVoiceClient::setDevicesListUpdated(bool state)
+{
+ mDevicesListUpdated = state;
+}
+
+void LLVivoxVoiceClient::clearRenderDevices()
+{
+ LL_DEBUGS("Voice") << "called" << LL_ENDL;
+ mRenderDevices.clear();
+}
+
+void LLVivoxVoiceClient::addRenderDevice(const LLVoiceDevice& device)
+{
+ LL_DEBUGS("Voice") << "display: '" << device.display_name << "' device: '" << device.full_name << "'" << LL_ENDL;
+ mRenderDevices.push_back(device);
+}
+
+LLVoiceDeviceList& LLVivoxVoiceClient::getRenderDevices()
+{
+ return mRenderDevices;
+}
+
+void LLVivoxVoiceClient::setRenderDevice(const std::string& name)
+{
+ if(name == "Default")
+ {
+ if(!mRenderDevice.empty())
+ {
+ mRenderDevice.clear();
+ mRenderDeviceDirty = true;
+ }
+ }
+ else
+ {
+ if(mRenderDevice != name)
+ {
+ mRenderDevice = name;
+ mRenderDeviceDirty = true;
+ }
+ }
+
+}
+
+void LLVivoxVoiceClient::tuningStart()
+{
+ LL_DEBUGS("Voice") << "Starting tuning" << LL_ENDL;
+ mTuningMode = true;
+ if (!mIsCoroutineActive)
+ {
+ LLCoros::instance().launch("LLVivoxVoiceClient::voiceControlCoro",
+ boost::bind(&LLVivoxVoiceClient::voiceControlCoro, LLVivoxVoiceClient::getInstance()));
+ }
+ else if (mIsInChannel)
+ {
+ LL_DEBUGS("Voice") << "no channel" << LL_ENDL;
+ sessionTerminate();
+ }
+}
+
+void LLVivoxVoiceClient::tuningStop()
+{
+ mTuningMode = false;
+}
+
+bool LLVivoxVoiceClient::inTuningMode()
+{
+ return mIsInTuningMode;
+}
+
+void LLVivoxVoiceClient::tuningRenderStartSendMessage(const std::string& name, bool loop)
+{
+ mTuningAudioFile = name;
+ std::ostringstream stream;
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.RenderAudioStart.1\">"
+ << "<SoundFilePath>" << mTuningAudioFile << "</SoundFilePath>"
+ << "<Loop>" << (loop?"1":"0") << "</Loop>"
+ << "</Request>\n\n\n";
+
+ writeString(stream.str());
+}
+
+void LLVivoxVoiceClient::tuningRenderStopSendMessage()
+{
+ std::ostringstream stream;
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.RenderAudioStop.1\">"
+ << "<SoundFilePath>" << mTuningAudioFile << "</SoundFilePath>"
+ << "</Request>\n\n\n";
+
+ writeString(stream.str());
+}
+
+void LLVivoxVoiceClient::tuningCaptureStartSendMessage(int loop)
+{
+ LL_DEBUGS("Voice") << "sending CaptureAudioStart" << LL_ENDL;
+
+ std::ostringstream stream;
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.CaptureAudioStart.1\">"
+ << "<Duration>-1</Duration>"
+ << "<LoopToRenderDevice>" << loop << "</LoopToRenderDevice>"
+ << "</Request>\n\n\n";
+
+ writeString(stream.str());
+}
+
+void LLVivoxVoiceClient::tuningCaptureStopSendMessage()
+{
+ LL_DEBUGS("Voice") << "sending CaptureAudioStop" << LL_ENDL;
+
+ std::ostringstream stream;
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.CaptureAudioStop.1\">"
+ << "</Request>\n\n\n";
+
+ writeString(stream.str());
+
+ mTuningEnergy = 0.0f;
+}
+
+void LLVivoxVoiceClient::tuningSetMicVolume(float volume)
+{
+ int scaled_volume = scale_mic_volume(volume);
+
+ if(scaled_volume != mTuningMicVolume)
+ {
+ mTuningMicVolume = scaled_volume;
+ mTuningMicVolumeDirty = true;
+ }
+}
+
+void LLVivoxVoiceClient::tuningSetSpeakerVolume(float volume)
+{
+ int scaled_volume = scale_speaker_volume(volume);
+
+ if(scaled_volume != mTuningSpeakerVolume)
+ {
+ mTuningSpeakerVolume = scaled_volume;
+ mTuningSpeakerVolumeDirty = true;
+ }
+}
+
+float LLVivoxVoiceClient::tuningGetEnergy(void)
+{
+ return mTuningEnergy;
+}
+
+bool LLVivoxVoiceClient::deviceSettingsAvailable()
+{
+ bool result = true;
+
+ if(!sConnected)
+ result = false;
+
+ if(mRenderDevices.empty())
+ result = false;
+
+ return result;
+}
+bool LLVivoxVoiceClient::deviceSettingsUpdated()
+{
+ bool updated = mDevicesListUpdated;
+ if (mDevicesListUpdated)
+ {
+ // a hot swap event or a polling of the audio devices has been parsed since the last redraw of the input and output device panel.
+ mDevicesListUpdated = false; // toggle the setting
+ }
+ return updated;
+}
+
+void LLVivoxVoiceClient::refreshDeviceLists(bool clearCurrentList)
+{
+ if(clearCurrentList)
+ {
+ clearCaptureDevices();
+ clearRenderDevices();
+ }
+ getCaptureDevicesSendMessage();
+ getRenderDevicesSendMessage();
+}
+
+void LLVivoxVoiceClient::daemonDied()
+{
+ // The daemon died, so the connection is gone. Reset everything and start over.
+ LL_WARNS("Voice") << "Connection to vivox daemon lost. Resetting state."<< LL_ENDL;
+
+ //TODO: Try to relaunch the daemon
+}
+
+void LLVivoxVoiceClient::giveUp()
+{
+ // All has failed. Clean up and stop trying.
+ LL_WARNS("Voice") << "Terminating Voice Service" << LL_ENDL;
+ closeSocket();
+ cleanUp();
+}
+
+static void oldSDKTransform (LLVector3 &left, LLVector3 &up, LLVector3 &at, LLVector3d &pos, LLVector3 &vel)
+{
+ F32 nat[3], nup[3], nl[3]; // the new at, up, left vectors and the new position and velocity
+// F32 nvel[3];
+ F64 npos[3];
+
+ // The original XML command was sent like this:
+ /*
+ << "<Position>"
+ << "<X>" << pos[VX] << "</X>"
+ << "<Y>" << pos[VZ] << "</Y>"
+ << "<Z>" << pos[VY] << "</Z>"
+ << "</Position>"
+ << "<Velocity>"
+ << "<X>" << mAvatarVelocity[VX] << "</X>"
+ << "<Y>" << mAvatarVelocity[VZ] << "</Y>"
+ << "<Z>" << mAvatarVelocity[VY] << "</Z>"
+ << "</Velocity>"
+ << "<AtOrientation>"
+ << "<X>" << l.mV[VX] << "</X>"
+ << "<Y>" << u.mV[VX] << "</Y>"
+ << "<Z>" << a.mV[VX] << "</Z>"
+ << "</AtOrientation>"
+ << "<UpOrientation>"
+ << "<X>" << l.mV[VZ] << "</X>"
+ << "<Y>" << u.mV[VY] << "</Y>"
+ << "<Z>" << a.mV[VZ] << "</Z>"
+ << "</UpOrientation>"
+ << "<LeftOrientation>"
+ << "<X>" << l.mV [VY] << "</X>"
+ << "<Y>" << u.mV [VZ] << "</Y>"
+ << "<Z>" << a.mV [VY] << "</Z>"
+ << "</LeftOrientation>";
+ */
+
+#if 1
+ // This was the original transform done when building the XML command
+ nat[0] = left.mV[VX];
+ nat[1] = up.mV[VX];
+ nat[2] = at.mV[VX];
+
+ nup[0] = left.mV[VZ];
+ nup[1] = up.mV[VY];
+ nup[2] = at.mV[VZ];
+
+ nl[0] = left.mV[VY];
+ nl[1] = up.mV[VZ];
+ nl[2] = at.mV[VY];
+
+ npos[0] = pos.mdV[VX];
+ npos[1] = pos.mdV[VZ];
+ npos[2] = pos.mdV[VY];
+
+// nvel[0] = vel.mV[VX];
+// nvel[1] = vel.mV[VZ];
+// nvel[2] = vel.mV[VY];
+
+ for(int i=0;i<3;++i) {
+ at.mV[i] = nat[i];
+ up.mV[i] = nup[i];
+ left.mV[i] = nl[i];
+ pos.mdV[i] = npos[i];
+ }
+
+ // This was the original transform done in the SDK
+ nat[0] = at.mV[2];
+ nat[1] = 0; // y component of at vector is always 0, this was up[2]
+ nat[2] = -1 * left.mV[2];
+
+ // We override whatever the application gives us
+ nup[0] = 0; // x component of up vector is always 0
+ nup[1] = 1; // y component of up vector is always 1
+ nup[2] = 0; // z component of up vector is always 0
+
+ nl[0] = at.mV[0];
+ nl[1] = 0; // y component of left vector is always zero, this was up[0]
+ nl[2] = -1 * left.mV[0];
+
+ npos[2] = pos.mdV[2] * -1.0;
+ npos[1] = pos.mdV[1];
+ npos[0] = pos.mdV[0];
+
+ for(int i=0;i<3;++i) {
+ at.mV[i] = nat[i];
+ up.mV[i] = nup[i];
+ left.mV[i] = nl[i];
+ pos.mdV[i] = npos[i];
+ }
+#else
+ // This is the compose of the two transforms (at least, that's what I'm trying for)
+ nat[0] = at.mV[VX];
+ nat[1] = 0; // y component of at vector is always 0, this was up[2]
+ nat[2] = -1 * up.mV[VZ];
+
+ // We override whatever the application gives us
+ nup[0] = 0; // x component of up vector is always 0
+ nup[1] = 1; // y component of up vector is always 1
+ nup[2] = 0; // z component of up vector is always 0
+
+ nl[0] = left.mV[VX];
+ nl[1] = 0; // y component of left vector is always zero, this was up[0]
+ nl[2] = -1 * left.mV[VY];
+
+ npos[0] = pos.mdV[VX];
+ npos[1] = pos.mdV[VZ];
+ npos[2] = pos.mdV[VY] * -1.0;
+
+ nvel[0] = vel.mV[VX];
+ nvel[1] = vel.mV[VZ];
+ nvel[2] = vel.mV[VY];
+
+ for(int i=0;i<3;++i) {
+ at.mV[i] = nat[i];
+ up.mV[i] = nup[i];
+ left.mV[i] = nl[i];
+ pos.mdV[i] = npos[i];
+ }
+
+#endif
+}
+
+void LLVivoxVoiceClient::setHidden(bool hidden)
+{
+ mHidden = hidden;
+
+ if (mHidden && inSpatialChannel())
+ {
+ // get out of the channel entirely
+ leaveAudioSession();
+ }
+ else
+ {
+ sendPositionAndVolumeUpdate();
+ }
+}
+
+void LLVivoxVoiceClient::sendPositionAndVolumeUpdate(void)
+{
+ std::ostringstream stream;
+
+ if (mSpatialCoordsDirty && inSpatialChannel())
+ {
+ LLVector3 l, u, a, vel;
+ LLVector3d pos;
+
+ mSpatialCoordsDirty = false;
+
+ // Always send both speaker and listener positions together.
+ stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.Set3DPosition.1\">"
+ << "<SessionHandle>" << getAudioSessionHandle() << "</SessionHandle>";
+
+ stream << "<SpeakerPosition>";
+
+ LLMatrix3 avatarRot = mAvatarRot.getMatrix3();
+
+// LL_DEBUGS("Voice") << "Sending speaker position " << mAvatarPosition << LL_ENDL;
+ l = avatarRot.getLeftRow();
+ u = avatarRot.getUpRow();
+ a = avatarRot.getFwdRow();
+
+ pos = mAvatarPosition;
+ vel = mAvatarVelocity;
+
+ // SLIM SDK: the old SDK was doing a transform on the passed coordinates that the new one doesn't do anymore.
+ // The old transform is replicated by this function.
+ oldSDKTransform(l, u, a, pos, vel);
+
+ if (mHidden)
+ {
+ for (int i=0;i<3;++i)
+ {
+ pos.mdV[i] = VX_NULL_POSITION;
+ }
+ }
+
+ stream
+ << "<Position>"
+ << "<X>" << pos.mdV[VX] << "</X>"
+ << "<Y>" << pos.mdV[VY] << "</Y>"
+ << "<Z>" << pos.mdV[VZ] << "</Z>"
+ << "</Position>"
+ << "<Velocity>"
+ << "<X>" << vel.mV[VX] << "</X>"
+ << "<Y>" << vel.mV[VY] << "</Y>"
+ << "<Z>" << vel.mV[VZ] << "</Z>"
+ << "</Velocity>"
+ << "<AtOrientation>"
+ << "<X>" << a.mV[VX] << "</X>"
+ << "<Y>" << a.mV[VY] << "</Y>"
+ << "<Z>" << a.mV[VZ] << "</Z>"
+ << "</AtOrientation>"
+ << "<UpOrientation>"
+ << "<X>" << u.mV[VX] << "</X>"
+ << "<Y>" << u.mV[VY] << "</Y>"
+ << "<Z>" << u.mV[VZ] << "</Z>"
+ << "</UpOrientation>"
+ << "<LeftOrientation>"
+ << "<X>" << l.mV [VX] << "</X>"
+ << "<Y>" << l.mV [VY] << "</Y>"
+ << "<Z>" << l.mV [VZ] << "</Z>"
+ << "</LeftOrientation>"
+ ;
+
+ stream << "</SpeakerPosition>";
+
+ stream << "<ListenerPosition>";
+
+ LLVector3d earPosition;
+ LLVector3 earVelocity;
+ LLMatrix3 earRot;
+
+ switch(mEarLocation)
+ {
+ case earLocCamera:
+ default:
+ earPosition = mCameraPosition;
+ earVelocity = mCameraVelocity;
+ earRot = mCameraRot;
+ break;
+
+ case earLocAvatar:
+ earPosition = mAvatarPosition;
+ earVelocity = mAvatarVelocity;
+ earRot = avatarRot;
+ break;
+
+ case earLocMixed:
+ earPosition = mAvatarPosition;
+ earVelocity = mAvatarVelocity;
+ earRot = mCameraRot;
+ break;
+ }
+
+ l = earRot.getLeftRow();
+ u = earRot.getUpRow();
+ a = earRot.getFwdRow();
+
+ pos = earPosition;
+ vel = earVelocity;
+
+
+ oldSDKTransform(l, u, a, pos, vel);
+
+ if (mHidden)
+ {
+ for (int i=0;i<3;++i)
+ {
+ pos.mdV[i] = VX_NULL_POSITION;
+ }
+ }
+
+ stream
+ << "<Position>"
+ << "<X>" << pos.mdV[VX] << "</X>"
+ << "<Y>" << pos.mdV[VY] << "</Y>"
+ << "<Z>" << pos.mdV[VZ] << "</Z>"
+ << "</Position>"
+ << "<Velocity>"
+ << "<X>" << vel.mV[VX] << "</X>"
+ << "<Y>" << vel.mV[VY] << "</Y>"
+ << "<Z>" << vel.mV[VZ] << "</Z>"
+ << "</Velocity>"
+ << "<AtOrientation>"
+ << "<X>" << a.mV[VX] << "</X>"
+ << "<Y>" << a.mV[VY] << "</Y>"
+ << "<Z>" << a.mV[VZ] << "</Z>"
+ << "</AtOrientation>"
+ << "<UpOrientation>"
+ << "<X>" << u.mV[VX] << "</X>"
+ << "<Y>" << u.mV[VY] << "</Y>"
+ << "<Z>" << u.mV[VZ] << "</Z>"
+ << "</UpOrientation>"
+ << "<LeftOrientation>"
+ << "<X>" << l.mV [VX] << "</X>"
+ << "<Y>" << l.mV [VY] << "</Y>"
+ << "<Z>" << l.mV [VZ] << "</Z>"
+ << "</LeftOrientation>"
+ ;
+
+ stream << "</ListenerPosition>";
+
+ stream << "<ReqDispositionType>1</ReqDispositionType>"; //do not generate responses for update requests
+ stream << "</Request>\n\n\n";
+ }
+
+ if(mAudioSession && (mAudioSession->mVolumeDirty || mAudioSession->mMuteDirty))
+ {
+ participantMap::iterator iter = mAudioSession->mParticipantsByURI.begin();
+
+ mAudioSession->mVolumeDirty = false;
+ mAudioSession->mMuteDirty = false;
+
+ for(; iter != mAudioSession->mParticipantsByURI.end(); iter++)
+ {
+ participantStatePtr_t p(iter->second);
+
+ if(p->mVolumeDirty)
+ {
+ // Can't set volume/mute for yourself
+ if(!p->mIsSelf)
+ {
+ // scale from the range 0.0-1.0 to vivox volume in the range 0-100
+ S32 volume = ll_round(p->mVolume / VOLUME_SCALE_VIVOX);
+ bool mute = p->mOnMuteList;
+
+ if(mute)
+ {
+ // SetParticipantMuteForMe doesn't work in p2p sessions.
+ // If we want the user to be muted, set their volume to 0 as well.
+ // This isn't perfect, but it will at least reduce their volume to a minimum.
+ volume = 0;
+ // Mark the current volume level as set to prevent incoming events
+ // changing it to 0, so that we can return to it when unmuting.
+ p->mVolumeSet = true;
+ }
+
+ if(volume == 0)
+ {
+ mute = true;
+ }
+
+ LL_DEBUGS("Voice") << "Setting volume/mute for avatar " << p->mAvatarID << " to " << volume << (mute?"/true":"/false") << LL_ENDL;
+
+ // SLIM SDK: Send both volume and mute commands.
+
+ // Send a "volume for me" command for the user.
+ stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.SetParticipantVolumeForMe.1\">"
+ << "<SessionHandle>" << getAudioSessionHandle() << "</SessionHandle>"
+ << "<ParticipantURI>" << p->mURI << "</ParticipantURI>"
+ << "<Volume>" << volume << "</Volume>"
+ << "</Request>\n\n\n";
+
+ if(!mAudioSession->mIsP2P)
+ {
+ // Send a "mute for me" command for the user
+ // Doesn't work in P2P sessions
+ stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.SetParticipantMuteForMe.1\">"
+ << "<SessionHandle>" << getAudioSessionHandle() << "</SessionHandle>"
+ << "<ParticipantURI>" << p->mURI << "</ParticipantURI>"
+ << "<Mute>" << (mute?"1":"0") << "</Mute>"
+ << "<Scope>Audio</Scope>"
+ << "</Request>\n\n\n";
+ }
+ }
+
+ p->mVolumeDirty = false;
+ }
+ }
+ }
+
+ std::string update(stream.str());
+ if(!update.empty())
+ {
+ LL_DEBUGS("VoiceUpdate") << "sending update " << update << LL_ENDL;
+ writeString(update);
+ }
+
+}
+
+void LLVivoxVoiceClient::buildSetCaptureDevice(std::ostringstream &stream)
+{
+ if(mCaptureDeviceDirty)
+ {
+ LL_DEBUGS("Voice") << "Setting input device = \"" << mCaptureDevice << "\"" << LL_ENDL;
+
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetCaptureDevice.1\">"
+ << "<CaptureDeviceSpecifier>" << mCaptureDevice << "</CaptureDeviceSpecifier>"
+ << "</Request>"
+ << "\n\n\n";
+
+ mCaptureDeviceDirty = false;
+ }
+}
+
+void LLVivoxVoiceClient::buildSetRenderDevice(std::ostringstream &stream)
+{
+ if(mRenderDeviceDirty)
+ {
+ LL_DEBUGS("Voice") << "Setting output device = \"" << mRenderDevice << "\"" << LL_ENDL;
+
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetRenderDevice.1\">"
+ << "<RenderDeviceSpecifier>" << mRenderDevice << "</RenderDeviceSpecifier>"
+ << "</Request>"
+ << "\n\n\n";
+ mRenderDeviceDirty = false;
+ }
+}
+
+void LLVivoxVoiceClient::sendLocalAudioUpdates()
+{
+ // Check all of the dirty states and then send messages to those needing to be changed.
+ // Tuningmode hands its own mute settings.
+ std::ostringstream stream;
+
+ if (mMuteMicDirty && !mTuningMode)
+ {
+ mMuteMicDirty = false;
+
+ // Send a local mute command.
+
+ LL_INFOS("Voice") << "Sending MuteLocalMic command with parameter " << (mMuteMic ? "true" : "false") << LL_ENDL;
+
+ stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.MuteLocalMic.1\">"
+ << "<ConnectorHandle>" << LLVivoxSecurity::getInstance()->connectorHandle() << "</ConnectorHandle>"
+ << "<Value>" << (mMuteMic ? "true" : "false") << "</Value>"
+ << "</Request>\n\n\n";
+
+ }
+
+ if (mSpeakerMuteDirty && !mTuningMode)
+ {
+ const char *muteval = ((mSpeakerVolume <= scale_speaker_volume(0)) ? "true" : "false");
+
+ mSpeakerMuteDirty = false;
+
+ LL_INFOS("Voice") << "Setting speaker mute to " << muteval << LL_ENDL;
+
+ stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.MuteLocalSpeaker.1\">"
+ << "<ConnectorHandle>" << LLVivoxSecurity::getInstance()->connectorHandle() << "</ConnectorHandle>"
+ << "<Value>" << muteval << "</Value>"
+ << "</Request>\n\n\n";
+
+ }
+
+ if (mSpeakerVolumeDirty)
+ {
+ mSpeakerVolumeDirty = false;
+
+ LL_INFOS("Voice") << "Setting speaker volume to " << mSpeakerVolume << LL_ENDL;
+
+ stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.SetLocalSpeakerVolume.1\">"
+ << "<ConnectorHandle>" << LLVivoxSecurity::getInstance()->connectorHandle() << "</ConnectorHandle>"
+ << "<Value>" << mSpeakerVolume << "</Value>"
+ << "</Request>\n\n\n";
+
+ }
+
+ if (mMicVolumeDirty)
+ {
+ mMicVolumeDirty = false;
+
+ LL_INFOS("Voice") << "Setting mic volume to " << mMicVolume << LL_ENDL;
+
+ stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.SetLocalMicVolume.1\">"
+ << "<ConnectorHandle>" << LLVivoxSecurity::getInstance()->connectorHandle() << "</ConnectorHandle>"
+ << "<Value>" << mMicVolume << "</Value>"
+ << "</Request>\n\n\n";
+ }
+
+
+ if (!stream.str().empty())
+ {
+ writeString(stream.str());
+ }
+}
+
+/**
+ * Because of the recurring voice cutout issues (SL-15072) we are going to try
+ * to disable the automatic VAD (Voice Activity Detection) and set the associated
+ * parameters directly. We will expose them via Debug Settings and that should
+ * let us iterate on a collection of values that work for us. Hopefully!
+ *
+ * From the VIVOX Docs:
+ *
+ * VadAuto: A flag indicating if the automatic VAD is enabled (1) or disabled (0)
+ *
+ * VadHangover: The time (in milliseconds) that it takes
+ * for the VAD to switch back to silence from speech mode after the last speech
+ * frame has been detected.
+ *
+ * VadNoiseFloor: A dimensionless value between 0 and
+ * 20000 (default 576) that controls the maximum level at which the noise floor
+ * may be set at by the VAD's noise tracking. Too low of a value will make noise
+ * tracking ineffective (A value of 0 disables noise tracking and the VAD then
+ * relies purely on the sensitivity property). Too high of a value will make
+ * long speech classifiable as noise.
+ *
+ * VadSensitivity: A dimensionless value between 0 and
+ * 100, indicating the 'sensitivity of the VAD'. Increasing this value corresponds
+ * to decreasing the sensitivity of the VAD (i.e. '0' is most sensitive,
+ * while 100 is 'least sensitive')
+ */
+void LLVivoxVoiceClient::setupVADParams(unsigned int vad_auto,
+ unsigned int vad_hangover,
+ unsigned int vad_noise_floor,
+ unsigned int vad_sensitivity)
+{
+ std::ostringstream stream;
+
+ LL_INFOS("Voice") << "Setting the automatic VAD to "
+ << (vad_auto ? "True" : "False")
+ << " and discrete values to"
+ << " VadHangover = " << vad_hangover
+ << ", VadSensitivity = " << vad_sensitivity
+ << ", VadNoiseFloor = " << vad_noise_floor
+ << LL_ENDL;
+
+ // Create a request to set the VAD parameters:
+ stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetVadProperties.1\">"
+ << "<VadAuto>" << vad_auto << "</VadAuto>"
+ << "<VadHangover>" << vad_hangover << "</VadHangover>"
+ << "<VadSensitivity>" << vad_sensitivity << "</VadSensitivity>"
+ << "<VadNoiseFloor>" << vad_noise_floor << "</VadNoiseFloor>"
+ << "</Request>\n\n\n";
+
+ if (!stream.str().empty())
+ {
+ writeString(stream.str());
+ }
+}
+
+void LLVivoxVoiceClient::onVADSettingsChange()
+{
+ // pick up the VAD variables (one of which was changed)
+ unsigned int vad_auto = gSavedSettings.getU32("VivoxVadAuto");
+ unsigned int vad_hangover = gSavedSettings.getU32("VivoxVadHangover");
+ unsigned int vad_noise_floor = gSavedSettings.getU32("VivoxVadNoiseFloor");
+ unsigned int vad_sensitivity = gSavedSettings.getU32("VivoxVadSensitivity");
+
+ // build a VAD params change request and send it to SLVoice
+ setupVADParams(vad_auto, vad_hangover, vad_noise_floor, vad_sensitivity);
+}
+
+/////////////////////////////
+// Response/Event handlers
+
+void LLVivoxVoiceClient::connectorCreateResponse(int statusCode, std::string &statusString, std::string &connectorHandle, std::string &versionID)
+{
+ LLSD result = LLSD::emptyMap();
+
+ if(statusCode == 0)
+ {
+ // Connector created, move forward.
+ if (connectorHandle == LLVivoxSecurity::getInstance()->connectorHandle())
+ {
+ LL_INFOS("Voice") << "Voice connector succeeded, Vivox SDK version is " << versionID << " connector handle " << connectorHandle << LL_ENDL;
+ mVoiceVersion.serverVersion = versionID;
+ mConnectorEstablished = true;
+ mTerminateDaemon = false;
+
+ result["connector"] = LLSD::Boolean(true);
+ }
+ else
+ {
+ // This shouldn't happen - we are somehow out of sync with SLVoice
+ // or possibly there are two things trying to run SLVoice at once
+ // or someone is trying to hack into it.
+ LL_WARNS("Voice") << "Connector returned wrong handle "
+ << "(" << connectorHandle << ")"
+ << " expected (" << LLVivoxSecurity::getInstance()->connectorHandle() << ")"
+ << LL_ENDL;
+ result["connector"] = LLSD::Boolean(false);
+ // Give up.
+ mTerminateDaemon = true;
+ }
+ }
+ else if (statusCode == 10028) // web request timeout prior to login
+ {
+ // this is usually fatal, but a long timeout might work
+ result["connector"] = LLSD::Boolean(false);
+ result["retry"] = LLSD::Real(CONNECT_ATTEMPT_TIMEOUT);
+
+ LL_WARNS("Voice") << "Voice connection failed" << LL_ENDL;
+ }
+ else if (statusCode == 10006) // name resolution failure - a shorter retry may work
+ {
+ // some networks have slower DNS, but a short timeout might let it catch up
+ result["connector"] = LLSD::Boolean(false);
+ result["retry"] = LLSD::Real(CONNECT_DNS_TIMEOUT);
+
+ LL_WARNS("Voice") << "Voice connection DNS lookup failed" << LL_ENDL;
+ }
+ else // unknown failure - give up
+ {
+ LL_WARNS("Voice") << "Voice connection failure ("<< statusCode << "): " << statusString << LL_ENDL;
+ mTerminateDaemon = true;
+ result["connector"] = LLSD::Boolean(false);
+ }
+
+ mVivoxPump.post(result);
+}
+
+void LLVivoxVoiceClient::loginResponse(int statusCode, std::string &statusString, std::string &accountHandle, int numberOfAliases)
+{
+ LLSD result = LLSD::emptyMap();
+
+ LL_DEBUGS("Voice") << "Account.Login response (" << statusCode << "): " << statusString << LL_ENDL;
+
+ // Status code of 20200 means "bad password". We may want to special-case that at some point.
+
+ if ( statusCode == HTTP_UNAUTHORIZED )
+ {
+ // Login failure which is probably caused by the delay after a user's password being updated.
+ LL_INFOS("Voice") << "Account.Login response failure (" << statusCode << "): " << statusString << LL_ENDL;
+ result["login"] = LLSD::String("retry");
+ }
+ else if(statusCode != 0)
+ {
+ LL_WARNS("Voice") << "Account.Login response failure (" << statusCode << "): " << statusString << LL_ENDL;
+ result["login"] = LLSD::String("failed");
+ }
+ else
+ {
+ // Login succeeded, move forward.
+ mAccountLoggedIn = true;
+ mNumberOfAliases = numberOfAliases;
+ result["login"] = LLSD::String("response_ok");
+ }
+
+ mVivoxPump.post(result);
+
+}
+
+void LLVivoxVoiceClient::sessionCreateResponse(std::string &requestId, int statusCode, std::string &statusString, std::string &sessionHandle)
+{
+ sessionStatePtr_t session(findSessionBeingCreatedByURI(requestId));
+
+ if(session)
+ {
+ session->mCreateInProgress = false;
+ }
+
+ if(statusCode != 0)
+ {
+ LL_WARNS("Voice") << "Session.Create response failure (" << statusCode << "): " << statusString << LL_ENDL;
+ if(session)
+ {
+ session->mErrorStatusCode = statusCode;
+ session->mErrorStatusString = statusString;
+ if(session == mAudioSession)
+ {
+ LLSD vivoxevent(LLSDMap("handle", LLSD::String(sessionHandle))
+ ("session", "failed")
+ ("reason", LLSD::Integer(statusCode)));
+
+ mVivoxPump.post(vivoxevent);
+ }
+ else
+ {
+ reapSession(session);
+ }
+ }
+ }
+ else
+ {
+ LL_INFOS("Voice") << "Session.Create response received (success), session handle is " << sessionHandle << LL_ENDL;
+ if(session)
+ {
+ setSessionHandle(session, sessionHandle);
+ }
+ LLSD vivoxevent(LLSDMap("handle", LLSD::String(sessionHandle))
+ ("session", "created"));
+
+ mVivoxPump.post(vivoxevent);
+ }
+}
+
+void LLVivoxVoiceClient::sessionGroupAddSessionResponse(std::string &requestId, int statusCode, std::string &statusString, std::string &sessionHandle)
+{
+ sessionStatePtr_t session(findSessionBeingCreatedByURI(requestId));
+
+ if(session)
+ {
+ session->mCreateInProgress = false;
+ }
+
+ if(statusCode != 0)
+ {
+ LL_WARNS("Voice") << "SessionGroup.AddSession response failure (" << statusCode << "): " << statusString << LL_ENDL;
+ if(session)
+ {
+ session->mErrorStatusCode = statusCode;
+ session->mErrorStatusString = statusString;
+ if(session == mAudioSession)
+ {
+ LLSD vivoxevent(LLSDMap("handle", LLSD::String(sessionHandle))
+ ("session", "failed"));
+
+ mVivoxPump.post(vivoxevent);
+ }
+ else
+ {
+ reapSession(session);
+ }
+ }
+ }
+ else
+ {
+ LL_DEBUGS("Voice") << "SessionGroup.AddSession response received (success), session handle is " << sessionHandle << LL_ENDL;
+ if(session)
+ {
+ setSessionHandle(session, sessionHandle);
+ }
+
+ LLSD vivoxevent(LLSDMap("handle", LLSD::String(sessionHandle))
+ ("session", "added"));
+
+ mVivoxPump.post(vivoxevent);
+
+ }
+}
+
+void LLVivoxVoiceClient::sessionConnectResponse(std::string &requestId, int statusCode, std::string &statusString)
+{
+ sessionStatePtr_t session(findSession(requestId));
+ // 1026 is session already has media, somehow mediaconnect was called twice on the same session.
+ // set the session info to reflect that the user is already connected.
+ if (statusCode == 1026)
+ {
+ session->mVoiceActive = true;
+ session->mMediaConnectInProgress = false;
+ session->mMediaStreamState = streamStateConnected;
+ //session->mTextStreamState = streamStateConnected;
+ session->mErrorStatusCode = 0;
+ }
+ else if (statusCode != 0)
+ {
+ LL_WARNS("Voice") << "Session.Connect response failure (" << statusCode << "): " << statusString << LL_ENDL;
+ if (session)
+ {
+ session->mMediaConnectInProgress = false;
+ session->mErrorStatusCode = statusCode;
+ session->mErrorStatusString = statusString;
+ }
+ }
+ else
+ {
+ LL_DEBUGS("Voice") << "Session.Connect response received (success)" << LL_ENDL;
+ }
+}
+
+void LLVivoxVoiceClient::logoutResponse(int statusCode, std::string &statusString)
+{
+ if(statusCode != 0)
+ {
+ LL_WARNS("Voice") << "Account.Logout response failure: " << statusString << LL_ENDL;
+ // Should this ever fail? do we care if it does?
+ }
+ LLSD vivoxevent(LLSDMap("logout", LLSD::Boolean(true)));
+
+ mVivoxPump.post(vivoxevent);
+}
+
+void LLVivoxVoiceClient::connectorShutdownResponse(int statusCode, std::string &statusString)
+{
+ if(statusCode != 0)
+ {
+ LL_WARNS("Voice") << "Connector.InitiateShutdown response failure: " << statusString << LL_ENDL;
+ // Should this ever fail? do we care if it does?
+ }
+
+ sConnected = false;
+ mShutdownComplete = true;
+
+ LLSD vivoxevent(LLSDMap("connector", LLSD::Boolean(false)));
+
+ mVivoxPump.post(vivoxevent);
+}
+
+void LLVivoxVoiceClient::sessionAddedEvent(
+ std::string &uriString,
+ std::string &alias,
+ std::string &sessionHandle,
+ std::string &sessionGroupHandle,
+ bool isChannel,
+ bool incoming,
+ std::string &nameString,
+ std::string &applicationString)
+{
+ sessionStatePtr_t session;
+
+ LL_INFOS("Voice") << "session " << uriString << ", alias " << alias << ", name " << nameString << " handle " << sessionHandle << LL_ENDL;
+
+ session = addSession(uriString, sessionHandle);
+ if(session)
+ {
+ session->mGroupHandle = sessionGroupHandle;
+ session->mIsChannel = isChannel;
+ session->mIncoming = incoming;
+ session->mAlias = alias;
+
+ // Generate a caller UUID -- don't need to do this for channels
+ if(!session->mIsChannel)
+ {
+ if(IDFromName(session->mSIPURI, session->mCallerID))
+ {
+ // Normal URI(base64-encoded UUID)
+ }
+ else if(!session->mAlias.empty() && IDFromName(session->mAlias, session->mCallerID))
+ {
+ // Wrong URI, but an alias is available. Stash the incoming URI as an alternate
+ session->mAlternateSIPURI = session->mSIPURI;
+
+ // and generate a proper URI from the ID.
+ setSessionURI(session, sipURIFromID(session->mCallerID));
+ }
+ else
+ {
+ LL_INFOS("Voice") << "Could not generate caller id from uri, using hash of uri " << session->mSIPURI << LL_ENDL;
+ session->mCallerID.generate(session->mSIPURI);
+ session->mSynthesizedCallerID = true;
+
+ // Can't look up the name in this case -- we have to extract it from the URI.
+ std::string namePortion = nameFromsipURI(session->mSIPURI);
+ if(namePortion.empty())
+ {
+ // Didn't seem to be a SIP URI, just use the whole provided name.
+ namePortion = nameString;
+ }
+
+ // Some incoming names may be separated with an underscore instead of a space. Fix this.
+ LLStringUtil::replaceChar(namePortion, '_', ' ');
+
+ // Act like we just finished resolving the name (this stores it in all the right places)
+ avatarNameResolved(session->mCallerID, namePortion);
+ }
+
+ LL_INFOS("Voice") << "caller ID: " << session->mCallerID << LL_ENDL;
+
+ if(!session->mSynthesizedCallerID)
+ {
+ // If we got here, we don't have a proper name. Initiate a lookup.
+ lookupName(session->mCallerID);
+ }
+ }
+ }
+}
+
+void LLVivoxVoiceClient::sessionGroupAddedEvent(std::string &sessionGroupHandle)
+{
+ LL_DEBUGS("Voice") << "handle " << sessionGroupHandle << LL_ENDL;
+
+#if USE_SESSION_GROUPS
+ if(mMainSessionGroupHandle.empty())
+ {
+ // This is the first (i.e. "main") session group. Save its handle.
+ mMainSessionGroupHandle = sessionGroupHandle;
+ }
+ else
+ {
+ LL_DEBUGS("Voice") << "Already had a session group handle " << mMainSessionGroupHandle << LL_ENDL;
+ }
+#endif
+}
+
+void LLVivoxVoiceClient::joinedAudioSession(const sessionStatePtr_t &session)
+{
+ LL_DEBUGS("Voice") << "Joined Audio Session" << LL_ENDL;
+ if(mAudioSession != session)
+ {
+ sessionStatePtr_t oldSession = mAudioSession;
+
+ mAudioSession = session;
+ mAudioSessionChanged = true;
+
+ // The old session may now need to be deleted.
+ reapSession(oldSession);
+ }
+
+ // This is the session we're joining.
+ if(mIsJoiningSession)
+ {
+ LLSD vivoxevent(LLSDMap("handle", LLSD::String(session->mHandle))
+ ("session", "joined"));
+
+ mVivoxPump.post(vivoxevent);
+
+ // Add the current user as a participant here.
+ participantStatePtr_t participant(session->addParticipant(sipURIFromName(mAccountName)));
+ if(participant)
+ {
+ participant->mIsSelf = true;
+ lookupName(participant->mAvatarID);
+
+ LL_INFOS("Voice") << "added self as participant \"" << participant->mAccountName
+ << "\" (" << participant->mAvatarID << ")"<< LL_ENDL;
+ }
+
+ if(!session->mIsChannel)
+ {
+ // this is a p2p session. Make sure the other end is added as a participant.
+ participantStatePtr_t participant(session->addParticipant(session->mSIPURI));
+ if(participant)
+ {
+ if(participant->mAvatarIDValid)
+ {
+ lookupName(participant->mAvatarID);
+ }
+ else if(!session->mName.empty())
+ {
+ participant->mDisplayName = session->mName;
+ avatarNameResolved(participant->mAvatarID, session->mName);
+ }
+
+ // TODO: Question: Do we need to set up mAvatarID/mAvatarIDValid here?
+ LL_INFOS("Voice") << "added caller as participant \"" << participant->mAccountName
+ << "\" (" << participant->mAvatarID << ")"<< LL_ENDL;
+ }
+ }
+ }
+}
+
+void LLVivoxVoiceClient::sessionRemovedEvent(
+ std::string &sessionHandle,
+ std::string &sessionGroupHandle)
+{
+ LL_INFOS("Voice") << "handle " << sessionHandle << LL_ENDL;
+
+ sessionStatePtr_t session(findSession(sessionHandle));
+ if(session)
+ {
+ leftAudioSession(session);
+
+ // This message invalidates the session's handle. Set it to empty.
+ clearSessionHandle(session);
+
+ // This also means that the session's session group is now empty.
+ // Terminate the session group so it doesn't leak.
+ sessionGroupTerminateSendMessage(session);
+
+ // Reset the media state (we now have no info)
+ session->mMediaStreamState = streamStateUnknown;
+ //session->mTextStreamState = streamStateUnknown;
+
+ // Conditionally delete the session
+ reapSession(session);
+ }
+ else
+ {
+ // Already reaped this session.
+ LL_DEBUGS("Voice") << "unknown session " << sessionHandle << " removed" << LL_ENDL;
+ }
+
+}
+
+void LLVivoxVoiceClient::reapSession(const sessionStatePtr_t &session)
+{
+ if(session)
+ {
+
+ if(session->mCreateInProgress)
+ {
+ LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (create in progress)" << LL_ENDL;
+ }
+ else if(session->mMediaConnectInProgress)
+ {
+ LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (connect in progress)" << LL_ENDL;
+ }
+ else if(session == mAudioSession)
+ {
+ LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (it's the current session)" << LL_ENDL;
+ }
+ else if(session == mNextAudioSession)
+ {
+ LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (it's the next session)" << LL_ENDL;
+ }
+ else
+ {
+ // We don't have a reason to keep tracking this session, so just delete it.
+ LL_DEBUGS("Voice") << "deleting session " << session->mSIPURI << LL_ENDL;
+ deleteSession(session);
+ }
+ }
+ else
+ {
+// LL_DEBUGS("Voice") << "session is NULL" << LL_ENDL;
+ }
+}
+
+// Returns true if the session seems to indicate we've moved to a region on a different voice server
+bool LLVivoxVoiceClient::sessionNeedsRelog(const sessionStatePtr_t &session)
+{
+ bool result = false;
+
+ if(session)
+ {
+ // Only make this check for spatial channels (so it won't happen for group or p2p calls)
+ if(session->mIsSpatial)
+ {
+ std::string::size_type atsign;
+
+ atsign = session->mSIPURI.find("@");
+
+ if(atsign != std::string::npos)
+ {
+ std::string urihost = session->mSIPURI.substr(atsign + 1);
+ if(stricmp(urihost.c_str(), mVoiceSIPURIHostName.c_str()))
+ {
+ // The hostname in this URI is different from what we expect. This probably means we need to relog.
+
+ // We could make a ProvisionVoiceAccountRequest and compare the result with the current values of
+ // mVoiceSIPURIHostName and mVoiceAccountServerURI to be really sure, but this is a pretty good indicator.
+
+ result = true;
+ }
+ }
+ }
+ }
+
+ return result;
+}
+
+void LLVivoxVoiceClient::leftAudioSession(const sessionStatePtr_t &session)
+{
+ if (mAudioSession == session)
+ {
+ LLSD vivoxevent(LLSDMap("handle", LLSD::String(session->mHandle))
+ ("session", "removed"));
+
+ mVivoxPump.post(vivoxevent);
+ }
+}
+
+void LLVivoxVoiceClient::accountLoginStateChangeEvent(
+ std::string &accountHandle,
+ int statusCode,
+ std::string &statusString,
+ int state)
+{
+ LLSD levent = LLSD::emptyMap();
+
+ /*
+ According to Mike S., status codes for this event are:
+ login_state_logged_out=0,
+ login_state_logged_in = 1,
+ login_state_logging_in = 2,
+ login_state_logging_out = 3,
+ login_state_resetting = 4,
+ login_state_error=100
+ */
+
+ LL_DEBUGS("Voice") << "state change event: " << state << LL_ENDL;
+ switch(state)
+ {
+ case 1:
+ levent["login"] = LLSD::String("account_login");
+
+ mVivoxPump.post(levent);
+ break;
+ case 2:
+ break;
+
+ case 3:
+ levent["login"] = LLSD::String("account_loggingOut");
+
+ mVivoxPump.post(levent);
+ break;
+
+ case 4:
+ break;
+
+ case 100:
+ LL_WARNS("Voice") << "account state event error" << LL_ENDL;
+ break;
+
+ case 0:
+ levent["login"] = LLSD::String("account_logout");
+
+ mVivoxPump.post(levent);
+ break;
+
+ default:
+ //Used to be a commented out warning
+ LL_WARNS("Voice") << "unknown account state event: " << state << LL_ENDL;
+ break;
+ }
+}
+
+void LLVivoxVoiceClient::mediaCompletionEvent(std::string &sessionGroupHandle, std::string &mediaCompletionType)
+{
+ LLSD result;
+
+ if (mediaCompletionType == "AuxBufferAudioCapture")
+ {
+ mCaptureBufferRecording = false;
+ result["recplay"] = "end";
+ }
+ else if (mediaCompletionType == "AuxBufferAudioRender")
+ {
+ // Ignore all but the last stop event
+ if (--mPlayRequestCount <= 0)
+ {
+ mCaptureBufferPlaying = false;
+ result["recplay"] = "end";
+// result["recplay"] = "done";
+ }
+ }
+ else
+ {
+ LL_WARNS("Voice") << "Unknown MediaCompletionType: " << mediaCompletionType << LL_ENDL;
+ }
+
+ if (!result.isUndefined())
+ mVivoxPump.post(result);
+}
+
+void LLVivoxVoiceClient::mediaStreamUpdatedEvent(
+ std::string &sessionHandle,
+ std::string &sessionGroupHandle,
+ int statusCode,
+ std::string &statusString,
+ int state,
+ bool incoming)
+{
+ sessionStatePtr_t session(findSession(sessionHandle));
+
+ LL_DEBUGS("Voice") << "session " << sessionHandle << ", status code " << statusCode << ", string \"" << statusString << "\"" << LL_ENDL;
+
+ if(session)
+ {
+ // We know about this session
+
+ // Save the state for later use
+ session->mMediaStreamState = state;
+
+ switch(statusCode)
+ {
+ case 0:
+ case HTTP_OK:
+ // generic success
+ // Don't change the saved error code (it may have been set elsewhere)
+ break;
+ default:
+ // save the status code for later
+ session->mErrorStatusCode = statusCode;
+ break;
+ }
+
+ switch(state)
+ {
+ case streamStateDisconnecting:
+ case streamStateIdle:
+ // Standard "left audio session", Vivox state 'disconnected'
+ session->mVoiceActive = false;
+ session->mMediaConnectInProgress = false;
+ leftAudioSession(session);
+ break;
+
+ case streamStateConnected:
+ session->mVoiceActive = true;
+ session->mMediaConnectInProgress = false;
+ joinedAudioSession(session);
+ case streamStateConnecting: // do nothing, but prevents a warning getting into the logs.
+ break;
+
+ case streamStateRinging:
+ if(incoming)
+ {
+ // Send the voice chat invite to the GUI layer
+ // TODO: Question: Should we correlate with the mute list here?
+ session->mIMSessionID = LLIMMgr::computeSessionID(IM_SESSION_P2P_INVITE, session->mCallerID);
+ session->mVoiceInvitePending = true;
+ if(session->mName.empty())
+ {
+ lookupName(session->mCallerID);
+ }
+ else
+ {
+ // Act like we just finished resolving the name
+ avatarNameResolved(session->mCallerID, session->mName);
+ }
+ }
+ break;
+
+ default:
+ LL_WARNS("Voice") << "unknown state " << state << LL_ENDL;
+ break;
+
+ }
+
+ }
+ else
+ {
+ // session disconnectintg and disconnected events arriving after we have already left the session.
+ LL_DEBUGS("Voice") << "session " << sessionHandle << " not found"<< LL_ENDL;
+ }
+}
+
+void LLVivoxVoiceClient::participantAddedEvent(
+ std::string &sessionHandle,
+ std::string &sessionGroupHandle,
+ std::string &uriString,
+ std::string &alias,
+ std::string &nameString,
+ std::string &displayNameString,
+ int participantType)
+{
+ sessionStatePtr_t session(findSession(sessionHandle));
+ if(session)
+ {
+ participantStatePtr_t participant(session->addParticipant(uriString));
+ if(participant)
+ {
+ participant->mAccountName = nameString;
+
+ LL_DEBUGS("Voice") << "added participant \"" << participant->mAccountName
+ << "\" (" << participant->mAvatarID << ")"<< LL_ENDL;
+
+ if(participant->mAvatarIDValid)
+ {
+ // Initiate a lookup
+ lookupName(participant->mAvatarID);
+ }
+ else
+ {
+ // If we don't have a valid avatar UUID, we need to fill in the display name to make the active speakers floater work.
+ std::string namePortion = nameFromsipURI(uriString);
+ if(namePortion.empty())
+ {
+ // Problem with the SIP URI, fall back to the display name
+ namePortion = displayNameString;
+ }
+ if(namePortion.empty())
+ {
+ // Problems with both of the above, fall back to the account name
+ namePortion = nameString;
+ }
+
+ // Set the display name (which is a hint to the active speakers window not to do its own lookup)
+ participant->mDisplayName = namePortion;
+ avatarNameResolved(participant->mAvatarID, namePortion);
+ }
+ }
+ }
+}
+
+void LLVivoxVoiceClient::participantRemovedEvent(
+ std::string &sessionHandle,
+ std::string &sessionGroupHandle,
+ std::string &uriString,
+ std::string &alias,
+ std::string &nameString)
+{
+ sessionStatePtr_t session(findSession(sessionHandle));
+ if(session)
+ {
+ participantStatePtr_t participant(session->findParticipant(uriString));
+ if(participant)
+ {
+ session->removeParticipant(participant);
+ }
+ else
+ {
+ LL_DEBUGS("Voice") << "unknown participant " << uriString << LL_ENDL;
+ }
+ }
+ else
+ {
+ // a late arriving event on a session we have already left.
+ LL_DEBUGS("Voice") << "unknown session " << sessionHandle << LL_ENDL;
+ }
+}
+
+
+void LLVivoxVoiceClient::participantUpdatedEvent(
+ std::string &sessionHandle,
+ std::string &sessionGroupHandle,
+ std::string &uriString,
+ std::string &alias,
+ bool isModeratorMuted,
+ bool isSpeaking,
+ int volume,
+ F32 energy)
+{
+ sessionStatePtr_t session(findSession(sessionHandle));
+ if(session)
+ {
+ participantStatePtr_t participant(session->findParticipant(uriString));
+
+ if(participant)
+ {
+ //LL_INFOS("Voice") << "Participant Update for " << participant->mDisplayName << LL_ENDL;
+
+ participant->mIsSpeaking = isSpeaking;
+ participant->mIsModeratorMuted = isModeratorMuted;
+
+ // SLIM SDK: convert range: ensure that energy is set to zero if is_speaking is false
+ if (isSpeaking)
+ {
+ participant->mSpeakingTimeout.reset();
+ participant->mPower = energy;
+ }
+ else
+ {
+ participant->mPower = 0.0f;
+ }
+
+ // Ignore incoming volume level if it has been explicitly set, or there
+ // is a volume or mute change pending.
+ if ( !participant->mVolumeSet && !participant->mVolumeDirty)
+ {
+ participant->mVolume = (F32)volume * VOLUME_SCALE_VIVOX;
+ }
+
+ // *HACK: mantipov: added while working on EXT-3544
+ /*
+ Sometimes LLVoiceClient::participantUpdatedEvent callback is called BEFORE
+ LLViewerChatterBoxSessionAgentListUpdates::post() sometimes AFTER.
+
+ participantUpdatedEvent updates voice participant state in particular participantState::mIsModeratorMuted
+ Originally we wanted to update session Speaker Manager to fire LLSpeakerVoiceModerationEvent to fix the EXT-3544 bug.
+ Calling of the LLSpeakerMgr::update() method was added into LLIMMgr::processAgentListUpdates.
+
+ But in case participantUpdatedEvent() is called after LLViewerChatterBoxSessionAgentListUpdates::post()
+ voice participant mIsModeratorMuted is changed after speakers are updated in Speaker Manager
+ and event is not fired.
+
+ So, we have to call LLSpeakerMgr::update() here.
+ */
+ LLVoiceChannel* voice_cnl = LLVoiceChannel::getCurrentVoiceChannel();
+
+ // ignore session ID of local chat
+ if (voice_cnl && voice_cnl->getSessionID().notNull())
+ {
+ LLSpeakerMgr* speaker_manager = LLIMModel::getInstance()->getSpeakerManager(voice_cnl->getSessionID());
+ if (speaker_manager)
+ {
+ speaker_manager->update(true);
+
+ // also initialize voice moderate_mode depend on Agent's participant. See EXT-6937.
+ // *TODO: remove once a way to request the current voice channel moderation mode is implemented.
+ if (gAgent.getID() == participant->mAvatarID)
+ {
+ speaker_manager->initVoiceModerateMode();
+ }
+ }
+ }
+ }
+ else
+ {
+ LL_WARNS("Voice") << "unknown participant: " << uriString << LL_ENDL;
+ }
+ }
+ else
+ {
+ LL_DEBUGS("Voice") << "unknown session " << sessionHandle << LL_ENDL;
+ }
+}
+
+void LLVivoxVoiceClient::messageEvent(
+ std::string &sessionHandle,
+ std::string &uriString,
+ std::string &alias,
+ std::string &messageHeader,
+ std::string &messageBody,
+ std::string &applicationString)
+{
+ LL_DEBUGS("Voice") << "Message event, session " << sessionHandle << " from " << uriString << LL_ENDL;
+// LL_DEBUGS("Voice") << " header " << messageHeader << ", body: \n" << messageBody << LL_ENDL;
+
+ LL_INFOS("Voice") << "Vivox raw message:" << std::endl << messageBody << LL_ENDL;
+
+ if(messageHeader.find(HTTP_CONTENT_TEXT_HTML) != std::string::npos)
+ {
+ std::string message;
+
+ {
+ const std::string startMarker = "<body";
+ const std::string startMarker2 = ">";
+ const std::string endMarker = "</body>";
+ const std::string startSpan = "<span";
+ const std::string endSpan = "</span>";
+ std::string::size_type start;
+ std::string::size_type end;
+
+ // Default to displaying the raw string, so the message gets through.
+ message = messageBody;
+
+ // Find the actual message text within the XML fragment
+ start = messageBody.find(startMarker);
+ start = messageBody.find(startMarker2, start);
+ end = messageBody.find(endMarker);
+
+ if(start != std::string::npos)
+ {
+ start += startMarker2.size();
+
+ if(end != std::string::npos)
+ end -= start;
+
+ message.assign(messageBody, start, end);
+ }
+ else
+ {
+ // Didn't find a <body>, try looking for a <span> instead.
+ start = messageBody.find(startSpan);
+ start = messageBody.find(startMarker2, start);
+ end = messageBody.find(endSpan);
+
+ if(start != std::string::npos)
+ {
+ start += startMarker2.size();
+
+ if(end != std::string::npos)
+ end -= start;
+
+ message.assign(messageBody, start, end);
+ }
+ }
+ }
+
+// LL_DEBUGS("Voice") << " raw message = \n" << message << LL_ENDL;
+
+ // strip formatting tags
+ {
+ std::string::size_type start;
+ std::string::size_type end;
+
+ while((start = message.find('<')) != std::string::npos)
+ {
+ if((end = message.find('>', start + 1)) != std::string::npos)
+ {
+ // Strip out the tag
+ message.erase(start, (end + 1) - start);
+ }
+ else
+ {
+ // Avoid an infinite loop
+ break;
+ }
+ }
+ }
+
+ // Decode ampersand-escaped chars
+ {
+ std::string::size_type mark = 0;
+
+ // The text may contain text encoded with <, >, and &
+ mark = 0;
+ while((mark = message.find("<", mark)) != std::string::npos)
+ {
+ message.replace(mark, 4, "<");
+ mark += 1;
+ }
+
+ mark = 0;
+ while((mark = message.find(">", mark)) != std::string::npos)
+ {
+ message.replace(mark, 4, ">");
+ mark += 1;
+ }
+
+ mark = 0;
+ while((mark = message.find("&", mark)) != std::string::npos)
+ {
+ message.replace(mark, 5, "&");
+ mark += 1;
+ }
+ }
+
+ // strip leading/trailing whitespace (since we always seem to get a couple newlines)
+ LLStringUtil::trim(message);
+
+// LL_DEBUGS("Voice") << " stripped message = \n" << message << LL_ENDL;
+
+ sessionStatePtr_t session(findSession(sessionHandle));
+ if(session)
+ {
+ bool is_do_not_disturb = gAgent.isDoNotDisturb();
+ bool is_muted = LLMuteList::getInstance()->isMuted(session->mCallerID, session->mName, LLMute::flagTextChat);
+ bool is_linden = LLMuteList::isLinden(session->mName);
+ LLChat chat;
+
+ chat.mMuted = is_muted && !is_linden;
+
+ if(!chat.mMuted)
+ {
+ chat.mFromID = session->mCallerID;
+ chat.mFromName = session->mName;
+ chat.mSourceType = CHAT_SOURCE_AGENT;
+
+ if(is_do_not_disturb && !is_linden)
+ {
+ // TODO: Question: Return do not disturb mode response here? Or maybe when session is started instead?
+ }
+
+ LL_DEBUGS("Voice") << "adding message, name " << session->mName << " session " << session->mIMSessionID << ", target " << session->mCallerID << LL_ENDL;
+ LLIMMgr::getInstance()->addMessage(session->mIMSessionID,
+ session->mCallerID,
+ session->mName.c_str(),
+ message.c_str(),
+ false,
+ LLStringUtil::null, // default arg
+ IM_NOTHING_SPECIAL, // default arg
+ 0, // default arg
+ LLUUID::null, // default arg
+ LLVector3::zero); // default arg
+ }
+ }
+ }
+}
+
+void LLVivoxVoiceClient::sessionNotificationEvent(std::string &sessionHandle, std::string &uriString, std::string ¬ificationType)
+{
+ sessionStatePtr_t session(findSession(sessionHandle));
+
+ if(session)
+ {
+ participantStatePtr_t participant(session->findParticipant(uriString));
+ if(participant)
+ {
+ if (!stricmp(notificationType.c_str(), "Typing"))
+ {
+ // Other end started typing
+ // TODO: The proper way to add a typing notification seems to be LLIMMgr::processIMTypingStart().
+ // It requires some info for the message, which we don't have here.
+ }
+ else if (!stricmp(notificationType.c_str(), "NotTyping"))
+ {
+ // Other end stopped typing
+ // TODO: The proper way to remove a typing notification seems to be LLIMMgr::processIMTypingStop().
+ // It requires some info for the message, which we don't have here.
+ }
+ else
+ {
+ LL_DEBUGS("Voice") << "Unknown notification type " << notificationType << "for participant " << uriString << " in session " << session->mSIPURI << LL_ENDL;
+ }
+ }
+ else
+ {
+ LL_DEBUGS("Voice") << "Unknown participant " << uriString << " in session " << session->mSIPURI << LL_ENDL;
+ }
+ }
+ else
+ {
+ LL_DEBUGS("Voice") << "Unknown session handle " << sessionHandle << LL_ENDL;
+ }
+}
+
+void LLVivoxVoiceClient::voiceServiceConnectionStateChangedEvent(int statusCode, std::string &statusString, std::string &build_id)
+{
+ // We don't generally need to process this. However, one occurence is when we first connect, and so it is the
+ // earliest opportunity to learn what we're connected to.
+ if (statusCode)
+ {
+ LL_WARNS("Voice") << "VoiceServiceConnectionStateChangedEvent statusCode: " << statusCode <<
+ "statusString: " << statusString << LL_ENDL;
+ return;
+ }
+ if (build_id.empty())
+ {
+ return;
+ }
+ mVoiceVersion.mBuildVersion = build_id;
+}
+
+void LLVivoxVoiceClient::auxAudioPropertiesEvent(F32 energy)
+{
+ LL_DEBUGS("VoiceEnergy") << "got energy " << energy << LL_ENDL;
+ mTuningEnergy = energy;
+}
+
+void LLVivoxVoiceClient::muteListChanged()
+{
+ // The user's mute list has been updated. Go through the current participant list and sync it with the mute list.
+ if(mAudioSession)
+ {
+ participantMap::iterator iter = mAudioSession->mParticipantsByURI.begin();
+
+ for(; iter != mAudioSession->mParticipantsByURI.end(); iter++)
+ {
+ participantStatePtr_t p(iter->second);
+
+ // Check to see if this participant is on the mute list already
+ if(p->updateMuteState())
+ mAudioSession->mVolumeDirty = true;
+ }
+ }
+}
+
+/////////////////////////////
+// Managing list of participants
+LLVivoxVoiceClient::participantState::participantState(const std::string &uri) :
+ mURI(uri),
+ mPTT(false),
+ mIsSpeaking(false),
+ mIsModeratorMuted(false),
+ mLastSpokeTimestamp(0.f),
+ mPower(0.f),
+ mVolume(LLVoiceClient::VOLUME_DEFAULT),
+ mUserVolume(0),
+ mOnMuteList(false),
+ mVolumeSet(false),
+ mVolumeDirty(false),
+ mAvatarIDValid(false),
+ mIsSelf(false)
+{
+}
+
+LLVivoxVoiceClient::participantStatePtr_t LLVivoxVoiceClient::sessionState::addParticipant(const std::string &uri)
+{
+ participantStatePtr_t result;
+ bool useAlternateURI = false;
+
+ // Note: this is mostly the body of LLVivoxVoiceClient::sessionState::findParticipant(), but since we need to know if it
+ // matched the alternate SIP URI (so we can add it properly), we need to reproduce it here.
+ {
+ participantMap::iterator iter = mParticipantsByURI.find(uri);
+
+ if(iter == mParticipantsByURI.end())
+ {
+ if(!mAlternateSIPURI.empty() && (uri == mAlternateSIPURI))
+ {
+ // This is a p2p session (probably with the SLIM client) with an alternate URI for the other participant.
+ // Use mSIPURI instead, since it will be properly encoded.
+ iter = mParticipantsByURI.find(mSIPURI);
+ useAlternateURI = true;
+ }
+ }
+
+ if(iter != mParticipantsByURI.end())
+ {
+ result = iter->second;
+ }
+ }
+
+ if(!result)
+ {
+ // participant isn't already in one list or the other.
+ result.reset(new participantState(useAlternateURI?mSIPURI:uri));
+ mParticipantsByURI.insert(participantMap::value_type(result->mURI, result));
+ mParticipantsChanged = true;
+
+ // Try to do a reverse transform on the URI to get the GUID back.
+ {
+ LLUUID id;
+ if(LLVivoxVoiceClient::getInstance()->IDFromName(result->mURI, id))
+ {
+ result->mAvatarIDValid = true;
+ result->mAvatarID = id;
+ }
+ else
+ {
+ // Create a UUID by hashing the URI, but do NOT set mAvatarIDValid.
+ // This indicates that the ID will not be in the name cache.
+ result->mAvatarID.generate(uri);
+ }
+ }
+
+ if(result->updateMuteState())
+ {
+ mMuteDirty = true;
+ }
+
+ mParticipantsByUUID.insert(participantUUIDMap::value_type(result->mAvatarID, result));
+
+ if (LLSpeakerVolumeStorage::getInstance()->getSpeakerVolume(result->mAvatarID, result->mVolume))
+ {
+ result->mVolumeDirty = true;
+ mVolumeDirty = true;
+ }
+
+ LL_DEBUGS("Voice") << "participant \"" << result->mURI << "\" added." << LL_ENDL;
+ }
+
+ return result;
+}
+
+bool LLVivoxVoiceClient::participantState::updateMuteState()
+{
+ bool result = false;
+
+ bool isMuted = LLMuteList::getInstance()->isMuted(mAvatarID, LLMute::flagVoiceChat);
+ if(mOnMuteList != isMuted)
+ {
+ mOnMuteList = isMuted;
+ mVolumeDirty = true;
+ result = true;
+ }
+ return result;
+}
+
+bool LLVivoxVoiceClient::participantState::isAvatar()
+{
+ return mAvatarIDValid;
+}
+
+void LLVivoxVoiceClient::sessionState::removeParticipant(const LLVivoxVoiceClient::participantStatePtr_t &participant)
+{
+ if(participant)
+ {
+ participantMap::iterator iter = mParticipantsByURI.find(participant->mURI);
+ participantUUIDMap::iterator iter2 = mParticipantsByUUID.find(participant->mAvatarID);
+
+ LL_DEBUGS("Voice") << "participant \"" << participant->mURI << "\" (" << participant->mAvatarID << ") removed." << LL_ENDL;
+
+ if(iter == mParticipantsByURI.end())
+ {
+ LL_WARNS("Voice") << "Internal error: participant " << participant->mURI << " not in URI map" << LL_ENDL;
+ }
+ else if(iter2 == mParticipantsByUUID.end())
+ {
+ LL_WARNS("Voice") << "Internal error: participant ID " << participant->mAvatarID << " not in UUID map" << LL_ENDL;
+ }
+ else if(iter->second != iter2->second)
+ {
+ LL_WARNS("Voice") << "Internal error: participant mismatch!" << LL_ENDL;
+ }
+ else
+ {
+ mParticipantsByURI.erase(iter);
+ mParticipantsByUUID.erase(iter2);
+
+ mParticipantsChanged = true;
+ }
+ }
+}
+
+void LLVivoxVoiceClient::sessionState::removeAllParticipants()
+{
+ LL_DEBUGS("Voice") << "called" << LL_ENDL;
+
+ while(!mParticipantsByURI.empty())
+ {
+ removeParticipant(mParticipantsByURI.begin()->second);
+ }
+
+ if(!mParticipantsByUUID.empty())
+ {
+ LL_WARNS("Voice") << "Internal error: empty URI map, non-empty UUID map" << LL_ENDL;
+ }
+}
+
+/*static*/
+void LLVivoxVoiceClient::sessionState::VerifySessions()
+{
+ std::set<wptr_t>::iterator it = mSession.begin();
+ while (it != mSession.end())
+ {
+ if ((*it).expired())
+ {
+ LL_WARNS("Voice") << "Expired session found! removing" << LL_ENDL;
+ it = mSession.erase(it);
+ }
+ else
+ ++it;
+ }
+}
+
+
+void LLVivoxVoiceClient::getParticipantList(std::set<LLUUID> &participants)
+{
+ if(mAudioSession)
+ {
+ for(participantUUIDMap::iterator iter = mAudioSession->mParticipantsByUUID.begin();
+ iter != mAudioSession->mParticipantsByUUID.end();
+ iter++)
+ {
+ participants.insert(iter->first);
+ }
+ }
+}
+
+bool LLVivoxVoiceClient::isParticipant(const LLUUID &speaker_id)
+{
+ if(mAudioSession)
+ {
+ return (mAudioSession->mParticipantsByUUID.find(speaker_id) != mAudioSession->mParticipantsByUUID.end());
+ }
+ return false;
+}
+
+
+LLVivoxVoiceClient::participantStatePtr_t LLVivoxVoiceClient::sessionState::findParticipant(const std::string &uri)
+{
+ participantStatePtr_t result;
+
+ participantMap::iterator iter = mParticipantsByURI.find(uri);
+
+ if(iter == mParticipantsByURI.end())
+ {
+ if(!mAlternateSIPURI.empty() && (uri == mAlternateSIPURI))
+ {
+ // This is a p2p session (probably with the SLIM client) with an alternate URI for the other participant.
+ // Look up the other URI
+ iter = mParticipantsByURI.find(mSIPURI);
+ }
+ }
+
+ if(iter != mParticipantsByURI.end())
+ {
+ result = iter->second;
+ }
+
+ return result;
+}
+
+LLVivoxVoiceClient::participantStatePtr_t LLVivoxVoiceClient::sessionState::findParticipantByID(const LLUUID& id)
+{
+ participantStatePtr_t result;
+ participantUUIDMap::iterator iter = mParticipantsByUUID.find(id);
+
+ if(iter != mParticipantsByUUID.end())
+ {
+ result = iter->second;
+ }
+
+ return result;
+}
+
+LLVivoxVoiceClient::participantStatePtr_t LLVivoxVoiceClient::findParticipantByID(const LLUUID& id)
+{
+ participantStatePtr_t result;
+
+ if(mAudioSession)
+ {
+ result = mAudioSession->findParticipantByID(id);
+ }
+
+ return result;
+}
+
+
+
+// Check for parcel boundary crossing
+bool LLVivoxVoiceClient::checkParcelChanged(bool update)
+{
+ LLViewerRegion *region = gAgent.getRegion();
+ LLParcel *parcel = LLViewerParcelMgr::getInstance()->getAgentParcel();
+
+ if(region && parcel)
+ {
+ S32 parcelLocalID = parcel->getLocalID();
+ std::string regionName = region->getName();
+
+ // LL_DEBUGS("Voice") << "Region name = \"" << regionName << "\", parcel local ID = " << parcelLocalID << ", cap URI = \"" << capURI << "\"" << LL_ENDL;
+
+ // The region name starts out empty and gets filled in later.
+ // Also, the cap gets filled in a short time after the region cross, but a little too late for our purposes.
+ // If either is empty, wait for the next time around.
+ if(!regionName.empty())
+ {
+ if((parcelLocalID != mCurrentParcelLocalID) || (regionName != mCurrentRegionName))
+ {
+ // We have changed parcels. Initiate a parcel channel lookup.
+ if (update)
+ {
+ mCurrentParcelLocalID = parcelLocalID;
+ mCurrentRegionName = regionName;
+ }
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+bool LLVivoxVoiceClient::switchChannel(
+ std::string uri,
+ bool spatial,
+ bool no_reconnect,
+ bool is_p2p,
+ std::string hash)
+{
+ bool needsSwitch = !mIsInChannel;
+
+ if (mIsInChannel)
+ {
+ if (mSessionTerminateRequested)
+ {
+ // If a terminate has been requested, we need to compare against where the URI we're already headed to.
+ if(mNextAudioSession)
+ {
+ if(mNextAudioSession->mSIPURI != uri)
+ needsSwitch = true;
+ }
+ else
+ {
+ // mNextAudioSession is null -- this probably means we're on our way back to spatial.
+ if(!uri.empty())
+ {
+ // We do want to process a switch in this case.
+ needsSwitch = true;
+ }
+ }
+ }
+ else
+ {
+ // Otherwise, compare against the URI we're in now.
+ if(mAudioSession)
+ {
+ if(mAudioSession->mSIPURI != uri)
+ {
+ needsSwitch = true;
+ }
+ }
+ else
+ {
+ if(!uri.empty())
+ {
+ // mAudioSession is null -- it's not clear what case would cause this.
+ // For now, log it as a warning and see if it ever crops up.
+ LL_WARNS("Voice") << "No current audio session... Forcing switch" << LL_ENDL;
+ needsSwitch = true;
+ }
+ }
+ }
+ }
+
+ if(needsSwitch)
+ {
+ if(uri.empty())
+ {
+ // Leave any channel we may be in
+ LL_DEBUGS("Voice") << "leaving channel" << LL_ENDL;
+
+ sessionStatePtr_t oldSession = mNextAudioSession;
+ mNextAudioSession.reset();
+
+ // The old session may now need to be deleted.
+ reapSession(oldSession);
+
+ // If voice was on, turn it off
+ if (LLVoiceClient::getInstance()->getUserPTTState())
+ {
+ LLVoiceClient::getInstance()->setUserPTTState(false);
+ }
+
+ notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED);
+ }
+ else
+ {
+ LL_DEBUGS("Voice") << "switching to channel " << uri << LL_ENDL;
+
+ mNextAudioSession = addSession(uri);
+ mNextAudioSession->mHash = hash;
+ mNextAudioSession->mIsSpatial = spatial;
+ mNextAudioSession->mReconnect = !no_reconnect;
+ mNextAudioSession->mIsP2P = is_p2p;
+ }
+
+ if (mIsInChannel)
+ {
+ // If we're already in a channel, or if we're joining one, terminate
+ // so we can rejoin with the new session data.
+ sessionTerminate();
+ }
+ }
+
+ return needsSwitch;
+}
+
+void LLVivoxVoiceClient::joinSession(const sessionStatePtr_t &session)
+{
+ mNextAudioSession = session;
+
+ if (mIsInChannel)
+ {
+ // If we're already in a channel, or if we're joining one, terminate
+ // so we can rejoin with the new session data.
+ sessionTerminate();
+ }
+}
+
+void LLVivoxVoiceClient::setNonSpatialChannel(
+ const std::string &uri,
+ const std::string &credentials)
+{
+ switchChannel(uri, false, false, false, credentials);
+}
+
+bool LLVivoxVoiceClient::setSpatialChannel(
+ const std::string &uri,
+ const std::string &credentials)
+{
+ mSpatialSessionURI = uri;
+ mSpatialSessionCredentials = credentials;
+ mAreaVoiceDisabled = mSpatialSessionURI.empty();
+
+ LL_DEBUGS("Voice") << "got spatial channel uri: \"" << uri << "\"" << LL_ENDL;
+
+ if((mIsInChannel && mAudioSession && !(mAudioSession->mIsSpatial)) || (mNextAudioSession && !(mNextAudioSession->mIsSpatial)))
+ {
+ // User is in a non-spatial chat or joining a non-spatial chat. Don't switch channels.
+ LL_INFOS("Voice") << "in non-spatial chat, not switching channels" << LL_ENDL;
+ return false;
+ }
+ else
+ {
+ return switchChannel(mSpatialSessionURI, true, false, false, mSpatialSessionCredentials);
+ }
+}
+
+void LLVivoxVoiceClient::callUser(const LLUUID &uuid)
+{
+ std::string userURI = sipURIFromID(uuid);
+
+ switchChannel(userURI, false, true, true);
+}
+
+#if 0
+// Vivox text IMs are not in use.
+LLVivoxVoiceClient::sessionStatePtr_t LLVivoxVoiceClient::startUserIMSession(const LLUUID &uuid)
+{
+ // Figure out if a session with the user already exists
+ sessionStatePtr_t session(findSession(uuid));
+ if(!session)
+ {
+ // No session with user, need to start one.
+ std::string uri = sipURIFromID(uuid);
+ session = addSession(uri);
+
+ llassert(session);
+ if (!session)
+ return session;
+
+ session->mIsSpatial = false;
+ session->mReconnect = false;
+ session->mIsP2P = true;
+ session->mCallerID = uuid;
+ }
+
+ if(session->mHandle.empty())
+ {
+ // Session isn't active -- start it up.
+ sessionCreateSendMessage(session, false, false);
+ }
+ else
+ {
+ // Session is already active -- start up text.
+ sessionTextConnectSendMessage(session);
+ }
+
+ return session;
+}
+#endif
+
+void LLVivoxVoiceClient::endUserIMSession(const LLUUID &uuid)
+{
+#if 0
+ // Vivox text IMs are not in use.
+
+ // Figure out if a session with the user exists
+ sessionStatePtr_t session(findSession(uuid));
+ if(session)
+ {
+ // found the session
+ if(!session->mHandle.empty())
+ {
+ // sessionTextDisconnectSendMessage(session); // a SLim leftover, not used any more.
+ }
+ }
+ else
+ {
+ LL_DEBUGS("Voice") << "Session not found for participant ID " << uuid << LL_ENDL;
+ }
+#endif
+}
+bool LLVivoxVoiceClient::isValidChannel(std::string &sessionHandle)
+{
+ return(findSession(sessionHandle) != NULL);
+
+}
+bool LLVivoxVoiceClient::answerInvite(std::string &sessionHandle)
+{
+ // this is only ever used to answer incoming p2p call invites.
+
+ sessionStatePtr_t session(findSession(sessionHandle));
+ if(session)
+ {
+ session->mIsSpatial = false;
+ session->mReconnect = false;
+ session->mIsP2P = true;
+
+ joinSession(session);
+ return true;
+ }
+
+ return false;
+}
+
+bool LLVivoxVoiceClient::isVoiceWorking() const
+{
+
+ //Added stateSessionTerminated state to avoid problems with call in parcels with disabled voice (EXT-4758)
+ // Condition with joining spatial num was added to take into account possible problems with connection to voice
+ // server(EXT-4313). See bug descriptions and comments for MAX_NORMAL_JOINING_SPATIAL_NUM for more info.
+ return (mSpatialJoiningNum < MAX_NORMAL_JOINING_SPATIAL_NUM) && mIsProcessingChannels;
+// return (mSpatialJoiningNum < MAX_NORMAL_JOINING_SPATIAL_NUM) && (stateLoggedIn <= mState) && (mState <= stateSessionTerminated);
+}
+
+// Returns true if the indicated participant in the current audio session is really an SL avatar.
+// Currently this will be false only for PSTN callers into group chats, and PSTN p2p calls.
+bool LLVivoxVoiceClient::isParticipantAvatar(const LLUUID &id)
+{
+ bool result = true;
+ sessionStatePtr_t session(findSession(id));
+
+ if(session)
+ {
+ // this is a p2p session with the indicated caller, or the session with the specified UUID.
+ if(session->mSynthesizedCallerID)
+ result = false;
+ }
+ else
+ {
+ // Didn't find a matching session -- check the current audio session for a matching participant
+ if(mAudioSession)
+ {
+ participantStatePtr_t participant(findParticipantByID(id));
+ if(participant)
+ {
+ result = participant->isAvatar();
+ }
+ }
+ }
+
+ return result;
+}
+
+// Returns true if calling back the session URI after the session has closed is possible.
+// Currently this will be false only for PSTN P2P calls.
+bool LLVivoxVoiceClient::isSessionCallBackPossible(const LLUUID &session_id)
+{
+ bool result = true;
+ sessionStatePtr_t session(findSession(session_id));
+
+ if(session != NULL)
+ {
+ result = session->isCallBackPossible();
+ }
+
+ return result;
+}
+
+// Returns true if the session can accept text IM's.
+// Currently this will be false only for PSTN P2P calls.
+bool LLVivoxVoiceClient::isSessionTextIMPossible(const LLUUID &session_id)
+{
+ bool result = true;
+ sessionStatePtr_t session(findSession(session_id));
+
+ if(session != NULL)
+ {
+ result = session->isTextIMPossible();
+ }
+
+ return result;
+}
+
+
+void LLVivoxVoiceClient::declineInvite(std::string &sessionHandle)
+{
+ sessionStatePtr_t session(findSession(sessionHandle));
+ if(session)
+ {
+ sessionMediaDisconnectSendMessage(session);
+ }
+}
+
+void LLVivoxVoiceClient::leaveNonSpatialChannel()
+{
+ LL_DEBUGS("Voice") << "Request to leave spacial channel." << LL_ENDL;
+
+ // Make sure we don't rejoin the current session.
+ sessionStatePtr_t oldNextSession(mNextAudioSession);
+ mNextAudioSession.reset();
+
+ // Most likely this will still be the current session at this point, but check it anyway.
+ reapSession(oldNextSession);
+
+ verifySessionState();
+
+ sessionTerminate();
+}
+
+std::string LLVivoxVoiceClient::getCurrentChannel()
+{
+ std::string result;
+
+ if (mIsInChannel && !mSessionTerminateRequested)
+ {
+ result = getAudioSessionURI();
+ }
+
+ return result;
+}
+
+bool LLVivoxVoiceClient::inProximalChannel()
+{
+ bool result = false;
+
+ if (mIsInChannel && !mSessionTerminateRequested)
+ {
+ result = inSpatialChannel();
+ }
+
+ return result;
+}
+
+std::string LLVivoxVoiceClient::sipURIFromID(const LLUUID &id)
+{
+ std::string result;
+ result = "sip:";
+ result += nameFromID(id);
+ result += "@";
+ result += mVoiceSIPURIHostName;
+
+ return result;
+}
+
+std::string LLVivoxVoiceClient::sipURIFromAvatar(LLVOAvatar *avatar)
+{
+ std::string result;
+ if(avatar)
+ {
+ result = "sip:";
+ result += nameFromID(avatar->getID());
+ result += "@";
+ result += mVoiceSIPURIHostName;
+ }
+
+ return result;
+}
+
+std::string LLVivoxVoiceClient::nameFromAvatar(LLVOAvatar *avatar)
+{
+ std::string result;
+ if(avatar)
+ {
+ result = nameFromID(avatar->getID());
+ }
+ return result;
+}
+
+std::string LLVivoxVoiceClient::nameFromID(const LLUUID &uuid)
+{
+ std::string result;
+
+ if (uuid.isNull()) {
+ //VIVOX, the uuid emtpy look for the mURIString and return that instead.
+ //result.assign(uuid.mURIStringName);
+ LLStringUtil::replaceChar(result, '_', ' ');
+ return result;
+ }
+ // Prepending this apparently prevents conflicts with reserved names inside the vivox code.
+ result = "x";
+
+ // Base64 encode and replace the pieces of base64 that are less compatible
+ // with e-mail local-parts.
+ // See RFC-4648 "Base 64 Encoding with URL and Filename Safe Alphabet"
+ result += LLBase64::encode(uuid.mData, UUID_BYTES);
+ LLStringUtil::replaceChar(result, '+', '-');
+ LLStringUtil::replaceChar(result, '/', '_');
+
+ // If you need to transform a GUID to this form on the macOS command line, this will do so:
+ // echo -n x && (echo e669132a-6c43-4ee1-a78d-6c82fff59f32 |xxd -r -p |openssl base64|tr '/+' '_-')
+
+ // The reverse transform can be done with:
+ // echo 'x5mkTKmxDTuGnjWyC__WfMg==' |cut -b 2- -|tr '_-' '/+' |openssl base64 -d|xxd -p
+
+ return result;
+}
+
+bool LLVivoxVoiceClient::IDFromName(const std::string inName, LLUUID &uuid)
+{
+ bool result = false;
+
+ // SLIM SDK: The "name" may actually be a SIP URI such as: "sip:xFnPP04IpREWNkuw1cOXlhw==@bhr.vivox.com"
+ // If it is, convert to a bare name before doing the transform.
+ std::string name = nameFromsipURI(inName);
+
+ // Doesn't look like a SIP URI, assume it's an actual name.
+ if(name.empty())
+ name = inName;
+
+ // This will only work if the name is of the proper form.
+ // As an example, the account name for Monroe Linden (UUID 1673cfd3-8229-4445-8d92-ec3570e5e587) is:
+ // "xFnPP04IpREWNkuw1cOXlhw=="
+
+ if((name.size() == 25) && (name[0] == 'x') && (name[23] == '=') && (name[24] == '='))
+ {
+ // The name appears to have the right form.
+
+ // Reverse the transforms done by nameFromID
+ std::string temp = name;
+ LLStringUtil::replaceChar(temp, '-', '+');
+ LLStringUtil::replaceChar(temp, '_', '/');
+
+ U8 rawuuid[UUID_BYTES + 1];
+ int len = apr_base64_decode_binary(rawuuid, temp.c_str() + 1);
+ if(len == UUID_BYTES)
+ {
+ // The decode succeeded. Stuff the bits into the result's UUID
+ memcpy(uuid.mData, rawuuid, UUID_BYTES);
+ result = true;
+ }
+ }
+
+ if(!result)
+ {
+ // VIVOX: not a standard account name, just copy the URI name mURIString field
+ // and hope for the best. bpj
+ uuid.setNull(); // VIVOX, set the uuid field to nulls
+ }
+
+ return result;
+}
+
+std::string LLVivoxVoiceClient::displayNameFromAvatar(LLVOAvatar *avatar)
+{
+ return avatar->getFullname();
+}
+
+std::string LLVivoxVoiceClient::sipURIFromName(std::string &name)
+{
+ std::string result;
+ result = "sip:";
+ result += name;
+ result += "@";
+ result += mVoiceSIPURIHostName;
+
+// LLStringUtil::toLower(result);
+
+ return result;
+}
+
+std::string LLVivoxVoiceClient::nameFromsipURI(const std::string &uri)
+{
+ std::string result;
+
+ std::string::size_type sipOffset, atOffset;
+ sipOffset = uri.find("sip:");
+ atOffset = uri.find("@");
+ if((sipOffset != std::string::npos) && (atOffset != std::string::npos))
+ {
+ result = uri.substr(sipOffset + 4, atOffset - (sipOffset + 4));
+ }
+
+ return result;
+}
+
+bool LLVivoxVoiceClient::inSpatialChannel(void)
+{
+ bool result = false;
+
+ if(mAudioSession)
+ {
+ result = mAudioSession->mIsSpatial;
+ }
+
+ return result;
+}
+
+std::string LLVivoxVoiceClient::getAudioSessionURI()
+{
+ std::string result;
+
+ if(mAudioSession)
+ result = mAudioSession->mSIPURI;
+
+ return result;
+}
+
+std::string LLVivoxVoiceClient::getAudioSessionHandle()
+{
+ std::string result;
+
+ if(mAudioSession)
+ result = mAudioSession->mHandle;
+
+ return result;
+}
+
+
+/////////////////////////////
+// Sending updates of current state
+
+void LLVivoxVoiceClient::enforceTether(void)
+{
+ LLVector3d tethered = mCameraRequestedPosition;
+
+ // constrain 'tethered' to within 50m of mAvatarPosition.
+ {
+ F32 max_dist = 50.0f;
+ LLVector3d camera_offset = mCameraRequestedPosition - mAvatarPosition;
+ F32 camera_distance = (F32)camera_offset.magVec();
+ if(camera_distance > max_dist)
+ {
+ tethered = mAvatarPosition +
+ (max_dist / camera_distance) * camera_offset;
+ }
+ }
+
+ if(dist_vec_squared(mCameraPosition, tethered) > 0.01)
+ {
+ mCameraPosition = tethered;
+ mSpatialCoordsDirty = true;
+ }
+}
+
+void LLVivoxVoiceClient::updatePosition(void)
+{
+
+ LLViewerRegion *region = gAgent.getRegion();
+ if(region && isAgentAvatarValid())
+ {
+ LLMatrix3 rot;
+ LLVector3d pos;
+ LLQuaternion qrot;
+
+ // TODO: If camera and avatar velocity are actually used by the voice system, we could compute them here...
+ // They're currently always set to zero.
+
+ // Send the current camera position to the voice code
+ rot.setRows(LLViewerCamera::getInstance()->getAtAxis(), LLViewerCamera::getInstance()->getLeftAxis (), LLViewerCamera::getInstance()->getUpAxis());
+ pos = gAgent.getRegion()->getPosGlobalFromRegion(LLViewerCamera::getInstance()->getOrigin());
+
+ LLVivoxVoiceClient::getInstance()->setCameraPosition(
+ pos, // position
+ LLVector3::zero, // velocity
+ rot); // rotation matrix
+
+ // Send the current avatar position to the voice code
+ qrot = gAgentAvatarp->getRootJoint()->getWorldRotation();
+ pos = gAgentAvatarp->getPositionGlobal();
+
+ // TODO: Can we get the head offset from outside the LLVOAvatar?
+ // pos += LLVector3d(mHeadOffset);
+ pos += LLVector3d(0.f, 0.f, 1.f);
+
+ LLVivoxVoiceClient::getInstance()->setAvatarPosition(
+ pos, // position
+ LLVector3::zero, // velocity
+ qrot); // rotation matrix
+ }
+}
+
+void LLVivoxVoiceClient::setCameraPosition(const LLVector3d &position, const LLVector3 &velocity, const LLMatrix3 &rot)
+{
+ mCameraRequestedPosition = position;
+
+ if(mCameraVelocity != velocity)
+ {
+ mCameraVelocity = velocity;
+ mSpatialCoordsDirty = true;
+ }
+
+ if(mCameraRot != rot)
+ {
+ mCameraRot = rot;
+ mSpatialCoordsDirty = true;
+ }
+}
+
+void LLVivoxVoiceClient::setAvatarPosition(const LLVector3d &position, const LLVector3 &velocity, const LLQuaternion &rot)
+{
+ if(dist_vec_squared(mAvatarPosition, position) > 0.01)
+ {
+ mAvatarPosition = position;
+ mSpatialCoordsDirty = true;
+ }
+
+ if(mAvatarVelocity != velocity)
+ {
+ mAvatarVelocity = velocity;
+ mSpatialCoordsDirty = true;
+ }
+
+ // If the two rotations are not exactly equal test their dot product
+ // to get the cos of the angle between them.
+ // If it is too small, don't update.
+ F32 rot_cos_diff = llabs(dot(mAvatarRot, rot));
+ if ((mAvatarRot != rot) && (rot_cos_diff < MINUSCULE_ANGLE_COS))
+ {
+ mAvatarRot = rot;
+ mSpatialCoordsDirty = true;
+ }
+}
+
+bool LLVivoxVoiceClient::channelFromRegion(LLViewerRegion *region, std::string &name)
+{
+ bool result = false;
+
+ if(region)
+ {
+ name = region->getName();
+ }
+
+ if(!name.empty())
+ result = true;
+
+ return result;
+}
+
+void LLVivoxVoiceClient::leaveChannel(void)
+{
+ if (mIsInChannel)
+ {
+ LL_DEBUGS("Voice") << "leaving channel for teleport/logout" << LL_ENDL;
+ mChannelName.clear();
+ sessionTerminate();
+ }
+}
+
+void LLVivoxVoiceClient::setMuteMic(bool muted)
+{
+ if(mMuteMic != muted)
+ {
+ mMuteMic = muted;
+ mMuteMicDirty = true;
+ }
+}
+
+void LLVivoxVoiceClient::setVoiceEnabled(bool enabled)
+{
+ LL_DEBUGS("Voice")
+ << "( " << (enabled ? "enabled" : "disabled") << " )"
+ << " was "<< (mVoiceEnabled ? "enabled" : "disabled")
+ << " coro "<< (mIsCoroutineActive ? "active" : "inactive")
+ << LL_ENDL;
+
+ if (enabled != mVoiceEnabled)
+ {
+ // TODO: Refactor this so we don't call into LLVoiceChannel, but simply
+ // use the status observer
+ mVoiceEnabled = enabled;
+ LLVoiceClientStatusObserver::EStatusType status;
+
+ if (enabled)
+ {
+ LL_DEBUGS("Voice") << "enabling" << LL_ENDL;
+ LLVoiceChannel::getCurrentVoiceChannel()->activate();
+ status = LLVoiceClientStatusObserver::STATUS_VOICE_ENABLED;
+
+ if (!mIsCoroutineActive)
+ {
+ LLCoros::instance().launch("LLVivoxVoiceClient::voiceControlCoro",
+ boost::bind(&LLVivoxVoiceClient::voiceControlCoro, LLVivoxVoiceClient::getInstance()));
+ }
+ else
+ {
+ LL_DEBUGS("Voice") << "coro should be active.. not launching" << LL_ENDL;
+ }
+ }
+ else
+ {
+ // Turning voice off looses your current channel -- this makes sure the UI isn't out of sync when you re-enable it.
+ LLVoiceChannel::getCurrentVoiceChannel()->deactivate();
+ gAgent.setVoiceConnected(false);
+ status = LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED;
+ }
+
+ notifyStatusObservers(status);
+ }
+ else
+ {
+ LL_DEBUGS("Voice") << " no-op" << LL_ENDL;
+ }
+}
+
+bool LLVivoxVoiceClient::voiceEnabled()
+{
+ return gSavedSettings.getBOOL("EnableVoiceChat") &&
+ !gSavedSettings.getBOOL("CmdLineDisableVoice") &&
+ !gNonInteractive;
+}
+
+void LLVivoxVoiceClient::setLipSyncEnabled(bool enabled)
+{
+ mLipSyncEnabled = enabled;
+}
+
+bool LLVivoxVoiceClient::lipSyncEnabled()
+{
+
+ if ( mVoiceEnabled )
+ {
+ return mLipSyncEnabled;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+
+void LLVivoxVoiceClient::setEarLocation(S32 loc)
+{
+ if(mEarLocation != loc)
+ {
+ LL_DEBUGS("Voice") << "Setting mEarLocation to " << loc << LL_ENDL;
+
+ mEarLocation = loc;
+ mSpatialCoordsDirty = true;
+ }
+}
+
+void LLVivoxVoiceClient::setVoiceVolume(F32 volume)
+{
+ int scaled_volume = scale_speaker_volume(volume);
+
+ if(scaled_volume != mSpeakerVolume)
+ {
+ int min_volume = scale_speaker_volume(0);
+ if((scaled_volume == min_volume) || (mSpeakerVolume == min_volume))
+ {
+ mSpeakerMuteDirty = true;
+ }
+
+ mSpeakerVolume = scaled_volume;
+ mSpeakerVolumeDirty = true;
+ }
+}
+
+void LLVivoxVoiceClient::setMicGain(F32 volume)
+{
+ int scaled_volume = scale_mic_volume(volume);
+
+ if(scaled_volume != mMicVolume)
+ {
+ mMicVolume = scaled_volume;
+ mMicVolumeDirty = true;
+ }
+}
+
+/////////////////////////////
+// Accessors for data related to nearby speakers
+bool LLVivoxVoiceClient::getVoiceEnabled(const LLUUID& id)
+{
+ bool result = false;
+ participantStatePtr_t participant(findParticipantByID(id));
+ if(participant)
+ {
+ // I'm not sure what the semantics of this should be.
+ // For now, if we have any data about the user that came through the chat channel, assume they're voice-enabled.
+ result = true;
+ }
+
+ return result;
+}
+
+std::string LLVivoxVoiceClient::getDisplayName(const LLUUID& id)
+{
+ std::string result;
+ participantStatePtr_t participant(findParticipantByID(id));
+ if(participant)
+ {
+ result = participant->mDisplayName;
+ }
+
+ return result;
+}
+
+
+
+bool LLVivoxVoiceClient::getIsSpeaking(const LLUUID& id)
+{
+ bool result = false;
+
+ participantStatePtr_t participant(findParticipantByID(id));
+ if(participant)
+ {
+ if (participant->mSpeakingTimeout.getElapsedTimeF32() > SPEAKING_TIMEOUT)
+ {
+ participant->mIsSpeaking = false;
+ }
+ result = participant->mIsSpeaking;
+ }
+
+ return result;
+}
+
+bool LLVivoxVoiceClient::getIsModeratorMuted(const LLUUID& id)
+{
+ bool result = false;
+
+ participantStatePtr_t participant(findParticipantByID(id));
+ if(participant)
+ {
+ result = participant->mIsModeratorMuted;
+ }
+
+ return result;
+}
+
+F32 LLVivoxVoiceClient::getCurrentPower(const LLUUID& id)
+{
+ F32 result = 0;
+ participantStatePtr_t participant(findParticipantByID(id));
+ if(participant)
+ {
+ result = participant->mPower;
+ }
+
+ return result;
+}
+
+
+
+bool LLVivoxVoiceClient::getUsingPTT(const LLUUID& id)
+{
+ bool result = false;
+
+ participantStatePtr_t participant(findParticipantByID(id));
+ if(participant)
+ {
+ // I'm not sure what the semantics of this should be.
+ // Does "using PTT" mean they're configured with a push-to-talk button?
+ // For now, we know there's no PTT mechanism in place, so nobody is using it.
+ }
+
+ return result;
+}
+
+bool LLVivoxVoiceClient::getOnMuteList(const LLUUID& id)
+{
+ bool result = false;
+
+ participantStatePtr_t participant(findParticipantByID(id));
+ if(participant)
+ {
+ result = participant->mOnMuteList;
+ }
+
+ return result;
+}
+
+// External accessors.
+F32 LLVivoxVoiceClient::getUserVolume(const LLUUID& id)
+{
+ // Minimum volume will be returned for users with voice disabled
+ F32 result = LLVoiceClient::VOLUME_MIN;
+
+ participantStatePtr_t participant(findParticipantByID(id));
+ if(participant)
+ {
+ result = participant->mVolume;
+
+ // Enable this when debugging voice slider issues. It's way to spammy even for debug-level logging.
+ // LL_DEBUGS("Voice") << "mVolume = " << result << " for " << id << LL_ENDL;
+ }
+
+ return result;
+}
+
+void LLVivoxVoiceClient::setUserVolume(const LLUUID& id, F32 volume)
+{
+ if(mAudioSession)
+ {
+ participantStatePtr_t participant(findParticipantByID(id));
+ if (participant && !participant->mIsSelf)
+ {
+ if (!is_approx_equal(volume, LLVoiceClient::VOLUME_DEFAULT))
+ {
+ // Store this volume setting for future sessions if it has been
+ // changed from the default
+ LLSpeakerVolumeStorage::getInstance()->storeSpeakerVolume(id, volume);
+ }
+ else
+ {
+ // Remove stored volume setting if it is returned to the default
+ LLSpeakerVolumeStorage::getInstance()->removeSpeakerVolume(id);
+ }
+
+ participant->mVolume = llclamp(volume, LLVoiceClient::VOLUME_MIN, LLVoiceClient::VOLUME_MAX);
+ participant->mVolumeDirty = true;
+ mAudioSession->mVolumeDirty = true;
+ }
+ }
+}
+
+std::string LLVivoxVoiceClient::getGroupID(const LLUUID& id)
+{
+ std::string result;
+
+ participantStatePtr_t participant(findParticipantByID(id));
+ if(participant)
+ {
+ result = participant->mGroupID;
+ }
+
+ return result;
+}
+
+bool LLVivoxVoiceClient::getAreaVoiceDisabled()
+{
+ return mAreaVoiceDisabled;
+}
+
+void LLVivoxVoiceClient::recordingLoopStart(int seconds, int deltaFramesPerControlFrame)
+{
+// LL_DEBUGS("Voice") << "sending SessionGroup.ControlRecording (Start)" << LL_ENDL;
+
+ if(!mMainSessionGroupHandle.empty())
+ {
+ std::ostringstream stream;
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.ControlRecording.1\">"
+ << "<SessionGroupHandle>" << mMainSessionGroupHandle << "</SessionGroupHandle>"
+ << "<RecordingControlType>Start</RecordingControlType>"
+ << "<DeltaFramesPerControlFrame>" << deltaFramesPerControlFrame << "</DeltaFramesPerControlFrame>"
+ << "<Filename>" << "" << "</Filename>"
+ << "<EnableAudioRecordingEvents>false</EnableAudioRecordingEvents>"
+ << "<LoopModeDurationSeconds>" << seconds << "</LoopModeDurationSeconds>"
+ << "</Request>\n\n\n";
+
+
+ writeString(stream.str());
+ }
+}
+
+void LLVivoxVoiceClient::recordingLoopSave(const std::string& filename)
+{
+// LL_DEBUGS("Voice") << "sending SessionGroup.ControlRecording (Flush)" << LL_ENDL;
+
+ if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty())
+ {
+ std::ostringstream stream;
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.ControlRecording.1\">"
+ << "<SessionGroupHandle>" << mMainSessionGroupHandle << "</SessionGroupHandle>"
+ << "<RecordingControlType>Flush</RecordingControlType>"
+ << "<Filename>" << filename << "</Filename>"
+ << "</Request>\n\n\n";
+
+ writeString(stream.str());
+ }
+}
+
+void LLVivoxVoiceClient::recordingStop()
+{
+// LL_DEBUGS("Voice") << "sending SessionGroup.ControlRecording (Stop)" << LL_ENDL;
+
+ if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty())
+ {
+ std::ostringstream stream;
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.ControlRecording.1\">"
+ << "<SessionGroupHandle>" << mMainSessionGroupHandle << "</SessionGroupHandle>"
+ << "<RecordingControlType>Stop</RecordingControlType>"
+ << "</Request>\n\n\n";
+
+ writeString(stream.str());
+ }
+}
+
+void LLVivoxVoiceClient::filePlaybackStart(const std::string& filename)
+{
+// LL_DEBUGS("Voice") << "sending SessionGroup.ControlPlayback (Start)" << LL_ENDL;
+
+ if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty())
+ {
+ std::ostringstream stream;
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.ControlPlayback.1\">"
+ << "<SessionGroupHandle>" << mMainSessionGroupHandle << "</SessionGroupHandle>"
+ << "<RecordingControlType>Start</RecordingControlType>"
+ << "<Filename>" << filename << "</Filename>"
+ << "</Request>\n\n\n";
+
+ writeString(stream.str());
+ }
+}
+
+void LLVivoxVoiceClient::filePlaybackStop()
+{
+// LL_DEBUGS("Voice") << "sending SessionGroup.ControlPlayback (Stop)" << LL_ENDL;
+
+ if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty())
+ {
+ std::ostringstream stream;
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.ControlPlayback.1\">"
+ << "<SessionGroupHandle>" << mMainSessionGroupHandle << "</SessionGroupHandle>"
+ << "<RecordingControlType>Stop</RecordingControlType>"
+ << "</Request>\n\n\n";
+
+ writeString(stream.str());
+ }
+}
+
+void LLVivoxVoiceClient::filePlaybackSetPaused(bool paused)
+{
+ // TODO: Implement once Vivox gives me a sample
+}
+
+void LLVivoxVoiceClient::filePlaybackSetMode(bool vox, float speed)
+{
+ // TODO: Implement once Vivox gives me a sample
+}
+
+//------------------------------------------------------------------------
+std::set<LLVivoxVoiceClient::sessionState::wptr_t, std::owner_less<LLVivoxVoiceClient::sessionState::wptr_t>> LLVivoxVoiceClient::sessionState::mSession;
+
+
+LLVivoxVoiceClient::sessionState::sessionState() :
+ mErrorStatusCode(0),
+ mMediaStreamState(streamStateUnknown),
+ mCreateInProgress(false),
+ mMediaConnectInProgress(false),
+ mVoiceInvitePending(false),
+ mTextInvitePending(false),
+ mSynthesizedCallerID(false),
+ mIsChannel(false),
+ mIsSpatial(false),
+ mIsP2P(false),
+ mIncoming(false),
+ mVoiceActive(false),
+ mReconnect(false),
+ mVolumeDirty(false),
+ mMuteDirty(false),
+ mParticipantsChanged(false)
+{
+}
+
+/*static*/
+LLVivoxVoiceClient::sessionState::ptr_t LLVivoxVoiceClient::sessionState::createSession()
+{
+ sessionState::ptr_t ptr(new sessionState());
+
+ std::pair<std::set<wptr_t>::iterator, bool> result = mSession.insert(ptr);
+
+ if (result.second)
+ ptr->mMyIterator = result.first;
+
+ return ptr;
+}
+
+LLVivoxVoiceClient::sessionState::~sessionState()
+{
+ LL_INFOS("Voice") << "Destroying session handle=" << mHandle << " SIP=" << mSIPURI << LL_ENDL;
+ if (mMyIterator != mSession.end())
+ mSession.erase(mMyIterator);
+
+ removeAllParticipants();
+}
+
+bool LLVivoxVoiceClient::sessionState::isCallBackPossible()
+{
+ // This may change to be explicitly specified by vivox in the future...
+ // Currently, only PSTN P2P calls cannot be returned.
+ // Conveniently, this is also the only case where we synthesize a caller UUID.
+ return !mSynthesizedCallerID;
+}
+
+bool LLVivoxVoiceClient::sessionState::isTextIMPossible()
+{
+ // This may change to be explicitly specified by vivox in the future...
+ return !mSynthesizedCallerID;
+}
+
+
+/*static*/
+LLVivoxVoiceClient::sessionState::ptr_t LLVivoxVoiceClient::sessionState::matchSessionByHandle(const std::string &handle)
+{
+ sessionStatePtr_t result;
+
+ // *TODO: My kingdom for a lambda!
+ std::set<wptr_t>::iterator it = std::find_if(mSession.begin(), mSession.end(), boost::bind(testByHandle, _1, handle));
+
+ if (it != mSession.end())
+ result = (*it).lock();
+
+ return result;
+}
+
+/*static*/
+LLVivoxVoiceClient::sessionState::ptr_t LLVivoxVoiceClient::sessionState::matchCreatingSessionByURI(const std::string &uri)
+{
+ sessionStatePtr_t result;
+
+ // *TODO: My kingdom for a lambda!
+ std::set<wptr_t>::iterator it = std::find_if(mSession.begin(), mSession.end(), boost::bind(testByCreatingURI, _1, uri));
+
+ if (it != mSession.end())
+ result = (*it).lock();
+
+ return result;
+}
+
+/*static*/
+LLVivoxVoiceClient::sessionState::ptr_t LLVivoxVoiceClient::sessionState::matchSessionByURI(const std::string &uri)
+{
+ sessionStatePtr_t result;
+
+ // *TODO: My kingdom for a lambda!
+ std::set<wptr_t>::iterator it = std::find_if(mSession.begin(), mSession.end(), boost::bind(testBySIPOrAlterateURI, _1, uri));
+
+ if (it != mSession.end())
+ result = (*it).lock();
+
+ return result;
+}
+
+/*static*/
+LLVivoxVoiceClient::sessionState::ptr_t LLVivoxVoiceClient::sessionState::matchSessionByParticipant(const LLUUID &participant_id)
+{
+ sessionStatePtr_t result;
+
+ // *TODO: My kingdom for a lambda!
+ std::set<wptr_t>::iterator it = std::find_if(mSession.begin(), mSession.end(), boost::bind(testByCallerId, _1, participant_id));
+
+ if (it != mSession.end())
+ result = (*it).lock();
+
+ return result;
+}
+
+void LLVivoxVoiceClient::sessionState::for_each(sessionFunc_t func)
+{
+ std::for_each(mSession.begin(), mSession.end(), boost::bind(for_eachPredicate, _1, func));
+}
+
+// simple test predicates.
+// *TODO: These should be made into lambdas when we can pull the trigger on newer C++ features.
+bool LLVivoxVoiceClient::sessionState::testByHandle(const LLVivoxVoiceClient::sessionState::wptr_t &a, std::string handle)
+{
+ ptr_t aLock(a.lock());
+
+ return aLock ? aLock->mHandle == handle : false;
+}
+
+bool LLVivoxVoiceClient::sessionState::testByCreatingURI(const LLVivoxVoiceClient::sessionState::wptr_t &a, std::string uri)
+{
+ ptr_t aLock(a.lock());
+
+ return aLock ? (aLock->mCreateInProgress && (aLock->mSIPURI == uri)) : false;
+}
+
+bool LLVivoxVoiceClient::sessionState::testBySIPOrAlterateURI(const LLVivoxVoiceClient::sessionState::wptr_t &a, std::string uri)
+{
+ ptr_t aLock(a.lock());
+
+ return aLock ? ((aLock->mSIPURI == uri) || (aLock->mAlternateSIPURI == uri)) : false;
+}
+
+
+bool LLVivoxVoiceClient::sessionState::testByCallerId(const LLVivoxVoiceClient::sessionState::wptr_t &a, LLUUID participantId)
+{
+ ptr_t aLock(a.lock());
+
+ return aLock ? ((aLock->mCallerID == participantId) || (aLock->mIMSessionID == participantId)) : false;
+}
+
+/*static*/
+void LLVivoxVoiceClient::sessionState::for_eachPredicate(const LLVivoxVoiceClient::sessionState::wptr_t &a, sessionFunc_t func)
+{
+ ptr_t aLock(a.lock());
+
+ if (aLock)
+ func(aLock);
+ else
+ {
+ LL_WARNS("Voice") << "Stale handle in session map!" << LL_ENDL;
+ }
+}
+
+
+
+LLVivoxVoiceClient::sessionStatePtr_t LLVivoxVoiceClient::findSession(const std::string &handle)
+{
+ sessionStatePtr_t result;
+ sessionMap::iterator iter = mSessionsByHandle.find(handle);
+ if(iter != mSessionsByHandle.end())
+ {
+ result = iter->second;
+ }
+
+ return result;
+}
+
+LLVivoxVoiceClient::sessionStatePtr_t LLVivoxVoiceClient::findSessionBeingCreatedByURI(const std::string &uri)
+{
+ sessionStatePtr_t result = sessionState::matchCreatingSessionByURI(uri);
+
+ return result;
+}
+
+LLVivoxVoiceClient::sessionStatePtr_t LLVivoxVoiceClient::findSession(const LLUUID &participant_id)
+{
+ sessionStatePtr_t result = sessionState::matchSessionByParticipant(participant_id);
+
+ return result;
+}
+
+LLVivoxVoiceClient::sessionStatePtr_t LLVivoxVoiceClient::addSession(const std::string &uri, const std::string &handle)
+{
+ sessionStatePtr_t result;
+
+ if(handle.empty())
+ {
+ // No handle supplied.
+ // Check whether there's already a session with this URI
+ result = sessionState::matchSessionByURI(uri);
+ }
+ else // (!handle.empty())
+ {
+ // Check for an existing session with this handle
+ sessionMap::iterator iter = mSessionsByHandle.find(handle);
+
+ if(iter != mSessionsByHandle.end())
+ {
+ result = iter->second;
+ }
+ }
+
+ if(!result)
+ {
+ // No existing session found.
+
+ LL_DEBUGS("Voice") << "adding new session: handle \"" << handle << "\" URI " << uri << LL_ENDL;
+ result = sessionState::createSession();
+ result->mSIPURI = uri;
+ result->mHandle = handle;
+
+ if (LLVoiceClient::instance().getVoiceEffectEnabled())
+ {
+ result->mVoiceFontID = LLVoiceClient::instance().getVoiceEffectDefault();
+ }
+
+ if(!result->mHandle.empty())
+ {
+ // *TODO: Rider: This concerns me. There is a path (via switchChannel) where
+ // we do not track the session. In theory this means that we could end up with
+ // a mAuidoSession that does not match the session tracked in mSessionsByHandle
+ mSessionsByHandle.insert(sessionMap::value_type(result->mHandle, result));
+ }
+ }
+ else
+ {
+ // Found an existing session
+
+ if(uri != result->mSIPURI)
+ {
+ // TODO: Should this be an internal error?
+ LL_DEBUGS("Voice") << "changing uri from " << result->mSIPURI << " to " << uri << LL_ENDL;
+ setSessionURI(result, uri);
+ }
+
+ if(handle != result->mHandle)
+ {
+ if(handle.empty())
+ {
+ // There's at least one race condition where where addSession was clearing an existing session handle, which caused things to break.
+ LL_DEBUGS("Voice") << "NOT clearing handle " << result->mHandle << LL_ENDL;
+ }
+ else
+ {
+ // TODO: Should this be an internal error?
+ LL_DEBUGS("Voice") << "changing handle from " << result->mHandle << " to " << handle << LL_ENDL;
+ setSessionHandle(result, handle);
+ }
+ }
+
+ LL_DEBUGS("Voice") << "returning existing session: handle " << handle << " URI " << uri << LL_ENDL;
+ }
+
+ verifySessionState();
+
+ return result;
+}
+
+void LLVivoxVoiceClient::clearSessionHandle(const sessionStatePtr_t &session)
+{
+ if (session)
+ {
+ if (!session->mHandle.empty())
+ {
+ sessionMap::iterator iter = mSessionsByHandle.find(session->mHandle);
+ if (iter != mSessionsByHandle.end())
+ {
+ mSessionsByHandle.erase(iter);
+ }
+ }
+ else
+ {
+ LL_WARNS("Voice") << "Session has empty handle!" << LL_ENDL;
+ }
+ }
+ else
+ {
+ LL_WARNS("Voice") << "Attempt to clear NULL session!" << LL_ENDL;
+ }
+
+}
+
+void LLVivoxVoiceClient::setSessionHandle(const sessionStatePtr_t &session, const std::string &handle)
+{
+ // Have to remove the session from the handle-indexed map before changing the handle, or things will break badly.
+
+ if(!session->mHandle.empty())
+ {
+ // Remove session from the map if it should have been there.
+ sessionMap::iterator iter = mSessionsByHandle.find(session->mHandle);
+ if(iter != mSessionsByHandle.end())
+ {
+ if(iter->second != session)
+ {
+ LL_WARNS("Voice") << "Internal error: session mismatch! Session may have been duplicated. Removing version in map." << LL_ENDL;
+ }
+
+ mSessionsByHandle.erase(iter);
+ }
+ else
+ {
+ LL_WARNS("Voice") << "Attempt to remove session with handle " << session->mHandle << " not found in map!" << LL_ENDL;
+ }
+ }
+
+ session->mHandle = handle;
+
+ if(!handle.empty())
+ {
+ mSessionsByHandle.insert(sessionMap::value_type(session->mHandle, session));
+ }
+
+ verifySessionState();
+}
+
+void LLVivoxVoiceClient::setSessionURI(const sessionStatePtr_t &session, const std::string &uri)
+{
+ // There used to be a map of session URIs to sessions, which made this complex....
+ session->mSIPURI = uri;
+
+ verifySessionState();
+}
+
+void LLVivoxVoiceClient::deleteSession(const sessionStatePtr_t &session)
+{
+ // Remove the session from the handle map
+ if(!session->mHandle.empty())
+ {
+ sessionMap::iterator iter = mSessionsByHandle.find(session->mHandle);
+ if(iter != mSessionsByHandle.end())
+ {
+ if(iter->second != session)
+ {
+ LL_WARNS("Voice") << "Internal error: session mismatch, removing session in map." << LL_ENDL;
+ }
+ mSessionsByHandle.erase(iter);
+ }
+ }
+
+ // At this point, the session should be unhooked from all lists and all state should be consistent.
+ verifySessionState();
+
+ // If this is the current audio session, clean up the pointer which will soon be dangling.
+ if(mAudioSession == session)
+ {
+ mAudioSession.reset();
+ mAudioSessionChanged = true;
+ }
+
+ // ditto for the next audio session
+ if(mNextAudioSession == session)
+ {
+ mNextAudioSession.reset();
+ }
+
+}
+
+void LLVivoxVoiceClient::deleteAllSessions()
+{
+ LL_DEBUGS("Voice") << LL_ENDL;
+
+ while (!mSessionsByHandle.empty())
+ {
+ const sessionStatePtr_t session = mSessionsByHandle.begin()->second;
+ deleteSession(session);
+ }
+
+}
+
+void LLVivoxVoiceClient::verifySessionState(void)
+{
+ LL_DEBUGS("Voice") << "Sessions in handle map=" << mSessionsByHandle.size() << LL_ENDL;
+ sessionState::VerifySessions();
+}
+
+void LLVivoxVoiceClient::addObserver(LLVoiceClientParticipantObserver* observer)
+{
+ mParticipantObservers.insert(observer);
+}
+
+void LLVivoxVoiceClient::removeObserver(LLVoiceClientParticipantObserver* observer)
+{
+ mParticipantObservers.erase(observer);
+}
+
+void LLVivoxVoiceClient::notifyParticipantObservers()
+{
+ for (observer_set_t::iterator it = mParticipantObservers.begin();
+ it != mParticipantObservers.end();
+ )
+ {
+ LLVoiceClientParticipantObserver* observer = *it;
+ observer->onParticipantsChanged();
+ // In case onParticipantsChanged() deleted an entry.
+ it = mParticipantObservers.upper_bound(observer);
+ }
+}
+
+void LLVivoxVoiceClient::addObserver(LLVoiceClientStatusObserver* observer)
+{
+ mStatusObservers.insert(observer);
+}
+
+void LLVivoxVoiceClient::removeObserver(LLVoiceClientStatusObserver* observer)
+{
+ mStatusObservers.erase(observer);
+}
+
+void LLVivoxVoiceClient::notifyStatusObservers(LLVoiceClientStatusObserver::EStatusType status)
+{
+ LL_DEBUGS("Voice") << "( " << LLVoiceClientStatusObserver::status2string(status) << " )"
+ << " mAudioSession=" << mAudioSession
+ << LL_ENDL;
+
+ if(mAudioSession)
+ {
+ if(status == LLVoiceClientStatusObserver::ERROR_UNKNOWN)
+ {
+ switch(mAudioSession->mErrorStatusCode)
+ {
+ case 20713: status = LLVoiceClientStatusObserver::ERROR_CHANNEL_FULL; break;
+ case 20714: status = LLVoiceClientStatusObserver::ERROR_CHANNEL_LOCKED; break;
+ case 20715:
+ //invalid channel, we may be using a set of poorly cached
+ //info
+ status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE;
+ break;
+ case 1009:
+ //invalid username and password
+ status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE;
+ break;
+ }
+
+ // Reset the error code to make sure it won't be reused later by accident.
+ mAudioSession->mErrorStatusCode = 0;
+ }
+ else if(status == LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL)
+ {
+ switch(mAudioSession->mErrorStatusCode)
+ {
+ case HTTP_NOT_FOUND: // NOT_FOUND
+ // *TODO: Should this be 503?
+ case 480: // TEMPORARILY_UNAVAILABLE
+ case HTTP_REQUEST_TIME_OUT: // REQUEST_TIMEOUT
+ // call failed because other user was not available
+ // treat this as an error case
+ status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE;
+
+ // Reset the error code to make sure it won't be reused later by accident.
+ mAudioSession->mErrorStatusCode = 0;
+ break;
+ }
+ }
+ }
+
+ LL_DEBUGS("Voice")
+ << " " << LLVoiceClientStatusObserver::status2string(status)
+ << ", session URI " << getAudioSessionURI()
+ << ", proximal is " << inSpatialChannel()
+ << LL_ENDL;
+
+ for (status_observer_set_t::iterator it = mStatusObservers.begin();
+ it != mStatusObservers.end();
+ )
+ {
+ LLVoiceClientStatusObserver* observer = *it;
+ observer->onChange(status, getAudioSessionURI(), inSpatialChannel());
+ // In case onError() deleted an entry.
+ it = mStatusObservers.upper_bound(observer);
+ }
+
+ // skipped to avoid speak button blinking
+ if ( status != LLVoiceClientStatusObserver::STATUS_JOINING
+ && status != LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL
+ && status != LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED)
+ {
+ bool voice_status = LLVoiceClient::getInstance()->voiceEnabled() && LLVoiceClient::getInstance()->isVoiceWorking();
+
+ gAgent.setVoiceConnected(voice_status);
+
+ if (voice_status)
+ {
+ LLFirstUse::speak(true);
+ }
+ }
+}
+
+void LLVivoxVoiceClient::addObserver(LLFriendObserver* observer)
+{
+ mFriendObservers.insert(observer);
+}
+
+void LLVivoxVoiceClient::removeObserver(LLFriendObserver* observer)
+{
+ mFriendObservers.erase(observer);
+}
+
+void LLVivoxVoiceClient::notifyFriendObservers()
+{
+ for (friend_observer_set_t::iterator it = mFriendObservers.begin();
+ it != mFriendObservers.end();
+ )
+ {
+ LLFriendObserver* observer = *it;
+ it++;
+ // The only friend-related thing we notify on is online/offline transitions.
+ observer->changed(LLFriendObserver::ONLINE);
+ }
+}
+
+void LLVivoxVoiceClient::lookupName(const LLUUID &id)
+{
+ if (mAvatarNameCacheConnection.connected())
+ {
+ mAvatarNameCacheConnection.disconnect();
+ }
+ mAvatarNameCacheConnection = LLAvatarNameCache::get(id, boost::bind(&LLVivoxVoiceClient::onAvatarNameCache, this, _1, _2));
+}
+
+void LLVivoxVoiceClient::onAvatarNameCache(const LLUUID& agent_id,
+ const LLAvatarName& av_name)
+{
+ mAvatarNameCacheConnection.disconnect();
+ std::string display_name = av_name.getDisplayName();
+ avatarNameResolved(agent_id, display_name);
+}
+
+void LLVivoxVoiceClient::predAvatarNameResolution(const LLVivoxVoiceClient::sessionStatePtr_t &session, LLUUID id, std::string name)
+{
+ participantStatePtr_t participant(session->findParticipantByID(id));
+ if (participant)
+ {
+ // Found -- fill in the name
+ participant->mAccountName = name;
+ // and post a "participants updated" message to listeners later.
+ session->mParticipantsChanged = true;
+ }
+
+ // Check whether this is a p2p session whose caller name just resolved
+ if (session->mCallerID == id)
+ {
+ // this session's "caller ID" just resolved. Fill in the name.
+ session->mName = name;
+ if (session->mTextInvitePending)
+ {
+ session->mTextInvitePending = false;
+
+ // We don't need to call LLIMMgr::getInstance()->addP2PSession() here. The first incoming message will create the panel.
+ }
+ if (session->mVoiceInvitePending)
+ {
+ session->mVoiceInvitePending = false;
+
+ LLIMMgr::getInstance()->inviteToSession(
+ session->mIMSessionID,
+ session->mName,
+ session->mCallerID,
+ session->mName,
+ IM_SESSION_P2P_INVITE,
+ LLIMMgr::INVITATION_TYPE_VOICE,
+ session->mHandle,
+ session->mSIPURI);
+ }
+
+ }
+}
+
+void LLVivoxVoiceClient::avatarNameResolved(const LLUUID &id, const std::string &name)
+{
+ sessionState::for_each(boost::bind(predAvatarNameResolution, _1, id, name));
+}
+
+bool LLVivoxVoiceClient::setVoiceEffect(const LLUUID& id)
+{
+ if (!mAudioSession)
+ {
+ return false;
+ }
+
+ if (!id.isNull())
+ {
+ if (mVoiceFontMap.empty())
+ {
+ LL_DEBUGS("Voice") << "Voice fonts not available." << LL_ENDL;
+ return false;
+ }
+ else if (mVoiceFontMap.find(id) == mVoiceFontMap.end())
+ {
+ LL_DEBUGS("Voice") << "Invalid voice font " << id << LL_ENDL;
+ return false;
+ }
+ }
+
+ // *TODO: Check for expired fonts?
+ mAudioSession->mVoiceFontID = id;
+
+ // *TODO: Separate voice font defaults for spatial chat and IM?
+ gSavedPerAccountSettings.setString("VoiceEffectDefault", id.asString());
+
+ sessionSetVoiceFontSendMessage(mAudioSession);
+ notifyVoiceFontObservers();
+
+ return true;
+}
+
+const LLUUID LLVivoxVoiceClient::getVoiceEffect()
+{
+ return mAudioSession ? mAudioSession->mVoiceFontID : LLUUID::null;
+}
+
+LLSD LLVivoxVoiceClient::getVoiceEffectProperties(const LLUUID& id)
+{
+ LLSD sd;
+
+ voice_font_map_t::iterator iter = mVoiceFontMap.find(id);
+ if (iter != mVoiceFontMap.end())
+ {
+ sd["template_only"] = false;
+ }
+ else
+ {
+ // Voice effect is not in the voice font map, see if there is a template
+ iter = mVoiceFontTemplateMap.find(id);
+ if (iter == mVoiceFontTemplateMap.end())
+ {
+ LL_WARNS("Voice") << "Voice effect " << id << "not found." << LL_ENDL;
+ return sd;
+ }
+ sd["template_only"] = true;
+ }
+
+ voiceFontEntry *font = iter->second;
+ sd["name"] = font->mName;
+ sd["expiry_date"] = font->mExpirationDate;
+ sd["is_new"] = font->mIsNew;
+
+ return sd;
+}
+
+LLVivoxVoiceClient::voiceFontEntry::voiceFontEntry(LLUUID& id) :
+ mID(id),
+ mFontIndex(0),
+ mFontType(VOICE_FONT_TYPE_NONE),
+ mFontStatus(VOICE_FONT_STATUS_NONE),
+ mIsNew(false)
+{
+ mExpiryTimer.stop();
+ mExpiryWarningTimer.stop();
+}
+
+LLVivoxVoiceClient::voiceFontEntry::~voiceFontEntry()
+{
+}
+
+void LLVivoxVoiceClient::refreshVoiceEffectLists(bool clear_lists)
+{
+ if (clear_lists)
+ {
+ mVoiceFontsReceived = false;
+ deleteAllVoiceFonts();
+ deleteVoiceFontTemplates();
+ }
+
+ accountGetSessionFontsSendMessage();
+ accountGetTemplateFontsSendMessage();
+}
+
+const voice_effect_list_t& LLVivoxVoiceClient::getVoiceEffectList() const
+{
+ return mVoiceFontList;
+}
+
+const voice_effect_list_t& LLVivoxVoiceClient::getVoiceEffectTemplateList() const
+{
+ return mVoiceFontTemplateList;
+}
+
+void LLVivoxVoiceClient::addVoiceFont(const S32 font_index,
+ const std::string &name,
+ const std::string &description,
+ const LLDate &expiration_date,
+ bool has_expired,
+ const S32 font_type,
+ const S32 font_status,
+ const bool template_font)
+{
+ // Vivox SessionFontIDs are not guaranteed to remain the same between
+ // sessions or grids so use a UUID for the name.
+
+ // If received name is not a UUID, fudge one by hashing the name and type.
+ LLUUID font_id;
+ if (LLUUID::validate(name))
+ {
+ font_id = LLUUID(name);
+ }
+ else
+ {
+ font_id.generate(STRINGIZE(font_type << ":" << name));
+ }
+
+ voiceFontEntry *font = NULL;
+
+ voice_font_map_t& font_map = template_font ? mVoiceFontTemplateMap : mVoiceFontMap;
+ voice_effect_list_t& font_list = template_font ? mVoiceFontTemplateList : mVoiceFontList;
+
+ // Check whether we've seen this font before.
+ voice_font_map_t::iterator iter = font_map.find(font_id);
+ bool new_font = (iter == font_map.end());
+
+ // Override the has_expired flag if we have passed the expiration_date as a double check.
+ if (expiration_date.secondsSinceEpoch() < (LLDate::now().secondsSinceEpoch() + VOICE_FONT_EXPIRY_INTERVAL))
+ {
+ has_expired = true;
+ }
+
+ if (has_expired)
+ {
+ LL_DEBUGS("VoiceFont") << "Expired " << (template_font ? "Template " : "")
+ << expiration_date.asString() << " " << font_id
+ << " (" << font_index << ") " << name << LL_ENDL;
+
+ // Remove existing session fonts that have expired since we last saw them.
+ if (!new_font && !template_font)
+ {
+ deleteVoiceFont(font_id);
+ }
+ return;
+ }
+
+ if (new_font)
+ {
+ // If it is a new font create a new entry.
+ font = new voiceFontEntry(font_id);
+ }
+ else
+ {
+ // Not a new font, update the existing entry
+ font = iter->second;
+ }
+
+ if (font)
+ {
+ font->mFontIndex = font_index;
+ // Use the description for the human readable name if available, as the
+ // "name" may be a UUID.
+ font->mName = description.empty() ? name : description;
+ font->mFontType = font_type;
+ font->mFontStatus = font_status;
+
+ // If the font is new or the expiration date has changed the expiry timers need updating.
+ if (!template_font && (new_font || font->mExpirationDate != expiration_date))
+ {
+ font->mExpirationDate = expiration_date;
+
+ // Set the expiry timer to trigger a notification when the voice font can no longer be used.
+ font->mExpiryTimer.start();
+ font->mExpiryTimer.setExpiryAt(expiration_date.secondsSinceEpoch() - VOICE_FONT_EXPIRY_INTERVAL);
+
+ // Set the warning timer to some interval before actual expiry.
+ S32 warning_time = gSavedSettings.getS32("VoiceEffectExpiryWarningTime");
+ if (warning_time != 0)
+ {
+ font->mExpiryWarningTimer.start();
+ F64 expiry_time = (expiration_date.secondsSinceEpoch() - (F64)warning_time);
+ font->mExpiryWarningTimer.setExpiryAt(expiry_time - VOICE_FONT_EXPIRY_INTERVAL);
+ }
+ else
+ {
+ // Disable the warning timer.
+ font->mExpiryWarningTimer.stop();
+ }
+
+ // Only flag new session fonts after the first time we have fetched the list.
+ if (mVoiceFontsReceived)
+ {
+ font->mIsNew = true;
+ mVoiceFontsNew = true;
+ }
+ }
+
+ LL_DEBUGS("VoiceFont") << (template_font ? "Template " : "")
+ << font->mExpirationDate.asString() << " " << font->mID
+ << " (" << font->mFontIndex << ") " << name << LL_ENDL;
+
+ if (new_font)
+ {
+ font_map.insert(voice_font_map_t::value_type(font->mID, font));
+ font_list.insert(voice_effect_list_t::value_type(font->mName, font->mID));
+ }
+
+ mVoiceFontListDirty = true;
+
+ // Debugging stuff
+
+ if (font_type < VOICE_FONT_TYPE_NONE || font_type >= VOICE_FONT_TYPE_UNKNOWN)
+ {
+ LL_WARNS("VoiceFont") << "Unknown voice font type: " << font_type << LL_ENDL;
+ }
+ if (font_status < VOICE_FONT_STATUS_NONE || font_status >= VOICE_FONT_STATUS_UNKNOWN)
+ {
+ LL_WARNS("VoiceFont") << "Unknown voice font status: " << font_status << LL_ENDL;
+ }
+ }
+}
+
+void LLVivoxVoiceClient::expireVoiceFonts()
+{
+ // *TODO: If we are selling voice fonts in packs, there are probably
+ // going to be a number of fonts with the same expiration time, so would
+ // be more efficient to just keep a list of expiration times rather
+ // than checking each font individually.
+
+ bool have_expired = false;
+ bool will_expire = false;
+ bool expired_in_use = false;
+
+ LLUUID current_effect = LLVoiceClient::instance().getVoiceEffectDefault();
+
+ voice_font_map_t::iterator iter;
+ for (iter = mVoiceFontMap.begin(); iter != mVoiceFontMap.end(); ++iter)
+ {
+ voiceFontEntry* voice_font = iter->second;
+ LLFrameTimer& expiry_timer = voice_font->mExpiryTimer;
+ LLFrameTimer& warning_timer = voice_font->mExpiryWarningTimer;
+
+ // Check for expired voice fonts
+ if (expiry_timer.getStarted() && expiry_timer.hasExpired())
+ {
+ // Check whether it is the active voice font
+ if (voice_font->mID == current_effect)
+ {
+ // Reset to no voice effect.
+ setVoiceEffect(LLUUID::null);
+ expired_in_use = true;
+ }
+
+ LL_DEBUGS("Voice") << "Voice Font " << voice_font->mName << " has expired." << LL_ENDL;
+ deleteVoiceFont(voice_font->mID);
+ have_expired = true;
+ }
+
+ // Check for voice fonts that will expire in less that the warning time
+ if (warning_timer.getStarted() && warning_timer.hasExpired())
+ {
+ LL_DEBUGS("VoiceFont") << "Voice Font " << voice_font->mName << " will expire soon." << LL_ENDL;
+ will_expire = true;
+ warning_timer.stop();
+ }
+ }
+
+ LLSD args;
+ args["URL"] = LLTrans::getString("voice_morphing_url");
+ args["PREMIUM_URL"] = LLTrans::getString("premium_voice_morphing_url");
+
+ // Give a notification if any voice fonts have expired.
+ if (have_expired)
+ {
+ if (expired_in_use)
+ {
+ LLNotificationsUtil::add("VoiceEffectsExpiredInUse", args);
+ }
+ else
+ {
+ LLNotificationsUtil::add("VoiceEffectsExpired", args);
+ }
+
+ // Refresh voice font lists in the UI.
+ notifyVoiceFontObservers();
+ }
+
+ // Give a warning notification if any voice fonts are due to expire.
+ if (will_expire)
+ {
+ S32Seconds seconds(gSavedSettings.getS32("VoiceEffectExpiryWarningTime"));
+ args["INTERVAL"] = llformat("%d", LLUnit<S32, LLUnits::Days>(seconds).value());
+
+ LLNotificationsUtil::add("VoiceEffectsWillExpire", args);
+ }
+}
+
+void LLVivoxVoiceClient::deleteVoiceFont(const LLUUID& id)
+{
+ // Remove the entry from the voice font list.
+ voice_effect_list_t::iterator list_iter = mVoiceFontList.begin();
+ while (list_iter != mVoiceFontList.end())
+ {
+ if (list_iter->second == id)
+ {
+ LL_DEBUGS("VoiceFont") << "Removing " << id << " from the voice font list." << LL_ENDL;
+ list_iter = mVoiceFontList.erase(list_iter);
+ mVoiceFontListDirty = true;
+ }
+ else
+ {
+ ++list_iter;
+ }
+ }
+
+ // Find the entry in the voice font map and erase its data.
+ voice_font_map_t::iterator map_iter = mVoiceFontMap.find(id);
+ if (map_iter != mVoiceFontMap.end())
+ {
+ delete map_iter->second;
+ }
+
+ // Remove the entry from the voice font map.
+ mVoiceFontMap.erase(map_iter);
+}
+
+void LLVivoxVoiceClient::deleteAllVoiceFonts()
+{
+ mVoiceFontList.clear();
+
+ voice_font_map_t::iterator iter;
+ for (iter = mVoiceFontMap.begin(); iter != mVoiceFontMap.end(); ++iter)
+ {
+ delete iter->second;
+ }
+ mVoiceFontMap.clear();
+}
+
+void LLVivoxVoiceClient::deleteVoiceFontTemplates()
+{
+ mVoiceFontTemplateList.clear();
+
+ voice_font_map_t::iterator iter;
+ for (iter = mVoiceFontTemplateMap.begin(); iter != mVoiceFontTemplateMap.end(); ++iter)
+ {
+ delete iter->second;
+ }
+ mVoiceFontTemplateMap.clear();
+}
+
+S32 LLVivoxVoiceClient::getVoiceFontIndex(const LLUUID& id) const
+{
+ S32 result = 0;
+ if (!id.isNull())
+ {
+ voice_font_map_t::const_iterator it = mVoiceFontMap.find(id);
+ if (it != mVoiceFontMap.end())
+ {
+ result = it->second->mFontIndex;
+ }
+ else
+ {
+ LL_WARNS("VoiceFont") << "Selected voice font " << id << " is not available." << LL_ENDL;
+ }
+ }
+ return result;
+}
+
+S32 LLVivoxVoiceClient::getVoiceFontTemplateIndex(const LLUUID& id) const
+{
+ S32 result = 0;
+ if (!id.isNull())
+ {
+ voice_font_map_t::const_iterator it = mVoiceFontTemplateMap.find(id);
+ if (it != mVoiceFontTemplateMap.end())
+ {
+ result = it->second->mFontIndex;
+ }
+ else
+ {
+ LL_WARNS("VoiceFont") << "Selected voice font template " << id << " is not available." << LL_ENDL;
+ }
+ }
+ return result;
+}
+
+void LLVivoxVoiceClient::accountGetSessionFontsSendMessage()
+{
+ if(mAccountLoggedIn)
+ {
+ std::ostringstream stream;
+
+ LL_DEBUGS("VoiceFont") << "Requesting voice font list." << LL_ENDL;
+
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.GetSessionFonts.1\">"
+ << "<AccountHandle>" << LLVivoxSecurity::getInstance()->accountHandle() << "</AccountHandle>"
+ << "</Request>"
+ << "\n\n\n";
+
+ writeString(stream.str());
+ }
+}
+
+void LLVivoxVoiceClient::accountGetTemplateFontsSendMessage()
+{
+ if(mAccountLoggedIn)
+ {
+ std::ostringstream stream;
+
+ LL_DEBUGS("VoiceFont") << "Requesting voice font template list." << LL_ENDL;
+
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.GetTemplateFonts.1\">"
+ << "<AccountHandle>" << LLVivoxSecurity::getInstance()->accountHandle() << "</AccountHandle>"
+ << "</Request>"
+ << "\n\n\n";
+
+ writeString(stream.str());
+ }
+}
+
+void LLVivoxVoiceClient::sessionSetVoiceFontSendMessage(const sessionStatePtr_t &session)
+{
+ S32 font_index = getVoiceFontIndex(session->mVoiceFontID);
+ LL_DEBUGS("VoiceFont") << "Requesting voice font: " << session->mVoiceFontID << " (" << font_index << "), session handle: " << session->mHandle << LL_ENDL;
+
+ std::ostringstream stream;
+
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.SetVoiceFont.1\">"
+ << "<SessionHandle>" << session->mHandle << "</SessionHandle>"
+ << "<SessionFontID>" << font_index << "</SessionFontID>"
+ << "</Request>\n\n\n";
+
+ writeString(stream.str());
+}
+
+void LLVivoxVoiceClient::accountGetSessionFontsResponse(int statusCode, const std::string &statusString)
+{
+ if (mIsWaitingForFonts)
+ {
+ // *TODO: We seem to get multiple events of this type. Should figure a way to advance only after
+ // receiving the last one.
+ LLSD result(LLSDMap("voice_fonts", LLSD::Boolean(true)));
+
+ mVivoxPump.post(result);
+ }
+ notifyVoiceFontObservers();
+ mVoiceFontsReceived = true;
+}
+
+void LLVivoxVoiceClient::accountGetTemplateFontsResponse(int statusCode, const std::string &statusString)
+{
+ // Voice font list entries were updated via addVoiceFont() during parsing.
+ notifyVoiceFontObservers();
+}
+void LLVivoxVoiceClient::addObserver(LLVoiceEffectObserver* observer)
+{
+ mVoiceFontObservers.insert(observer);
+}
+
+void LLVivoxVoiceClient::removeObserver(LLVoiceEffectObserver* observer)
+{
+ mVoiceFontObservers.erase(observer);
+}
+
+// method checks the item in VoiceMorphing menu for appropriate current voice font
+bool LLVivoxVoiceClient::onCheckVoiceEffect(const std::string& voice_effect_name)
+{
+ LLVoiceEffectInterface * effect_interfacep = LLVoiceClient::instance().getVoiceEffectInterface();
+ if (NULL != effect_interfacep)
+ {
+ const LLUUID& currect_voice_effect_id = effect_interfacep->getVoiceEffect();
+
+ if (currect_voice_effect_id.isNull())
+ {
+ if (voice_effect_name == "NoVoiceMorphing")
+ {
+ return true;
+ }
+ }
+ else
+ {
+ const LLSD& voice_effect_props = effect_interfacep->getVoiceEffectProperties(currect_voice_effect_id);
+ if (voice_effect_props["name"].asString() == voice_effect_name)
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+// method changes voice font for selected VoiceMorphing menu item
+void LLVivoxVoiceClient::onClickVoiceEffect(const std::string& voice_effect_name)
+{
+ LLVoiceEffectInterface * effect_interfacep = LLVoiceClient::instance().getVoiceEffectInterface();
+ if (NULL != effect_interfacep)
+ {
+ if (voice_effect_name == "NoVoiceMorphing")
+ {
+ effect_interfacep->setVoiceEffect(LLUUID());
+ return;
+ }
+ const voice_effect_list_t& effect_list = effect_interfacep->getVoiceEffectList();
+ if (!effect_list.empty())
+ {
+ for (voice_effect_list_t::const_iterator it = effect_list.begin(); it != effect_list.end(); ++it)
+ {
+ if (voice_effect_name == it->first)
+ {
+ effect_interfacep->setVoiceEffect(it->second);
+ return;
+ }
+ }
+ }
+ }
+}
+
+// it updates VoiceMorphing menu items in accordance with purchased properties
+void LLVivoxVoiceClient::updateVoiceMorphingMenu()
+{
+ if (mVoiceFontListDirty)
+ {
+ LLVoiceEffectInterface * effect_interfacep = LLVoiceClient::instance().getVoiceEffectInterface();
+ if (effect_interfacep)
+ {
+ const voice_effect_list_t& effect_list = effect_interfacep->getVoiceEffectList();
+ if (!effect_list.empty())
+ {
+ LLMenuGL * voice_morphing_menup = gMenuBarView->findChildMenuByName("VoiceMorphing", true);
+
+ if (NULL != voice_morphing_menup)
+ {
+ S32 items = voice_morphing_menup->getItemCount();
+ if (items > 0)
+ {
+ voice_morphing_menup->erase(1, items - 3, false);
+
+ S32 pos = 1;
+ for (voice_effect_list_t::const_iterator it = effect_list.begin(); it != effect_list.end(); ++it)
+ {
+ LLMenuItemCheckGL::Params p;
+ p.name = it->first;
+ p.label = it->first;
+ p.on_check.function(boost::bind(&LLVivoxVoiceClient::onCheckVoiceEffect, this, it->first));
+ p.on_click.function(boost::bind(&LLVivoxVoiceClient::onClickVoiceEffect, this, it->first));
+ LLMenuItemCheckGL * voice_effect_itemp = LLUICtrlFactory::create<LLMenuItemCheckGL>(p);
+ voice_morphing_menup->insert(pos++, voice_effect_itemp, false);
+ }
+
+ voice_morphing_menup->needsArrange();
+ }
+ }
+ }
+ }
+ }
+}
+void LLVivoxVoiceClient::notifyVoiceFontObservers()
+{
+ LL_DEBUGS("VoiceFont") << "Notifying voice effect observers. Lists changed: " << mVoiceFontListDirty << LL_ENDL;
+
+ updateVoiceMorphingMenu();
+
+ for (voice_font_observer_set_t::iterator it = mVoiceFontObservers.begin();
+ it != mVoiceFontObservers.end();)
+ {
+ LLVoiceEffectObserver* observer = *it;
+ observer->onVoiceEffectChanged(mVoiceFontListDirty);
+ // In case onVoiceEffectChanged() deleted an entry.
+ it = mVoiceFontObservers.upper_bound(observer);
+ }
+ mVoiceFontListDirty = false;
+
+ // If new Voice Fonts have been added notify the user.
+ if (mVoiceFontsNew)
+ {
+ if (mVoiceFontsReceived)
+ {
+ LLNotificationsUtil::add("VoiceEffectsNew");
+ }
+ mVoiceFontsNew = false;
+ }
+}
+
+void LLVivoxVoiceClient::enablePreviewBuffer(bool enable)
+{
+ LLSD result;
+ mCaptureBufferMode = enable;
+
+ if (enable)
+ result["recplay"] = "start";
+ else
+ result["recplay"] = "quit";
+
+ mVivoxPump.post(result);
+
+ if(mCaptureBufferMode && mIsInChannel)
+ {
+ LL_DEBUGS("Voice") << "no channel" << LL_ENDL;
+ sessionTerminate();
+ }
+}
+
+void LLVivoxVoiceClient::recordPreviewBuffer()
+{
+ if (!mCaptureBufferMode)
+ {
+ LL_DEBUGS("Voice") << "Not in voice effect preview mode, cannot start recording." << LL_ENDL;
+ mCaptureBufferRecording = false;
+ return;
+ }
+
+ mCaptureBufferRecording = true;
+
+ LLSD result(LLSDMap("recplay", "record"));
+ mVivoxPump.post(result);
+}
+
+void LLVivoxVoiceClient::playPreviewBuffer(const LLUUID& effect_id)
+{
+ if (!mCaptureBufferMode)
+ {
+ LL_DEBUGS("Voice") << "Not in voice effect preview mode, no buffer to play." << LL_ENDL;
+ mCaptureBufferRecording = false;
+ return;
+ }
+
+ if (!mCaptureBufferRecorded)
+ {
+ // Can't play until we have something recorded!
+ mCaptureBufferPlaying = false;
+ return;
+ }
+
+ mPreviewVoiceFont = effect_id;
+ mCaptureBufferPlaying = true;
+
+ LLSD result(LLSDMap("recplay", "playback"));
+ mVivoxPump.post(result);
+}
+
+void LLVivoxVoiceClient::stopPreviewBuffer()
+{
+ mCaptureBufferRecording = false;
+ mCaptureBufferPlaying = false;
+
+ LLSD result(LLSDMap("recplay", "quit"));
+ mVivoxPump.post(result);
+}
+
+bool LLVivoxVoiceClient::isPreviewRecording()
+{
+ return (mCaptureBufferMode && mCaptureBufferRecording);
+}
+
+bool LLVivoxVoiceClient::isPreviewPlaying()
+{
+ return (mCaptureBufferMode && mCaptureBufferPlaying);
+}
+
+void LLVivoxVoiceClient::captureBufferRecordStartSendMessage()
+{ if(mAccountLoggedIn)
+ {
+ std::ostringstream stream;
+
+ LL_DEBUGS("Voice") << "Starting audio capture to buffer." << LL_ENDL;
+
+ // Start capture
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.StartBufferCapture.1\">"
+ << "</Request>"
+ << "\n\n\n";
+
+ // Unmute the mic
+ stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.MuteLocalMic.1\">"
+ << "<ConnectorHandle>" << LLVivoxSecurity::getInstance()->connectorHandle() << "</ConnectorHandle>"
+ << "<Value>false</Value>"
+ << "</Request>\n\n\n";
+
+ // Dirty the mute mic state so that it will get reset when we finishing previewing
+ mMuteMicDirty = true;
+
+ writeString(stream.str());
+ }
+}
+
+void LLVivoxVoiceClient::captureBufferRecordStopSendMessage()
+{
+ if(mAccountLoggedIn)
+ {
+ std::ostringstream stream;
+
+ LL_DEBUGS("Voice") << "Stopping audio capture to buffer." << LL_ENDL;
+
+ // Mute the mic. Mic mute state was dirtied at recording start, so will be reset when finished previewing.
+ stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.MuteLocalMic.1\">"
+ << "<ConnectorHandle>" << LLVivoxSecurity::getInstance()->connectorHandle() << "</ConnectorHandle>"
+ << "<Value>true</Value>"
+ << "</Request>\n\n\n";
+
+ // Stop capture
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.CaptureAudioStop.1\">"
+ << "<AccountHandle>" << LLVivoxSecurity::getInstance()->accountHandle() << "</AccountHandle>"
+ << "</Request>"
+ << "\n\n\n";
+
+ writeString(stream.str());
+ }
+}
+
+void LLVivoxVoiceClient::captureBufferPlayStartSendMessage(const LLUUID& voice_font_id)
+{
+ if(mAccountLoggedIn)
+ {
+ // Track how may play requests are sent, so we know how many stop events to
+ // expect before play actually stops.
+ ++mPlayRequestCount;
+
+ std::ostringstream stream;
+
+ LL_DEBUGS("Voice") << "Starting audio buffer playback." << LL_ENDL;
+
+ S32 font_index = getVoiceFontTemplateIndex(voice_font_id);
+ LL_DEBUGS("Voice") << "With voice font: " << voice_font_id << " (" << font_index << ")" << LL_ENDL;
+
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.PlayAudioBuffer.1\">"
+ << "<AccountHandle>" << LLVivoxSecurity::getInstance()->accountHandle() << "</AccountHandle>"
+ << "<TemplateFontID>" << font_index << "</TemplateFontID>"
+ << "<FontDelta />"
+ << "</Request>"
+ << "\n\n\n";
+
+ writeString(stream.str());
+ }
+}
+
+void LLVivoxVoiceClient::captureBufferPlayStopSendMessage()
+{
+ if(mAccountLoggedIn)
+ {
+ std::ostringstream stream;
+
+ LL_DEBUGS("Voice") << "Stopping audio buffer playback." << LL_ENDL;
+
+ stream
+ << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.RenderAudioStop.1\">"
+ << "<AccountHandle>" << LLVivoxSecurity::getInstance()->accountHandle() << "</AccountHandle>"
+ << "</Request>"
+ << "\n\n\n";
+
+ writeString(stream.str());
+ }
+}
+
+LLVivoxProtocolParser::LLVivoxProtocolParser()
+{
+ parser = XML_ParserCreate(NULL);
+
+ reset();
+}
+
+void LLVivoxProtocolParser::reset()
+{
+ responseDepth = 0;
+ ignoringTags = false;
+ accumulateText = false;
+ energy = 0.f;
+ hasText = false;
+ hasAudio = false;
+ hasVideo = false;
+ terminated = false;
+ ignoreDepth = 0;
+ isChannel = false;
+ incoming = false;
+ enabled = false;
+ isEvent = false;
+ isLocallyMuted = false;
+ isModeratorMuted = false;
+ isSpeaking = false;
+ participantType = 0;
+ returnCode = -1;
+ state = 0;
+ statusCode = 0;
+ volume = 0;
+ textBuffer.clear();
+ alias.clear();
+ numberOfAliases = 0;
+ applicationString.clear();
+}
+
+//virtual
+LLVivoxProtocolParser::~LLVivoxProtocolParser()
+{
+ if (parser)
+ XML_ParserFree(parser);
+}
+
+static LLTrace::BlockTimerStatHandle FTM_VIVOX_PROCESS("Vivox Process");
+
+// virtual
+LLIOPipe::EStatus LLVivoxProtocolParser::process_impl(
+ const LLChannelDescriptors& channels,
+ buffer_ptr_t& buffer,
+ bool& eos,
+ LLSD& context,
+ LLPumpIO* pump)
+{
+ LL_RECORD_BLOCK_TIME(FTM_VIVOX_PROCESS);
+ LLBufferStream istr(channels, buffer.get());
+ std::ostringstream ostr;
+ while (istr.good())
+ {
+ char buf[1024];
+ istr.read(buf, sizeof(buf));
+ mInput.append(buf, istr.gcount());
+ }
+
+ // Look for input delimiter(s) in the input buffer. If one is found, send the message to the xml parser.
+ int start = 0;
+ int delim;
+ while((delim = mInput.find("\n\n\n", start)) != std::string::npos)
+ {
+
+ // Reset internal state of the LLVivoxProtocolParser (no effect on the expat parser)
+ reset();
+
+ XML_ParserReset(parser, NULL);
+ XML_SetElementHandler(parser, ExpatStartTag, ExpatEndTag);
+ XML_SetCharacterDataHandler(parser, ExpatCharHandler);
+ XML_SetUserData(parser, this);
+ XML_Parse(parser, mInput.data() + start, delim - start, false);
+
+ LL_DEBUGS("VivoxProtocolParser") << "parsing: " << mInput.substr(start, delim - start) << LL_ENDL;
+ start = delim + 3;
+ }
+
+ if(start != 0)
+ mInput = mInput.substr(start);
+
+ LL_DEBUGS("VivoxProtocolParser") << "at end, mInput is: " << mInput << LL_ENDL;
+
+ if(!LLVivoxVoiceClient::sConnected)
+ {
+ // If voice has been disabled, we just want to close the socket. This does so.
+ LL_INFOS("Voice") << "returning STATUS_STOP" << LL_ENDL;
+ return STATUS_STOP;
+ }
+
+ return STATUS_OK;
+}
+
+void XMLCALL LLVivoxProtocolParser::ExpatStartTag(void *data, const char *el, const char **attr)
+{
+ if (data)
+ {
+ LLVivoxProtocolParser *object = (LLVivoxProtocolParser*)data;
+ object->StartTag(el, attr);
+ }
+}
+
+// --------------------------------------------------------------------------------
+
+void XMLCALL LLVivoxProtocolParser::ExpatEndTag(void *data, const char *el)
+{
+ if (data)
+ {
+ LLVivoxProtocolParser *object = (LLVivoxProtocolParser*)data;
+ object->EndTag(el);
+ }
+}
+
+// --------------------------------------------------------------------------------
+
+void XMLCALL LLVivoxProtocolParser::ExpatCharHandler(void *data, const XML_Char *s, int len)
+{
+ if (data)
+ {
+ LLVivoxProtocolParser *object = (LLVivoxProtocolParser*)data;
+ object->CharData(s, len);
+ }
+}
+
+// --------------------------------------------------------------------------------
+
+
+void LLVivoxProtocolParser::StartTag(const char *tag, const char **attr)
+{
+ // Reset the text accumulator. We shouldn't have strings that are inturrupted by new tags
+ textBuffer.clear();
+ // only accumulate text if we're not ignoring tags.
+ accumulateText = !ignoringTags;
+
+ if (responseDepth == 0)
+ {
+ isEvent = !stricmp("Event", tag);
+
+ if (!stricmp("Response", tag) || isEvent)
+ {
+ // Grab the attributes
+ while (*attr)
+ {
+ const char *key = *attr++;
+ const char *value = *attr++;
+
+ if (!stricmp("requestId", key))
+ {
+ requestId = value;
+ }
+ else if (!stricmp("action", key))
+ {
+ actionString = value;
+ }
+ else if (!stricmp("type", key))
+ {
+ eventTypeString = value;
+ }
+ }
+ }
+ LL_DEBUGS("VivoxProtocolParser") << tag << " (" << responseDepth << ")" << LL_ENDL;
+ }
+ else
+ {
+ if (ignoringTags)
+ {
+ LL_DEBUGS("VivoxProtocolParser") << "ignoring tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL;
+ }
+ else
+ {
+ LL_DEBUGS("VivoxProtocolParser") << tag << " (" << responseDepth << ")" << LL_ENDL;
+
+ // Ignore the InputXml stuff so we don't get confused
+ if (!stricmp("InputXml", tag))
+ {
+ ignoringTags = true;
+ ignoreDepth = responseDepth;
+ accumulateText = false;
+
+ LL_DEBUGS("VivoxProtocolParser") << "starting ignore, ignoreDepth is " << ignoreDepth << LL_ENDL;
+ }
+ else if (!stricmp("CaptureDevices", tag))
+ {
+ LLVivoxVoiceClient::getInstance()->clearCaptureDevices();
+ }
+ else if (!stricmp("RenderDevices", tag))
+ {
+ LLVivoxVoiceClient::getInstance()->clearRenderDevices();
+ }
+ else if (!stricmp("CaptureDevice", tag))
+ {
+ deviceString.clear();
+ }
+ else if (!stricmp("RenderDevice", tag))
+ {
+ deviceString.clear();
+ }
+ else if (!stricmp("SessionFont", tag))
+ {
+ id = 0;
+ nameString.clear();
+ descriptionString.clear();
+ expirationDate = LLDate();
+ hasExpired = false;
+ fontType = 0;
+ fontStatus = 0;
+ }
+ else if (!stricmp("TemplateFont", tag))
+ {
+ id = 0;
+ nameString.clear();
+ descriptionString.clear();
+ expirationDate = LLDate();
+ hasExpired = false;
+ fontType = 0;
+ fontStatus = 0;
+ }
+ else if (!stricmp("MediaCompletionType", tag))
+ {
+ mediaCompletionType.clear();
+ }
+ }
+ }
+ responseDepth++;
+}
+
+// --------------------------------------------------------------------------------
+
+void LLVivoxProtocolParser::EndTag(const char *tag)
+{
+ const std::string& string = textBuffer;
+
+ responseDepth--;
+
+ if (ignoringTags)
+ {
+ if (ignoreDepth == responseDepth)
+ {
+ LL_DEBUGS("VivoxProtocolParser") << "end of ignore" << LL_ENDL;
+ ignoringTags = false;
+ }
+ else
+ {
+ LL_DEBUGS("VivoxProtocolParser") << "ignoring tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL;
+ }
+ }
+
+ if (!ignoringTags)
+ {
+ LL_DEBUGS("VivoxProtocolParser") << "processing tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL;
+
+ // Closing a tag. Finalize the text we've accumulated and reset
+ if (!stricmp("ReturnCode", tag))
+ returnCode = strtol(string.c_str(), NULL, 10);
+ else if (!stricmp("SessionHandle", tag))
+ sessionHandle = string;
+ else if (!stricmp("SessionGroupHandle", tag))
+ sessionGroupHandle = string;
+ else if (!stricmp("StatusCode", tag))
+ statusCode = strtol(string.c_str(), NULL, 10);
+ else if (!stricmp("StatusString", tag))
+ statusString = string;
+ else if (!stricmp("ParticipantURI", tag))
+ uriString = string;
+ else if (!stricmp("Volume", tag))
+ volume = strtol(string.c_str(), NULL, 10);
+ else if (!stricmp("Energy", tag))
+ energy = (F32)strtod(string.c_str(), NULL);
+ else if (!stricmp("IsModeratorMuted", tag))
+ isModeratorMuted = !stricmp(string.c_str(), "true");
+ else if (!stricmp("IsSpeaking", tag))
+ isSpeaking = !stricmp(string.c_str(), "true");
+ else if (!stricmp("Alias", tag))
+ alias = string;
+ else if (!stricmp("NumberOfAliases", tag))
+ numberOfAliases = strtol(string.c_str(), NULL, 10);
+ else if (!stricmp("Application", tag))
+ applicationString = string;
+ else if (!stricmp("ConnectorHandle", tag))
+ connectorHandle = string;
+ else if (!stricmp("VersionID", tag))
+ versionID = string;
+ else if (!stricmp("Version", tag))
+ mBuildID = string;
+ else if (!stricmp("AccountHandle", tag))
+ accountHandle = string;
+ else if (!stricmp("State", tag))
+ state = strtol(string.c_str(), NULL, 10);
+ else if (!stricmp("URI", tag))
+ uriString = string;
+ else if (!stricmp("IsChannel", tag))
+ isChannel = !stricmp(string.c_str(), "true");
+ else if (!stricmp("Incoming", tag))
+ incoming = !stricmp(string.c_str(), "true");
+ else if (!stricmp("Enabled", tag))
+ enabled = !stricmp(string.c_str(), "true");
+ else if (!stricmp("Name", tag))
+ nameString = string;
+ else if (!stricmp("AudioMedia", tag))
+ audioMediaString = string;
+ else if (!stricmp("ChannelName", tag))
+ nameString = string;
+ else if (!stricmp("DisplayName", tag))
+ displayNameString = string;
+ else if (!stricmp("Device", tag))
+ deviceString = string;
+ else if (!stricmp("AccountName", tag))
+ nameString = string;
+ else if (!stricmp("ParticipantType", tag))
+ participantType = strtol(string.c_str(), NULL, 10);
+ else if (!stricmp("IsLocallyMuted", tag))
+ isLocallyMuted = !stricmp(string.c_str(), "true");
+ else if (!stricmp("MicEnergy", tag))
+ energy = (F32)strtod(string.c_str(), NULL);
+ else if (!stricmp("ChannelName", tag))
+ nameString = string;
+ else if (!stricmp("ChannelURI", tag))
+ uriString = string;
+ else if (!stricmp("BuddyURI", tag))
+ uriString = string;
+ else if (!stricmp("Presence", tag))
+ statusString = string;
+ else if (!stricmp("CaptureDevices", tag))
+ {
+ LLVivoxVoiceClient::getInstance()->setDevicesListUpdated(true);
+ }
+ else if (!stricmp("RenderDevices", tag))
+ {
+ LLVivoxVoiceClient::getInstance()->setDevicesListUpdated(true);
+ }
+ else if (!stricmp("CaptureDevice", tag))
+ {
+ LLVivoxVoiceClient::getInstance()->addCaptureDevice(LLVoiceDevice(displayNameString, deviceString));
+ }
+ else if (!stricmp("RenderDevice", tag))
+ {
+ LLVivoxVoiceClient::getInstance()->addRenderDevice(LLVoiceDevice(displayNameString, deviceString));
+ }
+ else if (!stricmp("BlockMask", tag))
+ blockMask = string;
+ else if (!stricmp("PresenceOnly", tag))
+ presenceOnly = string;
+ else if (!stricmp("AutoAcceptMask", tag))
+ autoAcceptMask = string;
+ else if (!stricmp("AutoAddAsBuddy", tag))
+ autoAddAsBuddy = string;
+ else if (!stricmp("MessageHeader", tag))
+ messageHeader = string;
+ else if (!stricmp("MessageBody", tag))
+ messageBody = string;
+ else if (!stricmp("NotificationType", tag))
+ notificationType = string;
+ else if (!stricmp("HasText", tag))
+ hasText = !stricmp(string.c_str(), "true");
+ else if (!stricmp("HasAudio", tag))
+ hasAudio = !stricmp(string.c_str(), "true");
+ else if (!stricmp("HasVideo", tag))
+ hasVideo = !stricmp(string.c_str(), "true");
+ else if (!stricmp("Terminated", tag))
+ terminated = !stricmp(string.c_str(), "true");
+ else if (!stricmp("SubscriptionHandle", tag))
+ subscriptionHandle = string;
+ else if (!stricmp("SubscriptionType", tag))
+ subscriptionType = string;
+ else if (!stricmp("SessionFont", tag))
+ {
+ LLVivoxVoiceClient::getInstance()->addVoiceFont(id, nameString, descriptionString, expirationDate, hasExpired, fontType, fontStatus, false);
+ }
+ else if (!stricmp("TemplateFont", tag))
+ {
+ LLVivoxVoiceClient::getInstance()->addVoiceFont(id, nameString, descriptionString, expirationDate, hasExpired, fontType, fontStatus, true);
+ }
+ else if (!stricmp("ID", tag))
+ {
+ id = strtol(string.c_str(), NULL, 10);
+ }
+ else if (!stricmp("Description", tag))
+ {
+ descriptionString = string;
+ }
+ else if (!stricmp("ExpirationDate", tag))
+ {
+ expirationDate = expiryTimeStampToLLDate(string);
+ }
+ else if (!stricmp("Expired", tag))
+ {
+ hasExpired = !stricmp(string.c_str(), "1");
+ }
+ else if (!stricmp("Type", tag))
+ {
+ fontType = strtol(string.c_str(), NULL, 10);
+ }
+ else if (!stricmp("Status", tag))
+ {
+ fontStatus = strtol(string.c_str(), NULL, 10);
+ }
+ else if (!stricmp("MediaCompletionType", tag))
+ {
+ mediaCompletionType = string;;
+ }
+
+ textBuffer.clear();
+ accumulateText= false;
+
+ if (responseDepth == 0)
+ {
+ // We finished all of the XML, process the data
+ processResponse(tag);
+ }
+ }
+}
+
+// --------------------------------------------------------------------------------
+
+void LLVivoxProtocolParser::CharData(const char *buffer, int length)
+{
+ /*
+ This method is called for anything that isn't a tag, which can be text you
+ want that lies between tags, and a lot of stuff you don't want like file formatting
+ (tabs, spaces, CR/LF, etc).
+
+ Only copy text if we are in accumulate mode...
+ */
+ if (accumulateText)
+ textBuffer.append(buffer, length);
+}
+
+// --------------------------------------------------------------------------------
+
+LLDate LLVivoxProtocolParser::expiryTimeStampToLLDate(const std::string& vivox_ts)
+{
+ // *HACK: Vivox reports the time incorrectly. LLDate also only parses a
+ // subset of valid ISO 8601 dates (only handles Z, not offsets).
+ // So just use the date portion and fix the time here.
+ std::string time_stamp = vivox_ts.substr(0, 10);
+ time_stamp += VOICE_FONT_EXPIRY_TIME;
+
+ LL_DEBUGS("VivoxProtocolParser") << "Vivox timestamp " << vivox_ts << " modified to: " << time_stamp << LL_ENDL;
+
+ return LLDate(time_stamp);
+}
+
+// --------------------------------------------------------------------------------
+
+void LLVivoxProtocolParser::processResponse(std::string tag)
+{
+ LL_DEBUGS("VivoxProtocolParser") << tag << LL_ENDL;
+
+ // SLIM SDK: the SDK now returns a statusCode of "200" (OK) for success. This is a change vs. previous SDKs.
+ // According to Mike S., "The actual API convention is that responses with return codes of 0 are successful, regardless of the status code returned",
+ // so I believe this will give correct behavior.
+
+ if(returnCode == 0)
+ statusCode = 0;
+
+ if (isEvent)
+ {
+ const char *eventTypeCstr = eventTypeString.c_str();
+ LL_DEBUGS("LowVoice") << eventTypeCstr << LL_ENDL;
+
+ if (!stricmp(eventTypeCstr, "ParticipantUpdatedEvent"))
+ {
+ // These happen so often that logging them is pretty useless.
+ LL_DEBUGS("LowVoice") << "Updated Params: " << sessionHandle << ", " << sessionGroupHandle << ", " << uriString << ", " << alias << ", " << isModeratorMuted << ", " << isSpeaking << ", " << volume << ", " << energy << LL_ENDL;
+ LLVivoxVoiceClient::getInstance()->participantUpdatedEvent(sessionHandle, sessionGroupHandle, uriString, alias, isModeratorMuted, isSpeaking, volume, energy);
+ }
+ else if (!stricmp(eventTypeCstr, "AccountLoginStateChangeEvent"))
+ {
+ LLVivoxVoiceClient::getInstance()->accountLoginStateChangeEvent(accountHandle, statusCode, statusString, state);
+ }
+ else if (!stricmp(eventTypeCstr, "SessionAddedEvent"))
+ {
+ /*
+ <Event type="SessionAddedEvent">
+ <SessionGroupHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0</SessionGroupHandle>
+ <SessionHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==0</SessionHandle>
+ <Uri>sip:confctl-1408789@bhr.vivox.com</Uri>
+ <IsChannel>true</IsChannel>
+ <Incoming>false</Incoming>
+ <ChannelName />
+ </Event>
+ */
+ LLVivoxVoiceClient::getInstance()->sessionAddedEvent(uriString, alias, sessionHandle, sessionGroupHandle, isChannel, incoming, nameString, applicationString);
+ }
+ else if (!stricmp(eventTypeCstr, "SessionRemovedEvent"))
+ {
+ LLVivoxVoiceClient::getInstance()->sessionRemovedEvent(sessionHandle, sessionGroupHandle);
+ }
+ else if (!stricmp(eventTypeCstr, "SessionGroupUpdatedEvent"))
+ {
+ //nothng useful to process for this event, but we should not WARN that we have received it.
+ }
+ else if (!stricmp(eventTypeCstr, "SessionGroupAddedEvent"))
+ {
+ LLVivoxVoiceClient::getInstance()->sessionGroupAddedEvent(sessionGroupHandle);
+ }
+ else if (!stricmp(eventTypeCstr, "MediaStreamUpdatedEvent"))
+ {
+ /*
+ <Event type="MediaStreamUpdatedEvent">
+ <SessionGroupHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0</SessionGroupHandle>
+ <SessionHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==0</SessionHandle>
+ <StatusCode>200</StatusCode>
+ <StatusString>OK</StatusString>
+ <State>2</State>
+ <Incoming>false</Incoming>
+ </Event>
+ */
+ LLVivoxVoiceClient::getInstance()->mediaStreamUpdatedEvent(sessionHandle, sessionGroupHandle, statusCode, statusString, state, incoming);
+ }
+ else if (!stricmp(eventTypeCstr, "MediaCompletionEvent"))
+ {
+ /*
+ <Event type="MediaCompletionEvent">
+ <SessionGroupHandle />
+ <MediaCompletionType>AuxBufferAudioCapture</MediaCompletionType>
+ </Event>
+ */
+ LLVivoxVoiceClient::getInstance()->mediaCompletionEvent(sessionGroupHandle, mediaCompletionType);
+ }
+ else if (!stricmp(eventTypeCstr, "ParticipantAddedEvent"))
+ {
+ /*
+ <Event type="ParticipantAddedEvent">
+ <SessionGroupHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg4</SessionGroupHandle>
+ <SessionHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==4</SessionHandle>
+ <ParticipantUri>sip:xI5auBZ60SJWIk606-1JGRQ==@bhr.vivox.com</ParticipantUri>
+ <AccountName>xI5auBZ60SJWIk606-1JGRQ==</AccountName>
+ <DisplayName />
+ <ParticipantType>0</ParticipantType>
+ </Event>
+ */
+ LL_DEBUGS("LowVoice") << "Added Params: " << sessionHandle << ", " << sessionGroupHandle << ", " << uriString << ", " << alias << ", " << nameString << ", " << displayNameString << ", " << participantType << LL_ENDL;
+ LLVivoxVoiceClient::getInstance()->participantAddedEvent(sessionHandle, sessionGroupHandle, uriString, alias, nameString, displayNameString, participantType);
+ }
+ else if (!stricmp(eventTypeCstr, "ParticipantRemovedEvent"))
+ {
+ /*
+ <Event type="ParticipantRemovedEvent">
+ <SessionGroupHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg4</SessionGroupHandle>
+ <SessionHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==4</SessionHandle>
+ <ParticipantUri>sip:xtx7YNV-3SGiG7rA1fo5Ndw==@bhr.vivox.com</ParticipantUri>
+ <AccountName>xtx7YNV-3SGiG7rA1fo5Ndw==</AccountName>
+ </Event>
+ */
+ LL_DEBUGS("LowVoice") << "Removed params:" << sessionHandle << ", " << sessionGroupHandle << ", " << uriString << ", " << alias << ", " << nameString << LL_ENDL;
+
+ LLVivoxVoiceClient::getInstance()->participantRemovedEvent(sessionHandle, sessionGroupHandle, uriString, alias, nameString);
+ }
+ else if (!stricmp(eventTypeCstr, "AuxAudioPropertiesEvent"))
+ {
+ // These are really spammy in tuning mode
+ LLVivoxVoiceClient::getInstance()->auxAudioPropertiesEvent(energy);
+ }
+ else if (!stricmp(eventTypeCstr, "MessageEvent"))
+ {
+ //TODO: This probably is not received any more, it was used to support SLim clients
+ LLVivoxVoiceClient::getInstance()->messageEvent(sessionHandle, uriString, alias, messageHeader, messageBody, applicationString);
+ }
+ else if (!stricmp(eventTypeCstr, "SessionNotificationEvent"))
+ {
+ //TODO: This probably is not received any more, it was used to support SLim clients
+ LLVivoxVoiceClient::getInstance()->sessionNotificationEvent(sessionHandle, uriString, notificationType);
+ }
+ else if (!stricmp(eventTypeCstr, "SessionUpdatedEvent"))
+ {
+ /*
+ <Event type="SessionUpdatedEvent">
+ <SessionGroupHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0</SessionGroupHandle>
+ <SessionHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==0</SessionHandle>
+ <Uri>sip:confctl-9@bhd.vivox.com</Uri>
+ <IsMuted>0</IsMuted>
+ <Volume>50</Volume>
+ <TransmitEnabled>1</TransmitEnabled>
+ <IsFocused>0</IsFocused>
+ <SpeakerPosition><Position><X>0</X><Y>0</Y><Z>0</Z></Position></SpeakerPosition>
+ <SessionFontID>0</SessionFontID>
+ </Event>
+ */
+ // We don't need to process this, but we also shouldn't warn on it, since that confuses people.
+ }
+ else if (!stricmp(eventTypeCstr, "SessionGroupRemovedEvent"))
+ {
+ // We don't need to process this, but we also shouldn't warn on it, since that confuses people.
+ }
+ else if (!stricmp(eventTypeCstr, "VoiceServiceConnectionStateChangedEvent"))
+ {
+ LLVivoxVoiceClient::getInstance()->voiceServiceConnectionStateChangedEvent(statusCode, statusString, mBuildID);
+ }
+ else if (!stricmp(eventTypeCstr, "AudioDeviceHotSwapEvent"))
+ {
+ /*
+ <Event type = "AudioDeviceHotSwapEvent">
+ <EventType>RenderDeviceChanged< / EventType>
+ <RelevantDevice>
+ <Device>Speakers(Turtle Beach P11 Headset)< / Device>
+ <DisplayName>Speakers(Turtle Beach P11 Headset)< / DisplayName>
+ <Type>SpecificDevice< / Type>
+ < / RelevantDevice>
+ < / Event>
+ */
+ // an audio device was removed or added, fetch and update the local list of audio devices.
+ LLVivoxVoiceClient::getInstance()->getCaptureDevicesSendMessage();
+ LLVivoxVoiceClient::getInstance()->getRenderDevicesSendMessage();
+ }
+ else
+ {
+ LL_WARNS("VivoxProtocolParser") << "Unknown event type " << eventTypeString << LL_ENDL;
+ }
+ }
+ else
+ {
+ const char *actionCstr = actionString.c_str();
+ LL_DEBUGS("LowVoice") << actionCstr << LL_ENDL;
+
+ if (!stricmp(actionCstr, "Session.Set3DPosition.1"))
+ {
+ // We don't need to process these
+ }
+ else if (!stricmp(actionCstr, "Connector.Create.1"))
+ {
+ LLVivoxVoiceClient::getInstance()->connectorCreateResponse(statusCode, statusString, connectorHandle, versionID);
+ }
+ else if (!stricmp(actionCstr, "Account.Login.1"))
+ {
+ LLVivoxVoiceClient::getInstance()->loginResponse(statusCode, statusString, accountHandle, numberOfAliases);
+ }
+ else if (!stricmp(actionCstr, "Session.Create.1"))
+ {
+ LLVivoxVoiceClient::getInstance()->sessionCreateResponse(requestId, statusCode, statusString, sessionHandle);
+ }
+ else if (!stricmp(actionCstr, "SessionGroup.AddSession.1"))
+ {
+ LLVivoxVoiceClient::getInstance()->sessionGroupAddSessionResponse(requestId, statusCode, statusString, sessionHandle);
+ }
+ else if (!stricmp(actionCstr, "Session.Connect.1"))
+ {
+ LLVivoxVoiceClient::getInstance()->sessionConnectResponse(requestId, statusCode, statusString);
+ }
+ else if (!stricmp(actionCstr, "Account.Logout.1"))
+ {
+ LLVivoxVoiceClient::getInstance()->logoutResponse(statusCode, statusString);
+ }
+ else if (!stricmp(actionCstr, "Connector.InitiateShutdown.1"))
+ {
+ LLVivoxVoiceClient::getInstance()->connectorShutdownResponse(statusCode, statusString);
+ }
+ else if (!stricmp(actionCstr, "Account.GetSessionFonts.1"))
+ {
+ LLVivoxVoiceClient::getInstance()->accountGetSessionFontsResponse(statusCode, statusString);
+ }
+ else if (!stricmp(actionCstr, "Account.GetTemplateFonts.1"))
+ {
+ LLVivoxVoiceClient::getInstance()->accountGetTemplateFontsResponse(statusCode, statusString);
+ }
+ else if (!stricmp(actionCstr, "Aux.SetVadProperties.1"))
+ {
+ // both values of statusCode (old and more recent) indicate valid requests
+ if (statusCode != 0 && statusCode != 200)
+ {
+ LL_WARNS("Voice") << "Aux.SetVadProperties.1 request failed: "
+ << "statusCode: " << statusCode
+ << " and "
+ << "statusString: " << statusString
+ << LL_ENDL;
+ }
+ }
+ /*
+ else if (!stricmp(actionCstr, "Account.ChannelGetList.1"))
+ {
+ LLVoiceClient::getInstance()->channelGetListResponse(statusCode, statusString);
+ }
+ else if (!stricmp(actionCstr, "Connector.AccountCreate.1"))
+ {
+
+ }
+ else if (!stricmp(actionCstr, "Connector.MuteLocalMic.1"))
+ {
+
+ }
+ else if (!stricmp(actionCstr, "Connector.MuteLocalSpeaker.1"))
+ {
+
+ }
+ else if (!stricmp(actionCstr, "Connector.SetLocalMicVolume.1"))
+ {
+
+ }
+ else if (!stricmp(actionCstr, "Connector.SetLocalSpeakerVolume.1"))
+ {
+
+ }
+ else if (!stricmp(actionCstr, "Session.ListenerSetPosition.1"))
+ {
+
+ }
+ else if (!stricmp(actionCstr, "Session.SpeakerSetPosition.1"))
+ {
+
+ }
+ else if (!stricmp(actionCstr, "Session.AudioSourceSetPosition.1"))
+ {
+
+ }
+ else if (!stricmp(actionCstr, "Session.GetChannelParticipants.1"))
+ {
+
+ }
+ else if (!stricmp(actionCstr, "Account.ChannelCreate.1"))
+ {
+
+ }
+ else if (!stricmp(actionCstr, "Account.ChannelUpdate.1"))
+ {
+
+ }
+ else if (!stricmp(actionCstr, "Account.ChannelDelete.1"))
+ {
+
+ }
+ else if (!stricmp(actionCstr, "Account.ChannelCreateAndInvite.1"))
+ {
+
+ }
+ else if (!stricmp(actionCstr, "Account.ChannelFolderCreate.1"))
+ {
+
+ }
+ else if (!stricmp(actionCstr, "Account.ChannelFolderUpdate.1"))
+ {
+
+ }
+ else if (!stricmp(actionCstr, "Account.ChannelFolderDelete.1"))
+ {
+
+ }
+ else if (!stricmp(actionCstr, "Account.ChannelAddModerator.1"))
+ {
+
+ }
+ else if (!stricmp(actionCstr, "Account.ChannelDeleteModerator.1"))
+ {
+
+ }
+ */
+ }
+}
+
+LLVivoxSecurity::LLVivoxSecurity()
+{
+ // This size is an arbitrary choice; Vivox does not care
+ // Use a multiple of three so that there is no '=' padding in the base64 (purely an esthetic choice)
+ #define VIVOX_TOKEN_BYTES 9
+ U8 random_value[VIVOX_TOKEN_BYTES];
+
+ for (int b = 0; b < VIVOX_TOKEN_BYTES; b++)
+ {
+ random_value[b] = ll_rand() & 0xff;
+ }
+ mConnectorHandle = LLBase64::encode(random_value, VIVOX_TOKEN_BYTES);
+
+ for (int b = 0; b < VIVOX_TOKEN_BYTES; b++)
+ {
+ random_value[b] = ll_rand() & 0xff;
+ }
+ mAccountHandle = LLBase64::encode(random_value, VIVOX_TOKEN_BYTES);
+}
+
+LLVivoxSecurity::~LLVivoxSecurity()
+{
+}
|