PageRenderTime 174ms CodeModel.GetById 28ms app.highlight 126ms RepoModel.GetById 1ms app.codeStats 1ms

/indra/newview/llvoicevivox.cpp

https://bitbucket.org/lindenlab/viewer-beta/
C++ | 2376 lines | 1661 code | 397 blank | 318 comment | 208 complexity | bf3d28757257273bd32b19557d17e31c MD5 | raw file

Large files files are truncated, but you can click here to view the full file

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

Large files files are truncated, but you can click here to view the full file